@material-ui/lab#Pagination TypeScript Examples

The following examples show how to use @material-ui/lab#Pagination. 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: OnCallList.tsx    From backstage-plugin-opsgenie with MIT License 5 votes vote down vote up
SchedulesGrid = ({ schedules, cardsPerPage }: { schedules: Schedule[], cardsPerPage: number }) => {
    const classes = useStyles();
    const [results, setResults] = React.useState(schedules);
    const [search, setSearch] = React.useState("");
    const [page, setPage] = React.useState(1);
    const [offset, setOffset] = React.useState(0);
    const handleChange = (_: React.ChangeEvent<unknown>, value: number) => {
        setOffset((value - 1) * cardsPerPage);
        setPage(value);
    };
    const debouncedSearch = useDebounce(search, 300);

    // debounced search
    useEffect(
        () => {
            if (!debouncedSearch) {
                setResults(schedules);
                return;
            }

            const filtered = schedules.filter(schedule => {
                return schedule.name.toLowerCase().includes(debouncedSearch.toLowerCase());
            });
            setResults(filtered);
        },
        [debouncedSearch, schedules]
    );

    return (
        <div>
            <TextField
                fullWidth
                variant="outlined"
                className={classes.search}
                placeholder="Team…"
                InputProps={{
                    startAdornment: (
                        <InputAdornment position="start">
                            <SearchIcon />
                        </InputAdornment>
                    )
                }}
                onChange={e => setSearch(e.target.value)}
            />

            <ItemCardGrid classes={{ root: classes.onCallItemGrid }}>
                {results.filter((_, i) => i >= offset && i < offset + cardsPerPage).map(schedule => <OnCallForScheduleCard key={schedule.id} schedule={schedule} />)}
            </ItemCardGrid>

            <Pagination
                className={classes.pagination}
                count={Math.ceil(results.length / cardsPerPage)}
                page={page}
                onChange={handleChange}
                showFirstButton
                showLastButton
            />
        </div>
    );
}
Example #2
Source File: index.tsx    From frontend with Apache License 2.0 4 votes vote down vote up
BuildList: FunctionComponent = () => {
  const classes = useStyles();
  const navigate = useNavigate();
  const { buildList, selectedBuild, loading, total, take } = useBuildState();
  const buildDispatch = useBuildDispatch();
  const { enqueueSnackbar } = useSnackbar();
  const { selectedProjectId } = useProjectState();
  const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
  const [editDialogOpen, setEditDialogOpen] = React.useState(false);
  const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
  const [menuBuild, setMenuBuild] = React.useState<Build | null>();
  const [newCiBuildId, setNewCiBuildId] = React.useState("");
  const [paginationPage, setPaginationPage] = React.useState(1);

  const handleMenuClick = (
    event: React.MouseEvent<HTMLElement>,
    build: Build
  ) => {
    event.stopPropagation();
    setAnchorEl(event.currentTarget);
    setMenuBuild(build);
  };

  const handleMenuClose = () => {
    setMenuBuild(null);
  };

  const toggleDeleteDialogOpen = () => {
    setDeleteDialogOpen(!deleteDialogOpen);
  };

  const toggleEditDialogOpen = () => {
    setEditDialogOpen(!editDialogOpen);
  };

  const selectBuildCalback = React.useCallback(
    (id?: string) => navigate(buildTestRunLocation(id)),
    [navigate]
  );

  const handlePaginationChange = React.useCallback(
    (page: number) => {
      setPaginationPage(page);
      if (selectedProjectId) {
        buildDispatch({ type: "request" });
        buildsService
          .getList(selectedProjectId, take, take * (page - 1))
          .then((payload) => {
            buildDispatch({ type: "get", payload });
          })
          .catch((err: string) =>
            enqueueSnackbar(err, {
              variant: "error",
            })
          );
      }
    },
    [buildDispatch, enqueueSnackbar, selectedProjectId, take]
  );

  React.useEffect(() => {
    handlePaginationChange(1);
  }, [handlePaginationChange]);

  return (
    <React.Fragment>
      <Box height="91%" overflow="auto">
        <List>
          {loading ? (
            <SkeletonList />
          ) : buildList.length === 0 ? (
            <Typography variant="h5">No builds</Typography>
          ) : (
            buildList.map((build) => (
              <React.Fragment key={build.id}>
                <ListItem
                  selected={selectedBuild?.id === build.id}
                  button
                  onClick={() => selectBuildCalback(build.id)}
                  classes={{
                    container: classes.listItem,
                  }}
                >
                  <ListItemText
                    disableTypography
                    primary={
                      <Typography
                        variant="subtitle2"
                        style={{
                          wordWrap: "break-word",
                        }}
                      >
                        {`#${build.number} ${build.ciBuildId || ""}`}
                      </Typography>
                    }
                    secondary={
                      <Grid container direction="column">
                        <Grid item>
                          <Typography variant="caption" color="textPrimary">
                            {formatDateTime(build.createdAt)}
                          </Typography>
                        </Grid>
                        <Grid item>
                          <Grid container justifyContent="space-between">
                            <Grid item>
                              <Tooltip title={build.branchName}>
                                <Chip
                                  size="small"
                                  label={build.branchName}
                                  style={{ maxWidth: 180 }}
                                />
                              </Tooltip>
                            </Grid>
                            <Grid item>
                              <BuildStatusChip status={build.status} />
                            </Grid>
                          </Grid>
                        </Grid>
                      </Grid>
                    }
                  />

                  <ListItemSecondaryAction
                    className={classes.listItemSecondaryAction}
                  >
                    <IconButton
                      onClick={(event) => handleMenuClick(event, build)}
                    >
                      <MoreVert />
                    </IconButton>
                  </ListItemSecondaryAction>
                </ListItem>
                {build.isRunning && <LinearProgress />}
              </React.Fragment>
            ))
          )}
        </List>
      </Box>
      <Box height="9%">
        <Grid container justifyContent="center">
          <Grid item>
            <Pagination
              size="small"
              defaultPage={1}
              page={paginationPage}
              count={Math.ceil(total / take)}
              onChange={(event, page) => handlePaginationChange(page)}
            />
          </Grid>
        </Grid>
      </Box>

      {menuBuild && (
        <Menu anchorEl={anchorEl} open={!!menuBuild} onClose={handleMenuClose}>
          {menuBuild.isRunning && (
            <MenuItem
              onClick={() => {
                buildsService
                  .update(menuBuild.id, { isRunning: false })
                  .then((b) =>
                    enqueueSnackbar(`${menuBuild.id} finished`, {
                      variant: "success",
                    })
                  )
                  .catch((err) =>
                    enqueueSnackbar(err, {
                      variant: "error",
                    })
                  );
                handleMenuClose();
              }}
            >
              Stop
            </MenuItem>
          )}
          <MenuItem onClick={toggleEditDialogOpen}>Edit CI Build</MenuItem>
          <MenuItem onClick={toggleDeleteDialogOpen}>Delete</MenuItem>
        </Menu>
      )}
      {menuBuild && (
        <BaseModal
          open={editDialogOpen}
          title={"Edit CI Build ID"}
          submitButtonText={"Edit"}
          onCancel={toggleEditDialogOpen}
          content={
            <React.Fragment>
              <Typography>{`Edit the ci build id for build: #${
                menuBuild.number || menuBuild.id
              }`}</Typography>
              <TextValidator
                name="newCiBuildId"
                validators={["minStringLength:2"]}
                errorMessages={["Enter at least two characters."]}
                margin="dense"
                id="name"
                label="New CI Build Id"
                type="text"
                fullWidth
                required
                value={newCiBuildId}
                inputProps={{
                  onChange: (event: any) =>
                    setNewCiBuildId((event.target as HTMLInputElement).value),
                  "data-testid": "newCiBuildId",
                }}
              />
            </React.Fragment>
          }
          onSubmit={() => {
            buildsService
              .update(menuBuild.id, {
                ciBuildId: newCiBuildId,
              })
              .then((b) => {
                toggleEditDialogOpen();
              })
              .catch((err) =>
                enqueueSnackbar(err, {
                  variant: "error",
                })
              );
            handleMenuClose();
          }}
        />
      )}
      {menuBuild && (
        <BaseModal
          open={deleteDialogOpen}
          title={"Delete Build"}
          submitButtonText={"Delete"}
          onCancel={toggleDeleteDialogOpen}
          content={
            <Typography>{`Are you sure you want to delete build: #${
              menuBuild.number || menuBuild.id
            }?`}</Typography>
          }
          onSubmit={() => {
            deleteBuild(buildDispatch, menuBuild.id)
              .then((build) => {
                toggleDeleteDialogOpen();
                enqueueSnackbar(
                  `Build #${menuBuild.number || menuBuild.id} deleted`,
                  {
                    variant: "success",
                  }
                );
              })
              .then(() => handlePaginationChange(paginationPage))
              .then(() => {
                if (menuBuild.id === selectedBuild?.id) {
                  selectBuildCalback();
                }
              })
              .catch((err) =>
                enqueueSnackbar(err, {
                  variant: "error",
                })
              );
            handleMenuClose();
          }}
        />
      )}
    </React.Fragment>
  );
}
Example #3
Source File: Paginator.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
Paginator = <T extends object>(
  props: PropsWithChildren<PaginatorProps<T>>
) => {
  const { setLoading } = useAuthContext();
  const classes = useStyles();
  const {
    table: {
      pageCount,
      gotoPage,
      state: { pageIndex, pageSize }
    },
    totalResults
  } = props;
  return (
    <Paper classes={{ root: classes.pagination }}>
      <span>
        <strong>
          {pageCount === 0 ? 0 : pageIndex * pageSize + 1} -{' '}
          {Math.min(pageIndex * pageSize + pageSize, totalResults)}
        </strong>{' '}
        of <strong>{totalResults}</strong>
      </span>
      <Pagination
        count={pageCount}
        page={pageIndex + 1}
        onChange={(_, page) => {
          gotoPage(page - 1);
        }}
        color="primary"
        size="small"
      />
      {props.export && (
        <Button
          className={classes.exportButton}
          onClick={() => props.export && exportCSV(props.export, setLoading)}
        >
          Export Results
        </Button>
      )}
    </Paper>
  );
}
Example #4
Source File: Feeds.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
Feeds = () => {
  const classes = useStyles();
  const { apiGet, apiDelete } = useAuthContext();
  const [savedSearches, setSavedSearches] = useState<SavedSearch[]>([]);
  const [pageState, setPageState] = useState({
    totalResults: 0,
    current: 1,
    resultsPerPage: 20,
    totalPages: 0
  });
  const [noResults, setNoResults] = useState(false);
  const [showModal, setShowModal] = useState<Boolean>(false);
  const [selectedSearch, setSelectedSearch] = useState<string>('');

  const fetchSavedSearches = useCallback(
    async (page: number) => {
      try {
        const res = await apiGet<{ result: SavedSearch[]; count: number }>(
          `/saved-searches/?page=${page}&pageSize=${pageState.resultsPerPage}`
        );
        setSavedSearches(res.result);
        setPageState((pageState) => ({
          ...pageState,
          current: page,
          totalResults: res.count,
          totalPages: Math.ceil(res.count / pageState.resultsPerPage)
        }));
        setNoResults(res.count === 0);
      } catch (e) {
        console.error(e);
      }
      // eslint-disable-next-line
    },
    [apiGet, pageState.resultsPerPage]
  );

  useEffect(() => {
    fetchSavedSearches(1);
  }, [fetchSavedSearches]);

  const deleteSearch = async (id: string) => {
    try {
      await apiDelete(`/saved-searches/${id}`, { body: {} });
      setSavedSearches(savedSearches.filter((search) => search.id !== id));
    } catch (e) {
      console.log(e);
    }
  };

  return (
    <div className={classes.root}>
      <div className={classes.contentWrapper}>
        <Subnav
          items={[{ title: 'My Saved Searches', path: '/feeds', exact: true }]}
        ></Subnav>
        <div className={classes.content}>
          <div className={classes.panel}>
            {noResults && (
              <NoResults
                message={
                  "You don't currently have any saved searches. Try creating a saved search from a search result in your inventory."
                }
              ></NoResults>
            )}
            {savedSearches.map((search: SavedSearch) => {
              const filterDisplay: string[] = [];
              const filterMap: { [name: string]: string } = {
                'services.port': 'Port',
                fromRootDomain: 'Root Domain',
                'vulnerabilities.cve': 'CVE',
                'vulnerabilities.severity': 'Severity',
                ip: 'IP',
                name: 'Domain'
              };
              if (search.searchTerm)
                filterDisplay.push(`Search: ${search.searchTerm}`);
              for (const filter of search.filters) {
                const label =
                  filter.field in filterMap
                    ? filterMap[filter.field]
                    : filter.field;
                filterDisplay.push(`${label}: ${filter.values.join(', ')}`);
              }
              return (
                <a
                  href={
                    '/inventory' + search.searchPath + '&searchId=' + search.id
                  }
                  onClick={() => {
                    console.log('bbb');
                    localStorage.setItem('savedSearch', JSON.stringify(search));
                  }}
                  key={search.id}
                  style={{ textDecoration: 'none' }}
                >
                  <Paper
                    elevation={0}
                    classes={{ root: classes.cardRoot }}
                    aria-label="view domain details"
                  >
                    <div className={classes.cardInner}>
                      <div className={classes.domainRow}>
                        <div className={classes.cardAlerts}>
                          <h4>{search.count} items</h4>
                        </div>
                        <div className={classes.cardDetails}>
                          <h3>{search.name}</h3>
                          <p>{filterDisplay.join(', ')}</p>
                        </div>
                        <div className={classes.cardActions}>
                          <button
                            className={classes.button}
                            onClick={(event) => {
                              event.stopPropagation();
                              localStorage.setItem(
                                'savedSearch',
                                JSON.stringify({ ...search, editing: true })
                              );
                            }}
                          >
                            EDIT
                          </button>
                          <button
                            className={classes.button}
                            onClick={(event) => {
                              event.preventDefault();
                              setShowModal(true);
                              setSelectedSearch(search.id);
                            }}
                          >
                            DELETE
                          </button>
                        </div>
                      </div>
                    </div>
                  </Paper>
                </a>
              );
            })}
          </div>
        </div>

        <Paper classes={{ root: classes.pagination }}>
          <span>
            <strong>
              {pageState.totalResults === 0
                ? 0
                : (pageState.current - 1) * pageState.resultsPerPage + 1}{' '}
              -{' '}
              {Math.min(
                (pageState.current - 1) * pageState.resultsPerPage +
                  pageState.resultsPerPage,
                pageState.totalResults
              )}
            </strong>{' '}
            of <strong>{pageState.totalResults}</strong>
          </span>
          <Pagination
            count={pageState.totalPages}
            page={pageState.current}
            onChange={(_, page) => {
              fetchSavedSearches(page);
            }}
            color="primary"
            size="small"
          />
        </Paper>
      </div>
      {showModal && (
        <div>
          <Overlay />
          <ModalContainer>
            <Modal
              actions={
                <>
                  <Button
                    outline
                    type="button"
                    onClick={() => {
                      setShowModal(false);
                    }}
                  >
                    Cancel
                  </Button>
                  <Button
                    type="button"
                    onClick={() => {
                      deleteSearch(selectedSearch);
                      setShowModal(false);
                    }}
                  >
                    Delete
                  </Button>
                </>
              }
              title={<h2>Delete search?</h2>}
            >
              <p>
                Are you sure that you would like to delete this saved search?
              </p>
            </Modal>
          </ModalContainer>
        </div>
      )}
    </div>
  );
}
Example #5
Source File: Risk.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
Risk: React.FC = (props) => {
  const history = useHistory();
  const {
    currentOrganization,
    showAllOrganizations,
    user,
    apiPost
  } = useAuthContext();

  const [stats, setStats] = useState<Stats | undefined>(undefined);
  const [labels, setLabels] = useState([
    '',
    'Low',
    'Medium',
    'High',
    'Critical'
  ]);
  const [domainsWithVulns, setDomainsWithVulns] = useState(0);
  const [isLoading, setIsLoading] = useState(false);
  const [current, setCurrent] = useState(1);
  const cardClasses = useStyles(props);

  const geoStateUrl = 'https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json';

  const allColors = ['rgb(0, 111, 162)', 'rgb(0, 185, 227)'];

  const resultsPerPage = 30;

  const getSingleColor = () => {
    return '#FFBC78';
  };

  const truncateText = (text: string, len: number) => {
    if (text.length <= len) return text;
    return text.substring(0, len) + '...';
  };

  const fetchStats = useCallback(
    async (orgId?: string) => {
      const { result } = await apiPost<ApiResponse>('/stats', {
        body: {
          filters:
            (!orgId && showAllOrganizations) || !currentOrganization
              ? {}
              : orgId || 'rootDomains' in currentOrganization
              ? {
                  organization: orgId ? orgId : currentOrganization?.id
                }
              : { tag: currentOrganization.id }
        }
      });
      const max = Math.max(...result.vulnerabilities.byOrg.map((p) => p.value));
      // Adjust color scale based on highest count
      colorScale = scaleLinear<string>()
        .domain([0, Math.log(max)])
        .range(['#c7e8ff', '#135787']);
      setStats(result);
    },
    [showAllOrganizations, apiPost, currentOrganization]
  );

  useEffect(() => {
    fetchStats();
  }, [fetchStats]);

  const MyResponsivePie = ({
    data,
    colors,
    type
  }: {
    data: Point[];
    colors: any;
    type: string;
  }) => {
    return (
      <ResponsivePie
        data={data as any}
        innerRadius={0.5}
        padAngle={0.7}
        arcLabelsSkipAngle={10}
        arcLinkLabelsSkipAngle={10}
        colors={colors}
        margin={{
          left: 30,
          right: 50,
          top: 30,
          bottom: 50
        }}
        onClick={(event) => {
          if (type === 'vulns') {
            history.push(`/inventory/vulnerabilities?severity=${event.id}`);
          }
        }}
      />
    );
  };

  const MyResponsiveBar = ({
    data,
    xLabels,
    type,
    longXValues = false
  }: {
    data: Point[];
    xLabels: string[];
    type: string;
    longXValues?: boolean;
  }) => {
    const keys = xLabels;
    let dataVal: object[];
    const pageStart = (current - 1) * resultsPerPage;
    if (type === 'ports') {
      dataVal = data.map((e) => ({ ...e, [xLabels[0]]: e.value })) as any;
    } else {
      // Separate count by severity
      const domainToSevMap: any = {};
      for (const point of data) {
        const split = point.id.split('|');
        const domain = split[0];
        const severity = split[1];
        if (labels.includes(severity)) {
          if (!(domain in domainToSevMap)) domainToSevMap[domain] = {};
          domainToSevMap[domain][severity] = point.value;
        }
      }
      setDomainsWithVulns(Object.keys(domainToSevMap).length);
      dataVal = Object.keys(domainToSevMap)
        .map((key) => ({
          label: key,
          ...domainToSevMap[key]
        }))
        .sort((a, b) => {
          let diff = 0;
          for (const label of xLabels) {
            diff += (label in b ? b[label] : 0) - (label in a ? a[label] : 0);
          }
          return diff;
        })
        .slice(pageStart, Math.min(pageStart + 30, domainsWithVulns))
        .reverse();
    }
    // create the total vuln labels for each domain
    const totalLabels = ({ bars, width }: any) => {
      const fullWidth = width + 5;
      return bars.map(
        ({ data: { data, indexValue }, y, height, width }: any, i: number) => {
          const total = Object.keys(data)
            .filter((key) => key !== 'label')
            .reduce((a, key) => a + data[key], 0);
          if (i < dataVal.length) {
            return (
              <g
                transform={`translate(${fullWidth}, ${y})`}
                key={`${indexValue}-${i}`}
              >
                <text
                  x={10}
                  y={height / 2}
                  textAnchor="middle"
                  alignmentBaseline="central"
                  // add any style to the label here
                  style={{
                    fill: 'rgb(51, 51, 51)',
                    fontSize: 12
                  }}
                >
                  {total}
                </text>
              </g>
            );
          }
          return null;
        }
      );
    };
    return (
      <ResponsiveBar
        data={dataVal}
        keys={keys}
        layers={
          type === 'ports'
            ? ['grid', 'axes', 'bars', 'markers', 'legends']
            : ['grid', 'axes', 'bars', totalLabels, 'markers', 'legends']
        }
        indexBy="label"
        margin={{
          top: longXValues ? 10 : 30,
          right: 40,
          bottom: longXValues ? 150 : 75,
          left: longXValues ? 260 : 100
        }}
        theme={{
          fontSize: 12,
          axis: {
            legend: {
              text: {
                fontWeight: 'bold'
              }
            }
          }
        }}
        onClick={(event) => {
          if (type === 'vulns') {
            history.push(
              `/inventory/vulnerabilities?domain=${event.data.label}&severity=${event.id}`
            );
          } else if (type === 'ports') {
            history.push(
              `/inventory?filters[0][field]=services.port&filters[0][values][0]=n_${event.data.label}_n&filters[0][type]=any`
            );
            window.location.reload();
          }
        }}
        padding={0.5}
        colors={type === 'ports' ? getSingleColor : getSeverityColor}
        borderColor={{ from: 'color', modifiers: [['darker', 1.6]] }}
        axisTop={null}
        axisRight={null}
        axisBottom={{
          tickSize: 0,
          tickPadding: 5,
          tickRotation: 0,
          legend: type === 'ports' ? 'Count' : '',
          legendPosition: 'middle',
          legendOffset: 40
        }}
        axisLeft={{
          tickSize: 0,
          tickPadding: 20,
          tickRotation: 0,
          legend: type === 'ports' ? 'Port' : '',
          legendPosition: 'middle',
          legendOffset: -65
        }}
        animate={true}
        enableLabel={false}
        motionStiffness={90}
        motionDamping={15}
        layout={'horizontal'}
        enableGridX={true}
        enableGridY={false}
      />
    );
  };

  const VulnerabilityCard = ({
    title,
    showLatest,
    showCommon,
    data
  }: {
    title: string;
    showLatest: boolean;
    showCommon: boolean;
    data: VulnerabilityCount[];
  }) => (
    <Paper elevation={0} className={cardClasses.cardRoot}>
      <div className={cardClasses.cardSmall}>
        {showLatest && (
          <div className={cardClasses.seeAll}>
            <h4>
              <Link to="/inventory/vulnerabilities?sort=createdAt&desc=false">
                See All
              </Link>
            </h4>
          </div>
        )}
        {showCommon && (
          <div className={cardClasses.seeAll}>
            <h4>
              <Link to="/inventory/vulnerabilities/grouped">See All</Link>
            </h4>
          </div>
        )}
        <div className={cardClasses.header}>
          <h2>{title}</h2>
        </div>
        <div className={cardClasses.body}>
          {/* <h4 style={{ float: 'left' }}>Today:</h4> */}
          <div>
            {data.length === 0 && <h3>No open vulnerabilities</h3>}
            {data.length > 0 &&
              data.slice(0, 4).map((vuln) => (
                <Tooltip
                  title={
                    <span style={{ fontSize: 14 }}>
                      {truncateText(vuln.description, 120)}
                    </span>
                  }
                  placement="right"
                  arrow
                  key={vuln.title}
                >
                  <Paper
                    elevation={0}
                    className={cardClasses.miniCardRoot}
                    aria-label="view domain details"
                    onClick={() => {
                      history.push(
                        '/inventory/vulnerabilities?title=' +
                          vuln.title +
                          (vuln.domain ? '&domain=' + vuln.domain.name : '')
                      );
                    }}
                  >
                    <div className={cardClasses.cardInner}>
                      <div className={cardClasses.vulnCount}>{vuln.count}</div>
                      <div className={cardClasses.miniCardLeft}>
                        {vuln.title}
                      </div>
                      <div className={cardClasses.miniCardCenter}>
                        <p
                          className={cardClasses.underlined}
                          style={{
                            borderBottom: `6px solid ${getSeverityColor({
                              id: vuln.severity ?? ''
                            })}`
                          }}
                        >
                          {vuln.severity}
                        </p>
                      </div>
                      <button className={cardClasses.button}>DETAILS</button>
                    </div>
                    {
                      <hr
                        style={{
                          border: '1px solid #F0F0F0',
                          position: 'relative',
                          maxWidth: '90%'
                        }}
                      />
                    }
                  </Paper>
                </Tooltip>
              ))}
          </div>
        </div>
      </div>
    </Paper>
  );

  const offsets: any = {
    Vermont: [50, -8],
    'New Hampshire': [34, 2],
    Massachusetts: [30, -1],
    'Rhode Island': [28, 2],
    Connecticut: [35, 10],
    'New Jersey': [34, 1],
    Delaware: [33, 0],
    Maryland: [47, 10],
    'District of Columbia': [49, 21]
  };

  const MapCard = ({
    title,
    geoUrl,
    findFn,
    type
  }: {
    title: string;
    geoUrl: string;
    findFn: (geo: any) => Point | undefined;
    type: string;
  }) => (
    <Paper elevation={0} classes={{ root: cardClasses.cardRoot }}>
      <div>
        <div className={classes.chart}>
          <div className={cardClasses.header}>
            <h2>{title}</h2>
          </div>

          <ComposableMap
            data-tip="hello world"
            projection="geoAlbersUsa"
            style={{
              width: '90%',
              display: 'block',
              margin: 'auto'
            }}
          >
            <ZoomableGroup zoom={1}>
              <Geographies geography={geoUrl}>
                {({ geographies }) =>
                  geographies.map((geo) => {
                    const cur = findFn(geo) as
                      | (Point & {
                          orgId: string;
                        })
                      | undefined;
                    const centroid = geoCentroid(geo);
                    const name: string = geo.properties.name;
                    return (
                      <React.Fragment key={geo.rsmKey}>
                        <Geography
                          geography={geo}
                          fill={colorScale(cur ? Math.log(cur.value) : 0)}
                          onClick={() => {
                            if (cur) fetchStats(cur.orgId);
                          }}
                        />
                        <g>
                          {centroid[0] > -160 &&
                            centroid[0] < -67 &&
                            (Object.keys(offsets).indexOf(name) === -1 ? (
                              <Marker coordinates={centroid}>
                                <text y="2" fontSize={14} textAnchor="middle">
                                  {cur ? cur.value : 0}
                                </text>
                              </Marker>
                            ) : (
                              <Annotation
                                subject={centroid}
                                dx={offsets[name][0]}
                                dy={offsets[name][1]}
                                connectorProps={{}}
                              >
                                <text
                                  x={4}
                                  fontSize={14}
                                  alignmentBaseline="middle"
                                >
                                  {cur ? cur.value : 0}
                                </text>
                              </Annotation>
                            ))}
                        </g>
                      </React.Fragment>
                    );
                  })
                }
              </Geographies>
            </ZoomableGroup>
          </ComposableMap>
          {/* <ReactTooltip>{tooltipContent}</ReactTooltip> */}
        </div>
      </div>
    </Paper>
  );

  // Group latest vulns together
  const latestVulnsGrouped: {
    [key: string]: VulnerabilityCount;
  } = {};
  if (stats) {
    for (const vuln of stats.vulnerabilities.latestVulnerabilities) {
      if (vuln.title in latestVulnsGrouped)
        latestVulnsGrouped[vuln.title].count++;
      else {
        latestVulnsGrouped[vuln.title] = { ...vuln, count: 1 };
      }
    }
  }

  const latestVulnsGroupedArr = Object.values(latestVulnsGrouped).sort(
    (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
  );

  // Create severity object for Open Vulnerability chips
  const severities: VulnSeverities[] = [
    { label: 'All', sevList: ['', 'Low', 'Medium', 'High', 'Critical'] },
    { label: 'Critical', sevList: ['Critical'] },
    { label: 'High', sevList: ['High'] },
    { label: 'Medium', sevList: ['Medium'] },
    { label: 'Low', sevList: ['Low'] }
  ];

  if (stats) {
    for (const sev of severities) {
      if (
        stats.domains.numVulnerabilities.some((i) =>
          sev.sevList.includes(i.id.split('|')[1])
        )
      ) {
        sev.disable = false;
      } else {
        sev.disable = true;
      }
    }
  }

  const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));

  const generatePDF = async () => {
    setIsLoading(true);
    const input = document.getElementById('wrapper')!;
    input.style.width = '1400px';
    await delay(1);
    await html2canvas(input, {
      scrollX: 0,
      scrollY: 0,
      ignoreElements: function (element) {
        if ('mapWrapper' === element.id) {
          return true;
        }
        return false;
      }
    }).then((canvas) => {
      const imgData = canvas.toDataURL('image/png');
      const imgWidth = 190;
      const imgHeight = (canvas.height * imgWidth) / canvas.width;
      const pdf = new jsPDF('p', 'mm');
      pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight);
      pdf.save('Crossfeed_Report.pdf');
    });
    input.style.removeProperty('width');
    setIsLoading(false);
  };

  return (
    <div className={classes.root}>
      {isLoading && (
        <div className="cisa-crossfeed-loading">
          <div></div>
          <div></div>
        </div>
      )}
      <p>
        <USWDSButton
          outline
          type="button"
          onClick={() => {
            generatePDF();
          }}
        >
          Generate Report
        </USWDSButton>
      </p>
      <div id="wrapper" className={cardClasses.contentWrapper}>
        {stats && (
          <div className={cardClasses.content}>
            <div className={cardClasses.panel}>
              <VulnerabilityCard
                title={'Latest Vulnerabilities'}
                data={latestVulnsGroupedArr}
                showLatest={true}
                showCommon={false}
              ></VulnerabilityCard>
              {stats.domains.services.length > 0 && (
                <Paper elevation={0} className={cardClasses.cardRoot}>
                  <div className={cardClasses.cardSmall}>
                    <div className={cardClasses.header}>
                      <h2>Most common services</h2>
                    </div>
                    <div className={cardClasses.chartSmall}>
                      <MyResponsivePie
                        data={stats.domains.services}
                        colors={allColors}
                        type={'services'}
                      />
                    </div>
                  </div>
                </Paper>
              )}
              {stats.domains.ports.length > 0 && (
                <Paper elevation={0} classes={{ root: cardClasses.cardRoot }}>
                  <div className={cardClasses.cardSmall}>
                    <div className={cardClasses.header}>
                      <h2>Most common ports</h2>
                    </div>
                    <div className={cardClasses.chartSmall}>
                      <MyResponsiveBar
                        data={stats.domains.ports.slice(0, 5).reverse()}
                        type={'ports'}
                        xLabels={['Port']}
                      />
                    </div>
                  </div>
                </Paper>
              )}
              {stats.vulnerabilities.severity.length > 0 && (
                <Paper elevation={0} classes={{ root: cardClasses.cardRoot }}>
                  <div className={cardClasses.cardSmall}>
                    <div className={cardClasses.header}>
                      <h2>Severity Levels</h2>
                    </div>
                    <div className={cardClasses.chartSmall}>
                      <MyResponsivePie
                        data={stats.vulnerabilities.severity}
                        colors={getSeverityColor}
                        type={'vulns'}
                      />
                    </div>
                  </div>
                </Paper>
              )}
            </div>

            <div className={cardClasses.panel}>
              <Paper elevation={0} classes={{ root: cardClasses.cardRoot }}>
                <div>
                  {stats.domains.numVulnerabilities.length > 0 && (
                    <div className={cardClasses.cardBig}>
                      <div className={cardClasses.seeAll}>
                        <h4>
                          <Link to="/inventory/vulnerabilities">See All</Link>
                        </h4>
                      </div>
                      <div className={cardClasses.header}>
                        <h2>Open Vulnerabilities by Domain</h2>
                      </div>
                      <div className={cardClasses.chartLarge}>
                        {stats.domains.numVulnerabilities.length === 0 ? (
                          <h3>No open vulnerabilities</h3>
                        ) : (
                          <>
                            <p className={cardClasses.note}>
                              *Top 50 domains with open vulnerabilities
                            </p>
                            <div className={cardClasses.chipWrapper}>
                              {severities.map(
                                (sevFilter: VulnSeverities, i: number) => (
                                  <Chip
                                    key={i}
                                    className={cardClasses.chip}
                                    disabled={sevFilter.disable}
                                    label={sevFilter.label}
                                    onClick={() => {
                                      setLabels(sevFilter.sevList);
                                      setCurrent(1);
                                    }}
                                  ></Chip>
                                )
                              )}
                            </div>
                            <div className={cardClasses.chartHeader}>
                              <h5>Domain&emsp; Breakdown</h5>
                              <h5
                                style={{ textAlign: 'right', paddingLeft: 0 }}
                              >
                                Total
                              </h5>
                            </div>
                            <MyResponsiveBar
                              data={stats.domains.numVulnerabilities}
                              xLabels={labels}
                              type={'vulns'}
                              longXValues={true}
                            />
                          </>
                        )}
                      </div>
                      <div className={cardClasses.footer}>
                        <span>
                          <strong>
                            {(domainsWithVulns === 0
                              ? 0
                              : (current - 1) * resultsPerPage + 1
                            ).toLocaleString()}{' '}
                            -{' '}
                            {Math.min(
                              (current - 1) * resultsPerPage + resultsPerPage,
                              domainsWithVulns
                            ).toLocaleString()}
                          </strong>{' '}
                          of{' '}
                          <strong>{domainsWithVulns.toLocaleString()}</strong>
                        </span>
                        <Pagination
                          count={Math.ceil(domainsWithVulns / resultsPerPage)}
                          page={current}
                          onChange={(_, page) => setCurrent(page)}
                          color="primary"
                          size="small"
                        />
                      </div>
                    </div>
                  )}
                </div>
              </Paper>

              <VulnerabilityCard
                title={'Most Common Vulnerabilities'}
                data={stats.vulnerabilities.mostCommonVulnerabilities}
                showLatest={false}
                showCommon={true}
              ></VulnerabilityCard>

              <div id="mapWrapper">
                {(user?.userType === 'globalView' ||
                  user?.userType === 'globalAdmin') && (
                  <>
                    <MapCard
                      title={'State Vulnerabilities'}
                      geoUrl={geoStateUrl}
                      findFn={(geo) =>
                        stats?.vulnerabilities.byOrg.find(
                          (p) => p.label === geo.properties.name
                        )
                      }
                      type={'state'}
                    ></MapCard>
                    <MapCard
                      title={'County Vulnerabilities'}
                      geoUrl={geoStateUrl}
                      findFn={(geo) =>
                        stats?.vulnerabilities.byOrg.find(
                          (p) => p.label === geo.properties.name + ' Counties'
                        )
                      }
                      type={'county'}
                    ></MapCard>
                  </>
                )}
              </div>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}
Example #6
Source File: Dashboard.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
DashboardUI: React.FC<ContextType & { location: any }> = (
  props
) => {
  const {
    current,
    setCurrent,
    resultsPerPage,
    setResultsPerPage,
    filters,
    addFilter,
    removeFilter,
    results,
    facets,
    clearFilters,
    sortDirection,
    sortField,
    setSort,
    totalPages,
    totalResults,
    setSearchTerm,
    searchTerm,
    noResults
  } = props;
  const classes = useStyles();
  const [selectedDomain, setSelectedDomain] = useState('');
  const [resultsScrolled, setResultsScrolled] = useState(false);
  const {
    apiPost,
    apiPut,
    setLoading,
    showAllOrganizations,
    currentOrganization
  } = useAuthContext();

  const search:
    | (SavedSearch & {
        editing?: boolean;
      })
    | undefined = localStorage.getItem('savedSearch')
    ? JSON.parse(localStorage.getItem('savedSearch')!)
    : undefined;

  const [showSaveSearch, setShowSaveSearch] = useState<Boolean>(
    search && search.editing ? true : false
  );

  const [savedSearchValues, setSavedSearchValues] = useState<
    Partial<SavedSearch> & {
      name: string;
      vulnerabilityTemplate: Partial<Vulnerability>;
    }
  >(
    search
      ? search
      : {
          name: '',
          vulnerabilityTemplate: {},
          createVulnerabilities: false
        }
  );

  const onTextChange: React.ChangeEventHandler<
    HTMLInputElement | HTMLSelectElement
  > = (e) => onChange(e.target.name, e.target.value);

  const onChange = (name: string, value: any) => {
    setSavedSearchValues((values) => ({
      ...values,
      [name]: value
    }));
  };

  const onVulnerabilityTemplateChange = (e: any) => {
    (savedSearchValues.vulnerabilityTemplate as any)[e.target.name] =
      e.target.value;
    setSavedSearchValues(savedSearchValues);
  };

  const handleResultScroll = (e: React.UIEvent<HTMLElement>) => {
    if (e.currentTarget.scrollTop > 0) {
      setResultsScrolled(true);
    } else {
      setResultsScrolled(false);
    }
  };

  useEffect(() => {
    if (props.location.search === '') {
      // Search on initial load
      setSearchTerm('');
    }
    return () => {
      localStorage.removeItem('savedSearch');
    };
  }, [setSearchTerm, props.location.search]);

  useBeforeunload((event) => {
    localStorage.removeItem('savedSearch');
  });

  const fetchDomainsExport = async (): Promise<string> => {
    try {
      const body: any = {
        current,
        filters,
        resultsPerPage,
        searchTerm,
        sortDirection,
        sortField
      };
      if (!showAllOrganizations && currentOrganization) {
        if ('rootDomains' in currentOrganization)
          body.organizationId = currentOrganization.id;
        else body.tagId = currentOrganization.id;
      }
      const { url } = await apiPost('/search/export', {
        body
      });
      return url!;
    } catch (e) {
      console.error(e);
      return '';
    }
  };

  return (
    <div className={classes.root}>
      <FilterDrawer
        addFilter={addFilter}
        removeFilter={removeFilter}
        filters={filters}
        facets={facets}
        clearFilters={filters.length > 0 ? () => clearFilters([]) : undefined}
      />
      <div className={classes.contentWrapper}>
        <Subnav
          items={[
            { title: 'Search Results', path: '/inventory', exact: true },
            { title: 'All Domains', path: '/inventory/domains' },
            { title: 'All Vulnerabilities', path: '/inventory/vulnerabilities' }
          ]}
          styles={{
            paddingLeft: '0%'
          }}
        >
          <FilterTags filters={filters} removeFilter={removeFilter} />
        </Subnav>
        <SortBar
          sortField={sortField}
          sortDirection={sortDirection}
          setSort={setSort}
          isFixed={resultsScrolled}
          saveSearch={
            filters.length > 0 || searchTerm
              ? () => setShowSaveSearch(true)
              : undefined
          }
          existingSavedSearch={search}
        />
        {noResults && (
          <NoResults
            message={"We don't see any results that match your criteria."}
          ></NoResults>
        )}
        <div className={classes.content}>
          <div className={classes.panel} onScroll={handleResultScroll}>
            {results.map((result) => (
              <ResultCard
                key={result.id.raw}
                {...result}
                onDomainSelected={(id) => setSelectedDomain(id)}
                selected={result.id.raw === selectedDomain}
              />
            ))}
          </div>
          <div className={classes.panel}>
            {selectedDomain && <DomainDetails domainId={selectedDomain} />}
          </div>
        </div>
        <Paper classes={{ root: classes.pagination }}>
          <span>
            <strong>
              {(totalResults === 0
                ? 0
                : (current - 1) * resultsPerPage + 1
              ).toLocaleString()}{' '}
              -{' '}
              {Math.min(
                (current - 1) * resultsPerPage + resultsPerPage,
                totalResults
              ).toLocaleString()}
            </strong>{' '}
            of <strong>{totalResults.toLocaleString()}</strong>
          </span>
          <Pagination
            count={totalPages}
            page={current}
            onChange={(_, page) => setCurrent(page)}
            color="primary"
            size="small"
          />
          <FormControl
            variant="outlined"
            className={classes.pageSize}
            size="small"
          >
            <Typography id="results-per-page-label">
              Results per page:
            </Typography>
            <Select
              id="teststa"
              labelId="results-per-page-label"
              value={resultsPerPage}
              onChange={(e) => setResultsPerPage(e.target.value as number)}
            >
              {[15, 45, 90].map((perPage) => (
                <MenuItem key={perPage} value={perPage}>
                  {perPage}
                </MenuItem>
              ))}
            </Select>
          </FormControl>
          <USWDSButton
            className={classes.exportButton}
            outline
            type="button"
            onClick={() =>
              exportCSV(
                {
                  name: 'domains',
                  getDataToExport: fetchDomainsExport
                },
                setLoading
              )
            }
          >
            Export Results
          </USWDSButton>
        </Paper>
      </div>

      {showSaveSearch && (
        <div>
          <Overlay />
          <ModalContainer>
            <Modal
              className={classes.saveSearchModal}
              actions={
                <>
                  <USWDSButton
                    outline
                    type="button"
                    onClick={() => {
                      setShowSaveSearch(false);
                    }}
                  >
                    Cancel
                  </USWDSButton>
                  <USWDSButton
                    type="button"
                    onClick={async () => {
                      const body = {
                        body: {
                          ...savedSearchValues,
                          searchTerm,
                          filters,
                          count: totalResults,
                          searchPath: window.location.search,
                          sortField,
                          sortDirection
                        }
                      };
                      if (search) {
                        await apiPut('/saved-searches/' + search.id, body);
                      } else {
                        await apiPost('/saved-searches/', body);
                      }
                      setShowSaveSearch(false);
                    }}
                  >
                    Save
                  </USWDSButton>
                </>
              }
              title={search ? <h2>Update Search</h2> : <h2>Save Search</h2>}
            >
              <FormGroup>
                <Label htmlFor="name">Name Your Search</Label>
                <TextInput
                  required
                  id="name"
                  name="name"
                  type="text"
                  value={savedSearchValues.name}
                  onChange={onTextChange}
                />
                <p>When a new result is found:</p>
                {/* <FormControlLabel
                  control={
                    <Checkbox
                      // checked={gilad}
                      // onChange={handleChange}
                      name="email"
                    />
                  }
                  label="Email me"
                /> */}
                <FormControlLabel
                  control={
                    <Checkbox
                      checked={savedSearchValues.createVulnerabilities}
                      onChange={(e) =>
                        onChange(e.target.name, e.target.checked)
                      }
                      id="createVulnerabilities"
                      name="createVulnerabilities"
                    />
                  }
                  label="Create a vulnerability"
                />
                {savedSearchValues.createVulnerabilities && (
                  <>
                    <Label htmlFor="title">Title</Label>
                    <TextInput
                      required
                      id="title"
                      name="title"
                      type="text"
                      value={savedSearchValues.vulnerabilityTemplate.title}
                      onChange={onVulnerabilityTemplateChange}
                    />
                    <Label htmlFor="description">Description</Label>
                    <TextareaAutosize
                      required
                      id="description"
                      name="description"
                      style={{ padding: 10 }}
                      rowsMin={2}
                      value={
                        savedSearchValues.vulnerabilityTemplate.description
                      }
                      onChange={onVulnerabilityTemplateChange}
                    />
                    <Label htmlFor="description">Severity</Label>
                    <Dropdown
                      id="severity"
                      name="severity"
                      onChange={onVulnerabilityTemplateChange}
                      value={
                        savedSearchValues.vulnerabilityTemplate
                          .severity as string
                      }
                      style={{ display: 'inline-block', width: '150px' }}
                    >
                      <option value="None">None</option>
                      <option value="Low">Low</option>
                      <option value="Medium">Medium</option>
                      <option value="High">High</option>
                      <option value="Critical">Critical</option>
                    </Dropdown>
                  </>
                )}
                {/* <h3>Collaborators</h3>
                <p>
                  Collaborators can view vulnerabilities, and domains within
                  this search. Adding a team will make all members
                  collaborators.
                </p>
                <button className={classes.addButton} >
                  <AddCircleOutline></AddCircleOutline> ADD
                </button> */}
              </FormGroup>
            </Modal>
          </ModalContainer>
        </div>
      )}
    </div>
  );
}