date-fns#differenceInCalendarDays TypeScript Examples

The following examples show how to use date-fns#differenceInCalendarDays. 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: exposure.ts    From protect-scotland with Apache License 2.0 6 votes vote down vote up
getSelfIsolationRemainingDays = (
  isolationDuration: number,
  contacts?: CloseContact[]
) => {
  const exposureDate = getExposureDate(contacts);
  const remainingDays = exposureDate
    ? isolationDuration - differenceInCalendarDays(Date.now(), exposureDate)
    : -1;
  return remainingDays > 0 ? remainingDays : 0;
}
Example #2
Source File: exposure.ts    From protect-scotland with Apache License 2.0 6 votes vote down vote up
hasCurrentExposure = (
  isolationDuration: number,
  contacts?: CloseContact[]
) => {
  if (!contacts || contacts.length === 0) {
    return false;
  }

  const exposureDate = getExposureDate(contacts);
  const daysDiff = differenceInCalendarDays(new Date(), exposureDate!);
  return daysDiff < isolationDuration;
}
Example #3
Source File: PostDate.tsx    From norfolkdevelopers-website with MIT License 6 votes vote down vote up
export default function PostDate({ date }: PostDateProps) {
  const daysSincePosted = differenceInCalendarDays(new Date(), date);

  if (daysSincePosted > 7) {
    return <>on {dateFormat(date)}</>
  }

  return <span title={dateFormat(date)}>{formatDistance(date, new Date())} ago</span>
}
Example #4
Source File: datecalc.ts    From calendar-hack with MIT License 6 votes vote down vote up
export function calcPlanDates(planWeeks: number, planEndsOn: Date): PlanDates {
    const end = startOfDay(endOfWeek(planEndsOn, { weekStartsOn: 1 }));
    const planStart = subDays(planEndsOn, planWeeks * 7 - 1);
    const start = startOfWeek(planStart, { weekStartsOn: 1 });
    const totalDays = 1 + differenceInCalendarDays(end, start);
    if (0 !== totalDays % 7) {
        throw new Error("total days %7 !==0: " + totalDays);
    }
    const weekCount = totalDays / 7;
    let result = {
        start: start,
        planStartDate: planStart,
        planEndDate: planEndsOn, // before or on race day
        end: end, // race day or beyond
        weekCount: weekCount
    }
    return result;
}
Example #5
Source File: date.ts    From ngx-gantt with MIT License 5 votes vote down vote up
getDaysInQuarter() {
        return differenceInCalendarDays(this.endOfQuarter().addSeconds(1).value, this.startOfQuarter().value);
    }
Example #6
Source File: date.ts    From ngx-gantt with MIT License 5 votes vote down vote up
getDaysInYear() {
        return differenceInCalendarDays(this.endOfYear().addSeconds(1).value, this.startOfYear().value);
    }
Example #7
Source File: NftWidget.hooks.ts    From atlas with GNU General Public License v3.0 4 votes vote down vote up
useNftWidget = (videoId?: string): UseNftWidgetReturn => {
  const { activeMemberId } = useUser()
  const { nft, nftStatus } = useNft(videoId ?? '', { pollInterval: POLL_INTERVAL })
  const {
    isOwner,
    englishTimerState,
    canWithdrawBid,
    needsSettling,
    auctionPlannedEndDate,
    userBid,
    startsAtDate,
    isUserTopBidder,
    userBidUnlockDate,
    saleType,
    startsAtBlock,
    canChangeBid,
    isUserWhitelisted,
    plannedEndAtBlock,
    hasTimersLoaded,
  } = useNftState(nft)

  const { bids: userBids } = useBids(
    {
      where: {
        isCanceled_eq: false,
        nft: { id_eq: nft?.id },
        bidder: { id_eq: activeMemberId },
      },
    },
    {
      fetchPolicy: 'cache-and-network',
      skip: !nft?.id || !activeMemberId,
      onError: (error) =>
        SentryLogger.error('Failed to fetch member bids', 'useNftState', error, {
          data: {
            nft: nft?.id,
            member: activeMemberId,
          },
        }),
    }
  )

  const unwithdrawnUserBids = userBids?.filter(
    (bid) =>
      bid.auction.auctionType.__typename === 'AuctionTypeOpen' &&
      (nftStatus?.status !== 'auction' || bid.auction.id !== nftStatus.auctionId) &&
      bid.auction.winningMemberId !== activeMemberId
  )
  const bidFromPreviousAuction = unwithdrawnUserBids?.[0]

  const owner = nft?.ownerMember

  const { url: ownerAvatarUri } = useMemberAvatar(owner)
  const { url: topBidderAvatarUri } = useMemberAvatar(nftStatus?.status === 'auction' ? nftStatus.topBidder : undefined)

  const { entries: nftHistory } = useNftHistoryEntries(videoId || null, { pollInterval: POLL_INTERVAL })

  switch (nftStatus?.status) {
    case 'auction': {
      return {
        ownerHandle: owner?.handle,
        ownerAvatarUri,
        isOwner,
        needsSettling,
        bidFromPreviousAuction,
        nftStatus: {
          ...nftStatus,
          startsAtDate,
          canWithdrawBid,
          canChangeBid,
          englishTimerState,
          auctionPlannedEndDate,
          topBidderAvatarUri,
          isUserTopBidder,
          userBidUnlockDate,
          startsAtBlock,
          plannedEndAtBlock,
          hasTimersLoaded,
          auctionBeginsInDays: startsAtDate ? differenceInCalendarDays(startsAtDate, new Date()) : 0,
          auctionBeginsInSeconds: startsAtDate ? differenceInSeconds(startsAtDate, new Date()) : 0,
          topBidderHandle: nftStatus.topBidder?.handle,
          userBidAmount: Number(userBid?.amount) || undefined,
          isUserWhitelisted,
        },
        nftHistory,
        saleType,
      }
    }
    case 'buy-now':
      return {
        ownerHandle: owner?.handle,
        ownerAvatarUri,
        isOwner,
        needsSettling,
        bidFromPreviousAuction,
        nftStatus: {
          ...nftStatus,
        },
        nftHistory,
        saleType,
      }
    case 'idle':
      return {
        ownerHandle: owner?.handle,
        ownerAvatarUri,
        isOwner,
        needsSettling,
        bidFromPreviousAuction,
        nftStatus: {
          ...nftStatus,
        },
        nftHistory,
        saleType,
      }
  }

  return null
}
Example #8
Source File: dashboard.tsx    From protect-scotland with Apache License 2.0 4 votes vote down vote up
Dashboard: FC = () => {
  const {t} = useTranslation();
  const {
    initialised,
    enabled,
    status,
    contacts,
    getCloseContacts,
    permissions,
    readPermissions
  } = useExposure();
  const [appState] = useAppState();
  const {checked, paused} = useReminder();
  const navigation = useNavigation();
  const {onboarded, setContext, loadAppData} = useApplication();
  const {
    isolationDuration,
    isolationCompleteDuration,
    latestVersion: appLatestVersion
  } = useSettings();
  const [refreshing, setRefreshing] = useState(false);
  const {
    focusRef: tourFocus,
    focusA11yElement: focusTourElem
  } = useA11yElement();
  const {
    focusRef: dashboardFocus,
    focusA11yElement: focusDashboardElem
  } = useA11yElement();
  const isFocused = useIsFocused();
  const messageOpacity = useRef(new Animated.Value(0)).current;
  const contentOpacity = useRef(new Animated.Value(0)).current;
  const gridOpacity = useRef(new Animated.Value(0)).current;
  const exposureEnabled = useRef(enabled);
  const bluetoothDisabled = useRef(
    status.state === 'disabled' && status.type?.includes(StatusType.bluetooth)
  );
  const pushNotificationsDisabled = useRef(
    permissions.notifications.status === 'not_allowed'
  );

  const [state, setState] = useState<{
    stage: number;
    exposurePrompt: boolean;
    bluetoothPrompt: boolean;
    pushNotificationsPrompt: boolean;
    disabled: boolean;
    current: string;
    isolationMessage: string | null;
    isolationComplete: boolean;
    default: string;
    messages: string[];
  }>({
    stage: onboarded ? -1 : 0,
    exposurePrompt: false,
    bluetoothPrompt: false,
    pushNotificationsPrompt: false,
    disabled: false,
    current: t(
      getMessage({
        onboarded,
        enabled,
        status,
        messages: t('dashboard:tour', {returnObjects: true}),
        stage: onboarded ? -1 : 0,
        paused
      })
    ),
    isolationMessage: null,
    isolationComplete: false,
    messages: t('dashboard:tour', {returnObjects: true}),
    default: t('dashboard:message:standard')
  });

  const version = useVersion();

  const resetToNormal = () =>
    setState((s) => ({
      ...s,
      isolationComplete: false,
      isolationMessage: null
    }));

  const setExposed = () =>
    setState((s) => ({
      ...s,
      isolationComplete: false,
      isolationMessage: t('dashboard:exposed')
    }));

  const setIsolationComplete = () =>
    setState((s) => ({
      ...s,
      isolationComplete: true,
      isolationMessage: t('dashboard:isolationComplete')
    }));

  const processContactsForMessaging = async () => {
    let currentStatus = null;
    try {
      currentStatus = await SecureStore.getItemAsync('niexposuredate');
    } catch (err) {
      await SecureStore.deleteItemAsync('niexposuredate');
      console.log('processContactsForMessaging', err);
    }

    if (currentStatus) {
      const daysDiff = differenceInCalendarDays(
        new Date(),
        new Date(Number(currentStatus))
      );

      const withIsolation = isolationDuration + isolationCompleteDuration;

      if (daysDiff >= withIsolation) {
        await SecureStore.deleteItemAsync('niexposuredate');
        return resetToNormal();
      }

      if (daysDiff >= isolationDuration && daysDiff < withIsolation) {
        return setIsolationComplete();
      }

      if (contacts && contacts.length > 0) {
        return setExposed();
      }
    }

    return resetToNormal();
  };

  const checkLatestExposure = async () => {
    const latestExposure = getExposureDate(contacts);

    if (latestExposure) {
      await SecureStore.setItemAsync(
        'niexposuredate',
        String(latestExposure.getTime())
      );
    }

    processContactsForMessaging();
  };

  const onRefresh = () => {
    setRefreshing(true);
    loadAppData().then(() => setRefreshing(false));
  };

  useEffect(() => {
    onRefresh();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    getCloseContacts();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [status]);

  useEffect(() => {
    checkLatestExposure();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [contacts, status]);

  useFocusEffect(
    React.useCallback(() => {
      if (!isFocused || appState !== 'active') {
        return;
      }

      readPermissions();
    }, [isFocused, appState, readPermissions])
  );

  useEffect(() => {
    setState((s) => ({
      ...s,
      current: t(
        getMessage({
          onboarded,
          enabled,
          status,
          messages: state.messages,
          stage: state.stage,
          paused
        })
      )
    }));

    exposureEnabled.current = enabled;
    bluetoothDisabled.current =
      status.state === 'disabled' &&
      status.type?.includes(StatusType.bluetooth);
    pushNotificationsDisabled.current =
      permissions.notifications.status === 'not_allowed';

    if (!exposureEnabled.current && onboarded) {
      setTimeout(() => {
        if (!exposureEnabled.current) {
          setState((s) => ({
            ...s,
            exposurePrompt: true
          }));
        }
      }, PROMPT_OFFSET);
    } else if (bluetoothDisabled.current && onboarded) {
      setTimeout(() => {
        if (bluetoothDisabled.current) {
          setState((s) => ({
            ...s,
            bluetoothPrompt: true
          }));
        }
      }, PROMPT_OFFSET);
    } else if (pushNotificationsDisabled.current && onboarded) {
      setTimeout(() => {
        if (
          pushNotificationsDisabled.current &&
          exposureEnabled.current &&
          !bluetoothDisabled.current
        ) {
          setState((s) => ({
            ...s,
            pushNotificationsPrompt: true
          }));
        }
      }, PROMPT_OFFSET);
    } else if (onboarded && exposureEnabled.current) {
      setState((s) => ({
        ...s,
        exposurePrompt: false
      }));
    }

    setTimeout(() => checkLatestExposure(), 100);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [enabled, onboarded, status, permissions]);

  const animateTourIn = () => {
    setState((s) => ({...s, disabled: true}));
    Animated.parallel([
      Animated.timing(messageOpacity, {
        toValue: 1,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      }),
      Animated.timing(gridOpacity, {
        toValue: 1,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      })
    ]).start(() => {
      setState((s) => ({...s, disabled: false}));
    });
  };

  const animateTourOut = () => {
    setState((s) => ({...s, disabled: true}));
    Animated.parallel([
      Animated.timing(messageOpacity, {
        toValue: 0,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      }),
      Animated.timing(gridOpacity, {
        toValue: 0,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      })
    ]).start(() => {
      if (state.stage < state.messages.length - 1) {
        setState((s) => ({
          ...s,
          stage: state.stage + 1,
          current: getMessage({
            onboarded,
            enabled,
            status,
            messages: state.messages,
            stage: state.stage + 1
          })
        }));
        animateTourIn();
      } else {
        setState((s) => ({
          ...s,
          stage: -1,
          current: s.default
        }));
        setContext({onboarded: true});
        animateDashboard();
      }
    });
  };

  const animateDashboard = () => {
    setState((s) => ({...s, disabled: true}));
    Animated.parallel([
      Animated.timing(messageOpacity, {
        toValue: 1,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      }),
      Animated.timing(contentOpacity, {
        toValue: 1,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      }),
      Animated.timing(gridOpacity, {
        toValue: 1,
        duration: ANIMATION_DURATION,
        useNativeDriver: true
      })
    ]).start(() => {
      AsyncStorage.setItem('scot.onboarded', 'true');
      setState((s) => ({...s, disabled: false}));
    });
  };

  useEffect(() => {
    if (onboarded) {
      setTimeout(() => animateDashboard(), 200);
    } else {
      setTimeout(() => animateTourIn(), 200);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    if (!state.disabled) {
      focusTourElem();
    }
  }, [focusTourElem, state.disabled]);

  useEffect(() => {
    if (onboarded && !state.disabled) {
      focusDashboardElem();
    }
  }, [focusDashboardElem, onboarded, state.disabled]);

  const handleTour = () => {
    animateTourOut();
  };

  if (!initialised || !checked) {
    return (
      <>
        <Container center="both">
          <ActivityIndicator color={colors.darkGrey} size="large" />
        </Container>
      </>
    );
  }

  return (
    <>
      <Header />
      <ScrollView
        refreshControl={
          <RefreshControl
            refreshing={onboarded && refreshing}
            onRefresh={onRefresh}
          />
        }>
        {onboarded &&
          appLatestVersion &&
          version &&
          appLatestVersion !== version?.display && (
            <View style={blockStyles.block}>
              <NewVersionCard />
              <Spacing s={16} />
            </View>
          )}
        <Spacing s={onboarded ? 15 : 65} />
        {!onboarded && state.stage > -1 && (
          <>
            <View style={styles.dots}>
              {[-1, 0, 1, 2].map((x) => (
                <Animated.View
                  key={`step-${x}`}
                  style={[
                    styles.dot,
                    {
                      backgroundColor:
                        state.stage > x
                          ? colors.primaryPurple
                          : colors.lighterPurple
                    }
                  ]}
                />
              ))}
            </View>
            <A11yView
              ref={tourFocus}
              accessible
              accessibilityHint={
                t('dashboard:tourA11y', {returnObjects: true})[state.stage]
              }
            />
            <Animated.View style={{opacity: messageOpacity}}>
              <TouchableWithoutFeedback onPress={() => handleTour()}>
                <Markdown markdownStyles={markDownStyles}>
                  {state.current}
                </Markdown>
              </TouchableWithoutFeedback>
              <Spacing s={30} />
              <ArrowLink
                onPress={() => {
                  if (!state.disabled) {
                    handleTour();
                  }
                }}
                accessibilityHint={t('dashboard:tourActionHint')}
                invert>
                <Text variant="h3" color="pink" style={styles.nextLink}>
                  {t('dashboard:tourAction')}
                </Text>
              </ArrowLink>
            </Animated.View>
          </>
        )}

        {onboarded && state.isolationMessage && (
          <>
            <Animated.View
              style={{
                opacity: messageOpacity
              }}>
              <View accessible ref={dashboardFocus}>
                <Markdown
                  markdownStyles={
                    state.isolationComplete
                      ? markDownStyles
                      : markDownStylesExposed
                  }>
                  {state.isolationMessage}
                </Markdown>
              </View>
              {!state.isolationComplete && (
                <>
                  <Spacing s={30} />
                  <ArrowLink
                    onPress={() =>
                      navigation.navigate(ScreenNames.closeContact)
                    }
                    accessibilityHint={t('dashboard:exposedAction')}
                    invert>
                    <Text variant="h3" color="pink" style={styles.nextLink}>
                      {t('dashboard:tourAction')}
                    </Text>
                  </ArrowLink>
                </>
              )}
              {state.isolationComplete && (
                <>
                  <Spacing s={20} />
                  <Text style={blockStyles.block} inline color="darkGrey">
                    {t('dashboard:isolationCompleteSupplemental')}
                  </Text>
                </>
              )}
            </Animated.View>
            <Spacing s={30} />
            <Animated.View
              style={[{opacity: contentOpacity}, blockStyles.block]}>
              <Message />
            </Animated.View>
          </>
        )}

        {onboarded && !state.isolationMessage && (
          <Animated.View style={{opacity: messageOpacity}}>
            <View accessible ref={dashboardFocus}>
              <Markdown markdownStyles={markDownStyles}>
                {state.current}
              </Markdown>
            </View>
            {state.stage === -1 && !paused && (
              <>
                <Spacing s={20} />
                <Text style={blockStyles.block} inline color="darkGrey">
                  {t(`dashboard:message:bluetooth:${Platform.OS}`)}
                </Text>
              </>
            )}
            {state.stage === -1 && paused && (
              <>
                <Spacing s={20} />
                <Text style={blockStyles.block} inline color="darkGrey">
                  {t('dashboard:message:pausedSupplemental')}
                </Text>
              </>
            )}
          </Animated.View>
        )}
        <Spacing s={30} />
        <Grid
          onboarded={onboarded}
          stage={state.stage}
          opacity={gridOpacity}
          onboardingCallback={() => handleTour()}
        />
        {state.isolationMessage && <Spacing s={34} />}
        {onboarded && !state.isolationMessage && (
          <>
            <Animated.View
              style={[{opacity: contentOpacity}, blockStyles.block]}>
              <Spacing s={30} />
              <Message />
              <Spacing s={16} />
              <Message
                image={RestrictionsImage}
                markdown={t('restrictions:message')}
                accessibilityLabel={t('restrictions:a11y:label')}
                accessibilityHint={t('restrictions:a11y:hint')}
                link={t('links:r')}
              />
              <Spacing s={45} />
            </Animated.View>
          </>
        )}
        {onboarded && (
          <Text variant="h4" color="primaryPurple" align="center">
            {t('dashboard:thanks')}
          </Text>
        )}
        <Spacing s={60} />
      </ScrollView>
      {checked && !paused && state.exposurePrompt && (
        <ExposureNotificationsModal
          isVisible={state.exposurePrompt}
          onBackdropPress={() =>
            setState((s) => ({...s, exposurePrompt: false}))
          }
          onClose={() => setState((s) => ({...s, exposurePrompt: false}))}
        />
      )}
      {checked && !paused && state.bluetoothPrompt && (
        <BluetoothNotificationsModal
          isVisible={state.bluetoothPrompt}
          onBackdropPress={() =>
            setState((s) => ({...s, bluetoothPrompt: false}))
          }
          onClose={() => setState((s) => ({...s, bluetoothPrompt: false}))}
        />
      )}
      {checked && !paused && state.pushNotificationsPrompt && (
        <PushNotificationsModal
          isVisible={state.pushNotificationsPrompt}
          onBackdropPress={() =>
            setState((s) => ({...s, pushNotificationsPrompt: false}))
          }
          onClose={() =>
            setState((s) => ({...s, pushNotificationsPrompt: false}))
          }
        />
      )}
    </>
  );
}
Example #9
Source File: DomainDetails.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
DomainDetails: React.FC<Props> = (props) => {
  const { domainId } = props;
  const { getDomain } = useDomainApi(false);
  const { user } = useAuthContext();
  const [domain, setDomain] = useState<Domain>();
  const classes = useStyles();
  const history = useHistory();

  const fetchDomain = useCallback(async () => {
    try {
      setDomain(undefined);
      const result = await getDomain(domainId);
      setDomain(result);
    } catch (e) {
      console.error(e);
    }
  }, [domainId, getDomain]);

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

  const webInfo = useMemo(() => {
    if (!domain) {
      return [];
    }
    const categoriesToProducts: Record<string, Set<string>> = {};
    for (const service of domain.services) {
      for (const product of service.products) {
        const version = product.version ? ` ${product.version}` : '';
        const value = product.name + version;
        const name =
          product.tags && product.tags.length > 0 ? product.tags[0] : 'Misc';
        if (!categoriesToProducts[name]) {
          categoriesToProducts[name] = new Set();
        }
        categoriesToProducts[name].add(value);
      }
    }
    return Object.entries(categoriesToProducts).reduce(
      (acc, [name, value]) => [
        ...acc,
        {
          label: name,
          value: Array.from(value).join(', ')
        }
      ],
      [] as any
    );
  }, [domain]);

  const overviewInfo = useMemo(() => {
    if (!domain) {
      return [];
    }
    const ret = [];
    if (domain.ip) {
      ret.push({
        label: 'IP',
        value: domain.ip
      });
    }
    ret.push({
      label: 'First Seen',
      value: `${differenceInCalendarDays(
        Date.now(),
        parseISO(domain.createdAt)
      )} days ago`
    });
    ret.push({
      label: 'Last Seen',
      value: `${differenceInCalendarDays(
        Date.now(),
        parseISO(domain.updatedAt)
      )} days ago`
    });
    if (domain.country) {
      ret.push({
        label: 'Country',
        value: domain.country
      });
    }
    if (domain.cloudHosted) {
      ret.push({
        label: 'Cloud Hosted',
        value: 'Yes'
      });
    }
    ret.push({
      label: 'Organization',
      value: domain.organization.name
    });
    return ret;
  }, [domain]);

  const [hiddenRows, setHiddenRows] = React.useState<{
    [key: string]: boolean;
  }>({});

  const formatBytes = (bytes: number, decimals = 2): string => {
    if (bytes === 0) return '0 Bytes';

    const k = 1024;
    const dm = decimals < 0 ? 0 : decimals;
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];

    const i = Math.floor(Math.log(bytes) / Math.log(k));

    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
  };

  const generateWebpageList = (tree: any, prefix = '') => {
    return (
      <List
        className={`${classes.listRoot}${prefix ? ' ' + classes.nested : ''}`}
      >
        {Object.keys(tree).map((key) => {
          const isWebpage =
            'url' in tree[key] && typeof tree[key]['url'] === 'string';
          if (!isWebpage) {
            const newPrefix = prefix + '/' + key;
            return (
              <>
                <ListItem
                  button
                  onClick={() => {
                    setHiddenRows((hiddenRows: any) => {
                      hiddenRows[newPrefix] =
                        newPrefix in hiddenRows ? !hiddenRows[newPrefix] : true;
                      return { ...hiddenRows };
                    });
                  }}
                  key={newPrefix}
                >
                  <ListItemText primary={(prefix ? '' : '/') + key + '/'} />
                  {hiddenRows[newPrefix] ? <ExpandLess /> : <ExpandMore />}
                </ListItem>
                <Collapse
                  in={!hiddenRows[newPrefix]}
                  timeout="auto"
                  unmountOnExit
                >
                  {generateWebpageList(tree[key], newPrefix)}
                </Collapse>
              </>
            );
          }
          const page = tree[key] as Webpage;
          const parsed = new URL(page.url);
          const split = parsed.pathname
            .replace(/\/$/, '') // Remove trailing slash
            .split('/');
          return (
            <ListItem
              button
              divider={true}
              key={page.url}
              onClick={() => window.open(page.url, '_blank')}
            >
              <ListItemText
                primary={(prefix ? '' : '/') + split.pop()}
                secondary={
                  page.status + ' • ' + formatBytes(page.responseSize ?? 0, 1)
                }
              ></ListItemText>
            </ListItem>
          );
        })}
      </List>
    );
  };

  if (!domain) {
    return null;
  }

  const url =
    (domain.services.find((service) => service.port === 443)
      ? 'https://'
      : 'http://') + domain.name;

  const { webpages = [] } = domain;
  webpages.sort((a, b) => (a.url > b.url ? 1 : -1));
  const webpageTree = generateWebpageTree(webpages);
  const webpageList = generateWebpageList(webpageTree);

  return (
    <Paper classes={{ root: classes.root }}>
      <div className={classes.title}>
        <h4>
          <Link to={`/inventory/domain/${domain.id}`}>{domain.name}</Link>
        </h4>

        <a href={url} target="_blank" rel="noopener noreferrer">
          <LinkOffIcon />
        </a>
      </div>
      <div className={classes.inner}>
        {overviewInfo.length > 0 && (
          <div className={classes.section}>
            <h4 className={classes.subtitle}>Overview</h4>
            <DefinitionList items={overviewInfo} />
          </div>
        )}
        {webInfo.length > 0 && (
          <div className={classes.section}>
            <h4 className={classes.subtitle}>Known Products</h4>
            <DefinitionList items={webInfo} />
          </div>
        )}

        {domain.vulnerabilities.length > 0 && (
          <div className={classes.section}>
            <h4 className={classes.subtitle}>Vulnerabilities</h4>
            <Accordion className={classes.accordionHeaderRow} disabled>
              <AccordionSummary>
                <Typography className={classes.accordionHeading}>
                  Title
                </Typography>
                <Typography className={classes.vulnDescription}>
                  Serverity
                </Typography>
                <Typography className={classes.vulnDescription}>
                  State
                </Typography>
                <Typography className={classes.vulnDescription}>
                  Created
                </Typography>
              </AccordionSummary>
            </Accordion>
            {domain.vulnerabilities.map((vuln) => (
              <Accordion
                className={classes.accordion}
                key={vuln.id}
                onClick={(event) => {
                  event.stopPropagation();
                  history.push('/inventory/vulnerability/' + vuln.id);
                }}
              >
                <AccordionSummary>
                  <Typography className={classes.accordionHeading}>
                    {vuln.title}
                  </Typography>
                  <Typography className={classes.vulnDescription}>
                    {vuln.severity}
                  </Typography>
                  <Typography className={classes.vulnDescription}>
                    {vuln.state}
                  </Typography>
                  <Typography className={classes.vulnDescription}>
                    {vuln.createdAt
                      ? `${differenceInCalendarDays(
                          Date.now(),
                          parseISO(vuln.createdAt)
                        )} days ago`
                      : ''}
                  </Typography>
                </AccordionSummary>
              </Accordion>
            ))}
          </div>
        )}
        {domain.services.length > 0 && (
          <div className={classes.section}>
            <h4 className={classes.subtitle}>Ports</h4>
            <Accordion className={classes.accordionHeaderRow} disabled>
              <AccordionSummary expandIcon={<ExpandMore />}>
                <Typography className={classes.accordionHeading}>
                  Port
                </Typography>
                <Typography className={classes.accordionHeading}>
                  Products
                </Typography>
                <Typography className={classes.lastSeen}>Last Seen</Typography>
              </AccordionSummary>
            </Accordion>
            {domain.services.map((service) => {
              const products = service.products
                .map(
                  (product) =>
                    product.name +
                    (product.version ? ` ${product.version}` : '')
                )
                .join(', ');
              return (
                <Accordion className={classes.accordion} key={service.id}>
                  <AccordionSummary expandIcon={<ExpandMore />}>
                    <Typography className={classes.accordionHeading}>
                      {service.port}
                    </Typography>
                    <Typography className={classes.accordionHeading}>
                      {products}
                    </Typography>
                    <Typography className={classes.lastSeen}>
                      {service.lastSeen
                        ? `${differenceInCalendarDays(
                            Date.now(),
                            parseISO(service.lastSeen)
                          )} days ago`
                        : ''}
                    </Typography>
                  </AccordionSummary>
                  {service.products.length > 0 && (
                    <AccordionDetails>
                      <DefinitionList
                        items={[
                          {
                            label: 'Products',
                            value: products
                          },
                          {
                            label: 'Banner',
                            value:
                              (user?.userType === 'globalView' ||
                                user?.userType === 'globalAdmin') &&
                              service.banner
                                ? service.banner
                                : 'None'
                          }
                        ]}
                      />
                    </AccordionDetails>
                  )}
                </Accordion>
              );
            })}
          </div>
        )}
        {domain.webpages?.length > 0 && (
          <div className={classes.section}>
            <h4 className={classes.subtitle}>Site Map</h4>
            {webpageList}
          </div>
        )}
      </div>
    </Paper>
  );
}
Example #10
Source File: columns.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
createColumns = (updateVulnerability: any) =>
  [
    {
      Header: 'Vulnerability',
      accessor: 'title',
      Cell: ({ value, row }: CellProps<Vulnerability>) =>
        row.original.cve ? (
          <a
            href={`https://nvd.nist.gov/vuln/detail/${row.original.cve}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            {value} {extLink}
          </a>
        ) : (
          <p>{row.original.title}</p>
        ),
      width: 800,
      Filter: ColumnFilter
    },
    {
      Header: 'Severity',
      id: 'severity',
      accessor: ({ severity, substate }) => (
        <span
          style={{
            borderBottom: `6px solid ${getSeverityColor({
              id: severity ?? ''
            })}`,
            width: '80px'
          }}
          // className={substate === 'unconfirmed' ? classes.severity : undefined}
        >
          {severity}
        </span>
      ),
      width: 100,
      Filter: selectFilter(['Low', 'Medium', 'High', 'Critical', 'None'])
    },
    {
      Header: 'KEV',
      accessor: 'isKev',
      Cell: ({ value, row }: CellProps<Vulnerability>) =>
        value ? (
          <a
            href={`https://www.cisa.gov/known-exploited-vulnerabilities-catalog`}
            target="_blank"
            rel="noopener noreferrer"
          >
            Yes {extLink}
          </a>
        ) : (
          <p>No</p>
        ),
      width: 50,
      Filter: selectFilter([
        { value: 'true', label: 'Yes' },
        { value: 'false', label: 'No' }
      ])
    },
    {
      Header: 'Domain',
      id: 'domain',
      accessor: ({ domain }) => (
        <Link to={`/inventory/domain/${domain?.id}`}>{domain?.name}</Link>
      ),
      width: 800,
      Filter: ColumnFilter
    },
    {
      Header: 'Product',
      id: 'cpe',
      accessor: ({ cpe, service }) => {
        const product =
          service &&
          service.products.find(
            (product) => cpe && product.cpe && cpe.includes(product.cpe)
          );
        if (product)
          return product.name + (product.version ? ' ' + product.version : '');
        else return cpe;
      },
      width: 100,
      Filter: ColumnFilter
    },
    {
      Header: 'Days Open',
      id: 'createdAt',
      accessor: ({ createdAt, actions = [] }) => {
        // Calculates the total number of days a vulnerability has been open
        let daysOpen = 0;
        let lastOpenDate = createdAt;
        let lastState = 'open';
        actions.reverse();
        for (const action of actions) {
          if (action.state === 'closed' && lastState === 'open') {
            daysOpen += differenceInCalendarDays(
              parseISO(action.date),
              parseISO(lastOpenDate)
            );
            lastState = 'closed';
          } else if (action.state === 'open' && lastState === 'closed') {
            lastOpenDate = action.date;
            lastState = 'open';
          }
        }
        if (lastState === 'open') {
          daysOpen += differenceInCalendarDays(
            new Date(),
            parseISO(lastOpenDate)
          );
        }
        return daysOpen;
      },
      disableFilters: true
    },
    {
      Header: 'Status',
      id: 'state',
      width: 100,
      maxWidth: 200,
      accessor: 'state',
      Filter: selectFilter([
        'open',
        'open (unconfirmed)',
        'open (exploitable)',
        'closed',
        'closed (false positive)',
        'closed (accepted risk)',
        'closed (remediated)'
      ]),
      Cell: ({ row }: CellProps<Vulnerability>) => (
        <Dropdown
          id="state-dropdown"
          name="state-dropdown"
          onChange={(e) => {
            updateVulnerability(row.index, {
              substate: e.target.value
            });
          }}
          value={row.original.substate}
          style={{ display: 'inline-block', width: '200px' }}
        >
          <option value="unconfirmed">Open (Unconfirmed)</option>
          <option value="exploitable">Open (Exploitable)</option>
          <option value="false-positive">Closed (False Positive)</option>
          <option value="accepted-risk">Closed (Accepted Risk)</option>
          <option value="remediated">Closed (Remediated)</option>
        </Dropdown>
      )
    },
    {
      Header: 'Details',
      Cell: ({ row }: CellProps<Vulnerability>) => (
        <Link
          to={`/inventory/vulnerability/${row.original.id}`}
          style={{
            fontSize: '14px',
            cursor: 'pointer',
            color: '#484D51',
            textDecoration: 'none'
          }}
        >
          DETAILS
        </Link>
      ),
      disableFilters: true
    }
  ] as Column<Vulnerability>[]