types#ScanSchema TypeScript Examples

The following examples show how to use types#ScanSchema. 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: ScanForm.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
ScanForm: React.FC<{
  propValues: ScanFormValues;
  organizationOption: OrganizationOption[];
  tags: OrganizationTag[];
  global?: any;
  onSubmit: (values: any) => Promise<void>;
  type: 'create' | 'edit';
  scan?: Scan;
  scanSchema: ScanSchema;
}> = ({
  propValues,
  organizationOption,
  tags,
  global,
  onSubmit,
  type,
  scan,
  scanSchema
}) => {
  const setDefault = () => ({
    name: scan ? scan.name : 'censys',
    arguments: scan ? scan.arguments : '{}',
    frequency: scan ? scan.frequency : 1,
    frequencyUnit: scan ? propValues.frequencyUnit : 'minute',
    isGranular: scan ? scan.isGranular : false,
    isUserModifiable: scan ? scan.isUserModifiable : false,
    isSingleScan: scan ? scan.isSingleScan : false,
    organizations: scan ? propValues.organizations : [],
    tags: scan ? propValues.tags : []
  });
  const [organizationOptions, setOrganizationOptions] = useState<
    OrganizationOption[]
  >(organizationOption);
  const [tagOptions, setTagOptions] = useState<OrganizationOption[]>([]);
  const [values, setValues] = useState<ScanFormValues>(setDefault());
  const [schemaUpdated, setSchemaUpdated] = useState<boolean>(false);

  const onTextChange: React.ChangeEventHandler<
    HTMLInputElement | HTMLSelectElement
  > = (e) => {
    onChange(e.target.name, e.target.value);
    //Ensures global scans can't be granular
    if (type === 'create' && scanSchema[e.target.value]) {
      onChange('isGranular', false);
    }
  };

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

  const setDefaultValues = useCallback(async () => {
    try {
      setOrganizationOptions(organizationOption);
      setTagOptions(tags.map((tag) => ({ label: tag.name, value: tag.id })));
      if (scanSchema && scanSchema[values.name]) {
        setSchemaUpdated(true);
      }
      if (scan) {
        setValues((values) => ({
          ...values,
          name: scan.name,
          frequency: propValues.frequency,
          frequencyUnit: propValues.frequencyUnit,
          isGranular: scan.isGranular,
          isUserModifiable: scan.isUserModifiable,
          isSingleScan: scan.isSingleScan,
          organizations: propValues.organizations,
          tags: propValues.tags
        }));
      }
    } catch (e) {
      console.error(e);
    }
  }, [
    organizationOption,
    propValues.frequency,
    propValues.frequencyUnit,
    propValues.organizations,
    propValues.tags,
    tags,
    scan,
    scanSchema,
    values.name
  ]);

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

  return (
    <Form
      onSubmit={async (e) => {
        e.preventDefault();
        await onSubmit({
          name: values.name,
          arguments: values.arguments,
          organizations: values.organizations,
          tags: values.tags,
          frequency: values.frequency,
          frequencyUnit: values.frequencyUnit,
          isGranular: values.isGranular,
          isUserModifiable: values.isUserModifiable,
          isSingleScan: values.isSingleScan
        });
      }}
      className={classes.form}
    >
      {type === 'create' && scanSchema && <Label htmlFor="name">Name</Label> && (
        <Dropdown
          required
          id="name"
          name="name"
          className={classes.textField}
          onChange={onTextChange}
          value={values.name}
        >
          {Object.keys(scanSchema).map((i) => {
            return (
              <option key={i} value={i}>
                {i}
              </option>
            );
          })}
        </Dropdown>
      )}
      {schemaUpdated && <p>{scanSchema[values.name].description}</p>}
      {/* <Label htmlFor="arguments">Arguments</Label>
        <TextInput
          required
          id="arguments"
          name="arguments"
          className={classes.textField}
          type="text"
          value={values.arguments}
          onChange={onTextChange}
        /> */}
      {(values.name === 'censysIpv4' ||
        values.name === 'webscraper' ||
        values.name === 'censysCertificates' ||
        (schemaUpdated && !scanSchema[values.name].global) ||
        !global) && (
        <Checkbox
          id="isGranular"
          label="Limit enabled organizations"
          name="isGranular"
          checked={values.isGranular}
          onChange={(e) => {
            onChange('isGranular', e.target.checked);
            if (!e.target.checked) {
              // Only granular scans can be user-modifiable.
              onChange('isUserModifiable', false);
            }
          }}
        />
      )}
      {values.isGranular && (
        <>
          <Label htmlFor="organizations">Enabled Organizations</Label>
          <MultiSelect
            name="organizations"
            options={organizationOptions}
            value={values.organizations}
            onChange={(e) => onChange('organizations', e)}
            zIndex={100}
          />
          <Label htmlFor="tags">Enabled Organization Tags</Label>
          <MultiSelect
            name="tags"
            options={tagOptions}
            value={values.tags}
            onChange={(e) => onChange('tags', e)}
            zIndex={99}
          />
          <br />
        </>
      )}
      {values.isGranular && (
        <>
          <Checkbox
            id="isUserModifiable"
            label="Allow any organization's admins to toggle this scan on/off"
            name="isUserModifiable"
            checked={values.isUserModifiable}
            onChange={(e) => onChange('isUserModifiable', e.target.checked)}
          />
          <br />
        </>
      )}
      <Checkbox
        id="isSingleScan"
        label="Run scan once"
        name="isSingleScan"
        checked={values.isSingleScan}
        onChange={(e) => onChange('isSingleScan', e.target.checked)}
      />
      {!values.isSingleScan && (
        <div className="form-group form-inline">
          <label style={{ marginRight: '10px' }} htmlFor="frequency">
            Run every
          </label>
          <TextInput
            id="frequency"
            name="frequency"
            type="number"
            style={{
              display: 'inline-block',
              width: '150px',
              marginRight: '15px'
            }}
            value={values.frequency}
            onChange={(e) => {
              onChange(e.target.name, Number(e.target.value));
            }}
          />
          <Dropdown
            id="frequencyUnit"
            name="frequencyUnit"
            onChange={onTextChange}
            value={values.frequencyUnit}
            style={{ display: 'inline-block', width: '150px' }}
          >
            <option value="minute">Minute(s)</option>
            <option value="hour">Hour(s)</option>
            <option value="day">Day(s)</option>
          </Dropdown>
        </div>
      )}
      <br />
      {type === 'edit' && (
        <Link to={`/scans`}>
          <Button type="button" outline>
            {' '}
            Return to Scans
          </Button>
        </Link>
      )}
      <Button type="submit">
        {type === 'edit' ? 'Save Changes' : 'Create Scan'}
      </Button>
    </Form>
  );
}
Example #2
Source File: Organization.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
Organization: React.FC = () => {
  const {
    apiGet,
    apiPut,
    apiPost,
    user,
    setFeedbackMessage
  } = useAuthContext();
  const { organizationId } = useParams<{ organizationId: string }>();
  const [organization, setOrganization] = useState<OrganizationType>();
  const [tags, setTags] = useState<AutocompleteType[]>([]);
  const [userRoles, setUserRoles] = useState<Role[]>([]);
  const [scanTasks, setScanTasks] = useState<ScanTask[]>([]);
  const [scans, setScans] = useState<Scan[]>([]);
  const [scanSchema, setScanSchema] = useState<ScanSchema>({});
  const [newUserValues, setNewUserValues] = useState<{
    firstName: string;
    lastName: string;
    email: string;
    organization?: OrganizationType;
    role: string;
  }>({
    firstName: '',
    lastName: '',
    email: '',
    role: ''
  });
  const classes = useStyles();
  const [tagValue, setTagValue] = React.useState<AutocompleteType | null>(null);
  const [inputValue, setInputValue] = React.useState('');
  const [dialog, setDialog] = React.useState<{
    open: boolean;
    type?: 'rootDomains' | 'ipBlocks' | 'tags';
    label?: string;
  }>({ open: false });

  const dateAccessor = (date?: string) => {
    return !date || new Date(date).getTime() === new Date(0).getTime()
      ? 'None'
      : `${formatDistanceToNow(parseISO(date))} ago`;
  };

  const userRoleColumns: Column<Role>[] = [
    {
      Header: 'Name',
      accessor: ({ user }) => user.fullName,
      width: 200,
      disableFilters: true,
      id: 'name'
    },
    {
      Header: 'Email',
      accessor: ({ user }) => user.email,
      width: 150,
      minWidth: 150,
      id: 'email',
      disableFilters: true
    },
    {
      Header: 'Role',
      accessor: ({ approved, role, user }) => {
        if (approved) {
          if (user.invitePending) {
            return 'Invite pending';
          } else if (role === 'admin') {
            return 'Administrator';
          } else {
            return 'Member';
          }
        }
        return 'Pending approval';
      },
      width: 50,
      minWidth: 50,
      id: 'approved',
      disableFilters: true
    },
    {
      Header: () => {
        return (
          <div style={{ justifyContent: 'flex-center' }}>
            <Button color="secondary" onClick={() => setDialog({ open: true })}>
              <ControlPoint style={{ marginRight: '10px' }}></ControlPoint>
              Add member
            </Button>
          </div>
        );
      },
      id: 'action',
      Cell: ({ row }: { row: { index: number } }) => {
        const isApproved =
          !organization?.userRoles[row.index] ||
          organization?.userRoles[row.index].approved;
        return (
          <>
            {isApproved ? (
              <Button
                onClick={() => {
                  removeUser(row.index);
                }}
                color="secondary"
              >
                <p>Remove</p>
              </Button>
            ) : (
              <Button
                onClick={() => {
                  approveUser(row.index);
                }}
                color="secondary"
              >
                <p>Approve</p>
              </Button>
            )}
          </>
        );
      },
      disableFilters: true
    }
  ];

  const scanColumns: Column<Scan>[] = [
    {
      Header: 'Name',
      accessor: 'name',
      width: 150,
      id: 'name',
      disableFilters: true
    },
    {
      Header: 'Description',
      accessor: ({ name }) => scanSchema[name] && scanSchema[name].description,
      width: 200,
      minWidth: 200,
      id: 'description',
      disableFilters: true
    },
    {
      Header: 'Mode',
      accessor: ({ name }) =>
        scanSchema[name] && scanSchema[name].isPassive ? 'Passive' : 'Active',
      width: 150,
      minWidth: 150,
      id: 'mode',
      disableFilters: true
    },
    {
      Header: 'Action',
      id: 'action',
      maxWidth: 100,
      Cell: ({ row }: { row: { index: number } }) => {
        if (!organization) return;
        const enabled = organization.granularScans.find(
          (scan) => scan.id === scans[row.index].id
        );
        return (
          <Button
            type="button"
            onClick={() => {
              updateScan(scans[row.index], !enabled);
            }}
          >
            {enabled ? 'Disable' : 'Enable'}
          </Button>
        );
      },
      disableFilters: true
    }
  ];

  const scanTaskColumns: Column<ScanTask>[] = [
    {
      Header: 'ID',
      accessor: 'id',
      disableFilters: true
    },
    {
      Header: 'Status',
      accessor: 'status',
      disableFilters: true
    },
    {
      Header: 'Type',
      accessor: 'type',
      disableFilters: true
    },
    {
      Header: 'Name',
      accessor: ({ scan }) => scan?.name,
      disableFilters: true
    },
    {
      Header: 'Created At',
      accessor: ({ createdAt }) => dateAccessor(createdAt),
      disableFilters: true,
      disableSortBy: true
    },
    {
      Header: 'Requested At',
      accessor: ({ requestedAt }) => dateAccessor(requestedAt),
      disableFilters: true,
      disableSortBy: true
    },
    {
      Header: 'Started At',
      accessor: ({ startedAt }) => dateAccessor(startedAt),
      disableFilters: true,
      disableSortBy: true
    },
    {
      Header: 'Finished At',
      accessor: ({ finishedAt }) => dateAccessor(finishedAt),
      disableFilters: true,
      disableSortBy: true
    },
    {
      Header: 'Output',
      accessor: 'output',
      disableFilters: true
    }
  ];

  const fetchOrganization = useCallback(async () => {
    try {
      const organization = await apiGet<OrganizationType>(
        `/organizations/${organizationId}`
      );
      organization.scanTasks.sort(
        (a, b) =>
          new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
      );
      setOrganization(organization);
      setUserRoles(organization.userRoles);
      setScanTasks(organization.scanTasks);
      const tags = await apiGet<OrganizationTag[]>(`/organizations/tags`);
      setTags(tags);
    } catch (e) {
      console.error(e);
    }
  }, [apiGet, setOrganization, organizationId]);

  const fetchScans = useCallback(async () => {
    try {
      const response = await apiGet<{
        scans: Scan[];
        schema: ScanSchema;
      }>('/granularScans/');
      let { scans } = response;
      const { schema } = response;

      if (user?.userType !== 'globalAdmin')
        scans = scans.filter(
          (scan) =>
            scan.name !== 'censysIpv4' && scan.name !== 'censysCertificates'
        );

      setScans(scans);
      setScanSchema(schema);
    } catch (e) {
      console.error(e);
    }
  }, [apiGet, user]);

  const approveUser = async (user: number) => {
    try {
      await apiPost(
        `/organizations/${organization?.id}/roles/${organization?.userRoles[user].id}/approve`,
        { body: {} }
      );
      const copy = userRoles.map((role, id) =>
        id === user ? { ...role, approved: true } : role
      );
      setUserRoles(copy);
    } catch (e) {
      console.error(e);
    }
  };

  const removeUser = async (user: number) => {
    try {
      await apiPost(
        `/organizations/${organization?.id}/roles/${userRoles[user].id}/remove`,
        { body: {} }
      );
      const copy = userRoles.filter((_, ind) => ind !== user);
      setUserRoles(copy);
    } catch (e) {
      console.error(e);
    }
  };

  const updateOrganization = async (body: any) => {
    try {
      const org = await apiPut('/organizations/' + organization?.id, {
        body: organization
      });
      setOrganization(org);
      setFeedbackMessage({
        message: 'Organization successfully updated',
        type: 'success'
      });
    } catch (e) {
      setFeedbackMessage({
        message:
          e.status === 422
            ? 'Error updating organization'
            : e.message ?? e.toString(),
        type: 'error'
      });
      console.error(e);
    }
  };

  const updateScan = async (scan: Scan, enabled: boolean) => {
    try {
      if (!organization) return;
      await apiPost(
        `/organizations/${organization?.id}/granularScans/${scan.id}/update`,
        {
          body: {
            enabled
          }
        }
      );
      setOrganization({
        ...organization,
        granularScans: enabled
          ? organization.granularScans.concat([scan])
          : organization.granularScans.filter(
              (granularScan) => granularScan.id !== scan.id
            )
      });
    } catch (e) {
      setFeedbackMessage({
        message:
          e.status === 422 ? 'Error updating scan' : e.message ?? e.toString(),
        type: 'error'
      });
      console.error(e);
    }
  };

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

  const onInviteUserSubmit = async () => {
    try {
      const body = {
        firstName: newUserValues.firstName,
        lastName: newUserValues.lastName,
        email: newUserValues.email,
        organization: organization?.id,
        organizationAdmin: newUserValues.role === 'admin'
      };
      const user: User = await apiPost('/users/', {
        body
      });
      const newRole = user.roles[user.roles.length - 1];
      newRole.user = user;
      if (userRoles.find((role) => role.user.id === user.id)) {
        setUserRoles(
          userRoles.map((role) => (role.user.id === user.id ? newRole : role))
        );
      } else {
        setUserRoles(userRoles.concat([newRole]));
      }
    } catch (e) {
      setFeedbackMessage({
        message:
          e.status === 422 ? 'Error inviting user' : e.message ?? e.toString(),
        type: 'error'
      });
      console.log(e);
    }
  };

  const onInviteUserTextChange: React.ChangeEventHandler<
    HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
  > = (e) => onInviteUserChange(e.target.name, e.target.value);

  const onInviteUserChange = (name: string, value: any) => {
    setNewUserValues((values) => ({
      ...values,
      [name]: value
    }));
  };
  const filter = createFilterOptions<AutocompleteType>();

  const ListInput = (props: {
    type: 'rootDomains' | 'ipBlocks' | 'tags';
    label: string;
  }) => {
    if (!organization) return null;
    const elements: (string | OrganizationTag)[] = organization[props.type];
    return (
      <div className={classes.headerRow}>
        <label>{props.label}</label>
        <span>
          {elements &&
            elements.map((value: string | OrganizationTag, index: number) => (
              <Chip
                className={classes.chip}
                key={index}
                label={typeof value === 'string' ? value : value.name}
                onDelete={() => {
                  organization[props.type].splice(index, 1);
                  setOrganization({ ...organization });
                }}
              ></Chip>
            ))}
          <Chip
            label="ADD"
            variant="outlined"
            color="secondary"
            onClick={() => {
              setDialog({
                open: true,
                type: props.type,
                label: props.label
              });
            }}
          />
        </span>
      </div>
    );
  };

  if (!organization) return null;

  const views = [
    <Paper className={classes.settingsWrapper} key={0}>
      <Dialog
        open={dialog.open}
        onClose={() => setDialog({ open: false })}
        aria-labelledby="form-dialog-title"
        maxWidth="xs"
        fullWidth
      >
        <DialogTitle id="form-dialog-title">
          Add {dialog.label && dialog.label.slice(0, -1)}
        </DialogTitle>
        <DialogContent>
          {dialog.type === 'tags' ? (
            <>
              <DialogContentText>
                Select an existing tag or add a new one.
              </DialogContentText>
              <Autocomplete
                value={tagValue}
                onChange={(event, newValue) => {
                  if (typeof newValue === 'string') {
                    setTagValue({
                      name: newValue
                    });
                  } else {
                    setTagValue(newValue);
                  }
                }}
                filterOptions={(options, params) => {
                  const filtered = filter(options, params);
                  // Suggest the creation of a new value
                  if (
                    params.inputValue !== '' &&
                    !filtered.find(
                      (tag) =>
                        tag.name?.toLowerCase() ===
                        params.inputValue.toLowerCase()
                    )
                  ) {
                    filtered.push({
                      name: params.inputValue,
                      title: `Add "${params.inputValue}"`
                    });
                  }
                  return filtered;
                }}
                selectOnFocus
                clearOnBlur
                handleHomeEndKeys
                options={tags}
                getOptionLabel={(option) => {
                  return option.name ?? '';
                }}
                renderOption={(option) => {
                  if (option.title) return option.title;
                  return option.name ?? '';
                }}
                fullWidth
                freeSolo
                renderInput={(params) => (
                  <TextField {...params} variant="outlined" />
                )}
              />
            </>
          ) : (
            <TextField
              autoFocus
              margin="dense"
              id="name"
              label={dialog.label && dialog.label.slice(0, -1)}
              type="text"
              fullWidth
              onChange={(e) => setInputValue(e.target.value)}
            />
          )}
        </DialogContent>
        <DialogActions>
          <Button variant="outlined" onClick={() => setDialog({ open: false })}>
            Cancel
          </Button>
          <Button
            variant="contained"
            color="primary"
            onClick={() => {
              if (dialog.type && dialog.type !== 'tags') {
                if (inputValue) {
                  organization[dialog.type].push(inputValue);
                  setOrganization({ ...organization });
                }
              } else {
                if (tagValue) {
                  if (!organization.tags) organization.tags = [];
                  organization.tags.push(tagValue as any);
                  setOrganization({ ...organization });
                }
              }
              setDialog({ open: false });
              setInputValue('');
              setTagValue(null);
            }}
          >
            Add
          </Button>
        </DialogActions>
      </Dialog>
      <TextField
        value={organization.name}
        disabled
        variant="filled"
        InputProps={{
          className: classes.orgName
        }}
      ></TextField>
      <ListInput label="Root Domains" type="rootDomains"></ListInput>
      <ListInput label="IP Blocks" type="ipBlocks"></ListInput>
      <ListInput label="Tags" type="tags"></ListInput>
      <div className={classes.headerRow}>
        <label>Passive Mode</label>
        <span>
          <SwitchInput
            checked={organization.isPassive}
            onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
              setOrganization({
                ...organization,
                isPassive: event.target.checked
              });
            }}
            color="primary"
          />
        </span>
      </div>
      <div className={classes.buttons}>
        <Link to={`/organizations`}>
          <Button
            variant="outlined"
            style={{ marginRight: '10px', color: '#565C65' }}
          >
            Cancel
          </Button>
        </Link>
        <Button
          variant="contained"
          onClick={updateOrganization}
          style={{ background: '#565C65', color: 'white' }}
        >
          Save
        </Button>
      </div>
    </Paper>,
    <React.Fragment key={1}>
      <Table<Role> columns={userRoleColumns} data={userRoles} />
      <Dialog
        open={dialog.open}
        onClose={() => setDialog({ open: false })}
        aria-labelledby="form-dialog-title"
        maxWidth="xs"
        fullWidth
      >
        <DialogTitle id="form-dialog-title">Add Member</DialogTitle>
        <DialogContent>
          <p style={{ color: '#3D4551' }}>
            Organization members can view Organization-specific vulnerabilities,
            domains, and notes. Organization administrators can additionally
            manage members and update the organization.
          </p>
          <TextField
            margin="dense"
            id="firstName"
            name="firstName"
            label="First Name"
            type="text"
            fullWidth
            value={newUserValues.firstName}
            onChange={onInviteUserTextChange}
            variant="filled"
            InputProps={{
              className: classes.textField
            }}
          />
          <TextField
            margin="dense"
            id="lastName"
            name="lastName"
            label="Last Name"
            type="text"
            fullWidth
            value={newUserValues.lastName}
            onChange={onInviteUserTextChange}
            variant="filled"
            InputProps={{
              className: classes.textField
            }}
          />
          <TextField
            margin="dense"
            id="email"
            name="email"
            label="Email"
            type="text"
            fullWidth
            value={newUserValues.email}
            onChange={onInviteUserTextChange}
            variant="filled"
            InputProps={{
              className: classes.textField
            }}
          />
          <br></br>
          <br></br>
          <FormLabel component="legend">Role</FormLabel>
          <RadioGroup
            aria-label="role"
            name="role"
            value={newUserValues.role}
            onChange={onInviteUserTextChange}
          >
            <FormControlLabel
              value="standard"
              control={<Radio color="primary" />}
              label="Standard"
            />
            <FormControlLabel
              value="admin"
              control={<Radio color="primary" />}
              label="Administrator"
            />
          </RadioGroup>
        </DialogContent>
        <DialogActions>
          <Button variant="outlined" onClick={() => setDialog({ open: false })}>
            Cancel
          </Button>
          <Button
            variant="contained"
            color="primary"
            onClick={async () => {
              onInviteUserSubmit();
              setDialog({ open: false });
            }}
          >
            Add
          </Button>
        </DialogActions>
      </Dialog>
    </React.Fragment>,
    <React.Fragment key={2}>
      <OrganizationList parent={organization}></OrganizationList>
    </React.Fragment>,
    <React.Fragment key={3}>
      <Table<Scan> columns={scanColumns} data={scans} fetchData={fetchScans} />
      <h2>Organization Scan History</h2>
      <Table<ScanTask> columns={scanTaskColumns} data={scanTasks} />
    </React.Fragment>
  ];

  let navItems = [
    {
      title: 'Settings',
      path: `/organizations/${organizationId}`,
      exact: true
    },
    {
      title: 'Members',
      path: `/organizations/${organizationId}/members`
    }
  ];

  if (!organization.parent) {
    navItems = navItems.concat([
      // { title: 'Teams', path: `/organizations/${organizationId}/teams` },
      { title: 'Scans', path: `/organizations/${organizationId}/scans` }
    ]);
  }

  return (
    <div>
      <div className={classes.header}>
        <h1 className={classes.headerLabel}>
          <Link to="/organizations">Organizations</Link>
          {organization.parent && (
            <>
              <ChevronRight></ChevronRight>
              <Link to={'/organizations/' + organization.parent.id}>
                {organization.parent.name}
              </Link>
            </>
          )}
          <ChevronRight
            style={{
              verticalAlign: 'middle',
              lineHeight: '100%',
              fontSize: '26px'
            }}
          ></ChevronRight>
          <span style={{ color: '#07648D' }}>{organization.name}</span>
        </h1>
        <Subnav
          items={navItems}
          styles={{
            background: '#F9F9F9'
          }}
        ></Subnav>
      </div>
      <div className={classes.root}>
        <Switch>
          <Route
            path="/organizations/:organizationId"
            exact
            render={() => views[0]}
          />
          <Route
            path="/organizations/:organizationId/members"
            render={() => views[1]}
          />
          <Route
            path="/organizations/:organizationId/teams"
            render={() => views[2]}
          />
          <Route
            path="/organizations/:organizationId/scans"
            render={() => views[3]}
          />
        </Switch>
      </div>
    </div>
  );
}
Example #3
Source File: Scan.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
ScanComponent: React.FC = () => {
  const { scanId } = useParams();
  const { apiGet, apiPut, setFeedbackMessage } = useAuthContext();
  const [scan, setScan] = useState<Scan>();
  const [organizationOptions, setOrganizationOptions] = useState<
    OrganizationOption[]
  >([]);
  const [tags, setTags] = useState<OrganizationTag[]>([]);
  const [scanSchema, setScanSchema] = useState<ScanSchema>({});
  const [values, setValues] = useState<ScanFormValues>({
    name: 'scan.name',
    arguments: '{}',
    organizations: [],
    frequency: 1,
    frequencyUnit: 'minute',
    isGranular: false,
    isUserModifiable: false,
    isSingleScan: false,
    tags: []
  });

  const fetchScan = useCallback(async () => {
    try {
      const { scan, schema, organizations } = await apiGet<{
        scan: Scan;
        schema: ScanSchema;
        organizations: OrganizationType[];
      }>(`/scans/${scanId}`);
      setScan(scan);
      setDefaultValues(scan);
      setScanSchema(schema);
      setOrganizationOptions(
        organizations.map((e) => ({ label: e.name, value: e.id }))
      );
      const tags = await apiGet<OrganizationTag[]>(`/organizations/tags`);
      setTags(tags);
    } catch (e) {
      console.error(e);
    }
  }, [apiGet, setScan, scanId]);

  const onSubmit = async (body: ScanFormValues) => {
    try {
      setFrequency(body);
      await apiPut(`/scans/${scanId}`, {
        body: {
          ...body,
          organizations: body.organizations
            ? body.organizations.map((e) => e.value)
            : [],
          tags: body.tags
            ? body.tags.map((e) => ({
                id: e.value
              }))
            : []
        }
      });
      setFeedbackMessage({
        message: 'Scan successfully updated',
        type: 'success'
      });
    } catch (e) {
      setFeedbackMessage({
        message: 'Error updating scan',
        type: 'error'
      });
      console.log(e);
    }
  };

  const setDefaultValues = async (scan: Scan) => {
    let oldFrequencyUnit = 'minute';
    if (scan.frequency >= 86400) {
      scan.frequency = scan.frequency / (60 * 60 * 24);
      oldFrequencyUnit = 'day';
    } else if (scan.frequency >= 3600) {
      scan.frequency = scan.frequency / (60 * 60);
      oldFrequencyUnit = 'hour';
    } else if (scan.frequency >= 60) {
      scan.frequency = scan.frequency / 60;
      oldFrequencyUnit = 'minute';
    }

    setValues((values) => ({
      ...values,
      name: scan.name,
      frequency: scan.frequency,
      frequencyUnit: oldFrequencyUnit,
      isGranular: scan.isGranular,
      isSingleScan: scan.isSingleScan
    }));

    //retrieves the organizations that are currently
    //associated with this scan
    if (scan.isGranular) {
      const defaultOrganizations: OrganizationOption[] = [];
      for (const org in scan.organizations) {
        const thisOrganization: OrganizationType = scan.organizations[org];
        const thisOrganizationOption: OrganizationOption = {
          label: thisOrganization.name,
          value: thisOrganization.id
        };
        defaultOrganizations.push(thisOrganizationOption);
      }
      setValues((values) => ({
        ...values,
        organizations: defaultOrganizations,
        tags: scan.tags.map((tag) => ({
          label: tag.name,
          value: tag.id
        }))
      }));
    }
  };

  const isGlobal = scanSchema.global;
  useEffect(() => {
    fetchScan();
  }, [fetchScan]);

  if (!scan)
    return (
      <div className={classes.root}>
        <h1>No results found</h1>
      </div>
    );

  return (
    <div className={classes.root}>
      <Header>
        <div className="usa-nav-container">
          <div className="usa-navbar">
            <h1>{'Edit ' + scan.name}</h1>
          </div>
        </div>
      </Header>
      <ScanForm
        organizationOption={organizationOptions}
        tags={tags}
        propValues={values}
        global={isGlobal}
        onSubmit={onSubmit}
        type="edit"
        scan={scan}
        scanSchema={scanSchema}
      ></ScanForm>
    </div>
  );
}
Example #4
Source File: ScansView.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
ScansView: React.FC = () => {
  const { apiGet, apiPost, apiDelete } = useAuthContext();
  const [showModal, setShowModal] = useState<Boolean>(false);
  const [selectedRow, setSelectedRow] = useState<number>(0);
  const [scans, setScans] = useState<Scan[]>([]);
  const [organizationOptions, setOrganizationOptions] = useState<
    OrganizationOption[]
  >([]);
  const [tags, setTags] = useState<OrganizationTag[]>([]);
  const [scanSchema, setScanSchema] = useState<ScanSchema>({});

  const columns: Column<Scan>[] = [
    {
      Header: 'Run',
      id: 'run',
      Cell: ({ row }: { row: { index: number } }) => (
        <div
          style={{ textAlign: 'center' }}
          onClick={() => {
            runScan(row.index);
          }}
        >
          <FaPlayCircle />
        </div>
      ),
      disableFilters: true
    },
    {
      Header: 'Name',
      accessor: 'name',
      width: 200,
      id: 'name',
      disableFilters: true
    },
    {
      Header: 'Tags',
      accessor: ({ tags }) => tags.map((tag) => tag.name).join(', '),
      width: 150,
      minWidth: 150,
      id: 'tags',
      disableFilters: true
    },
    {
      Header: 'Mode',
      accessor: ({ name }) =>
        scanSchema[name] && scanSchema[name].isPassive ? 'Passive' : 'Active',
      width: 150,
      minWidth: 150,
      id: 'mode',
      disableFilters: true
    },
    {
      Header: 'Frequency',
      accessor: ({ frequency, isSingleScan }) => {
        let val, unit;
        if (frequency < 60 * 60) {
          val = frequency / 60;
          unit = 'minute';
        } else if (frequency < 60 * 60 * 24) {
          val = frequency / (60 * 60);
          unit = 'hour';
        } else {
          val = frequency / (60 * 60 * 24);
          unit = 'day';
        }
        if (isSingleScan) {
          return 'Single Scan';
        }
        return `Every ${val} ${unit}${val === 1 ? '' : 's'}`;
      },
      width: 200,
      id: 'frequency',
      disableFilters: true
    },
    {
      Header: 'Last Run',
      accessor: (args: Scan) => {
        return !args.lastRun ||
          new Date(args.lastRun).getTime() === new Date(0).getTime()
          ? 'None'
          : `${formatDistanceToNow(parseISO(args.lastRun))} ago`;
      },
      width: 200,
      id: 'lastRun',
      disableFilters: true
    },
    {
      Header: 'Edit',
      id: 'edit',
      Cell: ({ row }: CellProps<Scan>) => (
        <Link to={`/scans/${row.original.id}`} style={{ color: 'black' }}>
          <FaEdit />
        </Link>
      ),
      disableFilters: true
    },
    {
      Header: 'Delete',
      id: 'delete',
      Cell: ({ row }: { row: { index: number } }) => (
        <span
          onClick={() => {
            setShowModal(true);
            setSelectedRow(row.index);
          }}
        >
          <FaTimes />
        </span>
      ),
      disableFilters: true
    },
    {
      Header: 'Description',
      accessor: ({ name }) => scanSchema[name]?.description,
      width: 200,
      maxWidth: 200,
      id: 'description',
      disableFilters: true
    }
  ];
  const [errors, setErrors] = useState<Errors>({});

  const [values] = useState<ScanFormValues>({
    name: 'censys',
    arguments: '{}',
    organizations: [],
    frequency: 1,
    frequencyUnit: 'minute',
    isGranular: false,
    isUserModifiable: false,
    isSingleScan: false,
    tags: []
  });

  React.useEffect(() => {
    document.addEventListener('keyup', (e) => {
      //Escape
      if (e.keyCode === 27) {
        setShowModal(false);
      }
    });
  }, [apiGet]);

  const fetchScans = useCallback(async () => {
    try {
      const { scans, organizations, schema } = await apiGet<{
        scans: Scan[];
        organizations: Organization[];
        schema: ScanSchema;
      }>('/scans/');
      const tags = await apiGet<OrganizationTag[]>(`/organizations/tags`);
      setScans(scans);
      setScanSchema(schema);
      setOrganizationOptions(
        organizations.map((e) => ({ label: e.name, value: e.id }))
      );
      setTags(tags);
    } catch (e) {
      console.error(e);
    }
  }, [apiGet]);

  const deleteRow = async (index: number) => {
    try {
      const row = scans[index];
      await apiDelete(`/scans/${row.id}`, { body: {} });
      setScans(scans.filter((scan) => scan.id !== row.id));
    } catch (e) {
      setErrors({
        global:
          e.status === 422 ? 'Unable to delete scan' : e.message ?? e.toString()
      });
      console.log(e);
    }
  };

  const onSubmit = async (body: ScanFormValues) => {
    try {
      // For now, parse the arguments as JSON. We'll want to add a GUI for this in the future
      body.arguments = JSON.parse(body.arguments);
      setFrequency(body);

      const scan = await apiPost('/scans/', {
        body: {
          ...body,
          organizations: body.organizations
            ? body.organizations.map((e) => e.value)
            : [],
          tags: body.tags ? body.tags.map((e) => ({ id: e.value })) : []
        }
      });
      setScans(scans.concat(scan));
    } catch (e) {
      setErrors({
        global: e.message ?? e.toString()
      });
      console.log(e);
    }
  };

  const invokeScheduler = async () => {
    setErrors({ ...errors, scheduler: '' });
    try {
      await apiPost('/scheduler/invoke', { body: {} });
    } catch (e) {
      console.error(e);
      setErrors({ ...errors, scheduler: 'Invocation failed.' });
    }
  };

  /**
   * Manually runs a single scan, then immediately invokes the
   * scheduler so the scan is run.
   * @param index Row index
   */
  const runScan = async (index: number) => {
    const row = scans[index];
    try {
      await apiPost(`/scans/${row.id}/run`, { body: {} });
    } catch (e) {
      console.error(e);
      setErrors({ ...errors, scheduler: 'Run failed.' });
    }
    await invokeScheduler();
  };

  return (
    <>
      <Table<Scan> columns={columns} data={scans} fetchData={fetchScans} />
      <br></br>
      <Button type="submit" outline onClick={invokeScheduler}>
        Manually run scheduler
      </Button>
      {errors.scheduler && <p className={classes.error}>{errors.scheduler}</p>}
      <h2>Add a scan</h2>
      {errors.global && <p className={classes.error}>{errors.global}</p>}
      <ScanForm
        organizationOption={organizationOptions}
        tags={tags}
        propValues={values}
        onSubmit={onSubmit}
        type="create"
        scanSchema={scanSchema}
      ></ScanForm>
      <ImportExport<Scan>
        name="scans"
        fieldsToExport={['name', 'arguments', 'frequency']}
        onImport={async (results) => {
          // TODO: use a batch call here instead.
          const createdScans = [];
          for (const result of results) {
            createdScans.push(
              await apiPost('/scans/', {
                body: {
                  ...result,
                  // These fields are initially parsed as strings, so they need
                  // to be converted to objects.
                  arguments: JSON.parse(
                    ((result.arguments as unknown) as string) || ''
                  )
                }
              })
            );
          }
          setScans(scans.concat(...createdScans));
        }}
        getDataToExport={() =>
          scans.map((scan) => ({
            ...scan,
            arguments: JSON.stringify(scan.arguments)
          }))
        }
      />

      {showModal && (
        <div>
          <Overlay />
          <ModalContainer>
            <Modal
              actions={
                <>
                  <Button
                    outline
                    type="button"
                    onClick={() => {
                      setShowModal(false);
                    }}
                  >
                    Cancel
                  </Button>
                  <Button
                    type="button"
                    onClick={() => {
                      deleteRow(selectedRow);
                      setShowModal(false);
                    }}
                  >
                    Delete
                  </Button>
                </>
              }
              title={<h2>Delete scan?</h2>}
            >
              <p>
                Are you sure you would like to delete the{' '}
                <code>{scans[selectedRow].name}</code> scan?
              </p>
            </Modal>
          </ModalContainer>
        </div>
      )}
    </>
  );
}