@ant-design/icons#CheckOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#CheckOutlined. 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: index.tsx    From nanolooker with MIT License 6 votes vote down vote up
LivePreference: React.FC<Props> = ({ isDetailed }) => {
  const { t } = useTranslation();
  const {
    disableLiveTransactions,
    setDisableLiveTransactions,
  } = React.useContext(PreferencesContext);

  return (
    <Row>
      <Col xs={isDetailed ? 24 : 18}>
        <Text className={isDetailed ? "preference-detailed-title" : ""}>
          {t("preferences.liveTransactions")}
        </Text>
      </Col>
      {isDetailed ? (
        <Col xs={18}>
          <Text>
            {t("preferences.liveTransactionsDetailed")}
          </Text>
        </Col>
      ) : null}

      <Col xs={6} style={{ textAlign: "right" }}>
        <Switch
          checkedChildren={<CheckOutlined />}
          unCheckedChildren={<CloseOutlined />}
          onChange={(checked: boolean) => {
            setDisableLiveTransactions(!checked);
          }}
          checked={!disableLiveTransactions}
        />
      </Col>
    </Row>
  );
}
Example #2
Source File: index.tsx    From nanolooker with MIT License 6 votes vote down vote up
ThemePreference: React.FC<Props> = ({ isDetailed }) => {
  const { t } = useTranslation();
  const { theme, setTheme } = React.useContext(PreferencesContext);

  return (
    <Row>
      <Col xs={isDetailed ? 24 : 18}>
        <Text className={isDetailed ? "preference-detailed-title" : ""}>
          {t("preferences.darkMode")}
        </Text>
      </Col>
      {isDetailed ? (
        <Col xs={18}>
          <Text>{t("preferences.darkModeDetailed")}</Text>
        </Col>
      ) : null}

      <Col xs={6} style={{ textAlign: "right" }}>
        <Switch
          checkedChildren={<CheckOutlined />}
          unCheckedChildren={<CloseOutlined />}
          onChange={(checked: boolean) => {
            setTheme(checked ? Theme.DARK : Theme.LIGHT);
          }}
          checked={theme === Theme.DARK}
        />
      </Col>
    </Row>
  );
}
Example #3
Source File: BasicUnControlledTabPanel.tsx    From datart with Apache License 2.0 6 votes vote down vote up
EditableTabHeader: FC<{
  label: string;
  rowKey: string;
  editable?: Boolean;
  onChange: (key: string, value: string) => void;
}> = memo(({ label, rowKey, editable = true, onChange }) => {
  const [isEditing, setIsEditing] = useState(false);

  const render = () => {
    if (!editable) {
      return <span>{label}</span>;
    }
    return isEditing ? (
      <Search
        enterButton={<CheckOutlined />}
        size="small"
        onSearch={value => {
          if (!!value) {
            setIsEditing(false);
            onChange(rowKey, value);
          }
        }}
      />
    ) : (
      <Space>
        <Button
          block
          type="text"
          icon={<EditOutlined />}
          onClick={() => setIsEditing(true)}
        ></Button>
        <span>{label}</span>
      </Space>
    );
  };

  return render();
})
Example #4
Source File: StudentBanner.tsx    From office-hours with GNU General Public License v3.0 6 votes vote down vote up
function QuestionDetailRow({ studentQuestion }: { studentQuestion: Question }) {
  return (
    <QuestionDetails>
      <ColWithRightMargin flex="4 4">
        <InfoHeader>question</InfoHeader>
        <div>{studentQuestion.text}</div>
      </ColWithRightMargin>
      <Col flex="0.5 0.5 95px">
        <InfoHeader>type</InfoHeader>
        <div>{studentQuestion.questionType}</div>
      </Col>
      <Col flex="0 0 89px">
        <InfoHeader>groupable</InfoHeader>
        <div>
          {studentQuestion.groupable ? <CheckOutlined /> : <CloseOutlined />}
        </div>
      </Col>
    </QuestionDetails>
  );
}
Example #5
Source File: PluginUpdateButton.tsx    From posthog-foss with MIT License 6 votes vote down vote up
PluginUpdateButton = ({ updateStatus, pluginId, rearranging }: PluginUpdateButtonProps): JSX.Element => {
    const { editPlugin, updatePlugin } = useActions(pluginsLogic)
    const { pluginsUpdating } = useValues(pluginsLogic)
    return (
        <Button
            type={updateStatus?.updated ? 'default' : 'primary'}
            className="padding-under-500"
            onClick={() => (updateStatus?.updated ? editPlugin(pluginId) : updatePlugin(pluginId))}
            loading={pluginsUpdating.includes(pluginId)}
            icon={updateStatus?.updated ? <CheckOutlined /> : <CloudDownloadOutlined />}
            disabled={rearranging}
            data-attr="plugin-update"
        >
            <span className="show-over-500">{updateStatus?.updated ? 'Updated' : 'Update'}</span>
        </Button>
    )
}
Example #6
Source File: UnControlledTableHeaderPanel.tsx    From datart with Apache License 2.0 6 votes vote down vote up
EditableLabel: FC<{
  label: string;
  editable?: Boolean;
  onChange: (value: string) => void;
}> = memo(({ label, editable = true, onChange }) => {
  const [isEditing, setIsEditing] = useState(false);

  const render = () => {
    if (!editable) {
      return <span>{label}</span>;
    }
    return isEditing ? (
      <Search
        enterButton={<CheckOutlined />}
        placeholder={label}
        size="small"
        onSearch={value => {
          if (!!value) {
            setIsEditing(false);
            onChange(value);
          }
        }}
      />
    ) : (
      <>
        <span>{label}</span>
        <Button
          type="text"
          size="small"
          icon={<EditOutlined />}
          onClick={() => setIsEditing(true)}
        ></Button>
      </>
    );
  };

  return <StyledEditableLabel>{render()}</StyledEditableLabel>;
})
Example #7
Source File: FunctionDebuggerStatusbar.tsx    From next-basics with GNU General Public License v3.0 5 votes vote down vote up
export function FunctionDebuggerStatusbar({
  coverage,
  testStats,
}: FunctionDebuggerStatusbarProps): React.ReactElement {
  const coverageIsOk = coverage && coverage.status !== "failed";
  const totalCoverage = useMemo(
    () => (coverageIsOk ? getTotalCoverage(coverage) : null),
    [coverageIsOk, coverage]
  );
  const coverageStats = useMemo(
    () => (coverageIsOk ? getCoverageStats(coverage) : null),
    [coverageIsOk, coverage]
  );

  return (
    <div className={styles.debuggerStatusbar} data-override-theme="dark">
      <div className={styles.coverage}>
        {coverage == null ? (
          <span>
            <span className={styles.coverageIcon}>
              <QuestionOutlined />
            </span>
            <span>Coverage: expired</span>
          </span>
        ) : testStats?.failed > 0 ? (
          <span className={styles.hasFailedTests}>
            <span className={styles.coverageIcon}>
              <WarningOutlined />
            </span>
            <span>
              {testStats.failed}/{testStats.total} tests failed!
            </span>
          </span>
        ) : coverageIsOk ? (
          <>
            <span
              className={
                totalCoverage < 60
                  ? styles.coverageLow
                  : totalCoverage < 90
                  ? styles.coverageMedium
                  : totalCoverage < 100
                  ? styles.coverageHigh
                  : styles.coverageFull
              }
            >
              <span className={styles.coverageIcon}>
                {totalCoverage < 100 ? <WarningOutlined /> : <CheckOutlined />}
              </span>
              <span>Coverage: {totalCoverage}%</span>
            </span>
            {Object.entries(coverageStats).map(
              ([type, { covered, total, percentage }]) => (
                <span key={type} className={styles.subCoverage}>
                  <span>{upperFirst(type)}: </span>
                  <span>
                    {percentage}% ({covered}/{total})
                  </span>
                </span>
              )
            )}
          </>
        ) : (
          <span className={styles.coverageFailed}>
            <span className={styles.coverageIcon}>
              <CloseOutlined />
            </span>
            <span>{(coverage as RawCoverageFailed).error}</span>
          </span>
        )}
      </div>
    </div>
  );
}
Example #8
Source File: index.tsx    From nanolooker with MIT License 5 votes vote down vote up
FilterTransactionsPreferences: React.FC<Props> = ({ isDetailed }) => {
  const { t } = useTranslation();
  const {
    filterTransactions,
    filterTransactionsRange,
    setFilterTransactions,
  } = React.useContext(PreferencesContext);

  const range = `${
    units.find(({ raw }) => raw === filterTransactionsRange[1])?.display
  } - ${
    units.find(({ raw }) => raw === filterTransactionsRange[0])?.display ||
    t("pages.preferences.noLimit")
  }`;

  return (
    <>
      <Row>
        <Col xs={18}>
          <Text style={{ whiteSpace: "nowrap", paddingRight: "18px" }}>
            {isEqual(filterTransactionsRange, DEFAULT_UNITS) ? (
              t("preferences.filterTransactions")
            ) : (
              <>
                {t("preferences.filterTransactionsRange")}
                <br />
                <strong>{range}</strong>
              </>
            )}
          </Text>

          <br />
          <Link to="/preferences" style={{ whiteSpace: "nowrap" }}>
            {t("preferences.filterTransactionsRangeDetailed")}
          </Link>
        </Col>

        <Col xs={6} style={{ textAlign: "right" }}>
          <Switch
            checkedChildren={<CheckOutlined />}
            unCheckedChildren={<CloseOutlined />}
            onChange={(checked: boolean) => {
              setFilterTransactions(checked);
            }}
            checked={filterTransactions}
          />
        </Col>
      </Row>
    </>
  );
}
Example #9
Source File: index.tsx    From nanolooker with MIT License 5 votes vote down vote up
NatriconsPreferences: React.FC<Props> = ({ isDetailed }) => {
  const { t } = useTranslation();
  const [account] = React.useState(
    DEVELOPER_FUND_ACCOUNTS[
      Math.floor(Math.random() * DEVELOPER_FUND_ACCOUNTS.length)
    ],
  );
  const { natricons, setNatricons } = React.useContext(PreferencesContext);

  return (
    <div style={{ display: "flex", alignItems: "flex-start" }}>
      <Natricon
        account={account}
        style={{
          margin: "-12px -6px -18px -18px ",
          width: "80px",
          height: "80px",
        }}
      />

      <Row style={{ width: "100%" }}>
        <Col xs={isDetailed ? 24 : 18}>
          <Text className={isDetailed ? "preference-detailed-title" : ""}>
            {t("preferences.natricons")}
          </Text>
        </Col>
        {isDetailed ? (
          <Col xs={18}>
            <Text>{t("preferences.natriconsDetailed")}</Text>
          </Col>
        ) : null}

        <Col xs={6} style={{ textAlign: "right" }}>
          <Switch
            checkedChildren={<CheckOutlined />}
            unCheckedChildren={<CloseOutlined />}
            onChange={(checked: boolean) => {
              setNatricons(checked);
            }}
            checked={natricons}
          />
        </Col>
      </Row>
    </div>
  );
}
Example #10
Source File: ErrorCard.tsx    From jitsu with MIT License 5 votes vote down vote up
ErrorCard: FC<ErrorCardProps> = ({
  title,
  icon,
  error,
  description,
  descriptionWithContacts,
  stackTrace,
  className,
  onReload,
}) => {
  if (description === undefined && error !== undefined) {
    description = error.message
  }
  if (stackTrace === undefined && error !== undefined) {
    stackTrace = error.stack
  }
  return (
    <Card bordered={false} className={cn(className, "max-h-full")}>
      <Card.Meta
        avatar={icon || <ExclamationCircleOutlined className={styles.icon} />}
        title={title || "An Error Occured"}
        description={
          <>
            <Fragment key="description">
              {description !== undefined ? (
                description
              ) : (
                <span>
                  {descriptionWithContacts !== undefined ? (
                    <>
                      {descriptionWithContacts}
                      {descriptionWithContacts && <br />}
                    </>
                  ) : (
                    <>
                      {"The application component crashed because of an internal error."}
                      <br />
                    </>
                  )}
                  {"Please, try to reload the page first and if the problem is still present contact us at"}{" "}
                  <Typography.Paragraph copyable={{ tooltips: false }} className="inline">
                    {"[email protected]"}
                  </Typography.Paragraph>{" "}
                  {"and our engineers will fix the problem asap."}
                </span>
              )}
            </Fragment>
            {stackTrace && (
              <Collapse key="stack-trace" bordered={false} className={`mt-2 ${styles.stackTraceCard}`}>
                <Collapse.Panel key={1} header="Error Stack Trace">
                  <div className="overflow-y-auto">
                    <Typography.Paragraph
                      copyable={{
                        text: stackTrace,
                        icon: [<CopyOutlined />, <CheckOutlined />],
                      }}
                      className={`flex flex-row ${styles.errorStackContainer}`}
                    >
                      <pre className="text-xs">{stackTrace}</pre>
                    </Typography.Paragraph>
                  </div>
                </Collapse.Panel>
              </Collapse>
            )}
            {onReload && (
              <div key="reload-button" className="flex justify-center items-center mt-2">
                <Button type="default" onClick={onReload} icon={<ReloadOutlined />}>{`Reload`}</Button>
              </div>
            )}
          </>
        }
      />
    </Card>
  )
}
Example #11
Source File: index.tsx    From foodie with MIT License 5 votes vote down vote up
FollowButton: React.FC<IProps> = (props) => {
    const [isFollowing, setIsFollowing] = useState(props.isFollowing);
    const [isLoading, setLoading] = useState(false);
    const didMount = useDidMount();

    useEffect(() => {
        setIsFollowing(props.isFollowing);
    }, [props.isFollowing])

    const dispatchFollow = async () => {
        try {
            setLoading(true);
            if (isFollowing) {
                const result = await unfollowUser(props.userID);
                didMount && setIsFollowing(result.state);
            } else {
                const result = await followUser(props.userID);
                didMount && setIsFollowing(result.state);
            }

            didMount && setLoading(false);
        } catch (e) {
            didMount && setLoading(false);
            console.log(e);
        }
    };

    return (
        <button
            className={`${isFollowing && 'hover:bg-gray-200 bg-indigo-100 !border !border-indigo-500 text-indigo-700 dark:bg-indigo-1100 dark:text-indigo-400 dark:hover:bg-indigo-900 dark:hover:text-white'} flex items-center ${props.size === 'sm' && '!py-2 !px-3 !text-sm'}`}
            disabled={isLoading}
            onClick={dispatchFollow}
        >
            {isFollowing ? <CheckOutlined /> : <UserAddOutlined />}
                &nbsp;&nbsp;
            <span className={`${props.size === 'sm' && 'text-sm'}`}>
                {isLoading
                    ? 'Following'
                    : !isLoading && !isFollowing
                        ? 'Follow'
                        : 'Following'}
            </span>
        </button>
    );
}
Example #12
Source File: AggregationAction.tsx    From datart with Apache License 2.0 5 votes vote down vote up
AggregationAction: FC<{
  config: ChartDataSectionField;
  onConfigChange: (
    config: ChartDataSectionField,
    needRefresh?: boolean,
  ) => void;
  mode?: 'menu';
}> = ({ config, onConfigChange, mode }) => {
  const t = useI18NPrefix(`viz.common.enum.aggregateTypes`);
  const actionNeedNewRequest = true;
  const [aggregate, setAggregate] = useState(config?.aggregate);

  const onChange = selectedValue => {
    const newConfig = updateBy(config, draft => {
      draft.aggregate = selectedValue;
    });
    setAggregate(selectedValue);
    onConfigChange?.(newConfig, actionNeedNewRequest);
  };

  const renderOptions = mode => {
    if (mode === 'menu') {
      return (
        <>
          {AggregateFieldSubAggregateType[
            ChartDataSectionFieldActionType.Aggregate
          ]?.map(agg => {
            return (
              <Menu.Item
                key={agg}
                eventKey={agg}
                icon={aggregate === agg ? <CheckOutlined /> : ''}
                onClick={() => onChange(agg)}
              >
                {t(agg)}
              </Menu.Item>
            );
          })}
        </>
      );
    }

    return (
      <Radio.Group onChange={e => onChange(e.target?.value)} value={aggregate}>
        <Space direction="vertical">
          {AggregateFieldSubAggregateType[
            ChartDataSectionFieldActionType.Aggregate
          ]?.map(agg => {
            return (
              <Radio key={agg} value={agg}>
                {t(agg)}
              </Radio>
            );
          })}
        </Space>
      </Radio.Group>
    );
  };

  return renderOptions(mode);
}
Example #13
Source File: InputConfirm.tsx    From iot-center-v2 with MIT License 5 votes vote down vote up
InputConfirm: React.FC<TInputConfirmProps> = (props) => {
  const {value, onValueChange, tooltip} = props
  const [newValue, setNewValue] = useState('')
  const [isEditing, setIsEditing] = useState(false)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    setNewValue(value ?? '')
  }, [value])

  return (
    <Row>
      <Col flex="auto">
        {!isEditing ? (
          value ?? ''
        ) : (
          <Tooltip title={tooltip ?? ''}>
            <Input
              value={newValue}
              onChange={(e) => setNewValue(e.target.value)}
              style={{width: '100%'}}
            />
          </Tooltip>
        )}
      </Col>
      <Col>
        {!isEditing ? (
          <Tooltip title={'Edit'} key="edit">
            <Button
              size="small"
              type="text"
              icon={<EditOutlined />}
              onClick={() => setIsEditing(true)}
            ></Button>
          </Tooltip>
        ) : (
          <>
            <Tooltip title={loading ? '' : 'Cancel'} color="red" key="cancel">
              <Button
                size="small"
                type="text"
                disabled={loading}
                icon={<CloseOutlined />}
                onClick={() => {
                  setIsEditing(false)
                }}
                danger
              ></Button>
            </Tooltip>
            <Tooltip title={loading ? '' : 'Save'} color="green">
              <Button
                size="small"
                type="text"
                disabled={loading}
                loading={loading}
                style={{color: 'green'}}
                icon={<CheckOutlined />}
                onClick={async () => {
                  try {
                    setLoading(true)
                    await (onValueChange ?? (() => undefined))(newValue)
                  } finally {
                    setIsEditing(false)
                    setLoading(false)
                  }
                }}
              ></Button>
            </Tooltip>
          </>
        )}
      </Col>
    </Row>
  )
}
Example #14
Source File: CurrentGroupList.tsx    From office-hours with GNU General Public License v3.0 5 votes vote down vote up
export function CurrentGroupList({
  group,
  queueId,
  courseId,
}: {
  group?: QuestionGroup;
  queueId: number;
  courseId: number;
}): ReactElement {
  return (
    <div>
      <Header>
        <div>
          <strong>{`${group?.creator.name}'s Group Session`}</strong>
          <Description>
            {group?.questions.map((q) => q.creator.name).join(", ")}
          </Description>
        </div>
        <div>
          <Tooltip title="Finish Helping">
            <FinishHelpingButton
              icon={<CheckOutlined />}
              onClick={() => API.questions.resolveGroup(group?.id, queueId)}
              data-cy="finish-helping-button"
            />
          </Tooltip>
        </div>
      </Header>
      {group?.questions.map((q) => (
        <div key={q.id}>
          <TAQueueDetailQuestion
            courseId={courseId}
            question={q}
            queueId={queueId}
            showName
            showButtons
            hasUnresolvedRephraseAlert={false}
          />
        </div>
      ))}
    </div>
  );
}
Example #15
Source File: index.tsx    From amiya with MIT License 5 votes vote down vote up
export default function PopoverEdit(props: IProps) {
  const { onChange, children, title, options, hidden } = props
  // 是否可见
  const [visible, setVisible] = useState(false)
  // 当前输入值
  const [text, setText] = useState<number | undefined>()
  // 单位
  const [unit, setUnit] = useState<number | undefined>()

  useEffect(() => {
    if (visible === false) {
      setText(undefined)
      setUnit(undefined)
    } else {
      setUnitDefaultValue()
    }
  }, [visible])

  useEffect(() => setUnitDefaultValue(), [options])

  /** 设置单位默认值 */
  const setUnitDefaultValue = () => {
    if (options && options.length) {
      setUnit(options[0].value)
    }
  }

  /** 确定更改 */
  const handleConfirm = () => {
    setVisible(false)
    if (onChange) {
      onChange(text, unit)
    }
  }

  if (hidden === true) {
    return <div>{children}</div>
  }

  return (
    <Popover
      visible={visible}
      onVisibleChange={setVisible}
      title={title}
      trigger="click"
      content={
        <Space>
          <InputNumber
            value={text}
            onChange={(value: number) => setText(value)}
            addonBefore={options ? <AySelect value={unit} onChange={setUnit} options={options} /> : undefined}
          />
          <AyButton type="primary" icon={<CheckOutlined />} onClick={handleConfirm} />
        </Space>
      }
    >
      {children}
      <AyButton type="link">批量</AyButton>
    </Popover>
  )
}
Example #16
Source File: DateLevelMenuItems.tsx    From datart with Apache License 2.0 4 votes vote down vote up
DateLevelMenuItems = memo(
  ({ availableSourceFunctions, config, onChange }: DateLevelMenuItemsProps) => {
    const t = useI18NPrefix(`viz.workbench.dataview`);
    const handleChangeFn = useCallback(
      selectedConfig => {
        /**
         * If the current category is DateLevelComputedField
         */
        if (
          config.category === ChartDataViewFieldCategory.DateLevelComputedField
        ) {
          /**
           * If default is selected
           */
          if (selectedConfig.category === ChartDataViewFieldCategory.Field) {
            return onChange(
              updateBy(config, draft => {
                delete draft.expression;
                delete draft.field;
                draft.category = selectedConfig.category;
                draft.colName = selectedConfig.colName;
                draft[RUNTIME_DATE_LEVEL_KEY] = null;
              }),
            );
          }

          return onChange({
            ...config,
            colName: `${config.field}${selectedConfig.colName})`,
            expression: selectedConfig.expression,
            [RUNTIME_DATE_LEVEL_KEY]: null,
          });
        } else {
          /**
           * If the current category is Field, only the selected category is judged to be DateLevelComputedField
           */
          if (
            selectedConfig.category ===
            ChartDataViewFieldCategory.DateLevelComputedField
          ) {
            return onChange(
              updateBy(config, draft => {
                draft.expression = selectedConfig.expression;
                draft.field = config.colName;
                draft.category =
                  ChartDataViewFieldCategory.DateLevelComputedField;
                draft.colName = `${draft.colName}${selectedConfig.colName})`;
                draft[RUNTIME_DATE_LEVEL_KEY] = null;
              }),
            );
          }
        }
      },
      [config, onChange],
    );

    return (
      <>
        <Menu.Item
          icon={!config.expression ? <CheckOutlined /> : ''}
          onClick={() => {
            config.field &&
              handleChangeFn({
                category: ChartDataViewFieldCategory.Field,
                colName: config.field,
              });
          }}
        >
          {t('default')}
        </Menu.Item>
        {DATE_LEVELS.map(item => {
          if (availableSourceFunctions?.includes(item.expression)) {
            const colName = t(item.expression);
            const expression = `${item.expression}(${
              config.category === ChartDataViewFieldCategory.Field
                ? config.colName
                : config.field
            })`;

            return (
              <Menu.Item
                key={expression}
                eventKey={expression}
                icon={config.expression === expression ? <CheckOutlined /> : ''}
                onClick={() =>
                  handleChangeFn({
                    category: ChartDataViewFieldCategory.DateLevelComputedField,
                    colName,
                    expression,
                  })
                }
              >
                {colName}
              </Menu.Item>
            );
          }
          return null;
        })}
      </>
    );
  },
)
Example #17
Source File: index.tsx    From nanolooker with MIT License 4 votes vote down vote up
ConnectionPreferences: React.FC<Props> = ({ isDetailed }) => {
  const { t } = useTranslation();
  const {
    rpcDomain,
    setRpcDomain,
    websocketDomain,
    setWebsocketDomain,
  } = React.useContext(PreferencesContext);
  const [isEnabled, setIsEnabled] = React.useState(!!rpcDomain);
  const {
    control,
    handleSubmit,
    setValue,
    getValues,
    formState: { errors, isValid },
  } = useForm({
    defaultValues: {
      rpcDomain: rpcDomain || "",
      websocketDomain: websocketDomain || "",
    },
    mode: "onChange",
  });

  const onSubmit = async ({
    rpcDomain,
    websocketDomain,
  }: {
    rpcDomain?: string;
    websocketDomain?: string;
  }) => {
    if (rpcDomain) {
      setRpcDomain(rpcDomain);
    }
    if (websocketDomain) {
      setWebsocketDomain(websocketDomain);
    }
    window.location.reload();
  };

  return (
    <>
      <Row style={{ alignItems: !isDetailed ? "center" : "flex-start" }}>
        <Col xs={18}>
          <Text>{t("pages.preferences.connectionDetailed")}</Text>
        </Col>

        <Col xs={6} style={{ textAlign: "right" }}>
          <Switch
            checkedChildren={<CheckOutlined />}
            unCheckedChildren={<CloseOutlined />}
            onChange={(checked: boolean) => {
              setIsEnabled(checked);
              if (!checked) {
                setRpcDomain("");
                setWebsocketDomain("");
                setValue("rpcDomain", "");
                setValue("websocketDomain", "");
              }
            }}
            checked={isEnabled}
          />
        </Col>

        {isEnabled ? (
          <Col xs={24}>
            <form onSubmit={handleSubmit(onSubmit)}>
              <div
                style={{
                  display: "flex",
                  justifyContent: "flex-end",
                  marginTop: 12,
                }}
              >
                <Space size={12} direction="vertical">
                  <Space size={3} direction="vertical">
                    <Text>{t("pages.preferences.rpcDomain")}</Text>
                    <Controller
                      render={({ field }) => (
                        <Input
                          {...field}
                          type="text"
                          style={{ width: "400px", maxWidth: "100%" }}
                          placeholder={`http://127.0.0.1:7076`}
                          maxLength={255}
                          suffix={
                            getValues("rpcDomain") && !errors?.rpcDomain ? (
                              <CheckCircleTwoTone twoToneColor={"#52c41a"} />
                            ) : (
                              " "
                            )
                          }
                        />
                      )}
                      rules={{
                        // @ts-ignore
                        validate: (value: string) => {
                          return !value || value.length >= 15;
                        },
                      }}
                      control={control}
                      name="rpcDomain"
                      defaultValue={getValues("rpcDomain")}
                    />
                  </Space>
                  <Space size={3} direction="vertical">
                    <Text>{t("pages.preferences.websocketDomain")}</Text>
                    <Controller
                      render={({ field }) => (
                        <Input
                          {...field}
                          type="text"
                          style={{ width: "400px", maxWidth: "100%" }}
                          placeholder={`wss://www.nanolooker.com/ws`}
                          maxLength={255}
                          suffix={
                            getValues("websocketDomain") &&
                            !errors?.websocketDomain ? (
                              <CheckCircleTwoTone twoToneColor={"#52c41a"} />
                            ) : (
                              " "
                            )
                          }
                        />
                      )}
                      rules={{
                        // @ts-ignore
                        validate: (value: string) => {
                          return !value || value.length >= 15;
                        },
                      }}
                      control={control}
                      name="websocketDomain"
                      defaultValue={getValues("websocketDomain")}
                    />
                  </Space>
                </Space>
              </div>
              <div style={{ textAlign: "right", marginTop: 12 }}>
                <Button
                  type="primary"
                  disabled={!isValid}
                  onClick={handleSubmit(onSubmit)}
                >
                  Save
                </Button>
              </div>
            </form>
          </Col>
        ) : null}
      </Row>
    </>
  );
}
Example #18
Source File: SortAction.tsx    From datart with Apache License 2.0 4 votes vote down vote up
SortAction: FC<{
  config: ChartDataSectionField;
  dataset?: ChartDataSetDTO;
  onConfigChange: (
    config: ChartDataSectionField,
    needRefresh?: boolean,
  ) => void;
  mode?: 'menu';
  options?;
}> = ({ config, dataset, mode, options, onConfigChange }) => {
  const actionNeedNewRequest = isEmpty(options?.backendSort)
    ? true
    : Boolean(options?.backendSort);
  const t = useI18NPrefix(`viz.palette.data.actions`);
  const [direction, setDirection] = useState(
    config?.sort?.type || SortActionType.NONE,
  );
  const [sortValue, setSortValue] = useState(() => {
    const objDataColumns = transformToDataSet(dataset?.rows, dataset?.columns);
    return (
      config?.sort?.value ||
      Array.from(new Set(objDataColumns?.map(c => c.getCell(config))))
    );
  });

  const handleSortTypeChange = direction => {
    setDirection(direction);

    if (SortActionType.CUSTOMIZE !== direction) {
      onConfigChange &&
        onConfigChange(
          updateBy(config, draft => {
            draft.sort = { type: direction };
          }),
          actionNeedNewRequest,
        );
    }
  };

  const handleCustomSortListChange = values => {
    setSortValue(values);
    onConfigChange &&
      onConfigChange(
        updateBy(config, draft => {
          draft.sort = { type: SortActionType.CUSTOMIZE, value: values };
        }),
        !actionNeedNewRequest,
      );
  };

  const renderColumnsDataList = () => {
    if (
      !config.colName ||
      SortActionType.CUSTOMIZE !== direction ||
      !Array.isArray(sortValue)
    ) {
      return null;
    }

    const items =
      sortValue.map((value, index) => ({
        id: index,
        text: value,
      })) || [];
    return (
      <DraggableList source={items} onChange={handleCustomSortListChange} />
    );
  };

  const renderOptions = mode => {
    if (mode === 'menu') {
      return (
        <>
          {[SortActionType.NONE, SortActionType.ASC, SortActionType.DESC].map(
            sort => {
              return (
                <Menu.Item
                  key={sort}
                  eventKey={sort}
                  icon={direction === sort ? <CheckOutlined /> : ''}
                  onClick={() => handleSortTypeChange(sort)}
                >
                  {t(`sort.${sort?.toLowerCase()}`)}
                </Menu.Item>
              );
            },
          )}
        </>
      );
    }

    return (
      <StyledRow>
        <Col span={12}>
          <Radio.Group
            onChange={e => handleSortTypeChange(e.target?.value)}
            value={direction}
          >
            <Space direction="vertical">
              <Radio key={SortActionType.NONE} value={SortActionType.NONE}>
                {t('sort.none')}
              </Radio>
              <Radio key={SortActionType.ASC} value={SortActionType.ASC}>
                {t('sort.asc')}
              </Radio>
              <Radio key={SortActionType.DESC} value={SortActionType.DESC}>
                {t('sort.desc')}
              </Radio>
            </Space>
          </Radio.Group>
        </Col>
        {/* {SortActionType.CUSTOMIZE === direction && (
        <Col span={12}>{renderColumnsDataList()}</Col>
      )} */}
      </StyledRow>
    );
  };

  return renderOptions(mode);
}
Example #19
Source File: OrganizationList.tsx    From datart with Apache License 2.0 4 votes vote down vote up
export function OrganizationList() {
  const [formVisible, setFormVisible] = useState(false);
  const dispatch = useDispatch();
  const history = useHistory();
  const organizations = useSelector(selectOrganizations);
  const orgId = useSelector(selectOrgId);
  const listLoading = useSelector(selectOrganizationListLoading);
  const t = useI18NPrefix('main.nav.organization');

  const showForm = useCallback(() => {
    setFormVisible(true);
  }, []);

  const hideForm = useCallback(() => {
    setFormVisible(false);
  }, []);

  const menuSelect = useCallback(
    ({ key }) => {
      if (key !== orgId) {
        dispatch(switchOrganization(key));
        history.push(`/organizations/${key}`);
      }
    },
    [dispatch, history, orgId],
  );

  let list;

  if (listLoading) {
    list = (
      <LoadingWrapper>
        <LoadingOutlined />
      </LoadingWrapper>
    );
  } else {
    list = (
      <StyledMenu
        prefixCls="ant-dropdown-menu"
        selectable={false}
        onClick={menuSelect}
      >
        {organizations.map(o => {
          const itemClass = classnames({
            selected: orgId === o.id,
          });
          return (
            <MenuListItem
              key={o.id}
              className={itemClass}
              prefix={
                <Avatar size="small" src={`${BASE_RESOURCE_URL}${o.avatar}`}>
                  {o.name.substr(0, 1).toUpperCase()}
                </Avatar>
              }
              {...(orgId === o.id && {
                suffix: <CheckOutlined className="icon" />,
              })}
            >
              <p>{o.name}</p>
            </MenuListItem>
          );
        })}
      </StyledMenu>
    );
  }

  return (
    <Wrapper>
      <Title>
        <h2>{t('title')}</h2>
        <ToolbarButton
          size="small"
          icon={<PlusOutlined />}
          onClick={showForm}
        />
      </Title>
      {list}
      <OrganizationForm visible={formVisible} onCancel={hideForm} />
    </Wrapper>
  );
}
Example #20
Source File: DefaultValue.tsx    From datart with Apache License 2.0 4 votes vote down vote up
DefaultValue = memo(
  ({ type, expression, disabled, value = [], onChange }: DefaultValueProps) => {
    const [inputValue, setInputValue] = useState<any>(void 0);
    const t = useI18NPrefix('variable');

    useEffect(() => {
      setInputValue(void 0);
    }, [type]);

    const saveRegular = useCallback(
      (selectedValue?) => {
        let validValue;
        switch (type) {
          case VariableValueTypes.String:
            if (inputValue && (inputValue as string).trim()) {
              validValue = inputValue;
            }
            break;
          case VariableValueTypes.Number:
            if (inputValue !== null && !Number.isNaN(inputValue)) {
              validValue = inputValue;
            }
            break;
          case VariableValueTypes.Date:
            validValue = selectedValue;
            break;
        }

        if (validValue !== void 0) {
          onChange && onChange(value ? value.concat(validValue) : [validValue]);
          setInputValue(void 0);
        }
      },
      [value, type, inputValue, onChange],
    );

    const saveExpression = useCallback(
      e => {
        onChange && onChange([e.target.value]);
      },
      [onChange],
    );

    const inputChange = useCallback(e => {
      setInputValue(e.target.value);
    }, []);

    const inputNumberChange = useCallback(val => {
      setInputValue(val);
    }, []);

    const datePickerConfirm = useCallback(
      val => {
        saveRegular(val);
      },
      [saveRegular],
    );

    const tagClose = useCallback(
      index => e => {
        e.preventDefault();
        onChange && onChange(value ? value.filter((_, i) => index !== i) : []);
      },
      [value, onChange],
    );

    let conditionalInputComponent;

    switch (type) {
      case VariableValueTypes.Number:
        conditionalInputComponent = (
          <InputNumber
            placeholder={t('enterToAdd')}
            value={inputValue}
            className="input"
            disabled={!!disabled}
            onChange={inputNumberChange}
            onPressEnter={saveRegular}
          />
        );
        break;
      case VariableValueTypes.Date:
        conditionalInputComponent = (
          <DatePicker
            format={TIME_FORMATTER}
            className="input"
            disabled={!!disabled}
            onOk={datePickerConfirm}
            showNow
            showTime
          />
        );
        break;
      default:
        conditionalInputComponent = (
          <Input
            placeholder={t('enterToAdd')}
            value={inputValue}
            className="input"
            disabled={!!disabled}
            onChange={inputChange}
            onPressEnter={saveRegular}
          />
        );
        break;
    }

    return (
      <Wrapper direction="vertical" size={0}>
        {expression || type === VariableValueTypes.Expression ? (
          <Input.TextArea
            placeholder={t('enterExpression')}
            autoSize={{ minRows: 4, maxRows: 8 }}
            value={value ? value[0] : void 0}
            disabled={!!disabled}
            onChange={saveExpression}
          />
        ) : (
          <>
            {value && value.length > 0 && (
              <ValueTags key="valueTags">
                {value?.map((val, index) => {
                  const label =
                    type !== VariableValueTypes.Date
                      ? val
                      : moment(val).format(TIME_FORMATTER);
                  return (
                    <Tag
                      key={label}
                      className="tag"
                      closable
                      onClose={tagClose(index)}
                    >
                      {label}
                    </Tag>
                  );
                })}
              </ValueTags>
            )}
            <Space key="actions">
              {conditionalInputComponent}
              {type !== VariableValueTypes.Date && (
                <Button
                  size="small"
                  icon={<CheckOutlined />}
                  type="link"
                  onClick={saveRegular}
                />
              )}
            </Space>
          </>
        )}
      </Wrapper>
    );
  },
)
Example #21
Source File: ChartDrillContextMenu.tsx    From datart with Apache License 2.0 4 votes vote down vote up
ChartDrillContextMenu: FC<{ chartConfig?: ChartConfig }> = memo(
  ({ children, chartConfig }) => {
    const t = useI18NPrefix(`viz.palette.drill`);
    const {
      drillOption,
      onDrillOptionChange,
      availableSourceFunctions,
      onDateLevelChange,
    } = useContext(ChartDrillContext);

    const currentDrillLevel = drillOption?.getCurrentDrillLevel();

    const runtimeDateLevelFields = useMemo(() => {
      if (!drillOption) {
        return;
      }
      const allFields = drillOption.getAllFields();
      const currentFields = drillOption.getCurrentFields();
      const groupSection = chartConfig?.datas?.find(
        v => v.type === ChartDataSectionType.GROUP,
      );
      let rows: ChartDataSectionField[] | undefined = [];

      if (currentFields) {
        rows = groupSection?.rows?.filter(v =>
          currentFields.some(val => val.uid === v.uid),
        );
      } else {
        rows = groupSection?.rows?.filter(v => v.uid === allFields[0].uid);
      }
      return getRuntimeDateLevelFields(rows);
    }, [drillOption, chartConfig?.datas]);

    const handleDateLevelChange = useCallback(
      (config: ChartDataSectionField) => {
        const groupData = chartConfig?.datas?.find(
          v => v.type === ChartDataSectionType.GROUP,
        );

        if (groupData) {
          const _groupData = updateBy(groupData, draft => {
            if (draft.rows) {
              const index = draft.rows.findIndex(v => v.uid === config.uid);
              const runtimeDateLevel =
                draft.rows[index][RUNTIME_DATE_LEVEL_KEY];
              const replacedColName = runtimeDateLevel
                ? runtimeDateLevel.colName
                : draft.rows[index].colName;

              draft.rows[index][RUNTIME_DATE_LEVEL_KEY] = config;
              draft.replacedColName = replacedColName;
            }
          });

          onDateLevelChange?.('data', {
            needRefresh: true,
            ancestors: [0],
            value: _groupData,
          });
        }
      },
      [chartConfig?.datas, onDateLevelChange],
    );

    const selectDrillStatusMenu = useMemo(() => {
      return (
        <Menu.Item key="selectDrillStatus">
          <StyledMenuSwitch
            className={classnames({ on: !!drillOption?.isSelectedDrill })}
          >
            <p>
              {drillOption?.isSelectedDrill
                ? t('selectDrillOn')
                : t('selectDrillOff')}
            </p>
            <CheckOutlined className="icon" />
          </StyledMenuSwitch>
        </Menu.Item>
      );
    }, [drillOption?.isSelectedDrill, t]);

    const contextMenu = useMemo(() => {
      return (
        <StyledChartDrillMenu
          onClick={({ key }) => {
            if (!drillOption) {
              return;
            }
            if (key === 'selectDrillStatus') {
              drillOption?.toggleSelectedDrill(!drillOption?.isSelectedDrill);
              onDrillOptionChange?.(drillOption);
            } else if (key === DrillMode.Drill) {
              drillOption?.drillDown();
              onDrillOptionChange?.(drillOption);
            } else if (key === DrillMode.Expand) {
              drillOption?.expandDown();
              onDrillOptionChange?.(drillOption);
            } else if (key === 'rollUp') {
              drillOption?.rollUp();
              onDrillOptionChange?.(drillOption);
            }
          }}
        >
          {!!currentDrillLevel && (
            <Menu.Item key={'rollUp'}>{t('rollUp')}</Menu.Item>
          )}
          {drillOption?.mode !== DrillMode.Expand &&
            !drillOption?.isBottomLevel && (
              <Menu.Item key={DrillMode.Drill}>{t('showNextLevel')}</Menu.Item>
            )}
          {drillOption?.mode !== DrillMode.Drill &&
            !drillOption?.isBottomLevel && (
              <Menu.Item key={DrillMode.Expand}>
                {t('expandNextLevel')}
              </Menu.Item>
            )}
          {drillOption?.mode !== DrillMode.Expand && selectDrillStatusMenu}

          {runtimeDateLevelFields?.map((v, i) => {
            if (v.type === DataViewFieldType.DATE) {
              return (
                <Menu.SubMenu key={i} title={v.colName}>
                  <DateLevelMenuItems
                    availableSourceFunctions={availableSourceFunctions}
                    config={v[RUNTIME_DATE_LEVEL_KEY] || v}
                    onChange={config => handleDateLevelChange(config)}
                  />
                </Menu.SubMenu>
              );
            }
            return false;
          })}
        </StyledChartDrillMenu>
      );
    }, [
      currentDrillLevel,
      t,
      drillOption,
      selectDrillStatusMenu,
      runtimeDateLevelFields,
      onDrillOptionChange,
      handleDateLevelChange,
      availableSourceFunctions,
    ]);

    const hasContextMenu =
      drillOption?.isDrillable || runtimeDateLevelFields?.length;

    return (
      <StyledChartDrill className="chart-drill-menu-container">
        {hasContextMenu ? (
          <Dropdown
            disabled={!drillOption}
            overlay={contextMenu}
            destroyPopupOnHide={true}
            trigger={['contextMenu']}
          >
            <div style={{ height: '100%' }}>{children}</div>
          </Dropdown>
        ) : (
          <div style={{ height: '100%' }}>{children}</div>
        )}
      </StyledChartDrill>
    );
  },
)
Example #22
Source File: PieChart.tsx    From nanolooker with MIT License 4 votes vote down vote up
Representatives: React.FC<Props> = ({
  isIncludeOfflineRepresentatives,
  setIsIncludeOfflineRepresentatives,
  isGroupedByEntities,
  setIsGroupedByEntities,
}) => {
  const { t } = useTranslation();
  const { theme } = React.useContext(PreferencesContext);
  const {
    representatives,
    isLoading: isRepresentativesLoading,
  } = React.useContext(RepresentativesContext);
  const [nakamotoCoefficient, setNakamotoCoefficient] = React.useState(
    [] as Representative[],
  );
  const [
    principalRepresentatives,
    setPrincipalRepresentatives,
  ] = React.useState([] as Representative[]);
  const {
    confirmationQuorum: {
      principal_representative_min_weight: principalRepresentativeMinWeight = 0,
      online_weight_quorum_percent: onlineWeightQuorumPercent,
    },
    isLoading: isConfirmationQuorumLoading,
  } = React.useContext(ConfirmationQuorumContext);

  const [delegatedEntities, setDelegatedEntities] = React.useState(
    [] as KnownAccountsBalance[],
  );

  const representativesSkeletonProps = {
    active: true,
    paragraph: true,
    loading: isRepresentativesLoading,
  };

  React.useEffect(() => {
    getDelegatedEntity().then(delegatedEntities => {
      setDelegatedEntities(delegatedEntities || []);
    });
  }, []);

  React.useEffect(() => {
    if (
      isRepresentativesLoading ||
      isConfirmationQuorumLoading ||
      !principalRepresentatives.length ||
      !Array.isArray(delegatedEntities)
    )
      return;

    const aliasSeparator = "|||";
    // const stake = new BigNumber(rawToRai(onlineStakeTotal)).toNumber();

    let filteredRepresentatives = isIncludeOfflineRepresentatives
      ? [...principalRepresentatives]
      : [...principalRepresentatives].filter(({ isOnline }) => isOnline);

    let stake = 0;
    forEach(filteredRepresentatives, representative => {
      stake = new BigNumber(stake).plus(representative.weight).toNumber();
    });

    if (isGroupedByEntities && delegatedEntities.length) {
      // @TODO find a more scalable option
      const groups: { [key: string]: number } = {
        "Nano Foundation": 0,
        Binance: 0,
        Kraken: 0,
        Huobi: 0,
        Kucoin: 0,
      };

      // @ts-ignore added representative key
      delegatedEntities.forEach(({ alias, account, representative, total }) => {
        const accountIndex = filteredRepresentatives.findIndex(
          ({ account: representativeAccount }) =>
            representativeAccount === account,
        );

        const representativeIndex = filteredRepresentatives.findIndex(
          ({ account }) => account === representative,
        );

        if (accountIndex > -1) {
          filteredRepresentatives[accountIndex] = {
            ...filteredRepresentatives[accountIndex],
            weight: filteredRepresentatives[accountIndex].weight + total,
          };
        } else {
          filteredRepresentatives.push({
            alias,
            account,
            isOnline: true,
            isPrincipal: true,
            weight: total,
          });
        }

        if (representativeIndex > -1) {
          filteredRepresentatives[representativeIndex] = {
            ...filteredRepresentatives[representativeIndex],
            weight: filteredRepresentatives[representativeIndex].weight - total,
          };
        }
      });

      filteredRepresentatives = filteredRepresentatives.filter(
        ({ alias, weight }) => {
          const group = alias
            ? Object.keys(groups).find(group =>
                alias.toLowerCase()?.includes(group.toLowerCase()),
              )
            : null;

          if (group) {
            groups[group] = new BigNumber(groups[group])
              .plus(weight)
              .toNumber();
          }

          return !group;
        },
      );

      const groupedEntities = Object.entries(groups).map(([group, weight]) => ({
        account: "",
        weight: weight,
        isOnline: true,
        isPrincipal: true,
        alias: group,
      }));

      filteredRepresentatives = filteredRepresentatives
        .concat(groupedEntities)
        .filter(({ weight }) => weight >= principalRepresentativeMinWeight);

      filteredRepresentatives = orderBy(
        filteredRepresentatives,
        ["weight"],
        ["desc"],
      );
    }

    const nakamotoCoefficient: Representative[] = [];
    let nakamotoCoefficientWeight = 0;
    let totalWeight = 0;

    forEach(filteredRepresentatives, representative => {
      const nextWeight = new BigNumber(nakamotoCoefficientWeight)
        .plus(representative.weight)
        .toNumber();
      totalWeight = new BigNumber(totalWeight)
        .plus(representative.weight)
        .toNumber();
      const percent = new BigNumber(nextWeight)
        .times(100)
        .dividedBy(stake)
        .toNumber();

      nakamotoCoefficientWeight = nextWeight;
      nakamotoCoefficient.push(representative);

      if (percent > parseInt(onlineWeightQuorumPercent)) {
        return false;
      }
    });

    setNakamotoCoefficient(nakamotoCoefficient);

    const data = filteredRepresentatives.map(({ weight, account, alias }) => {
      const value = parseFloat(
        new BigNumber(weight).times(100).dividedBy(stake).toFixed(2),
      );

      return {
        // @NOTE Remove symbol characters as they are causing the chart to crash
        // on latest Safari & mobile chrome. Re-enable once it's fixed
        alias: `${alias?.replace(/\W/g, " ") || ""}${aliasSeparator}${account}`,
        value,
      };
    });

    const config = {
      data,
      angleField: "value",
      colorField: "alias",
      radius: 0.8,
      // theme: 'dark',
      label: {
        visible: true,
        type: "outer",
        style:
          theme === Theme.DARK
            ? {
                fill: "white",
                stroke: "none",
              }
            : {
                fill: "black",
                stroke: "#fff",
              },
      },
      legend: {
        visible: true,
        itemName: {
          // @ts-ignore
          formatter: (text: string) => {
            const [alias, account] = text.split(aliasSeparator);
            return alias || account || t("common.unknown");
          },
        },
      },
      tooltip: {
        showTitle: false,
        // @ts-ignore
        formatter: ({ value, alias: rawAlias }) => {
          const [alias, account] = rawAlias.split(aliasSeparator);
          return {
            name: alias || account || t("common.unknown"),
            value: `${value}%`,
          };
        },
      },
      interactions: [{ type: "element-active" }],
    };

    if (!representativesChart) {
      representativesChart = new Pie(
        document.getElementById("representatives-chart") as HTMLElement,
        // @ts-ignore
        config,
      );
      representativesChart.render();
    } else {
      // @ts-ignore
      representativesChart.update(config);
    }

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    theme,
    principalRepresentatives,
    isRepresentativesLoading,
    isConfirmationQuorumLoading,
    isIncludeOfflineRepresentatives,
    isGroupedByEntities,
    delegatedEntities,
  ]);

  React.useEffect(() => {
    if (isRepresentativesLoading || !representatives.length) return;
    const filteredRepresentatives = representatives.filter(
      ({ isPrincipal }) => isPrincipal,
    );

    setPrincipalRepresentatives(filteredRepresentatives);
  }, [representatives, isRepresentativesLoading]);

  React.useEffect(() => {
    return () => {
      representativesChart?.destroy();
      representativesChart = null;
    };
  }, []);

  return (
    <>
      <Title level={3}>{t("pages.representatives.voteDistribution")}</Title>
      <Card size="small" bordered={false} className="detail-layout">
        <Row gutter={6}>
          <Col xs={20} md={12}>
            {t("pages.representatives.includeOfflineRepresentatives")}
          </Col>
          <Col xs={4} md={12}>
            <Switch
              disabled={isRepresentativesLoading}
              checkedChildren={<CheckOutlined />}
              unCheckedChildren={<CloseOutlined />}
              onChange={(checked: boolean) => {
                setIsIncludeOfflineRepresentatives(checked);
              }}
              defaultChecked={isIncludeOfflineRepresentatives}
            />
          </Col>
        </Row>
        <Row gutter={6}>
          <Col xs={20} md={12}>
            {t("pages.representatives.groupByEntities")}
            <Tooltip placement="right" title={t("tooltips.groupByEntities")}>
              <QuestionCircle />
            </Tooltip>
          </Col>
          <Col xs={4} md={12}>
            <Switch
              disabled={isRepresentativesLoading || !delegatedEntities.length}
              checkedChildren={<CheckOutlined />}
              unCheckedChildren={<CloseOutlined />}
              onChange={(checked: boolean) => {
                setIsGroupedByEntities(checked);
              }}
              checked={
                // Ensure the API returns delegatedEntities to enable the switch
                delegatedEntities.length ? isGroupedByEntities : false
              }
            />
          </Col>
        </Row>
        <Row>
          <Col xs={24} md={12}>
            {t("pages.representatives.nakamotoCoefficient")}
            {onlineWeightQuorumPercent ? (
              <Tooltip
                placement="right"
                title={t("tooltips.nakamotoCoefficient", {
                  onlineWeightQuorumPercent: onlineWeightQuorumPercent,
                })}
              >
                <QuestionCircle />
              </Tooltip>
            ) : null}
          </Col>
          <Col xs={24} md={12}>
            <Skeleton
              active
              paragraph={false}
              loading={!nakamotoCoefficient.length}
            >
              <Text>{nakamotoCoefficient.length}</Text>
            </Skeleton>
          </Col>
        </Row>
        <Row>
          <Col xs={24}>
            <Text style={{ fontSize: "12px" }}>
              {t("pages.representatives.voteDistributionDescription")}
            </Text>
          </Col>
        </Row>

        <Skeleton {...representativesSkeletonProps}>
          <div id="representatives-chart" />
        </Skeleton>
      </Card>
    </>
  );
}
Example #23
Source File: PieChart.tsx    From nanolooker with MIT License 4 votes vote down vote up
Representatives: React.FC<Props> = ({ versions }) => {
  const { t } = useTranslation();
  const { theme } = React.useContext(PreferencesContext);
  const [isVersionByWeight, setIsVersionByWeight] = React.useState(true);
  const {
    confirmationQuorum: {
      online_weight_quorum_percent: onlineWeightQuorumPercent = 0,
      online_weight_minimum: onlineWeightMinimum = 0,
    },
  } = React.useContext(ConfirmationQuorumContext);

  React.useEffect(() => {
    if (!Object.keys(versions).length) return;

    let data = orderBy(
      Object.entries(versions).map(([version, { weight, count }]) => ({
        version,
        weight,
        count,
      })),
      ["version"],
      ["desc"],
    );

    let totalWeight = 0;
    if (isVersionByWeight) {
      data = data.filter(({ weight }) => {
        totalWeight += weight;
        return !!weight;
      });
    }

    const config = {
      padding: -12,
      data,
      angleField: isVersionByWeight ? "weight" : "count",
      colorField: "version",
      radius: 0.8,
      label: {
        visible: true,
        type: "outer",
        // @ts-ignore
        formatter: (text, item, index) => {
          return `${item._origin.version}`;
        },
        style:
          theme === Theme.DARK
            ? {
                fill: "white",
                stroke: "none",
              }
            : {
                fill: "black",
                stroke: "#fff",
              },
      },
      legend: {
        visible: false,
      },
      tooltip: {
        showTitle: false,
        formatter: ({
          weight,
          count,
          version,
        }: {
          weight: number;
          count: number;
          version: string;
        }) => ({
          name: version,
          value: isVersionByWeight
            ? `Ӿ ${new BigNumber(weight).toFormat(2)} - ${new BigNumber(weight)
                .times(100)
                .dividedBy(totalWeight)
                .toFormat(2)}%`
            : `${count} ${t("common.nodes")}`,
        }),
      },
      interactions: [{ type: "element-active" }],
    };

    if (!versionsChart) {
      versionsChart = new Pie(
        document.getElementById("versions-chart") as HTMLElement,
        // @ts-ignore
        config,
      );
      versionsChart.render();
    } else {
      versionsChart.update(config);
    }

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [theme, versions, isVersionByWeight]);

  React.useEffect(() => {
    return () => {
      versionsChart?.destroy();
      versionsChart = null;
    };
  }, []);

  return (
    <>
      <Title level={3}>{t("pages.status.nodeVersions")}</Title>

      <Card size="small" bordered={false} className="detail-layout">
        <Row gutter={6}>
          <Col xs={20} md={12}>
            {t("pages.status.versionsByWeight")}
            <Tooltip
              placement="right"
              title={t("tooltips.versionsByWeight", {
                onlineWeightMinimum: new BigNumber(
                  rawToRai(onlineWeightMinimum),
                ).toFormat(),
                onlineWeightQuorumPercent,
              })}
            >
              <QuestionCircle />
            </Tooltip>
          </Col>
          <Col xs={4} md={12}>
            <Switch
              disabled={!Object.keys(versions).length}
              checkedChildren={<CheckOutlined />}
              unCheckedChildren={<CloseOutlined />}
              onChange={(checked: boolean) => {
                setIsVersionByWeight(checked);
              }}
              defaultChecked={isVersionByWeight}
            />
          </Col>
        </Row>
        <Row>
          <Col xs={24}>
            <Skeleton loading={!Object.keys(versions).length} active>
              <div id="versions-chart" />
            </Skeleton>
          </Col>
        </Row>
      </Card>
    </>
  );
}
Example #24
Source File: index.tsx    From nanolooker with MIT License 4 votes vote down vote up
Distribution: React.FC = () => {
  const { t } = useTranslation();
  const {
    knownExchangeAccounts,
    isLoading: isKnownAccountsLoading,
  } = React.useContext(KnownAccountsContext);
  const [isIncludeExchanges, setIsIncludeExchanges] = React.useState<boolean>(
    true,
  );
  const [totalAccounts, setTotalAccounts] = React.useState<number>(0);
  const [totalBalance, setTotalBalance] = React.useState<number>(0);
  const [distributionData, setDistributionData] = React.useState<any[]>([]);
  const [isLogScale, setIsLogScale] = React.useState<boolean>(false);
  const [knownExchangeBalance, setKnownExchangeBalance] = React.useState("0");
  const { data } = useDistribution();

  React.useEffect(() => {
    return () => {
      distributionChart?.destroy();
      distributionChart = null;
    };
  }, []);

  React.useEffect(() => {
    if (
      !data?.distribution ||
      !data?.knownExchanges ||
      !knownExchangeAccounts.length
    )
      return;

    let knownExchangeDistribution: DistributionIndex[] = [];
    if (!isIncludeExchanges) {
      Object.values(data.knownExchanges).forEach(balance => {
        let index = balance >= 1 ? `${Math.floor(balance)}`.length : 0;

        knownExchangeDistribution[index] = {
          accounts: (knownExchangeDistribution[index]?.accounts || 0) + 1,
          balance: new BigNumber(balance)
            .plus(knownExchangeDistribution[index]?.balance || 0)
            .toNumber(),
        };
      });
    }

    const tmpDistributionData: any[] = [];
    let tmpTotalAccounts = 0;
    let tmpTotalBalance = 0;

    data.distribution.forEach(
      (
        { accounts, balance }: { accounts: number; balance: number },
        i: number,
      ): void => {
        const calcAccounts =
          accounts - (knownExchangeDistribution[i]?.accounts || 0);
        let calcBalance = new BigNumber(balance)
          .minus(knownExchangeDistribution[i]?.balance || 0)
          .toNumber();

        if (calcBalance < 0 && !calcAccounts) {
          calcBalance = 0;
        }

        tmpTotalAccounts += calcAccounts;
        tmpTotalBalance += calcBalance;

        tmpDistributionData.push({
          title: distributionMap[i],
          value: calcBalance,
          type: "balance",
        });
        tmpDistributionData.push({
          title: distributionMap[i],
          value: calcAccounts,
          type: "accounts",
        });
      },
    );

    const knownExchangeBalance = new BigNumber(
      Object.values(data?.knownExchanges || []).reduce(
        (acc, balance) => new BigNumber(acc).plus(balance).toNumber(),
        0,
      ),
    ).toFormat(2);

    setKnownExchangeBalance(knownExchangeBalance);
    setTotalAccounts(tmpTotalAccounts);
    setTotalBalance(tmpTotalBalance);
    setDistributionData(tmpDistributionData);
  }, [data, isIncludeExchanges, knownExchangeAccounts]);

  useDeepCompareEffect(() => {
    // @TODO: Validate data: https://nanocharts.info/p/05/balance-distribution
    // https://g2plot.antv.vision/en/examples/column/stacked#connected-area-interaction

    const config = {
      forceFit: true,
      responsive: true,
      padding: "auto",
      isStack: true,
      data: distributionData,
      xField: "title",
      yField: "value",
      seriesField: "type",
      yAxis: {
        type: isLogScale ? "log" : "linear",
        min: 0,
        base: 2,
      },
      meta: {
        value: {
          alias: " ",
          formatter: (text: number) => {
            return intToString(text);
          },
        },
        title: {
          alias: " ",
        },
      },
      tooltip: {
        // @ts-ignore
        formatter: ({ title, value, name }) => ({
          title,
          value: new BigNumber(value).toFormat(),
          name: isInteger(value) ? t("common.accounts") : t("common.balance"),
        }),
      },
      legend: {
        layout: "horizontal",
        position: "top",
        itemName: {
          style: {
            fontSize: 14,
          },
          formatter: (text: string) => t(`common.${text}`),
        },
      },
    };

    if (!distributionChart) {
      distributionChart = new Column(
        document.getElementById("distribution-chart") as HTMLElement,
        // @ts-ignore
        config,
      );
      distributionChart.render();
    } else {
      distributionChart.update(config);
    }
  }, [distributionData, isLogScale]);

  const i18nTotalAccounts = new BigNumber(totalAccounts).toFormat();
  const i18nTotalBalances = new BigNumber(totalBalance).toFormat();
  const knownExchangeList = knownExchangeAccounts
    .map(({ alias }) => alias)
    .join(", ");
  const date = data?.status?.date || t("common.notAvailable");

  return (
    <>
      <Helmet>
        <title>Nano {t("menu.distribution")}</title>
      </Helmet>
      <Title level={3}>{t("pages.distribution.title")}</Title>
      <Card size="small">
        <div style={{ marginBottom: "12px" }}>
          <Text style={{ fontSize: "12px" }}>
            {t("common.executionTimeAgo")}{" "}
            <TimeAgo
              locale={i18next.language}
              datetime={date}
              live={false}
              style={{ fontWeight: "bold" }}
            />
          </Text>
          <br />
          <Text style={{ fontSize: "12px" }}>
            <Trans i18nKey="pages.distribution.summary">
              <strong>{{ i18nTotalAccounts }}</strong>
              <strong>{{ i18nTotalBalances }}</strong>
            </Trans>
          </Text>
          <br />
          <Text style={{ fontSize: "12px" }}>
            <Trans i18nKey="pages.distribution.summaryMinBalance">
              <strong />
            </Trans>
          </Text>
        </div>

        <div style={{ marginBottom: "6px" }}>
          <Switch
            disabled={isKnownAccountsLoading}
            checkedChildren={<CheckOutlined />}
            unCheckedChildren={<CloseOutlined />}
            onChange={(checked: boolean) => {
              setIsIncludeExchanges(checked);
            }}
            defaultChecked={isIncludeExchanges}
          />
          <Text style={{ marginLeft: "6px" }}>
            {t("pages.distribution.includeKnownExchanges")}
          </Text>
          <Tooltip
            placement="right"
            title={t("tooltips.knownExchangeBalance", {
              knownExchangeList,
              knownExchangeBalance,
            })}
          >
            <QuestionCircle />
          </Tooltip>
        </div>
        <div style={{ marginBottom: "6px" }}>
          <Switch
            disabled={isKnownAccountsLoading}
            checkedChildren={<CheckOutlined />}
            unCheckedChildren={<CloseOutlined />}
            onChange={(checked: boolean) => {
              setIsLogScale(checked);
            }}
            defaultChecked={isLogScale}
          />
          <Text style={{ margin: "0 6px" }}>
            {t("pages.distribution.logScale")}
          </Text>
        </div>

        <div style={{ marginTop: 24 }} id="distribution-chart" />
      </Card>

      <RichList />

      <DormantFunds data={data?.dormantFunds} />
    </>
  );
}
Example #25
Source File: TAQueueDetailButtons.tsx    From office-hours with GNU General Public License v3.0 4 votes vote down vote up
export default function TAQueueDetailButtons({
  courseId,
  queueId,
  question,
  hasUnresolvedRephraseAlert,
}: {
  courseId: number;
  queueId: number;
  question: Question;
  hasUnresolvedRephraseAlert: boolean;
}): ReactElement {
  const defaultMessage = useDefaultMessage();
  const { mutateQuestions } = useQuestions(queueId);

  const changeStatus = useCallback(
    async (status: QuestionStatus) => {
      await API.questions.update(question.id, { status });
      mutateQuestions();
    },
    [question.id, mutateQuestions]
  );
  const { isCheckedIn, isHelping } = useTAInQueueInfo(queueId);

  const openTeams = useTeams(queueId, question.creator.email, defaultMessage);

  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const sendRephraseAlert = async () => {
    const payload: RephraseQuestionPayload = {
      queueId,
      questionId: question.id,
      courseId,
    };
    try {
      await API.alerts.create({
        alertType: AlertType.REPHRASE_QUESTION,
        courseId,
        payload,
        targetUserId: question.creator.id,
      });
      await mutateQuestions();
      message.success("Successfully asked student to rephrase their question.");
    } catch (e) {
      //If the ta creates an alert that already exists the error is caught and nothing happens
    }
  };

  const helpStudent = () => {
    changeStatus(OpenQuestionStatus.Helping);
    openTeams();
  };
  const deleteQuestion = async () => {
    await changeStatus(
      question.status === OpenQuestionStatus.Drafting
        ? ClosedQuestionStatus.DeletedDraft
        : LimboQuestionStatus.TADeleted
    );
    await API.questions.notify(question.id);
  };

  useHotkeys(
    "shift+d",
    () => {
      if (isCheckedIn) {
        deleteQuestion();
      }
    },
    [question]
  );

  if (question.status === OpenQuestionStatus.Helping) {
    return (
      <>
        <Popconfirm
          title="Are you sure you want to send this student back to the queue?"
          okText="Yes"
          cancelText="No"
          onConfirm={async () => {
            message.success(PRORITY_QUEUED_MESSAGE_TEXT, 2);
            await changeStatus(LimboQuestionStatus.ReQueueing);
          }}
        >
          <Tooltip title="Requeue Student">
            <RequeueButton
              icon={<UndoOutlined />}
              data-cy="requeue-student-button"
            />
          </Tooltip>
        </Popconfirm>
        <Popconfirm
          title="Are you sure you can't find this student?"
          okText="Yes"
          cancelText="No"
          onConfirm={async () => {
            message.success(PRORITY_QUEUED_MESSAGE_TEXT, 2);
            await changeStatus(LimboQuestionStatus.CantFind);
            await API.questions.notify(question.id);
          }}
        >
          <Tooltip title="Can't Find">
            <CantFindButton
              shape="circle"
              icon={<CloseOutlined />}
              data-cy="cant-find-button"
            />
          </Tooltip>
        </Popconfirm>
        <Tooltip title="Finish Helping">
          <FinishHelpingButton
            icon={<CheckOutlined />}
            onClick={() => changeStatus(ClosedQuestionStatus.Resolved)}
            data-cy="finish-helping-button"
          />
        </Tooltip>
      </>
    );
  } else {
    const [canHelp, helpTooltip] = ((): [boolean, string] => {
      if (!isCheckedIn) {
        return [false, "You must check in to help students!"];
      } else if (isHelping) {
        return [false, "You are already helping a student"];
      } else {
        return [true, "Help Student"];
      }
    })();
    const [canRephrase, rephraseTooltip] = ((): [boolean, string] => {
      if (!isCheckedIn) {
        return [
          false,
          "You must check in to ask this student to rephrase their question",
        ];
      } else if (hasUnresolvedRephraseAlert) {
        return [
          false,
          "The student has already been asked to rephrase their question",
        ];
      } else if (question.status === OpenQuestionStatus.Drafting) {
        return [
          false,
          "The student must finish drafting before they can be asked to rephrase their question",
        ];
      } else {
        return [true, "Ask the student to add more detail to their question"];
      }
    })();
    return (
      <>
        <Popconfirm
          title="Are you sure you want to delete this question from the queue?"
          disabled={!isCheckedIn}
          okText="Yes"
          cancelText="No"
          onConfirm={async () => {
            await deleteQuestion();
          }}
        >
          <Tooltip
            title={
              isCheckedIn
                ? "Remove From Queue"
                : "You must check in to remove students from the queue"
            }
          >
            <span>
              {/* This span is a workaround for tooltip-on-disabled-button 
              https://github.com/ant-design/ant-design/issues/9581#issuecomment-599668648 */}
              <BannerDangerButton
                shape="circle"
                icon={<DeleteOutlined />}
                data-cy="remove-from-queue"
                disabled={!isCheckedIn}
              />
            </span>
          </Tooltip>
        </Popconfirm>
        <Tooltip title={rephraseTooltip}>
          <span>
            <BannerOrangeButton
              shape="circle"
              icon={<QuestionOutlined />}
              onClick={sendRephraseAlert}
              data-cy="request-rephrase-question"
              disabled={!canRephrase}
            />
          </span>
        </Tooltip>
        <Tooltip title={helpTooltip}>
          <span>
            <BannerPrimaryButton
              icon={<PhoneOutlined />}
              onClick={() => helpStudent()}
              disabled={!canHelp}
              data-cy="help-student"
            />
          </span>
        </Tooltip>
      </>
    );
  }
}
Example #26
Source File: HTTPFlowTable.tsx    From yakit with GNU Affero General Public License v3.0 4 votes vote down vote up
HTTPFlowTable: React.FC<HTTPFlowTableProp> = (props) => {
    const [data, setData, getData] = useGetState<HTTPFlow[]>([])
    const [params, setParams] = useState<YakQueryHTTPFlowRequest>(
        props.params || {SourceType: "mitm"}
    )
    const [pagination, setPagination] = useState<PaginationSchema>({
        Limit: OFFSET_LIMIT,
        Order: "desc",
        OrderBy: "created_at",
        Page: 1
    });

    // const [autoReload, setAutoReload, getAutoReload] = useGetState(false);
    const autoReloadRef = useRef<boolean>(false);
    const autoReload = autoReloadRef.current;
    const setAutoReload = (b: boolean) => {
        autoReloadRef.current = b
    };
    const getAutoReload = () => autoReloadRef.current;

    const [total, setTotal] = useState<number>(0)
    const [loading, setLoading] = useState(false)
    const [selected, setSelected, getSelected] = useGetState<HTTPFlow>()
    const [_lastSelected, setLastSelected, getLastSelected] = useGetState<HTTPFlow>()

    const [compareLeft, setCompareLeft] = useState<CompateData>({content: '', language: 'http'})
    const [compareRight, setCompareRight] = useState<CompateData>({content: '', language: 'http'})
    const [compareState, setCompareState] = useState(0)
    const [tableContentHeight, setTableContentHeight, getTableContentHeight] = useGetState<number>(0);
    // 用于记录适合
    const [_scrollY, setScrollYRaw, getScrollY] = useGetState(0)
    const setScrollY = useThrottleFn(setScrollYRaw, {wait: 300}).run

    // 如果这个大于等于 0 ,就 Lock 住,否则忽略
    const [_trigger, setLockedScroll, getLockedScroll] = useGetState(-1);
    const lockScrollTimeout = (size: number, timeout: number) => {
        setLockedScroll(size)
        setTimeout(() => setLockedScroll(-1), timeout)
    }

    const tableRef = useRef(null)

    const ref = useHotkeys('ctrl+r, enter', e => {
        const selected = getSelected()
        if (selected) {
            ipcRenderer.invoke("send-to-tab", {
                type: "fuzzer",
                data: {
                    isHttps: selected?.IsHTTPS,
                    request: new Buffer(selected.Request).toString()
                }
            })
        }
    })

    // 使用上下箭头
    useHotkeys("up", () => {
        setLastSelected(getSelected())
        const data = getData();
        if (data.length <= 0) {
            return
        }
        if (!getSelected()) {
            setSelected(data[0])
            return
        }
        const expected = parseInt(`${parseInt(`${(getSelected()?.Id as number)}`) + 1}`);
        // 如果上点的话,应该是选择更新的内容
        for (let i = 0; i < data.length; i++) {
            let current = parseInt(`${data[i]?.Id}`);
            if (current === expected) {
                setSelected(data[i])
                return
            }
        }
        setSelected(undefined)
    })
    useHotkeys("down", () => {
        setLastSelected(getSelected())
        const data = getData();

        if (data.length <= 0) {
            return
        }
        if (!getSelected()) {
            setSelected(data[0])
            return
        }
        // 如果上点的话,应该是选择更新的内容
        for (let i = 0; i < data.length; i++) {
            if (data[i]?.Id == (getSelected()?.Id as number) - 1) {
                setSelected(data[i])
                return
            }
        }
        setSelected(undefined)
    })

    // 向主页发送对比数据
    useEffect(() => {
        if (compareLeft.content) {
            const params = {info: compareLeft, type: 1}
            setCompareState(compareState === 0 ? 1 : 0)

            ipcRenderer.invoke("add-data-compare", params)
        }
    }, [compareLeft])

    useEffect(() => {
        if (compareRight.content) {
            const params = {info: compareRight, type: 2}
            setCompareState(compareState === 0 ? 2 : 0)

            ipcRenderer.invoke("add-data-compare", params)
        }
    }, [compareRight])

    const update = useMemoizedFn((
        page?: number,
        limit?: number,
        order?: string,
        orderBy?: string,
        sourceType?: string,
        noLoading?: boolean
    ) => {
        const paginationProps = {
            Page: page || 1,
            Limit: limit || pagination.Limit,
            Order: order || "desc",
            OrderBy: orderBy || "id"
        }
        if (!noLoading) {
            setLoading(true)
            // setAutoReload(false)
        }
        // yakQueryHTTPFlow({
        //     SourceType: sourceType, ...params,
        //     Pagination: {...paginationProps},
        // })
        ipcRenderer
            .invoke("QueryHTTPFlows", {
                SourceType: sourceType,
                ...params,
                Pagination: {...paginationProps}
            })
            .then((rsp: YakQueryHTTPFlowResponse) => {
                setData((rsp?.Data || []))
                setPagination(rsp.Pagination)
                setTotal(rsp.Total)
            })
            .catch((e: any) => {
                failed(`query HTTP Flow failed: ${e}`)
            })
            .finally(() => setTimeout(() => setLoading(false), 300))
    })

    const getNewestId = useMemoizedFn(() => {
        let max = 0;
        (getData() || []).forEach(e => {
            const id = parseInt(`${e.Id}`)
            if (id >= max) {
                max = id
            }
        })
        return max
    })

    const getOldestId = useMemoizedFn(() => {
        if (getData().length <= 0) {
            return 0
        }
        let min = parseInt(`${getData()[0].Id}`);
        (getData() || []).forEach(e => {
            const id = parseInt(`${e.Id}`)
            if (id <= min) {
                min = id
            }
        })
        return min
    })

    // 第一次启动的时候加载一下
    useEffect(() => {
        update(1)
    }, [])

    const scrollTableTo = useMemoizedFn((size: number) => {
        if (!tableRef || !tableRef.current) return
        const table = tableRef.current as unknown as {
            scrollTop: (number) => any,
            scrollLeft: (number) => any,
        }
        table.scrollTop(size)
    })

    const scrollUpdateTop = useDebounceFn(useMemoizedFn(() => {
        const paginationProps = {
            Page: 1,
            Limit: OFFSET_STEP,
            Order: "desc",
            OrderBy: "id"
        }

        const offsetId = getNewestId()
        console.info("触顶:", offsetId)
        // 查询数据
        ipcRenderer
            .invoke("QueryHTTPFlows", {
                SourceType: "mitm",
                ...params,
                AfterId: offsetId,  // 用于计算增量的
                Pagination: {...paginationProps}
            })
            .then((rsp: YakQueryHTTPFlowResponse) => {
                const offsetDeltaData = (rsp?.Data || [])
                if (offsetDeltaData.length <= 0) {
                    // 没有增量数据
                    return
                }
                setLoading(true)
                let offsetData = offsetDeltaData.concat(data);
                if (offsetData.length > MAX_ROW_COUNT) {
                    offsetData = offsetData.splice(0, MAX_ROW_COUNT)
                }
                setData(offsetData);
                scrollTableTo((offsetDeltaData.length + 1) * ROW_HEIGHT)
            })
            .catch((e: any) => {
                failed(`query HTTP Flow failed: ${e}`)
            })
            .finally(() => setTimeout(() => setLoading(false), 200))
    }), {wait: 600, leading: true, trailing: false}).run
    const scrollUpdateButt = useDebounceFn(useMemoizedFn((tableClientHeight: number) => {
        const paginationProps = {
            Page: 1,
            Limit: OFFSET_STEP,
            Order: "desc",
            OrderBy: "id"
        }

        const offsetId = getOldestId();
        console.info("触底:", offsetId)

        // 查询数据
        ipcRenderer
            .invoke("QueryHTTPFlows", {
                SourceType: "mitm",
                ...params,
                BeforeId: offsetId,  // 用于计算增量的
                Pagination: {...paginationProps}
            })
            .then((rsp: YakQueryHTTPFlowResponse) => {
                const offsetDeltaData = (rsp?.Data || [])
                if (offsetDeltaData.length <= 0) {
                    // 没有增量数据
                    return
                }
                setLoading(true)
                const originDataLength = data.length;
                let offsetData = data.concat(offsetDeltaData);
                let metMax = false
                const originOffsetLength = offsetData.length;
                if (originOffsetLength > MAX_ROW_COUNT) {
                    metMax = true
                    offsetData = offsetData.splice(originOffsetLength - MAX_ROW_COUNT, MAX_ROW_COUNT)
                }
                setData(offsetData);
                setTimeout(() => {
                    if (!metMax) {
                        // 没有丢结果的裁剪问题
                        scrollTableTo((originDataLength + 1) * ROW_HEIGHT - tableClientHeight)
                    } else {
                        // 丢了结果之后的裁剪计算
                        const a = originOffsetLength - offsetDeltaData.length;
                        scrollTableTo((originDataLength + 1 + MAX_ROW_COUNT - originOffsetLength) * ROW_HEIGHT - tableClientHeight)
                    }
                }, 50)
            })
            .catch((e: any) => {
                failed(`query HTTP Flow failed: ${e}`)
            }).finally(() => setTimeout(() => setLoading(false), 60))
    }), {wait: 600, leading: true, trailing: false}).run

    const sortFilter = useMemoizedFn((column: string, type: any) => {
        const keyRelation: any = {
            UpdatedAt: "updated_at",
            BodyLength: "body_length",
            StatusCode: "status_code"
        }

        if (column && type) {
            update(1, OFFSET_LIMIT, type, keyRelation[column])
        } else {
            update(1, OFFSET_LIMIT)
        }
    })

    // 这是用来设置选中坐标的,不需要做防抖
    useEffect(() => {
        if (!getLastSelected() || !getSelected()) {
            return
        }

        const lastSelected = getLastSelected() as HTTPFlow;
        const up = parseInt(`${lastSelected?.Id}`) < parseInt(`${selected?.Id}`)
        // if (up) {
        //     console.info("up")
        // } else {
        //     console.info("down")
        // }
        // console.info(lastSelected.Id, selected?.Id)
        const screenRowCount = Math.floor(getTableContentHeight() / ROW_HEIGHT) - 1

        if (!autoReload) {
            let count = 0;
            const data = getData();
            for (let i = 0; i < data.length; i++) {
                if (data[i].Id != getSelected()?.Id) {
                    count++
                } else {
                    break
                }
            }

            let minCount = count
            if (minCount < 0) {
                minCount = 0
            }
            const viewHeightMin = getScrollY() + tableContentHeight
            const viewHeightMax = getScrollY() + tableContentHeight * 2
            const minHeight = minCount * ROW_HEIGHT;
            const maxHeight = minHeight + tableContentHeight
            const maxHeightBottom = minHeight + tableContentHeight + 3 * ROW_HEIGHT
            // console.info("top: ", minHeight, "maxHeight: ", maxHeight, "maxHeightBottom: ", maxHeightBottom)
            // console.info("viewTop: ", viewHeightMin, "viewButtom: ", viewHeightMax)
            if (maxHeight < viewHeightMin) {
                // 往下滚动
                scrollTableTo(minHeight)
                return
            }
            if (maxHeightBottom > viewHeightMax) {
                // 上滚动
                const offset = minHeight - (screenRowCount - 2) * ROW_HEIGHT;
                // console.info(screenRowCount, minHeight, minHeight - (screenRowCount - 1) * ROW_HEIGHT)
                if (offset > 0) {
                    scrollTableTo(offset)
                }
                return
            }
        }
    }, [selected])

    // 给设置做防抖
    useDebounceEffect(() => {
        props.onSelected && props.onSelected(selected)
    }, [selected], {wait: 400, trailing: true, leading: true})

    useEffect(() => {
        if (autoReload) {
            const id = setInterval(() => {
                update(1, undefined, "desc", undefined, undefined, true)
            }, 1000)
            return () => {
                clearInterval(id)
            }
        }
    }, [autoReload])

    return (
        // <AutoCard bodyStyle={{padding: 0, margin: 0}} bordered={false}>
        <div ref={ref as Ref<any>} tabIndex={-1}
             style={{width: "100%", height: "100%", overflow: "hidden"}}
        >
            <ReactResizeDetector
                onResize={(width, height) => {
                    if (!width || !height) {
                        return
                    }
                    setTableContentHeight(height - 38)
                }}
                handleWidth={true} handleHeight={true} refreshMode={"debounce"} refreshRate={50}/>
            {!props.noHeader && (
                <PageHeader
                    title={"HTTP History"}
                    subTitle={
                        <Space>
                            {"所有相关请求都在这里"}
                            <Button
                                icon={<ReloadOutlined/>}
                                type={"link"}
                                onClick={(e) => {
                                    update(1)
                                }}
                            />
                        </Space>
                    }
                    extra={[
                        <Space>
                            <Form.Item label={"选择 HTTP History 类型"} style={{marginBottom: 0}}>
                                <Select
                                    mode={"multiple"}
                                    value={params.SourceType}
                                    style={{minWidth: 200}}
                                    onChange={(e) => {
                                        setParams({...params, SourceType: e})
                                        setLoading(true)
                                        setTimeout(() => {
                                            update(1, undefined, undefined, undefined, e)
                                        }, 200)
                                    }}
                                >
                                    <Select.Option value={"mitm"}>mitm: 中间人劫持</Select.Option>
                                    <Select.Option value={"fuzzer"}>
                                        fuzzer: 模糊测试分析
                                    </Select.Option>
                                </Select>
                            </Form.Item>
                            <Popconfirm
                                title={"确定想要删除所有记录吗?不可恢复"}
                                onConfirm={(e) => {
                                    ipcRenderer.invoke("delete-http-flows-all")
                                    setLoading(true)
                                    info("正在删除...如自动刷新失败请手动刷新")
                                    setTimeout(() => {
                                        update(1)
                                        if (props.onSelected) props.onSelected(undefined)
                                    }, 400)
                                }}
                            >
                                <Button danger={true}>清除全部历史记录?</Button>
                            </Popconfirm>
                        </Space>
                    ]}
                />
            )}
            <Row style={{margin: "5px 0 5px 5px"}}>
                <Col span={12}>
                    <Space>
                        <span>HTTP History</span>
                        <Button
                            icon={<ReloadOutlined/>}
                            type={"link"}
                            size={"small"}
                            onClick={(e) => {
                                update(1, undefined, "desc")
                            }}
                        />
                        {/* <Space>
                            自动刷新:
                            <Switch size={"small"} checked={autoReload} onChange={setAutoReload}/>
                        </Space> */}
                        <Input.Search
                            placeholder={"URL关键字"}
                            enterButton={true}
                            size={"small"}
                            style={{width: 170}}
                            value={params.SearchURL}
                            onChange={(e) => {
                                setParams({...params, SearchURL: e.target.value})
                            }}
                            onSearch={(v) => {
                                update(1)
                            }}
                        />
                        {props.noHeader && (
                            <Popconfirm
                                title={"确定想要删除所有记录吗?不可恢复"}
                                onConfirm={(e) => {
                                    ipcRenderer.invoke("delete-http-flows-all")
                                    setLoading(true)
                                    info("正在删除...如自动刷新失败请手动刷新")
                                    setCompareLeft({content: '', language: 'http'})
                                    setCompareRight({content: '', language: 'http'})
                                    setCompareState(0)
                                    setTimeout(() => {
                                        update(1)
                                        if (props.onSelected) props.onSelected(undefined)
                                    }, 400)
                                }}
                            >
                                <Button danger={true} size={"small"}>
                                    删除历史记录
                                </Button>
                            </Popconfirm>
                        )}
                        {/*{autoReload && <Tag color={"green"}>自动刷新中...</Tag>}*/}
                    </Space>
                </Col>
                <Col span={12} style={{textAlign: "right"}}>
                    <Tag>{total} Records</Tag>
                </Col>
            </Row>
            <TableResizableColumn
                tableRef={tableRef}
                virtualized={true}
                className={"httpFlowTable"}
                loading={loading}
                columns={[
                    {
                        dataKey: "Id",
                        width: 80,
                        headRender: () => "序号",
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return `${rowData[dataKey] <= 0 ? "..." : rowData[dataKey]}`
                        }
                    },
                    {
                        dataKey: "Method",
                        width: 70,
                        headRender: (params1: any) => {
                            return (
                                <div
                                    style={{display: "flex", justifyContent: "space-between"}}
                                >
                                    方法
                                    <Popover
                                        placement='bottom'
                                        trigger='click'
                                        content={
                                            params &&
                                            setParams && (
                                                <HTTLFlowFilterDropdownForms
                                                    label={"搜索方法"}
                                                    params={params}
                                                    setParams={setParams}
                                                    filterName={"Methods"}
                                                    autoCompletions={["GET", "POST", "HEAD"]}
                                                    submitFilter={() => update(1)}
                                                />
                                            )
                                        }
                                    >
                                        <Button
                                            style={{
                                                paddingLeft: 4, paddingRight: 4, marginLeft: 4,
                                                color: !!params.Methods ? undefined : "gray",
                                            }}
                                            type={!!params.Methods ? "primary" : "link"} size={"small"}
                                            icon={<SearchOutlined/>}
                                        />
                                    </Popover>
                                </div>
                            )
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            // return (
                            //     <Tag color={"geekblue"} style={{marginRight: 20}}>
                            //         {rowData[dataKey]}
                            //     </Tag>
                            // )
                            return rowData[dataKey]
                        }
                    },
                    {
                        dataKey: "StatusCode",
                        width: 100,
                        sortable: true,
                        headRender: () => {
                            return (
                                <div
                                    style={{display: "inline-flex"}}
                                >
                                    状态码
                                    <Popover
                                        placement='bottom'
                                        trigger='click'
                                        content={
                                            params &&
                                            setParams && (
                                                <HTTLFlowFilterDropdownForms
                                                    label={"搜索状态码"}
                                                    params={params}
                                                    setParams={setParams}
                                                    filterName={"StatusCode"}
                                                    autoCompletions={[
                                                        "200",
                                                        "300-305",
                                                        "400-404",
                                                        "500-502",
                                                        "200-299",
                                                        "300-399",
                                                        "400-499"
                                                    ]}
                                                    submitFilter={() => update(1)}
                                                />
                                            )
                                        }
                                    >
                                        <Button
                                            style={{
                                                paddingLeft: 4, paddingRight: 4, marginLeft: 4,
                                                color: !!params.StatusCode ? undefined : "gray",
                                            }}
                                            type={!!params.StatusCode ? "primary" : "link"} size={"small"}
                                            icon={<SearchOutlined/>}
                                        />
                                    </Popover>
                                </div>
                            )
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return (
                                <div style={{color: StatusCodeToColor(rowData[dataKey])}}>
                                    {rowData[dataKey] === 0 ? "" : rowData[dataKey]}
                                </div>
                            )
                        }
                    },
                    {
                        dataKey: "Url",
                        resizable: true,
                        headRender: () => {
                            return (
                                <div
                                    style={{display: "flex", justifyContent: "space-between"}}
                                >
                                    URL
                                    <Popover
                                        placement='bottom'
                                        trigger='click'
                                        content={
                                            params &&
                                            setParams && (
                                                <HTTLFlowFilterDropdownForms
                                                    label={"搜索URL关键字"}
                                                    params={params}
                                                    setParams={setParams}
                                                    filterName={"SearchURL"}
                                                    pureString={true}
                                                    submitFilter={() => update(1)}
                                                />
                                            )
                                        }
                                    >
                                        <Button
                                            style={{
                                                paddingLeft: 4, paddingRight: 4, marginLeft: 4,
                                                color: !!params.SearchURL ? undefined : "gray",
                                            }}
                                            type={!!params.SearchURL ? "primary" : "link"} size={"small"}
                                            icon={<SearchOutlined/>}
                                        />
                                    </Popover>
                                </div>
                            )
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            if (rowData.IsPlaceholder) {
                                return <div style={{color: "#888585"}}>{"滚轮上滑刷新..."}</div>
                            }
                            return (
                                <div style={{width: "100%", display: "flex"}}>
                                    <div className='resize-ellipsis' title={rowData.Url}>
                                        {!params.SearchURL ? (
                                            rowData.Url
                                        ) : (
                                            rowData.Url
                                        )}
                                    </div>
                                </div>
                            )
                        },
                        width: 600
                    },
                    {
                        dataKey: "HtmlTitle",
                        width: 120,
                        resizable: true,
                        headRender: () => {
                            return "Title"
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return rowData[dataKey] ? rowData[dataKey] : ""
                        }
                    },
                    {
                        dataKey: "Tags",
                        width: 120,
                        resizable: true,
                        headRender: () => {
                            return "Tags"
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return rowData[dataKey] ? (
                                `${rowData[dataKey]}`.split("|").filter(i => !i.startsWith("YAKIT_COLOR_")).join(", ")
                            ) : ""
                        }
                    },
                    {
                        dataKey: "IPAddress",
                        width: 140, resizable: true,
                        headRender: () => {
                            return "IP"
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return rowData[dataKey] ? rowData[dataKey] : ""
                        }
                    },
                    {
                        dataKey: "BodyLength",
                        width: 120,
                        sortable: true,
                        headRender: () => {
                            return (
                                <div style={{display: "inline-block", position: "relative"}}>
                                    响应长度
                                    <Popover
                                        placement='bottom'
                                        trigger='click'
                                        content={
                                            params &&
                                            setParams && (
                                                <HTTLFlowFilterDropdownForms
                                                    label={"是否存在Body?"}
                                                    params={params}
                                                    setParams={setParams}
                                                    filterName={"HaveBody"}
                                                    pureBool={true}
                                                    submitFilter={() => update(1)}
                                                />
                                            )
                                        }
                                    >
                                        <Button
                                            style={{
                                                paddingLeft: 4, paddingRight: 4, marginLeft: 4,
                                                color: !!params.HaveBody ? undefined : "gray",
                                            }}
                                            type={!!params.HaveBody ? "primary" : "link"} size={"small"}
                                            icon={<SearchOutlined/>}
                                        />
                                    </Popover>
                                </div>
                            )
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return (
                                <div style={{width: 100}}>
                                    {/* 1M 以上的话,是红色*/}
                                    {rowData.BodyLength !== -1 ?
                                        (<div style={{color: rowData.BodyLength > 1000000 ? "red" : undefined}}>
                                            {rowData.BodySizeVerbose
                                                ? rowData.BodySizeVerbose
                                                : rowData.BodyLength}
                                        </div>)
                                        :
                                        (<div></div>)
                                    }
                                </div>
                            )
                        }
                    },
                    // {
                    //     dataKey: "UrlLength",
                    //     width: 90,
                    //     headRender: () => {
                    //         return "URL 长度"
                    //     },
                    //     cellRender: ({rowData, dataKey, ...props}: any) => {
                    //         const len = (rowData.Url || "").length
                    //         return len > 0 ? <div>{len}</div> : "-"
                    //     }
                    // },
                    {
                        dataKey: "GetParamsTotal",
                        width: 65,
                        align: "center",
                        headRender: () => {
                            return (
                                <div
                                    style={{display: "flex", justifyContent: "space-between"}}
                                >
                                    参数
                                    <Popover
                                        placement='bottom'
                                        trigger='click'
                                        content={
                                            params &&
                                            setParams && (
                                                <HTTLFlowFilterDropdownForms
                                                    label={"过滤是否存在基础参数"}
                                                    params={params}
                                                    setParams={setParams}
                                                    filterName={"HaveCommonParams"}
                                                    pureBool={true}
                                                    submitFilter={() => update(1)}
                                                />
                                            )
                                        }
                                    >
                                        <Button
                                            style={{
                                                paddingLeft: 4, paddingRight: 4, marginLeft: 4,
                                                color: !!params.HaveCommonParams ? undefined : "gray",
                                            }}
                                            type={!!params.HaveCommonParams ? "primary" : "link"} size={"small"}
                                            icon={<SearchOutlined/>}
                                        />
                                    </Popover>
                                </div>
                            )
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return (
                                <Space>
                                    {(rowData.GetParamsTotal > 0 ||
                                        rowData.PostParamsTotal > 0) && <CheckOutlined/>}
                                </Space>
                            )
                        }
                    },
                    {
                        dataKey: "ContentType",
                        resizable: true, width: 80,
                        headRender: () => {
                            return "响应类型"
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            let contentTypeFixed = rowData.ContentType.split(";")
                                .map((el: any) => el.trim())
                                .filter((i: any) => !i.startsWith("charset"))
                                .join(",") || "-"
                            if (contentTypeFixed.includes("/")) {
                                const contentTypeFixedNew = contentTypeFixed.split("/").pop()
                                if (!!contentTypeFixedNew) {
                                    contentTypeFixed = contentTypeFixedNew
                                }
                            }
                            return (
                                <div>
                                    {contentTypeFixed === "null" ? "" : contentTypeFixed}
                                </div>
                            )
                        }
                    },
                    {
                        dataKey: "UpdatedAt",
                        sortable: true,
                        width: 110,
                        headRender: () => {
                            return "请求时间"
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return <Tooltip
                                title={rowData[dataKey] === 0 ? "" : formatTimestamp(rowData[dataKey])}
                            >
                                {rowData[dataKey] === 0 ? "" : formatTime(rowData[dataKey])}
                            </Tooltip>
                        }
                    },
                    {
                        dataKey: "operate",
                        width: 90,
                        headRender: () => "操作",
                        cellRender: ({rowData}: any) => {
                            if (!rowData.Hash) return <></>
                            return (
                                <a
                                    onClick={(e) => {
                                        let m = showDrawer({
                                            width: "80%",
                                            content: onExpandHTTPFlow(
                                                rowData,
                                                () => m.destroy()
                                            )
                                        })
                                    }}
                                >
                                    详情
                                </a>
                            )
                        }
                    }
                ]}
                data={autoReload ? data : [TableFirstLinePlaceholder].concat(data)}
                autoHeight={tableContentHeight <= 0}
                height={tableContentHeight}
                sortFilter={sortFilter}
                renderRow={(children: ReactNode, rowData: any) => {
                    if (rowData)
                        return (
                            <div
                                id='http-flow-row'
                                ref={(node) => {
                                    const color =
                                        rowData.Hash === selected?.Hash ?
                                            "rgba(78, 164, 255, 0.4)" :
                                            rowData.Tags.indexOf("YAKIT_COLOR") > -1 ?
                                                TableRowColor(rowData.Tags.split("|").pop().split('_').pop().toUpperCase()) :
                                                "#ffffff"
                                    if (node) {
                                        if (color) node.style.setProperty("background-color", color, "important")
                                        else node.style.setProperty("background-color", "#ffffff")
                                    }
                                }}
                                style={{height: "100%"}}
                            >
                                {children}
                            </div>
                        )
                    return children
                }}
                onRowContextMenu={(rowData: HTTPFlow | any, event: React.MouseEvent) => {
                    if (rowData) {
                        setSelected(rowData);
                    }
                    showByCursorMenu(
                        {
                            content: [
                                {
                                    title: '发送到 Web Fuzzer',
                                    onClick: () => {
                                        ipcRenderer.invoke("send-to-tab", {
                                            type: "fuzzer",
                                            data: {
                                                isHttps: rowData.IsHTTPS,
                                                request: new Buffer(rowData.Request).toString("utf8")
                                            }
                                        })
                                    }
                                },
                                {
                                    title: '发送到 数据包扫描',
                                    onClick: () => {
                                        ipcRenderer
                                            .invoke("GetHTTPFlowByHash", {Hash: rowData.Hash})
                                            .then((i: HTTPFlow) => {
                                                ipcRenderer.invoke("send-to-packet-hack", {
                                                    request: i.Request,
                                                    ishttps: i.IsHTTPS,
                                                    response: i.Response
                                                })
                                            })
                                            .catch((e: any) => {
                                                failed(`Query Response failed: ${e}`)
                                            })
                                    }
                                },
                                {
                                    title: '复制 URL',
                                    onClick: () => {
                                        callCopyToClipboard(rowData.Url)
                                    },
                                },
                                {
                                    title: '复制为 Yak PoC 模版', onClick: () => {
                                    },
                                    subMenuItems: [
                                        {
                                            title: "数据包 PoC 模版", onClick: () => {
                                                const flow = rowData as HTTPFlow;
                                                if (!flow) return;
                                                generateYakCodeByRequest(flow.IsHTTPS, flow.Request, code => {
                                                    callCopyToClipboard(code)
                                                }, RequestToYakCodeTemplate.Ordinary)
                                            }
                                        },
                                        {
                                            title: "批量检测 PoC 模版", onClick: () => {
                                                const flow = rowData as HTTPFlow;
                                                if (!flow) return;
                                                generateYakCodeByRequest(flow.IsHTTPS, flow.Request, code => {
                                                    callCopyToClipboard(code)
                                                }, RequestToYakCodeTemplate.Batch)
                                            }
                                        },
                                    ]
                                },
                                {
                                    title: '标注颜色',
                                    subMenuItems: availableColors.map(i => {
                                        return {
                                            title: i.title,
                                            render: i.render,
                                            onClick: () => {
                                                const flow = rowData as HTTPFlow
                                                if (!flow) {
                                                    return
                                                }

                                                const existedTags = flow.Tags ? flow.Tags.split("|").filter(i => !!i && !i.startsWith("YAKIT_COLOR_")) : []
                                                existedTags.push(`YAKIT_COLOR_${i.color.toUpperCase()}`)
                                                ipcRenderer.invoke("SetTagForHTTPFlow", {
                                                    Id: flow.Id, Hash: flow.Hash,
                                                    Tags: existedTags,
                                                }).then(() => {
                                                    info(`设置 HTTPFlow 颜色成功`)
                                                    if (!autoReload) {
                                                        setData(data.map(item => {
                                                            if (item.Hash === flow.Hash) {
                                                                item.Tags = `YAKIT_COLOR_${i.color.toUpperCase()}`
                                                                return item
                                                            }
                                                            return item
                                                        }))
                                                    }
                                                })
                                            }
                                        }
                                    }),
                                    onClick: () => {
                                    }
                                },
                                {
                                    title: '移除颜色',
                                    onClick: () => {
                                        const flow = rowData as HTTPFlow
                                        if (!flow) return

                                        const existedTags = flow.Tags ? flow.Tags.split("|").filter(i => !!i && !i.startsWith("YAKIT_COLOR_")) : []
                                        existedTags.pop()
                                        ipcRenderer.invoke("SetTagForHTTPFlow", {
                                            Id: flow.Id, Hash: flow.Hash,
                                            Tags: existedTags,
                                        }).then(() => {
                                            info(`清除 HTTPFlow 颜色成功`)
                                            if (!autoReload) {
                                                setData(data.map(item => {
                                                    if (item.Hash === flow.Hash) {
                                                        item.Tags = ""
                                                        return item
                                                    }
                                                    return item
                                                }))
                                            }
                                        })
                                        return
                                    },
                                },
                                {
                                    title: "发送到对比器", onClick: () => {
                                    },
                                    subMenuItems: [
                                        {
                                            title: '发送到对比器左侧',
                                            onClick: () => {
                                                setCompareLeft({
                                                    content: new Buffer(rowData.Request).toString("utf8"),
                                                    language: 'http'
                                                })
                                            },
                                            disabled: [false, true, false][compareState]
                                        },
                                        {
                                            title: '发送到对比器右侧',
                                            onClick: () => {
                                                setCompareRight({
                                                    content: new Buffer(rowData.Request).toString("utf8"),
                                                    language: 'http'
                                                })
                                            },
                                            disabled: [false, false, true][compareState]
                                        }
                                    ]
                                },
                            ]
                        },
                        event.clientX,
                        event.clientY
                    )
                }}
                onRowClick={(rowDate: any) => {
                    if (!rowDate.Hash) return
                    if (rowDate.Hash !== selected?.Hash) {
                        setSelected(rowDate)
                    } else {
                        // setSelected(undefined)
                    }
                }}
                onScroll={(scrollX, scrollY) => {
                    setScrollY(scrollY)
                    // 防止无数据触发加载
                    if (data.length === 0 && !getAutoReload()) {
                        setAutoReload(true)
                        return
                    }

                    // 根据页面展示内容决定是否自动刷新
                    let contextHeight = (data.length + 1) * ROW_HEIGHT // +1 是要把表 title 算进去
                    let offsetY = scrollY + tableContentHeight;
                    if (contextHeight < tableContentHeight) {
                        setAutoReload(true)
                        return
                    }
                    setAutoReload(false)

                    // 向下刷新数据
                    if (contextHeight <= offsetY) {
                        setAutoReload(false)
                        scrollUpdateButt(tableContentHeight)
                        return
                    }

                    // 锁住滚轮
                    if (getLockedScroll() > 0 && getLockedScroll() >= scrollY) {
                        if (scrollY === getLockedScroll()) {
                            return
                        }
                        // scrollTableTo(getLockedScroll())
                        return
                    }
                    const toTop = scrollY <= 0;
                    if (toTop) {
                        lockScrollTimeout(ROW_HEIGHT, 600)
                        scrollUpdateTop()
                    }
                }}
            />
        </div>
        // </AutoCard>
    )
}
Example #27
Source File: MITMPage.tsx    From yakit with GNU Affero General Public License v3.0 4 votes vote down vote up
MITMPage: React.FC<MITMPageProp> = (props) => {
    const [status, setStatus] = useState<"idle" | "hijacked" | "hijacking">("idle");
    const [error, setError] = useState("");
    const [host, setHost] = useState("127.0.0.1");
    const [port, setPort] = useState(8083);
    const [downstreamProxy, setDownstreamProxy] = useState<string>();
    const [loading, setLoading] = useState(false);
    const [caCerts, setCaCerts] = useState<CaCertData>({
        CaCerts: new Buffer(""), LocalFile: "",
    });
    const [enableInitialPlugin, setEnableInitialPlugin] = useState(false);

    // 存储修改前和修改后的包!
    const [currentPacketInfo, setCurrentPacketInfo] = useState<{
        currentPacket: Uint8Array,
        currentPacketId: number,
        isHttp: boolean
    }>({currentPacketId: 0, currentPacket: new Buffer([]), isHttp: true});
    const {currentPacket, currentPacketId, isHttp} = currentPacketInfo;
    const clearCurrentPacket = () => {
        setCurrentPacketInfo({currentPacketId: 0, currentPacket: new Buffer([]), isHttp: true})
    }
    const [modifiedPacket, setModifiedPacket] = useState<Uint8Array>(new Buffer([]));

    // 自动转发 与 劫持响应的自动设置
    const [autoForward, setAutoForward, getAutoForward] = useGetState<"manual" | "log" | "passive">("log");
    const isManual = autoForward === "manual";

    const [hijackAllResponse, setHijackAllResponse] = useState(false); // 劫持所有请求
    const [allowHijackCurrentResponse, setAllowHijackCurrentResponse] = useState(false); // 仅劫持一个请求
    const [initialed, setInitialed] = useState(false);
    const [forResponse, setForResponse] = useState(false);
    const [haveSideCar, setHaveSideCar] = useState(true);

    const [urlInfo, setUrlInfo] = useState("监听中...")
    const [ipInfo, setIpInfo] = useState("")

    // 设置初始化启动的插件
    const [defaultPlugins, setDefaultPlugins] = useState<string[]>([]);

    // yakit log message
    const [logs, setLogs] = useState<ExecResultLog[]>([]);
    const latestLogs = useLatest<ExecResultLog[]>(logs);
    const [_, setLatestStatusHash, getLatestStatusHash] = useGetState("");
    const [statusCards, setStatusCards] = useState<StatusCardProps[]>([])

    // filter 过滤器
    const [mitmFilter, setMITMFilter] = useState<MITMFilterSchema>();

    // 内容替代模块
    const [replacers, setReplacers] = useState<MITMContentReplacerRule[]>([]);

    // mouse
    const mouseState = useMouse();

    // 操作系统类型
    const [system, setSystem] = useState<string>()

    useEffect(() => {
        ipcRenderer.invoke('fetch-system-name').then((res) => setSystem(res))
    }, [])

    useEffect(() => {
        // 设置 MITM 初始启动插件选项
        getValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN).then(a => {
            setEnableInitialPlugin(!!a)
        })
    }, [])

    // 用于接受后端传回的信息
    useEffect(() => {
        setInitialed(false)
        // 用于前端恢复状态
        ipcRenderer.invoke("mitm-have-current-stream").then(data => {
            const {haveStream, host, port} = data;
            if (haveStream) {
                setStatus("hijacking")
                setHost(host);
                setPort(port);
            }
        }).finally(() => {
            recover()
            setTimeout(() => setInitialed(true), 500)
        })

        // 用于启动 MITM 开始之后,接受开始成功之后的第一个消息,如果收到,则认为说 MITM 启动成功了
        ipcRenderer.on("client-mitm-start-success", () => {
            setStatus("hijacking")
            setTimeout(() => {
                setLoading(false)
            }, 300)
        })

        // 用于 MITM 的 Message (YakitLog)
        const messages: ExecResultLog[] = [];
        const statusMap = new Map<string, StatusCardProps>();
        let lastStatusHash = '';
        ipcRenderer.on("client-mitm-message", (e, data: ExecResult) => {
            let msg = ExtractExecResultMessage(data);
            if (msg !== undefined) {
                // logHandler.logs.push(msg as ExecResultLog)
                // if (logHandler.logs.length > 25) {
                //     logHandler.logs.shift()
                // }
                const currentLog = msg as ExecResultLog;
                if (currentLog.level === "feature-status-card-data") {
                    lastStatusHash = `${currentLog.timestamp}-${currentLog.data}`

                    try {
                        // 解析 Object
                        const obj = JSON.parse(currentLog.data)
                        const {id, data} = obj;
                        if (!data) {
                            statusMap.delete(`${id}`)
                        } else {
                            statusMap.set(`${id}`, {Data: data, Id: id, Timestamp: currentLog.timestamp})
                        }
                    } catch (e) {

                    }
                    return
                }
                messages.push(currentLog)
                if (messages.length > 25) {
                    messages.shift()
                }
            }
        })

        // let currentFlow: HTTPFlow[] = []
        ipcRenderer.on("client-mitm-history-update", (e: any, data: any) => {
            // currentFlow.push(data.historyHTTPFlow as HTTPFlow)
            //
            // if (currentFlow.length > 30) {
            //     currentFlow = [...currentFlow.slice(0, 30)]
            // }
            // setFlows([...currentFlow])
        })

        ipcRenderer.on("client-mitm-error", (e, msg) => {
            if (!msg) {
                info("MITM 劫持服务器已关闭")
            } else {
                failed("MITM 劫持服务器异常或被关闭")
                Modal.error({
                    mask: true, title: "启动 MITM 服务器 ERROR!",
                    content: <>{msg}</>
                })
            }
            ipcRenderer.invoke("mitm-stop-call")
            setError(`${msg}`)
            setStatus("idle")
            setTimeout(() => {
                setLoading(false)
            }, 300)
        });
        ipcRenderer.on("client-mitm-filter", (e, msg) => {
            ipcRenderer
                .invoke("get-value", DefaultMitmFilter)
                .then((res: any) => {
                    if (res) {
                        const filter = {
                            includeSuffix: res.includeSuffix,
                            excludeMethod: res.excludeMethod,
                            excludeSuffix: res.excludeSuffix,
                            includeHostname: res.includeHostname,
                            excludeHostname: res.excludeHostname,
                            excludeContentTypes: res.excludeContentTypes,
                        }
                        setMITMFilter(filter)
                        ipcRenderer.invoke("mitm-filter", {
                            updateFilter: true, ...filter
                        })
                    } else {
                        setMITMFilter({
                            includeSuffix: msg.includeSuffix,
                            excludeMethod: msg.excludeMethod,
                            excludeSuffix: msg.excludeSuffix,
                            includeHostname: msg.includeHostname,
                            excludeHostname: msg.excludeHostname,
                            excludeContentTypes: msg.excludeContentTypes,
                        })
                    }
                })
        })

        const updateLogs = () => {
            if (latestLogs.current.length !== messages.length) {
                setLogs([...messages])
                return
            }

            if (latestLogs.current.length > 0 && messages.length > 0) {
                if (latestLogs.current[0].data !== messages[0].data) {
                    setLogs([...messages])
                    return
                }
            }

            if (getLatestStatusHash() !== lastStatusHash) {
                setLatestStatusHash(lastStatusHash)

                const tmpCurrent: StatusCardProps[] = [];
                statusMap.forEach((value, key) => {
                    tmpCurrent.push(value)
                })
                setStatusCards(tmpCurrent.sort((a, b) => a.Id.localeCompare(b.Id)))
            }
        }
        updateLogs()
        let id = setInterval(() => {
            updateLogs()
        }, 1000)

        return () => {
            clearInterval(id);
            ipcRenderer.removeAllListeners("client-mitm-error")
            // ipcRenderer.invoke("mitm-close-stream")
        }
    }, [])

    useEffect(() => {
        if (hijackAllResponse && currentPacketId > 0) {
            allowHijackedResponseByRequest(currentPacketId)
        }
    }, [hijackAllResponse, currentPacketId])

    useEffect(() => {
        ipcRenderer.on("client-mitm-hijacked", forwardHandler);
        return () => {
            ipcRenderer.removeAllListeners("client-mitm-hijacked")
        }
    }, [autoForward])

    useEffect(() => {
        ipcRenderer.invoke("mitm-auto-forward", !isManual).finally(() => {
            console.info(`设置服务端自动转发:${!isManual}`)
        })
    }, [autoForward])

    useEffect(() => {
        ipcRenderer.on("client-mitm-content-replacer-update", (e, data: MITMResponse) => {
            setReplacers(data?.replacers || [])
            return
        });
        return () => {
            ipcRenderer.removeAllListeners("client-mitm-content-replacer-update")
        }
    }, [])

    useEffect(() => {
        if (currentPacketId <= 0 && status === "hijacked") {
            recover()
            const id = setInterval(() => {
                recover()
            }, 500)
            return () => {
                clearInterval(id)
            }
        }
    }, [currentPacketId])

    useEffect(() => {
        ipcRenderer.invoke("DownloadMITMCert", {}).then((data: CaCertData) => {
            setCaCerts(data)
        })
    }, [])

    const addr = `http://${host}:${port}`;

    // 自动转发劫持,进行的操作
    const forwardHandler = useMemoizedFn((e: any, msg: MITMResponse) => {
        setMITMFilter({
            includeSuffix: msg.includeSuffix,
            excludeMethod: msg.excludeMethod,
            excludeSuffix: msg.excludeSuffix,
            includeHostname: msg.includeHostname,
            excludeHostname: msg.excludeHostname,
            excludeContentTypes: msg.excludeContentTypes,
        })

        // passive 模式是 mitm 插件模式
        //    在这个模式下,应该直接转发,不应该操作数据包
        // if (passiveMode) {
        //     if (msg.forResponse) {
        //         forwardResponse(msg.responseId || 0)
        //     } else {
        //         forwardRequest(msg.id || 0)
        //     }
        //     return
        // }

        if (msg.forResponse) {
            if (!msg.response || !msg.responseId) {
                failed("BUG: MITM 错误,未能获取到正确的 Response 或 Response ID")
                return
            }
            if (!isManual) {
                forwardResponse(msg.responseId || 0)
                if (!!currentPacket) {
                    clearCurrentPacket()
                }
            } else {
                setForResponse(true)
                setStatus("hijacked")
                setCurrentPacketInfo({
                    currentPacket: msg.response,
                    currentPacketId: msg.responseId,
                    isHttp: msg.isHttps
                })
                // setCurrentPacket(new Buffer(msg.response).toString("utf8"))
                // setCurrentPacketId(msg.responseId || 0);
            }
        } else {
            if (msg.request) {
                if (!isManual) {
                    forwardRequest(msg.id)
                    if (!!currentPacket) {
                        clearCurrentPacket()
                    }
                    // setCurrentPacket(String.fromCharCode.apply(null, msg.request))
                } else {
                    setStatus("hijacked")
                    setForResponse(false)
                    // setCurrentPacket(msg.request)
                    // setCurrentPacketId(msg.id)
                    setCurrentPacketInfo({currentPacket: msg.request, currentPacketId: msg.id, isHttp: msg.isHttps})
                    setUrlInfo(msg.url)
                    ipcRenderer.invoke("fetch-url-ip", msg.url.split('://')[1].split('/')[0]).then((res) => {
                        setIpInfo(res)
                    })
                }
            }
        }
    })

    // 这个 Forward 主要用来转发修改后的内容,同时可以转发请求和响应
    const forward = useMemoizedFn(() => {
        // ID 不存在
        if (!currentPacketId) {
            return
        }

        setLoading(true);
        setStatus("hijacking");
        setAllowHijackCurrentResponse(false)
        setForResponse(false)

        if (forResponse) {
            ipcRenderer.invoke("mitm-forward-modified-response", modifiedPacket, currentPacketId).finally(() => {
                clearCurrentPacket()
                setTimeout(() => setLoading(false))
            })
        } else {
            ipcRenderer.invoke("mitm-forward-modified-request", modifiedPacket, currentPacketId).finally(() => {
                clearCurrentPacket()
                setTimeout(() => setLoading(false))
            })
        }
    })

    const recover = useMemoizedFn(() => {
        ipcRenderer.invoke("mitm-recover").then(() => {
            // success("恢复 MITM 会话成功")
        })
    })

    const start = useMemoizedFn(() => {
        setLoading(true)
        setError("")
        ipcRenderer.invoke("mitm-start-call", host, port, downstreamProxy).catch((e: any) => {
            notification["error"]({message: `启动中间人劫持失败:${e}`})
        })
    })

    const stop = useMemoizedFn(() => {
        setLoading(true)
        ipcRenderer.invoke("mitm-stop-call").then(() => {
            setStatus("idle")
        }).catch((e: any) => {
            notification["error"]({message: `停止中间人劫持失败:${e}`})
        }).finally(() => setTimeout(() => {
            setLoading(false)
        }, 300))
    })

    const hijacking = useMemoizedFn(() => {
        // setCurrentPacket(new Buffer([]));
        clearCurrentPacket()
        setLoading(true);
        setStatus("hijacking");
    })

    function getCurrentId() {
        return currentPacketId
    }

    const downloadCert = useMemoizedFn(() => {
        return <Tooltip title={'请先下载 SSL/TLS 证书'}>
            <Button
                type={"link"}
                style={{padding: '4px 6px'}}
                onClick={() => {
                    const text = `wget -e use_proxy=yes -e http_proxy=${addr} http://download-mitm-cert.yaklang.io -O yakit-mitm-cert.pem`
                    showModal({
                        title: "下载 SSL/TLS 证书以劫持 HTTPS",
                        width: "50%",
                        content: <Space direction={"vertical"} style={{width: "100%"}}>
                            <AutoCard
                                title={"证书配置"}
                                extra={<Button
                                    type={"link"}
                                    onClick={() => {
                                        saveABSFileToOpen("yakit证书.crt.pem", caCerts.CaCerts)
                                        // openABSFileLocated(caCerts.LocalFile)
                                    }}
                                >
                                    下载到本地并打开
                                </Button>} size={"small"} bodyStyle={{padding: 0}}>
                                <div style={{height: 360}}>
                                    <YakEditor bytes={true}
                                               valueBytes={caCerts.CaCerts}
                                    />
                                </div>
                            </AutoCard>
                            <Alert message={<Space>
                                在设置代理后访问:<CopyableField text={"http://download-mitm-cert.yaklang.io"}/> 可自动下载证书
                            </Space>}/>
                        </Space>
                    })
                }}
            >HTTPS 证书配置</Button>
        </Tooltip>
    })

    const contentReplacer = useMemoizedFn(() => {
        return <Button
            type={"link"} style={{padding: `4px 6px`}}
            onClick={() => {
                let m = showDrawer({
                    placement: "top", height: "50%",
                    content: (
                        <MITMContentReplacer
                            rules={replacers}
                            onSaved={rules => {
                                setReplacers(rules)
                                m.destroy()
                            }}/>
                    ),
                    maskClosable: false,
                })
            }}
        >
            匹配/标记/替换
        </Button>
    })

    const setFilter = useMemoizedFn(() => {
        return <Button type={"link"} style={{padding: '4px 6px'}}
                       onClick={() => {
                           let m = showDrawer({
                               placement: "top", height: "50%",
                               content: <>
                                   <MITMFilters
                                       filter={mitmFilter}
                                       onFinished={(filter) => {
                                           setMITMFilter({...filter})
                                           m.destroy()
                                       }}/>
                               </>
                           });
                       }}
        >过滤器</Button>
    })

    const handleAutoForward = useMemoizedFn((e: "manual" | "log" | "passive") => {
        if (!isManual) {
            info("切换为劫持自动放行模式(仅记录)")
            setHijackAllResponse(false)
        } else {
            info("切换为手动放行模式(可修改劫持)")
        }
        setAutoForward(e)
        if (currentPacket && currentPacketId) {
            forward()
        }
    })

    const execFuzzer = useMemoizedFn((value: string) => {
        ipcRenderer.invoke("send-to-tab", {
            type: "fuzzer",
            data: {isHttps: currentPacketInfo.isHttp, request: value}
        })
    })
    const execPlugin = useMemoizedFn((value: string) => {
        ipcRenderer.invoke("send-to-packet-hack", {
            request: currentPacketInfo.currentPacket,
            ishttps: currentPacketInfo.isHttp
        })
    })

    const shiftAutoForwardHotkey = useHotkeys('ctrl+t', () => {
        handleAutoForward(isManual ? "manual" : "log")
    }, [autoForward])

    if (!initialed) {
        return <div style={{textAlign: "center", paddingTop: 120}}>
            <Spin spinning={true} tip={"正在初始化 MITM"}/>
        </div>
    }
    return <div style={{height: "100%", width: "100%"}}>
        {(() => {
            switch (status) {
                case "idle":
                    return <Spin spinning={loading}>
                        <Form
                            style={{marginTop: 40}}
                            onSubmitCapture={e => {
                                e.preventDefault()
                                start()

                                if (enableInitialPlugin) {
                                    enableMITMPluginMode(defaultPlugins).then(() => {
                                        info("被动扫描插件模式已启动")
                                    })
                                }
                            }}
                            layout={"horizontal"} labelCol={{span: 7}}
                            wrapperCol={{span: 13}}
                        >
                            <Item label={"劫持代理监听主机"}>
                                <Input value={host} onChange={e => setHost(e.target.value)}/>
                            </Item>
                            <Item label={"劫持代理监听端口"}>
                                <InputNumber value={port} onChange={e => setPort(e)}/>
                            </Item>
                            {/*<SwitchItem label={"启动 MITM 插件"} size={"small"} setValue={e => {*/}
                            {/*    setEnableInitialPlugin(e)*/}
                            {/*    if (e) {*/}
                            {/*        saveValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN, "true")*/}
                            {/*    } else {*/}
                            {/*        saveValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN, "")*/}
                            {/*    }*/}
                            {/*}} value={enableInitialPlugin}/>*/}
                            <Item label={"选择插件"} colon={true}>
                                <div style={{height: 200, maxWidth: 420}}>
                                    <SimplePluginList
                                        disabled={!enableInitialPlugin}
                                        bordered={true}
                                        initialSelected={defaultPlugins}
                                        onSelected={(list: string[]) => {
                                            setDefaultPlugins(list)
                                        }} pluginTypes={"mitm,port-scan"}
                                        verbose={<div>MITM 与 端口扫描插件</div>}/>
                                </div>
                            </Item>
                            <Item label={"下游代理"} help={"为经过该 MITM 代理的请求再设置一个代理,通常用于访问中国大陆无法访问的网站或访问特殊网络/内网,也可用于接入被动扫描"}>
                                <Input value={downstreamProxy} onChange={e => setDownstreamProxy(e.target.value)}/>
                            </Item>
                            <Item label={"内容规则"} help={"使用规则进行匹配、替换、标记、染色,同时配置生效位置"}>
                                <Space>
                                    <Button
                                        onClick={() => {
                                            let m = showDrawer({
                                                placement: "top", height: "50%",
                                                content: (
                                                    <MITMContentReplacerViewer/>
                                                ),
                                                maskClosable: false,
                                            })
                                        }}
                                    >已有规则</Button>
                                    <Button type={"link"} onClick={() => {
                                        const m = showModal({
                                            title: "从 JSON 中导入",
                                            width: "60%",
                                            content: (
                                                <>
                                                    <MITMContentReplacerImport onClosed={() => {
                                                        m.destroy()
                                                    }}/>
                                                </>
                                            )
                                        })
                                    }}>从 JSON 导入</Button>
                                    <Button type={"link"} onClick={() => {
                                        showModal({
                                            title: "导出配置 JSON",
                                            width: "50%",
                                            content: (
                                                <>
                                                    <MITMContentReplacerExport/>
                                                </>
                                            )
                                        })
                                    }}>导出为 JSON</Button>
                                </Space>
                            </Item>
                            <Item label={" "} colon={false}>
                                <Space>
                                    <Button type={"primary"} htmlType={"submit"}>
                                        劫持启动
                                    </Button>
                                    <Divider type={"vertical"}/>
                                    <Checkbox
                                        checked={enableInitialPlugin}
                                        onChange={node => {
                                            const e = node.target.checked;
                                            setEnableInitialPlugin(e)
                                            if (e) {
                                                saveValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN, "true")
                                            } else {
                                                saveValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN, "")
                                            }
                                        }}
                                    >
                                        插件自动加载
                                    </Checkbox>
                                </Space>
                            </Item>
                        </Form>
                    </Spin>
                case "hijacking":
                case "hijacked":
                    return <div id={"mitm-hijacking-container"} ref={shiftAutoForwardHotkey as Ref<any>} tabIndex={-1}
                                style={{marginLeft: 12, marginRight: 12, height: "100%"}}>
                        <Row gutter={14} style={{height: "100%"}}>
                            <Col span={haveSideCar ? 24 : 24}
                                 style={{display: "flex", flexDirection: "column", height: "100%"}}>
                                <PageHeader
                                    className="mitm-header-title"
                                    title={'劫持 HTTP Request'} subTitle={`http://${host}:${port}`}
                                    style={{marginRight: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 8}}
                                    extra={
                                        <Space>
                                            <ChromeLauncherButton host={host} port={port}/>
                                            {contentReplacer()}
                                            {setFilter()}
                                            {downloadCert()}
                                            <Button danger={true} type={"link"}
                                                    onClick={() => {
                                                        stop()
                                                        setUrlInfo("监听中...")
                                                        setIpInfo("")
                                                    }} icon={<PoweroffOutlined/>}
                                            />
                                        </Space>}>
                                    <Row>
                                        <Col span={12}>
                                            <div style={{width: "100%", textAlign: "left"}}>
                                                <Space>
                                                    <Button
                                                        type={"primary"}
                                                        disabled={status === "hijacking"}
                                                        onClick={() => {
                                                            forward()
                                                        }}>提交数据</Button>
                                                    <Button
                                                        disabled={status === "hijacking"}
                                                        danger={true}
                                                        onClick={() => {
                                                            hijacking()
                                                            if (forResponse) {
                                                                dropResponse(currentPacketId).finally(() => {
                                                                    setTimeout(() => {
                                                                        setLoading(false)
                                                                    }, 300)
                                                                })
                                                            } else {
                                                                dropRequest(currentPacketId).finally(() => {
                                                                    setTimeout(() => setLoading(false), 300)
                                                                })
                                                            }
                                                            setUrlInfo("监听中...")
                                                            setIpInfo("")
                                                        }}>丢弃请求</Button>
                                                    {
                                                        (!forResponse && !!currentPacket) &&  // 劫持到的请求有内容
                                                        status === "hijacked" && // 劫持到的状态是 hijacked
                                                        !hijackAllResponse && // 如果已经设置了劫持所有请求,就不展示了
                                                        <Button
                                                            disabled={allowHijackCurrentResponse}
                                                            type={allowHijackCurrentResponse ? "primary" : "default"}
                                                            onClick={() => {
                                                                if (!allowHijackCurrentResponse) {
                                                                    allowHijackedResponseByRequest(currentPacketId)
                                                                    setAllowHijackCurrentResponse(true)
                                                                } else {
                                                                    setAllowHijackCurrentResponse(false)
                                                                }
                                                            }}>
                                                            劫持响应 {
                                                            allowHijackCurrentResponse &&
                                                            <CheckOutlined/>
                                                        }
                                                        </Button>}
                                                </Space>
                                            </div>
                                        </Col>
                                        <Col span={12}>
                                            <div style={{width: "100%", textAlign: "right"}}>
                                                <Space>
                                                    {isManual && <div>
                                                        <span style={{marginRight: 4}}>劫持响应:</span>
                                                        <Checkbox checked={hijackAllResponse} onClick={e => {
                                                            if (!hijackAllResponse) {
                                                                info("劫持所有响应内容")
                                                            } else {
                                                                info("仅劫持请求")
                                                            }
                                                            setHijackAllResponse(!hijackAllResponse)
                                                        }}/>
                                                    </div>}
                                                    <SelectOne
                                                        data={[
                                                            {text: "手动劫持", value: "manual"},
                                                            {text: "自动放行", value: "log"},
                                                            {text: "被动日志", value: "passive"},
                                                        ]}
                                                        value={autoForward}
                                                        formItemStyle={{marginBottom: 0}}
                                                        setValue={(e) => {
                                                            ipcRenderer.invoke("mitm-filter", {updateFilter: true, ...mitmFilter})
                                                            handleAutoForward(e)
                                                        }}
                                                    />
                                                </Space>
                                            </div>
                                        </Col>
                                    </Row>
                                    <Row>
                                        <Col span={12}>
                                            <div style={{
                                                width: "100%", textAlign: "left", height: '100%',
                                                display: 'flex'
                                            }}>
                                                {!isManual &&
                                                <Text style={{alignSelf: 'center'}}>
                                                    {`目标:自动放行中...`}</Text>}

                                                {autoForward === "manual" &&
                                                <>
                                                    <Text title={urlInfo} ellipsis={true} style={{
                                                        alignSelf: 'center',
                                                        maxWidth: 300
                                                    }}>{status === 'hijacking' ? '目标:监听中...' : `目标:${urlInfo}`}</Text>
                                                    {ipInfo && status !== 'hijacking' &&
                                                    <Tag
                                                        color='green'
                                                        title={ipInfo}
                                                        style={{
                                                            marginLeft: 5,
                                                            alignSelf: "center",
                                                            maxWidth: 140,
                                                            cursor: "pointer"
                                                        }}
                                                    >
                                                        {`${ipInfo}`}
                                                        <CopyToClipboard
                                                            text={`${ipInfo}`}
                                                            onCopy={(text, ok) => {
                                                                if (ok) success("已复制到粘贴板")
                                                            }}
                                                        >
                                                            <CopyOutlined style={{marginLeft: 5}}/>
                                                        </CopyToClipboard>
                                                    </Tag>
                                                    }
                                                </>
                                                }
                                            </div>
                                        </Col>
                                        <Col span={12}>
                                            <div style={{width: "100%", textAlign: "right"}}>
                                                <Button
                                                    type={"link"} onClick={() => recover()}
                                                    icon={<ReloadOutlined/>}
                                                >恢复请求</Button>
                                            </div>
                                        </Col>
                                    </Row>
                                </PageHeader>
                                <div style={{flex: 1, overflowY: 'hidden'}}>
                                    {/*<Spin wrapperClassName={"mitm-loading-spin"} spinning={status === "hijacking"}>*/}
                                    <div style={{height: "100%"}}>
                                        <ResizeBox
                                            isVer={false}
                                            firstNode={(
                                                <MITMPluginList
                                                    proxy={`http://${host}:${port}`}
                                                    downloadCertNode={downloadCert}
                                                    setFilterNode={setFilter}
                                                    onExit={() => {
                                                        stop()
                                                    }}
                                                    onSubmitScriptContent={e => {
                                                        ipcRenderer.invoke("mitm-exec-script-content", e)
                                                    }}
                                                    onSubmitYakScriptId={(id: number, params: YakExecutorParam[]) => {
                                                        info(`加载 MITM 插件[${id}]`)
                                                        ipcRenderer.invoke("mitm-exec-script-by-id", id, params)
                                                    }}
                                                />
                                                // <MITMPluginOperator />
                                            )}
                                            firstMinSize={"330px"}
                                            secondMinSize={"340px"}
                                            firstRatio={"330px"}
                                            secondNode={(
                                                <AutoCard
                                                    style={{margin: 0, padding: 0}}
                                                    bodyStyle={{margin: 0, padding: 0, overflowY: "hidden"}}
                                                >
                                                    {autoForward === "log" && (
                                                        <MITMPluginCard
                                                            onSubmitScriptContent={(e) => {
                                                                ipcRenderer.invoke("mitm-exec-script-content", e)
                                                            }}
                                                            onSubmitYakScriptId={(
                                                                id: number,
                                                                params: YakExecutorParam[]
                                                            ) => {
                                                                info(`加载 MITM 插件[${id}]`)
                                                                ipcRenderer.invoke("mitm-exec-script-by-id", id, params)
                                                            }}
                                                        />
                                                    )}
                                                    {autoForward === "manual" && (
                                                        <HTTPPacketEditor
                                                            originValue={currentPacket}
                                                            noHeader={true}
                                                            bordered={false}
                                                            onChange={setModifiedPacket}
                                                            noPacketModifier={true}
                                                            readOnly={status === "hijacking"}
                                                            refreshTrigger={
                                                                (forResponse ? `rsp` : `req`) + `${currentPacketId}`
                                                            }
                                                            actions={[
                                                                // {
                                                                //     id: "send-to-scan-packet", label: "发送到数据包扫描器",
                                                                //     run: e => {
                                                                //         // console.info(mouseState)
                                                                //         scanPacket(mouseState, false, "GET / HTTP/1.1\r\nHost: www.baidu.com", "")
                                                                //     }, contextMenuGroupId: "Scanners",
                                                                // },
                                                                ...(forResponse
                                                                    ? [
                                                                        {
                                                                            id: "trigger-auto-hijacked",
                                                                            label: "切换为自动劫持模式",
                                                                            keybindings: [
                                                                                monaco.KeyMod.Shift |
                                                                                (system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
                                                                                monaco.KeyCode.KEY_T
                                                                            ],
                                                                            run: () => {
                                                                                handleAutoForward(getAutoForward() === "manual" ? "log" : "manual")
                                                                            },
                                                                            contextMenuGroupId: "Actions"
                                                                        },
                                                                        {
                                                                            id: "forward-response",
                                                                            label: "放行该 HTTP Response",
                                                                            run: function () {
                                                                                forward()
                                                                                // hijacking()
                                                                                // forwardResponse(getCurrentId()).finally(() => {
                                                                                //     setTimeout(() => setLoading(false), 300)
                                                                                // })
                                                                            },
                                                                            contextMenuGroupId: "Actions"
                                                                        },
                                                                        {
                                                                            id: "drop-response",
                                                                            label: "丢弃该 HTTP Response",
                                                                            run: function () {
                                                                                hijacking()
                                                                                dropResponse(getCurrentId()).finally(
                                                                                    () => {
                                                                                        setTimeout(
                                                                                            () => setLoading(false),
                                                                                            300
                                                                                        )
                                                                                    }
                                                                                )
                                                                            },
                                                                            contextMenuGroupId: "Actions"
                                                                        }
                                                                    ]
                                                                    : [
                                                                        {
                                                                            id: "trigger-auto-hijacked",
                                                                            label: "切换为自动劫持模式",
                                                                            keybindings: [
                                                                                monaco.KeyMod.Shift |
                                                                                (system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
                                                                                monaco.KeyCode.KEY_T
                                                                            ],
                                                                            run: () => {
                                                                                handleAutoForward(getAutoForward() === "manual" ? "log" : "manual")
                                                                            },
                                                                            contextMenuGroupId: "Actions"
                                                                        },
                                                                        {
                                                                            id: "send-to-fuzzer",
                                                                            label: "发送到 Web Fuzzer",
                                                                            keybindings: [
                                                                                monaco.KeyMod.Shift |
                                                                                (system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
                                                                                monaco.KeyCode.KEY_R
                                                                            ],
                                                                            run: function (StandaloneEditor: any) {
                                                                                execFuzzer(StandaloneEditor.getModel().getValue())
                                                                            },
                                                                            contextMenuGroupId: "Actions"
                                                                        },
                                                                        {
                                                                            id: "send-to-plugin",
                                                                            label: "发送到 数据包扫描",
                                                                            keybindings: [
                                                                                monaco.KeyMod.Shift |
                                                                                (system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
                                                                                monaco.KeyCode.KEY_E
                                                                            ],
                                                                            run: function (StandaloneEditor: any) {
                                                                                if (!StandaloneEditor.getModel().getValue()) return
                                                                                execPlugin(StandaloneEditor.getModel().getValue())
                                                                            },
                                                                            contextMenuGroupId: "Actions"
                                                                        },
                                                                        {
                                                                            id: "forward-response",
                                                                            label: "放行该 HTTP Request",
                                                                            keybindings: [
                                                                                monaco.KeyMod.Shift |
                                                                                (system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
                                                                                monaco.KeyCode.KEY_F
                                                                            ],
                                                                            run: function () {
                                                                                forward()
                                                                                // hijacking()
                                                                                // forwardRequest(getCurrentId()).finally(() => {
                                                                                //     setTimeout(() => setLoading(false), 300)
                                                                                // })
                                                                            },
                                                                            contextMenuGroupId: "Actions"
                                                                        },
                                                                        {
                                                                            id: "drop-response",
                                                                            label: "丢弃该 HTTP Request",
                                                                            run: function () {
                                                                                hijacking()
                                                                                dropRequest(getCurrentId()).finally(
                                                                                    () => {
                                                                                        setTimeout(
                                                                                            () => setLoading(false),
                                                                                            300
                                                                                        )
                                                                                    }
                                                                                )
                                                                            },
                                                                            contextMenuGroupId: "Actions"
                                                                        },
                                                                        {
                                                                            id: "hijack-current-response",
                                                                            label: "劫持该 Request 对应的响应",
                                                                            run: function () {
                                                                                allowHijackedResponseByRequest(
                                                                                    getCurrentId()
                                                                                )
                                                                            },
                                                                            contextMenuGroupId: "Actions"
                                                                        }
                                                                    ])
                                                            ]}
                                                        />
                                                    )}
                                                    {autoForward === "passive" && (
                                                        <MITMPluginLogViewer
                                                            messages={logs} status={statusCards}
                                                        />
                                                    )}
                                                </AutoCard>
                                            )}
                                        />
                                    </div>
                                    {/*</Spin>*/}
                                </div>
                            </Col>
                        </Row>
                    </div>
                default:
                    return <div/>
            }
        })()}
    </div>
}
Example #28
Source File: FunctionDebuggerSidebar.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function FunctionDebuggerSidebar({
  functionName,
  functionModified,
  activeTab,
  tests,
  onActiveTabSwitch,
  onRunAllTests,
  onAddTest,
}: FunctionDebuggerSidebarProps): React.ReactElement {
  const [currentTab, setCurrentTab] = useState<string>(activeTab ?? "function");

  useEffect(() => {
    setCurrentTab(activeTab ?? "function");
  }, [activeTab]);

  const groups: SidebarGroup[] = useMemo(() => {
    const refinedTests = Array.isArray(tests) ? tests : [];
    return [
      {
        label: "Function",
        value: "function",
        items: [
          {
            label: functionName,
            value: "function",
            icon: <CodeOutlined />,
            modified: functionModified,
          },
        ],
      },
      {
        label: "Debug",
        value: "debug",
        items: [
          {
            label: "Debug",
            value: "debug",
            icon: <BugOutlined />,
          },
        ],
      },
      {
        label: `Tests (${refinedTests.length})`,
        value: "tests",
        items: refinedTests.map((test, index) => ({
          label: test.name ?? `Case ${index + 1}`,
          value: `test:${String(index)}`,
          modified: test.testModified,
          ...(test.testMatched
            ? {
                icon: <CheckOutlined />,
                className: styles.matched,
              }
            : test.testMatched === false
            ? {
                icon: <CloseOutlined />,
                className: styles.notMatched,
              }
            : {
                icon: <QuestionOutlined />,
              }),
        })),
      },
    ];
  }, [functionModified, functionName, tests]);

  const switchActiveTab = useCallback(
    (tab: string) => {
      if (currentTab !== tab) {
        setCurrentTab(tab);
        onActiveTabSwitch?.(tab);
      }
    },
    [currentTab, onActiveTabSwitch]
  );

  return (
    <div
      className={`${styles.sidebarContainer} ${sharedStyles.customScrollbarContainer}`}
      data-override-theme="dark"
    >
      <ul className={styles.sidebarGroups}>
        {groups.map((group) => (
          <li key={group.label}>
            <div className={styles.sidebarGroupLabel}>
              <span className={styles.groupText}>{group.label}</span>
              {group.value === "tests" && (
                <div className={styles.groupIconContainer}>
                  {group.items.length > 0 && (
                    <span
                      className={styles.groupIcon}
                      title="Run All Tests"
                      onClick={onRunAllTests}
                    >
                      <PlayCircleOutlined />
                    </span>
                  )}
                  <span
                    className={styles.groupIcon}
                    title="Add Test"
                    onClick={onAddTest}
                  >
                    <PlusCircleOutlined />
                  </span>
                </div>
              )}
            </div>
            <ul className={styles.sidebarItems}>
              {group.items.map((item) => (
                <li
                  key={item.label}
                  className={classNames({
                    [styles.active]: item.value === currentTab,
                  })}
                  onClick={() => switchActiveTab(item.value)}
                >
                  <span className={classNames(styles.icon, item.className)}>
                    {item.icon}
                  </span>
                  <span className={styles.text}>{item.label}</span>
                  {item.modified && <span className={styles.modified}></span>}
                </li>
              ))}
            </ul>
          </li>
        ))}
      </ul>
    </div>
  );
}
Example #29
Source File: palette.tsx    From jmix-frontend with Apache License 2.0 4 votes vote down vote up
palette = () =>
  <Palette>
    <Category name="Text">
      <Component name="Formatted Message">
        <Variant>
          <FormattedMessage />
        </Variant>
      </Component>
      <Component name="Heading">
        <Variant name='h1'>
          <Typography.Title></Typography.Title>
        </Variant>
        <Variant name='h2'>
          <Typography.Title level = {2}></Typography.Title>
        </Variant>
        <Variant name='h3'>
          <Typography.Title level = {3}></Typography.Title>
        </Variant>
        <Variant name='h4'>
          <Typography.Title level = {4}></Typography.Title>
        </Variant>
        <Variant name='h5'>
          <Typography.Title level = {5}></Typography.Title>
        </Variant>
      </Component>
      <Component name='Text'>
        <Variant>
          <Typography.Text></Typography.Text>
        </Variant>
        <Variant name = 'Secondary'>
          <Typography.Text type="secondary"></Typography.Text>
        </Variant>
        <Variant name = 'Success'>
          <Typography.Text type="success"></Typography.Text>
        </Variant>
        <Variant name = 'Warning'>
          <Typography.Text type="warning"></Typography.Text>
        </Variant>
        <Variant name = 'Danger'>
          <Typography.Text type="danger"></Typography.Text>
        </Variant>
        <Variant name = 'Disabled'>
          <Typography.Text disabled></Typography.Text>
        </Variant>
      </Component>
    </Category>
    <Category name="Layout">
      <Component name="Divider">
        <Variant>
          <Divider />
        </Variant>
      </Component>

      <Component name="Grid">
        <Variant name="Simple Row">
          <Row></Row>
        </Variant>
        <Variant name="Two columns">
          <Row>
            <Col span={12}></Col>
            <Col span={12}></Col>
          </Row>
        </Variant>
        <Variant name="Three columns">
          <Row>
            <Col span={8}></Col>
            <Col span={8}></Col>
            <Col span={8}></Col>
          </Row>
        </Variant>
      </Component>

      <Component name="Space">
        <Variant>
          <Space />
        </Variant>
        <Variant name="Small">
          <Space size={"small"} />
        </Variant>
        <Variant name="Large">
          <Space size={"large"} />
        </Variant>
      </Component>
    </Category>
    <Category name="Controls">
      <Component name="Autocomplete">
        <Variant>
          <AutoComplete placeholder="input here" />
        </Variant>
      </Component>

      <Component name="Button">
        <Variant>
          <Button></Button>
        </Variant>
        <Variant name="Primary">
          <Button type="primary" ></Button>
        </Variant>
        <Variant name="Link">
          <Button type="link" ></Button>
        </Variant>
        <Variant name="Dropdown">
          <Dropdown
            trigger={['click']}
            overlay={<Menu>
              <Menu.Item>
              </Menu.Item>
              <Menu.Item>
              </Menu.Item>
              <Menu.Item>
              </Menu.Item>
            </Menu>}
          >
            <Button></Button>
          </Dropdown>
        </Variant>
      </Component>

      <Component name="Checkbox">
        <Variant>
          <Checkbox />
        </Variant>
      </Component>

      <Component name='Switch'>
        <Variant>
          <Switch />
        </Variant>
      </Component>

      <Component name='Radio Group'>
        <Variant>
          <Radio.Group>
            <Radio value={1}>A</Radio>
            <Radio value={2}>B</Radio>
            <Radio value={3}>C</Radio>
            <Radio value={4}>D</Radio>
          </Radio.Group>
        </Variant>
        <Variant name = 'Button'>
          <Radio.Group>
            <Radio.Button value={1}>A</Radio.Button>
            <Radio.Button value={2}>B</Radio.Button>
            <Radio.Button value={3}>C</Radio.Button>
            <Radio.Button value={4}>D</Radio.Button>
          </Radio.Group>
        </Variant>
      </Component>

      <Component name="DatePicker">
        <Variant>
          <DatePicker />
        </Variant>
        <Variant name="Range">
          <DatePicker.RangePicker />
        </Variant>
      </Component>

      <Component name="TimePicker">
        <Variant>
          <TimePicker />
        </Variant>
        <Variant name="Range">
          <TimePicker.RangePicker />
        </Variant>
      </Component>

      <Component name="Input">
        <Variant>
          <Input />
        </Variant>
        <Variant name='Number'>
          <InputNumber />
        </Variant>
      </Component>

      <Component name='Select'>
        <Variant>
          <Select defaultValue="1">
            <Select.Option value="1">1</Select.Option>
            <Select.Option value="2">2</Select.Option>
          </Select>
        </Variant>
        <Variant name='Multiple'>
          <Select
            defaultValue={["1"]}
            mode="multiple"
            allowClear
          >
            <Select.Option value="1">1</Select.Option>
            <Select.Option value="2">2</Select.Option>
          </Select>
        </Variant>
      </Component>

      <Component name="Link">
        <Variant>
          <Typography.Link href="" target="_blank">
          </Typography.Link>
        </Variant>
      </Component>

      <Component name='Slider'>
        <Variant>
          <Slider defaultValue={30} />
        </Variant>
        <Variant name = 'Range'>
          <Slider range defaultValue={[20, 50]}/>
        </Variant>
      </Component>
    </Category>
    <Category name="Data Display">
    <Component name="Field">
        <Variant>
          <Field
            entityName={ENTITY_NAME}
            disabled={readOnlyMode}
            propertyName=''
            formItemProps={{
              style: { marginBottom: "12px" }
            }}
          />
        </Variant>
      </Component>
      <Component name="Card">
        <Variant>
          <Card />
        </Variant>
        <Variant name="With Title">
          <Card>
            <Card title="Card title">
              <p>Card content</p>
            </Card>
          </Card>
        </Variant>
        <Variant name="My custom card">
          <Card>
            <Card title="Card title">
              <p>Card content</p>
              <Avatar />
            </Card>
          </Card>
        </Variant>
      </Component>
      <Component name="Tabs">
        <Variant>
          <Tabs defaultActiveKey="1">
            <Tabs.TabPane tab="Tab 1" key="1">
              Content of Tab Pane 1
            </Tabs.TabPane>
            <Tabs.TabPane tab="Tab 2" key="2">
              Content of Tab Pane 2
            </Tabs.TabPane>
            <Tabs.TabPane tab="Tab 3" key="3">
              Content of Tab Pane 3
            </Tabs.TabPane>
          </Tabs>
        </Variant>
        <Variant name = "Tab Pane">
          <Tabs.TabPane>
          </Tabs.TabPane>
        </Variant>
      </Component>
      <Component name="Collapse">
        <Variant>
          <Collapse defaultActiveKey='1'>
            <Collapse.Panel header="This is panel header 1" key="1">
            </Collapse.Panel>
            <Collapse.Panel header="This is panel header 2" key="2">
            </Collapse.Panel>
            <Collapse.Panel header="This is panel header 3" key="3">
            </Collapse.Panel>
          </Collapse>
        </Variant>
      </Component>
      <Component name="Image">
        <Variant>
          <Image
            width={200}
            src=""
          />
        </Variant>
      </Component>
      <Component name="Avatar">
        <Variant>
          <Avatar icon={<UserOutlined />} />
        </Variant>
        <Variant name="Image">
          <Avatar src="https://joeschmoe.io/api/v1/random" />
        </Variant>
      </Component>
      <Component name="Badge">
        <Variant>
          <Badge count={1}>
          </Badge>
        </Variant>
      </Component>
      <Component name="Statistic">
        <Variant>
          <Statistic title="Title" value={112893} />
        </Variant>
      </Component>
      <Component name="Alert">
        <Variant name="Success">
          <Alert message="Text" type="success" />
        </Variant>
        <Variant name="Info">
          <Alert message="Text" type="info" />
        </Variant>
        <Variant name="Warning">
          <Alert message="Text" type="warning" />
        </Variant>
        <Variant name="Error">
          <Alert message="Text" type="error" />
        </Variant>
      </Component>
      <Component name='List'>
        <Variant>
          <List
            bordered
            dataSource={[]}
            renderItem={item => (
              <List.Item>
              </List.Item>
            )}
          />
        </Variant>
      </Component>
    </Category>
    <Category name="Icons">
      <Component name="Arrow">
        <Variant name = 'Up'>
          <ArrowUpOutlined />
        </Variant>
        <Variant name = 'Down'>
          <ArrowDownOutlined />
        </Variant>
        <Variant name = 'Left'>
          <ArrowLeftOutlined />
        </Variant>
        <Variant name = 'Right'>
          <ArrowRightOutlined />
        </Variant>
      </Component>
      <Component name = 'Question'>
        <Variant>
          <QuestionOutlined />
        </Variant>
        <Variant name = 'Circle'>
          <QuestionCircleOutlined />
        </Variant>
      </Component>
      <Component name = 'Plus'>
        <Variant>
          <PlusOutlined />
        </Variant>
        <Variant name = 'Circle'>
          <PlusCircleOutlined />
        </Variant>
      </Component>
      <Component name = 'Info'>
        <Variant>
          <InfoOutlined />
        </Variant>
        <Variant name = 'Circle'>
          <InfoCircleOutlined />
        </Variant>
      </Component>
      <Component name = 'Exclamation'>
        <Variant>
          <ExclamationOutlined />
        </Variant>
        <Variant name = 'Circle'>
          <ExclamationCircleOutlined />
        </Variant>
      </Component>
      <Component name = 'Close'>
        <Variant>
          <CloseOutlined />
        </Variant>
        <Variant name = 'Circle'>
          <CloseCircleOutlined />
        </Variant>
      </Component>
      <Component name = 'Check'>
        <Variant>
          <CheckOutlined />
        </Variant>
        <Variant name = 'Circle'>
          <CheckCircleOutlined />
        </Variant>
      </Component>
      <Component name = 'Edit'>
        <Variant>
          <EditOutlined />
        </Variant>
      </Component>
      <Component name = 'Copy'>
        <Variant>
          <CopyOutlined />
        </Variant>
      </Component>
      <Component name = 'Delete'>
        <Variant>
          <DeleteOutlined />
        </Variant>
      </Component>
      <Component name = 'Bars'>
        <Variant>
          <BarsOutlined />
        </Variant>
      </Component>
      <Component name = 'Bell'>
        <Variant>
          <BellOutlined />
        </Variant>
      </Component>
      <Component name = 'Clear'>
        <Variant>
          <ClearOutlined />
        </Variant>
      </Component>
      <Component name = 'Download'>
        <Variant>
          <DownloadOutlined />
        </Variant>
      </Component>
      <Component name = 'Upload'>
        <Variant>
          <UploadOutlined />
        </Variant>
      </Component>
      <Component name = 'Sync'>
        <Variant>
          <SyncOutlined />
        </Variant>
      </Component>
      <Component name = 'Save'>
        <Variant>
          <SaveOutlined />
        </Variant>
      </Component>
      <Component name = 'Search'>
        <Variant>
          <SearchOutlined />
        </Variant>
      </Component>
      <Component name = 'Settings'>
        <Variant>
          <SettingOutlined />
        </Variant>
      </Component>
      <Component name = 'Paperclip'>
        <Variant>
          <PaperClipOutlined />
        </Variant>
      </Component>
      <Component name = 'Phone'>
        <Variant>
          <PhoneOutlined />
        </Variant>
      </Component>
      <Component name = 'Mail'>
        <Variant>
          <MailOutlined />
        </Variant>
      </Component>
      <Component name = 'Home'>
        <Variant>
          <HomeOutlined />
        </Variant>
      </Component>
      <Component name = 'Contacts'>
        <Variant>
          <ContactsOutlined />
        </Variant>
      </Component>
      <Component name = 'User'>
        <Variant>
          <UserOutlined />
        </Variant>
        <Variant name = 'Add'>
          <UserAddOutlined />
        </Variant>
        <Variant name = 'Remove'>
          <UserDeleteOutlined />
        </Variant>
      </Component>
      <Component name = 'Team'>
        <Variant>
          <TeamOutlined />
        </Variant>
      </Component>
    </Category>
  </Palette>