@ant-design/icons#CopyOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#CopyOutlined. 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: CopyToClipboard.tsx    From posthog-foss with MIT License 6 votes vote down vote up
export function CopyToClipboardInput({
    value,
    placeholder,
    description,
    isValueSensitive = false,
    ...props
}: InputProps): JSX.Element {
    return (
        <Input
            className={isValueSensitive ? 'ph-no-capture' : ''}
            type="text"
            value={value}
            placeholder={placeholder || 'nothing to show here'}
            disabled={!value}
            suffix={
                value ? (
                    <Tooltip title="Copy to Clipboard">
                        <CopyOutlined
                            onClick={() => {
                                copyToClipboard(value, description)
                            }}
                        />
                    </Tooltip>
                ) : null
            }
            {...props}
        />
    )
}
Example #2
Source File: Export.tsx    From fe-v5 with Apache License 2.0 6 votes vote down vote up
function Export(props: ModalWrapProps & IProps) {
  const { visible, destroy, data } = props;
  const { t } = useTranslation();
  let str = data;
  try {
    str = JSON.stringify(JSON.parse(data), null, 4);
  } catch (e) {
    console.log(e);
  }

  return (
    <Modal
      title='导出配置'
      visible={visible}
      onCancel={() => {
        destroy();
      }}
      footer={null}
    >
      <>
        <div style={{ marginBottom: 10 }}>
          <a
            onClick={() => {
              download([data], 'download.json');
            }}
          >
            Download.json
          </a>
          <a style={{ float: 'right' }} onClick={() => copyToClipBoard(data, t)}>
            <CopyOutlined />
            复制JSON内容到剪贴板
          </a>
        </div>
        <Input.TextArea value={str} rows={10} />
      </>
    </Modal>
  );
}
Example #3
Source File: Export.tsx    From fe-v5 with Apache License 2.0 6 votes vote down vote up
function Export(props: IProps & ModalWrapProps) {
  const { visible, destroy } = props;
  const [data, setData] = useState(props.data);
  return (
    <Modal
      title='导出大盘'
      visible={visible}
      onCancel={() => {
        destroy();
      }}
      footer={null}
    >
      <p>
        <a
          onClick={() => {
            download([data], 'download.json');
          }}
        >
          Download.json
        </a>
        <a style={{ float: 'right' }} onClick={() => copyToClipBoard(data, (val) => val)}>
          <CopyOutlined />
          复制JSON内容到剪贴板
        </a>
      </p>
      <Input.TextArea
        value={data}
        onChange={(e) => {
          setData(e.target.value);
        }}
        rows={15}
      />
    </Modal>
  );
}
Example #4
Source File: index.tsx    From metaplex with Apache License 2.0 6 votes vote down vote up
SetupVariables: FC<Variables> = ({
  storeAddress,
  storeOwnerAddress,
}) => {
  const ref = useRef<HTMLDivElement>(null);

  const copySettings = useCallback(() => {
    const text = ref.current?.innerText;
    if (text) {
      navigator.clipboard.writeText(text);
    }
  }, []);

  if (!storeAddress && !storeOwnerAddress) {
    return null;
  }

  return (
    <Card
      title="Store configuration"
      extra={
        <Button
          type="dashed"
          onClick={copySettings}
          icon={<CopyOutlined />}
        ></Button>
      }
    >
      <div ref={ref}>
        {storeOwnerAddress && (
          <p>REACT_APP_STORE_OWNER_ADDRESS_ADDRESS={storeOwnerAddress}</p>
        )}
      </div>
    </Card>
  );
}
Example #5
Source File: index.tsx    From electron-playground with MIT License 6 votes vote down vote up
CodeBlock: React.FunctionComponent<ICodeBlockProps> = props => {
  const { value, language, src } = props
  const onCopy = () => message.info('已复制到剪贴板')

  const [code, setCode] = React.useState<string>(value || '')
  const [codePath, setCodePath] = React.useState<string>('')

  React.useEffect(() => {
    if (!src) {
      return
    }
    const codePath = getSrcRelativePath(src)
    setCode('loading ......')
    setCodePath(codePath)
    fs.readFile(codePath, (err, content) => {
      setCode(content.toString())
    })
  }, [src])

  const openFile = (path: string) => {
    shell.openPath(path)
  }

  return (
    <div className={style.container}>
      {codePath && <p>
        代码路径:  <a onClick={openFile.bind(null, codePath)}>{codePath}</a> 
      </p>}
      <SyntaxHighlighter language={language || 'javascript'} style={githubGist} >{code}</SyntaxHighlighter>
      <CopyToClipboard text={code} onCopy={onCopy}>
        <CopyOutlined className={style.copy} />
      </CopyToClipboard>
    </div>
  )
}
Example #6
Source File: config-toolbar.ts    From XFlow with MIT License 6 votes vote down vote up
registerIcon = () => {
  IconStore.set('SaveOutlined', SaveOutlined)
  IconStore.set('UndoOutlined', UndoOutlined)
  IconStore.set('RedoOutlined', RedoOutlined)
  IconStore.set('VerticalAlignTopOutlined', VerticalAlignTopOutlined)
  IconStore.set('VerticalAlignBottomOutlined', VerticalAlignBottomOutlined)
  IconStore.set('GatewayOutlined', GatewayOutlined)
  IconStore.set('GroupOutlined', GroupOutlined)
  IconStore.set('UngroupOutlined', UngroupOutlined)
  IconStore.set('CopyOutlined', CopyOutlined)
  IconStore.set('SnippetsOutlined', SnippetsOutlined)
}
Example #7
Source File: GeneralInput.tsx    From next-basics with GNU General Public License v3.0 6 votes vote down vote up
InputGroup = forwardRef<Input, InputGroupProps>(function InputGroup(
  props,
  ref
): React.ReactElement {
  const { t } = useTranslation(NS_FORMS);
  const { formElement, value, inputBoxStyle, copyButton, ...inputProps } =
    props;

  const handleCopy = (text: string, success: boolean) => {
    if (success) {
      message.success(t(K.COPY_SUCCESS));
    }
  };

  const getCopyButton = (): React.ReactElement => {
    return (
      <Clipboard text={value} onCopy={handleCopy}>
        <Button icon={<CopyOutlined />} />
      </Clipboard>
    );
  };

  const input = (
    <Input value={value} style={inputBoxStyle} ref={ref} {...inputProps} />
  );

  return copyButton ? (
    <Input.Group compact style={{ display: "flex" }}>
      {input}
      {getCopyButton()}
    </Input.Group>
  ) : (
    input
  );
})
Example #8
Source File: CopyPaste.tsx    From datart with Apache License 2.0 6 votes vote down vote up
CopyBtn: FC<{
  fn: (ids?: string[]) => void;
  title: string;
}> = ({ fn, title }) => {
  const selectedIds = useSelector(selectSelectedIds);

  const onCopy = () => {
    fn(selectedIds);
  };
  return (
    <Tooltip title={title}>
      <ToolbarButton
        disabled={!selectedIds.length}
        onClick={onCopy}
        icon={<CopyOutlined />}
      />
    </Tooltip>
  );
}
Example #9
Source File: node.tsx    From imove with MIT License 5 votes vote down vote up
nodeMenuConfig = [
  {
    key: 'copy',
    title: '复制',
    icon: <CopyOutlined />,
    handler: shortcuts.copy.handler,
  },
  {
    key: 'delete',
    title: '删除',
    icon: <DeleteOutlined />,
    handler: shortcuts.delete.handler,
  },
  {
    key: 'rename',
    title: '编辑文本',
    icon: <EditOutlined />,
    showDividerBehind: true,
    handler() {
      // TODO
    },
  },
  {
    key: 'bringToTop',
    title: '置于顶层',
    icon: <XIcon type={'icon-bring-to-top'} />,
    handler: shortcuts.bringToTop.handler,
  },
  {
    key: 'bringToBack',
    title: '置于底层',
    icon: <XIcon type={'icon-bring-to-bottom'} />,
    showDividerBehind: true,
    handler: shortcuts.bringToBack.handler,
  },
  {
    key: 'editCode',
    title: '编辑代码',
    icon: <FormOutlined />,
    disabled(flowChart: Graph) {
      return getSelectedNodes(flowChart).length !== 1;
    },
    handler(flowChart: Graph) {
      flowChart.trigger('graph:editCode');
    },
  },
  {
    key: 'executeCode',
    title: '执行代码',
    icon: <CodeOutlined />,
    disabled(flowChart: Graph) {
      return getSelectedNodes(flowChart).length !== 1;
    },
    handler(flowChart: Graph) {
      flowChart.trigger('graph:runCode');
    },
  },
]
Example #10
Source File: index.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
HostCopyTitle = (props: Props) => {
  const { t, i18n } = useTranslation();
  const handleCopyBtnClick: HandleCopyBtnClick = async (dataIndex, copyType) => {
    const { data } = props;
    let tobeCopy = [];

    if (copyType === 'all') {
      tobeCopy = _.map(data, (item) => item[dataIndex]);
    }

    if (_.isEmpty(tobeCopy)) {
      message.warning(t('host.copy.empty'));
      return;
    }

    const tobeCopyStr = _.join(tobeCopy, '\n');
    const copySucceeded = clipboard(tobeCopyStr);

    if (copySucceeded) {
      if (i18n.language === 'zh') {
        message.success(`复制成功${tobeCopy.length}条记录`);
      } else if (i18n.language === 'en') {
        message.success(`Successful copy ${tobeCopy.length} items`);
      }
    } else {
      Modal.warning({
        title: t('host.copy.error'),
        content: <Input.TextArea defaultValue={tobeCopyStr} />,
      });
    }
  }

  const { dataIndex } = props;

  return (
    <span>
      Host
      <CopyOutlined
        className="pointer"
        style={{ paddingLeft: 5 }}
        onClick={() => handleCopyBtnClick(dataIndex, 'all')}
      />
    </span>
  );
}
Example #11
Source File: index.tsx    From metaplex with Apache License 2.0 5 votes vote down vote up
Settings = ({
  additionalSettings,
}: {
  additionalSettings?: JSX.Element;
}) => {
  const { publicKey } = useWallet();

  return (
    <>
      <div
        style={{
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          padding: '15px 0',
        }}
      >
        <Identicon
          address={publicKey?.toBase58()}
          style={{
            width: 48,
          }}
        />
        {publicKey && (
          <>
            <Tooltip title="Address copied">
              <div
                style={{
                  fontWeight: 600,
                  letterSpacing: '-0.02em',
                  color: '#FFFFFF',
                }}
                onClick={() =>
                  navigator.clipboard.writeText(publicKey?.toBase58() || '')
                }
              >
                <CopyOutlined />
                &nbsp;{shortenAddress(publicKey?.toBase58())}
              </div>
            </Tooltip>
          </>
        )}
        <br />
        <span
          style={{
            borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
            width: 'calc(100% + 32px)',
            marginBottom: 10,
          }}
        ></span>
        {additionalSettings}
      </div>
    </>
  );
}
Example #12
Source File: index.tsx    From nanolooker with MIT License 5 votes vote down vote up
Copy = ({ text }: { text: string }) => {
  const { t } = useTranslation();
  const { theme } = React.useContext(PreferencesContext);
  const [isCopied, setIsCopied] = React.useState<boolean>(false);

  return (
    <Tooltip title={isCopied ? `${t("common.copied")}!` : t("common.copy")}>
      <CopyToClipboard
        text={text}
        onCopy={() => {
          setIsCopied(true);
          clearTimeout(copiedTimeout);
          copiedTimeout = window.setTimeout(() => {
            setIsCopied(false);
          }, 2000);
        }}
      >
        {isCopied ? (
          theme === Theme.DARK ? (
            <CheckCircleFilled
              style={{ fontSize: "24px", color: Colors.PENDING_DARK as string }}
            />
          ) : (
            <CheckCircleFilled
              style={{ fontSize: "24px", color: Colors.PENDING as string }}
            />
          )
        ) : (
          <Button
            shape="circle"
            size="small"
            disabled={isCopied}
            style={{
              borderColor: isCopied ? (Colors.RECEIVE as string) : undefined,
            }}
          >
            {theme === Theme.DARK ? <CopyFilled /> : <CopyOutlined />}
          </Button>
        )}
      </CopyToClipboard>
    </Tooltip>
  );
}
Example #13
Source File: components.tsx    From jitsu with MIT License 5 votes vote down vote up
export function CodeSnippet(props: ICodeSnippetProps) {
  const toolBarPos = props.toolbarPosition ? props.toolbarPosition : "bottom"

  const copy = () => {
    copyToClipboard(props.children, true)
    message.info("Code copied to clipboard")
  }

  const toolbar = (
    <Row className={cn("code-snippet-toolbar", "code-snippet-toolbar-" + toolBarPos)}>
      <Col span={16}>{props.extra}</Col>
      <Col span={8}>
        <Align horizontal="right">
          {toolBarPos === "bottom" ? (
            <ActionLink onClick={copy}>Copy To Clipboard</ActionLink>
          ) : (
            <a onClick={copy}>
              <CopyOutlined />
            </a>
          )}
        </Align>
      </Col>
    </Row>
  )

  const classes = [
    "code-snippet-wrapper-" + toolBarPos,
    "code-snippet-wrapper",
    props.size === "large" ? "code-snippet-large" : "code-snippet-small",
  ]
  if (props.className) {
    classes.push(props.className)
  }

  return (
    <div className={classes.join(" ")}>
      {toolBarPos === "top" ? toolbar : null}
      <SyntaxHighlighterAsync language={props.language}>{props.children}</SyntaxHighlighterAsync>
      {toolBarPos === "bottom" ? toolbar : null}
    </div>
  )
}
Example #14
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 #15
Source File: ExampleFluxQuery.tsx    From iot-center-v2 with MIT License 5 votes vote down vote up
ExampleFluxQuery: React.FC<TExampleFluxQueryProps> = (props) => {
  const {clientId, fields, start} = props

  const [bucket, setBucket] = useState('<your-bucket>')

  useEffect(() => {
    const asyncEffect = async () => {
      try {
        const config = await fetchDeviceConfig(clientId)
        await new Promise((r) => setTimeout(r, 10000))
        setBucket(config.influx_bucket)
      } catch (e) {
        // TODO: escalation
        console.error(e)
      }
    }

    asyncEffect()
  }, [clientId])

  const fieldsFilterString = fields
    .map((field) => `r["_field"] == "${field}"`)
    .join(' or ')
  const fieldsFilter =
    fields.length > 0
      ? flux`\n  |> filter(fn: (r) => ${fluxExpression(fieldsFilterString)})`
      : ''

  const fluxQuery = flux`\
from(bucket: ${bucket})
  |> range(start: ${fluxDuration(start)})
  |> filter(fn: (r) => r._measurement == "environment")
  |> filter(fn: (r) => r.clientId == ${clientId})\
${fluxExpression(fieldsFilter)}\
`.toString()

  const [copiedMsg, setCopiedMsg] = useState<string>()

  useEffect(() => {
    if (copiedMsg) {
      setTimeout(() => {
        setCopiedMsg(undefined)
      }, 2000)
    }
  }, [copiedMsg])

  return (
    <>
      <Col style={{position: 'relative'}}>
        <Tooltip title={copiedMsg} visible={!!copiedMsg}>
          <Button
            icon={<CopyOutlined />}
            style={{position: 'absolute', right: 0, top: 0}}
            size="small"
            type="dashed"
            onClick={() => {
              navigator.clipboard.writeText(fluxQuery)
              setCopiedMsg('Query copied into your clipboard')
            }}
          />
        </Tooltip>
        <code style={{whiteSpace: 'pre-wrap'}}>{fluxQuery}</code>
      </Col>
    </>
  )
}
Example #16
Source File: CodeSnippet.tsx    From posthog-foss with MIT License 5 votes vote down vote up
export function CodeSnippet({
    children,
    language = Language.Text,
    wrap = false,
    style = {},
    actions,
    copyDescription = 'code snippet',
    hideCopyButton = false,
}: CodeSnippetProps): JSX.Element {
    return (
        <div className="code-container" style={style}>
            <div className="action-icon-container">
                {actions &&
                    actions.map(({ Icon, callback, popconfirmProps, title }, index) =>
                        !popconfirmProps ? (
                            <Icon
                                key={`snippet-action-${index}`}
                                className="action-icon"
                                onClick={callback}
                                title={title}
                            />
                        ) : (
                            <Popconfirm key={`snippet-action-${index}`} {...popconfirmProps} onConfirm={callback}>
                                <Icon className="action-icon" title={title} />
                            </Popconfirm>
                        )
                    )}
                {!hideCopyButton && (
                    <CopyOutlined
                        className="action-icon"
                        onClick={() => {
                            children && copyToClipboard(children, copyDescription)
                        }}
                        title="Copy"
                    />
                )}
            </div>
            <SyntaxHighlighter
                style={okaidia}
                language={language}
                customStyle={{ borderRadius: 2 }}
                wrapLines={wrap}
                lineProps={{ style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }}
            >
                {children}
            </SyntaxHighlighter>
        </div>
    )
}
Example #17
Source File: index.tsx    From electron-playground with MIT License 5 votes vote down vote up
iconMap.set('copy', { icon: <CopyOutlined />, text: '复制' })
Example #18
Source File: index.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
export default function ImportAndDownloadModal(props: Props) {
  const { t } = useTranslation();
  const exportTextRef = useRef(null as any);
  const { status, title, exportData, description, onClose, onSubmit, crossCluster = true, onSuccess, label, fetchBuiltinFunc, submitBuiltinFunc, bgid } = props;
  const [form] = Form.useForm();
  const { clusters: clusterList } = useSelector<RootState, CommonStoreState>((state) => state.common);
  const [allList, setAllList] = useState<{ name: string }[]>([]);
  const [buildinList, setBuildinList] = useState<{ name: string }[]>([]);
  const [importResult, setImportResult] = useState<{ name: string; isTrue: boolean; msg: string }[]>();
  const columns = [
    {
      title: label,
      dataIndex: 'name',
    },
    {
      title: '导入结果',
      dataIndex: 'isTrue',
      render: (data) => {
        return data ? <CheckCircleOutlined style={{ color: '#389e0d', fontSize: '18px' }} /> : <CloseCircleOutlined style={{ color: '#d4380d', fontSize: '18px' }} />;
      },
    },
    {
      title: '错误消息',
      dataIndex: 'msg',
    },
  ];
  const builtinColumn = [
    {
      title: `${label}名称`,
      dataIndex: 'name',
    },
    {
      title: '操作',
      dataIndex: 'id',
      render(id, record) {
        return (
          <Button
            type='link'
            onClick={() => {
              submitBuiltinFunc &&
                submitBuiltinFunc(record.name, form.getFieldValue('cluster'), bgid!).then(({ dat }) => {
                  setImportResult(
                    Object.keys(dat).map((key) => {
                      return {
                        name: key,
                        key: key,
                        isTrue: !dat[key],
                        msg: dat[key],
                      };
                    }),
                  );
                });
            }}
          >
            导入
          </Button>
        );
      },
    },
  ];
  const handleClose = () => {
    onClose();
    importResult && importResult.some((item) => item.isTrue) && onSuccess && onSuccess();
  };
  useEffect(() => {
    if (status === ModalStatus.BuiltIn || status == ModalStatus.Import) {
      fetchBuiltinFunc &&
        fetchBuiltinFunc().then((res) => {
          let arr = res.dat.map((name) => ({ name }));
          setBuildinList(arr);
          setAllList(arr);
        });
    }
    setImportResult(undefined);
  }, [status]);

  const handleExportTxt = () => {
    download([exportData], 'download.json');
  };
  const computeTitle = isValidElement(title) ? title : status === ModalStatus.Export ? t('导出') + title : t('导入') + title;

  return (
    <Modal
      title={computeTitle}
      destroyOnClose={true}
      wrapClassName={isValidElement(title) ? 'import-modal-wrapper' : undefined}
      footer={
        status === ModalStatus.Import && (
          <>
            <Button key='delete' onClick={handleClose}>
              {t('取消')}
            </Button>
            {importResult ? (
              <Button type='primary' onClick={handleClose}>
                {t('关闭')}
              </Button>
            ) : (
              <Button
                key='submit'
                type='primary'
                onClick={async () => {
                  await form.validateFields();
                  const data = form.getFieldsValue();
                  try {
                    const importData = JSON.parse(data.import);
                    if (!Array.isArray(importData)) {
                      message.error(title + 'JSON需要时数组');
                      return;
                    }
                    const requstBody = importData.map((item) => {
                      return {
                        ...item,
                        cluster: crossCluster ? data.cluster : undefined,
                      };
                    });

                    const dat = await onSubmit(requstBody);
                    const dataSource = Object.keys(dat).map((key) => {
                      return {
                        name: key,
                        key: key,
                        isTrue: !dat[key],
                        msg: dat[key],
                      };
                    });
                    setImportResult(dataSource);
                    // 每个业务各自处理onSubmit
                  } catch (error) {
                    message.error(t('数据有误:') + error);
                  }
                }}
              >
                {t('确定')}
              </Button>
            )}
          </>
        )
      }
      onCancel={handleClose}
      afterClose={() => setImportResult(undefined)}
      visible={status !== 'hide'}
      width={600}
    >
      <div
        style={{
          color: '#999',
        }}
      >
        {description && <p>{description}</p>}
        {status === ModalStatus.Export && (
          <p>
            <a onClick={handleExportTxt}>Download.json</a>
            <a style={{ float: 'right' }} onClick={() => copyToClipBoard(exportData, t)}>
              <CopyOutlined />
              复制JSON内容到剪贴板
            </a>
          </p>
        )}
      </div>
      {(() => {
        switch (status) {
          case ModalStatus.Export:
            return (
              <div contentEditable='true' suppressContentEditableWarning={true} ref={exportTextRef} className='export-dialog code-area'>
                <pre>{exportData}</pre>
              </div>
            );

          case ModalStatus.BuiltIn:
            return (
              <>
                <Form form={form} preserve={false} layout='vertical'>
                  {crossCluster && (
                    <Form.Item
                      label={t('生效集群:')}
                      name='cluster'
                      initialValue={clusterList[0] || 'Default'}
                      rules={[
                        {
                          required: true,
                          message: t('生效集群不能为空'),
                        },
                      ]}
                    >
                      <Select suffixIcon={<CaretDownOutlined />}>
                        {clusterList?.map((item) => (
                          <Option value={item} key={item}>
                            {item}
                          </Option>
                        ))}
                      </Select>
                    </Form.Item>
                  )}
                </Form>
                <Input
                  placeholder={`请输入要查询的${label}名称`}
                  prefix={<SearchOutlined />}
                  style={{ marginBottom: '8px' }}
                  allowClear
                  onChange={(e) => {
                    let str = e.target.value;
                    let filterArr: { name: string }[] = [];
                    allList.forEach((el) => {
                      if (el.name.toLowerCase().indexOf(str.toLowerCase()) != -1) filterArr.push(el);
                    });
                    setBuildinList(filterArr);
                  }}
                />
                <Table className='samll_table' dataSource={buildinList} columns={builtinColumn} pagination={buildinList.length < 5 ? false : { pageSize: 5 }} size='small' />
                {importResult && (
                  <>
                    <Divider />
                    <Table className='samll_table' dataSource={importResult} columns={columns} size='small' pagination={importResult.length < 5 ? false : { pageSize: 5 }} />
                  </>
                )}
              </>
            );

          case ModalStatus.Import:
            return (
              <>
                <Form form={form} preserve={false} layout='vertical'>
                  {crossCluster ? (
                    <Form.Item
                      label={t('生效集群:')}
                      name='cluster'
                      initialValue={clusterList[0] || 'Default'}
                      rules={[
                        {
                          required: true,
                          message: t('生效集群不能为空'),
                        },
                      ]}
                    >
                      <Select suffixIcon={<CaretDownOutlined />}>
                        {clusterList?.map((item) => (
                          <Option value={item} key={item}>
                            {item}
                          </Option>
                        ))}
                      </Select>
                    </Form.Item>
                  ) : null}
                  <Form.Item
                    label={(!isValidElement(title) ? title : label) + t('JSON:')}
                    name='import'
                    rules={[
                      {
                        required: true,
                        message: t('请输入') + title,
                        validateTrigger: 'trigger',
                      },
                    ]}
                  >
                    <TextArea className='code-area' placeholder={t('请输入') + (!isValidElement(title) ? title : label)} rows={4}></TextArea>
                  </Form.Item>
                </Form>
                {importResult && (
                  <>
                    <Divider />
                    <Table className='samll_table' dataSource={importResult} columns={columns} pagination={false} size='small' />
                  </>
                )}
              </>
            );
        }
      })()}
    </Modal>
  );
}
Example #19
Source File: PreviewHeader.tsx    From datart with Apache License 2.0 4 votes vote down vote up
PreviewHeader: FC<{
  shareLink?: { password: string; token: string; usePassword: boolean };
  chartName?: string;
  chartDescription?: string;
  onRun?;
  onGotoEdit?;
  onGenerateShareLink?;
}> = memo(
  ({
    shareLink,
    chartName,
    chartDescription,
    onRun,
    onGotoEdit,
    onGenerateShareLink,
  }) => {
    const t = useI18NPrefix(`viz.action`);
    const [expireDate, setExpireDate] = useState<string>();
    const [enablePassword, setEnablePassword] = useState(false);
    const [showShareLinkModal, setShowShareLinkModal] = useState(false);

    const moreActionMenu = () => {
      const menus: any = [];
      if (onGenerateShareLink) {
        menus.push(
          <Menu.Item
            key="1"
            icon={<UserOutlined />}
            onClick={() => setShowShareLinkModal(true)}
          >
            {t('share.shareLink')}
          </Menu.Item>,
        );
      }

      return <Menu>{menus}</Menu>;
    };

    const handleCopyToClipboard = value => {
      const ta = document.createElement('textarea');
      ta.innerText = value;
      document.body.appendChild(ta);
      ta.select();
      document.execCommand('copy');
      ta.remove();
    };

    const getFullShareLinkPath = shareLink => {
      const encodeToken = encodeURIComponent(shareLink?.token);
      return `http://172.16.1.150:8080/share?token=${encodeToken}${
        shareLink?.usePassword ? '&usePassword=1' : ''
      }`;
    };

    return (
      <>
        <StyledPreviewHeader>
          <h1>{chartName}</h1>
          <Space>
            <Button key="run" icon={<CaretRightFilled />} onClick={onRun}>
              {t('run')}
            </Button>
            <Dropdown.Button
              key="edit"
              onClick={onGotoEdit}
              overlay={moreActionMenu()}
            >
              {t('edit')}
            </Dropdown.Button>
          </Space>
          <StyledShareLinkModal
            title={t('share.shareLink')}
            visible={showShareLinkModal}
            onOk={() => {
              setShowShareLinkModal(false);
            }}
            onCancel={() => setShowShareLinkModal(false)}
            destroyOnClose
          >
            <Form
              preserve={false}
              labelCol={{ span: 8 }}
              wrapperCol={{ span: 16 }}
              autoComplete="off"
            >
              <FormItemEx label={t('share.expireDate')}>
                <DatePicker
                  showTime
                  onChange={(date, dateString) => {
                    setExpireDate(dateString);
                  }}
                />
              </FormItemEx>
              <FormItemEx label={t('share.enablePassword')}>
                <Checkbox
                  checked={enablePassword}
                  onChange={e => {
                    setEnablePassword(e.target.checked);
                  }}
                />
              </FormItemEx>
              <FormItemEx label={t('share.generateLink')}>
                <Button
                  htmlType="button"
                  onClick={() =>
                    onGenerateShareLink(expireDate, enablePassword)
                  }
                >
                  {t('share.generateLink')}
                </Button>
              </FormItemEx>
              <FormItemEx label={t('share.link')} rules={[{ required: true }]}>
                <Input
                  value={getFullShareLinkPath(shareLink)}
                  addonAfter={
                    <CopyOutlined
                      onClick={() =>
                        handleCopyToClipboard(getFullShareLinkPath(shareLink))
                      }
                    />
                  }
                />
              </FormItemEx>
              {shareLink?.usePassword && (
                <FormItemEx label={t('share.password')}>
                  <Input
                    value={shareLink?.password}
                    addonAfter={
                      <CopyOutlined
                        onClick={() =>
                          handleCopyToClipboard(shareLink?.password)
                        }
                      />
                    }
                  />
                </FormItemEx>
              )}
            </Form>
          </StyledShareLinkModal>
        </StyledPreviewHeader>
        {chartDescription && <Description>{chartDescription}</Description>}
      </>
    );
  },
)
Example #20
Source File: SubscanLink.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
// eslint-disable-next-line complexity
export function SubscanLink({ address, extrinsic, children, copyable, block, ...other }: SubscanLinkProps) {
  const { network, rpc, networkConfig } = useApi();

  const [mainColor, linkColor] = useMemo(() => {
    return [getThemeColor(network), getLinkColor(network)];
  }, [network]);

  const { isCustomNetwork } = useMemo(() => {
    return {
      isCustomNetwork: isCustomRpc(rpc),
    };
  }, [rpc]);

  const openLink = (url: string) => {
    if (!url) {
      return;
    }
    window.open(url, '_blank');
  };

  if (address) {
    return (
      <Text
        copyable={
          copyable && {
            tooltips: false,
            text: address,
            icon: (
              <CopyOutlined
                className="rounded-full opacity-60 cursor-pointer p-1"
                style={{
                  color: mainColor,
                  backgroundColor: mainColor + '40',
                }}
                onClick={(e) => e.preventDefault()}
              />
            ),
          }
        }
        className="w-full"
        style={{
          wordBreak: 'break-all',
          color: !isCustomNetwork || networkConfig?.explorerHostName ? linkColor : '#302B3C',
          height: '20px',
          cursor: !isCustomNetwork || networkConfig?.explorerHostName ? 'pointer' : 'default',
        }}
      >
        <span
          onClick={() =>
            openLink(
              !isCustomNetwork
                ? `https://${network}.subscan.io/account/${address}`
                : networkConfig?.explorerHostName
                ? `https://${networkConfig?.explorerHostName}.subscan.io/account/${address}`
                : ''
            )
          }
        >
          {address}
        </span>
      </Text>
    );
  }

  if (extrinsic) {
    const { height, index } = extrinsic;

    return (
      <Text
        {...other}
        onClick={() => {
          openLink(
            !isCustomNetwork
              ? `https://${network}.subscan.io/extrinsic/${height}-${index}`
              : networkConfig?.explorerHostName
              ? `https://${networkConfig?.explorerHostName}.subscan.io/extrinsic/${height}-${index}`
              : ''
          );
        }}
        style={{
          color: !isCustomNetwork || networkConfig?.explorerHostName ? linkColor : '#302B3C',
          height: '20px',
          cursor: !isCustomNetwork || networkConfig?.explorerHostName ? 'pointer' : 'default',
        }}
      >
        {children}
      </Text>
    );
  }

  if (block) {
    return (
      <Text
        {...other}
        onClick={() => {
          openLink(
            !isCustomNetwork
              ? `https://${network}.subscan.io/block/${block}`
              : networkConfig?.explorerHostName
              ? `https://${networkConfig?.explorerHostName}.subscan.io/block/${block}`
              : ''
          );
        }}
        style={{
          color: !isCustomNetwork || networkConfig?.explorerHostName ? linkColor : '#302B3C',
          height: '20px',
          cursor: !isCustomNetwork || networkConfig?.explorerHostName ? 'pointer' : 'default',
        }}
      >
        {block}
      </Text>
    );
  }

  return null;
}
Example #21
Source File: InputCallDataModal.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
InputCallDataModal = (props: InputCallDataModalProps) => {
  const { t } = useTranslation();
  const { network } = useApi();
  const mainColor = useMemo(() => {
    return getThemeColor(network);
  }, [network]);

  const [selectedAddress, setSelectedAddress] = useState('');
  const [callData, setCallData] = useState('');

  useEffect(() => {
    if (props.availableAccounts && props.availableAccounts?.length > 0) {
      setSelectedAddress(props.availableAccounts[0].address);
    }
  }, [props.availableAccounts]);

  const onSubmit = () => {
    if (!selectedAddress) {
      message.warn(t('missing selected account'));
      return;
    }
    if (!callData.trim()) {
      message.warn(t('missing call data'));
      return;
    }

    props.onConfirm(selectedAddress, callData.trim());
  };

  return (
    <Modal
      title={null}
      footer={null}
      visible={props.visible}
      destroyOnClose
      onCancel={props.onCancel}
      closable={false}
      width={620}
      bodyStyle={{
        paddingLeft: '20px',
        paddingRight: '20px',
        paddingBottom: '30px',
      }}
    >
      <div className="overflow-auto hide-scrollbar" style={{ maxHeight: '500px' }}>
        <div className="flex items-center justify-center relative">
          <div
            className="font-bold capitalize text-black-800"
            style={{ fontSize: '16px', textTransform: 'capitalize' }}
          >
            {t('Transaction information')}
          </div>

          <CloseOutlined
            className="absolute cursor-pointer right-0"
            style={{ color: '#666666' }}
            onClick={props.onCancel}
          />
        </div>

        <div className="mt-6 font-bold text-black-800">{t('Approve Account')}*</div>

        <div className="mt-2">
          {props.availableAccounts && props.availableAccounts.length > 0 && (
            <Select
              style={{
                width: '100%',
              }}
              placeholder={t('Select approve account')}
              defaultValue={props.availableAccounts[0].address}
              onChange={setSelectedAddress}
            >
              {props.availableAccounts.map((acc) => (
                <Select.Option value={acc.address} key={acc.address}>
                  {acc.meta.name} - {acc.address}
                </Select.Option>
              ))}
            </Select>
          )}
        </div>

        <div className="mt-6 font-bold text-black-800">{t('call_hash')}</div>

        <div className="mt-2">
          <Typography.Text
            copyable={{
              tooltips: false,
              text: props.callHash,
              icon: (
                <CopyOutlined
                  className="rounded-full opacity-60 cursor-pointer p-1"
                  style={{
                    color: mainColor,
                    backgroundColor: mainColor + '40',
                  }}
                  onClick={(e) => e.preventDefault()}
                />
              ),
            }}
          >
            {props.callHash}
          </Typography.Text>
        </div>

        <div className="mt-6 font-bold text-black-800">{t('call_data')}*</div>

        <div className="mt-2">
          <Input.TextArea
            value={callData}
            onChange={(e) => {
              setCallData(e.target.value);
            }}
          />
        </div>

        <Row gutter={16} className={classNames('mt-10')}>
          <Col span={24}>
            <Button
              block
              style={{
                color: mainColor,
              }}
              onClick={onSubmit}
            >
              {t('confirm')}
            </Button>
          </Col>
        </Row>
      </div>
    </Modal>
  );
}
Example #22
Source File: createModalForm.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
CreateModalForm: React.FC<CreateModalFormProps> = (props) => {
  const [departmentSelectionVisible, setDepartmentSelectionVisible] = useState(false);
  const [selectedDepartments, setSelectedDepartments] = useState<DepartmentOption[]>([]);
  const [deletedTagIDs, setDeletedTagIDs] = useState<string[]>([]);
  const formRef = useRef<FormInstance>();

  return (
    <>
      <Modal
        width={568}
        className={'dialog from-item-label-100w'}
        visible={props.visible}
        onOk={() => {
          formRef.current?.submit();
        }}
        onCancel={() => {
          props.setVisible(false);
        }}
      >
        <ProForm
          submitter={{
            render: false,
          }}
          // 创建标签组modal
          initialValues={props.initialValues}
          formRef={formRef}
          layout={'horizontal'}
          onFinish={async (values) => {
            const params: GroupChatTagGroupItem = {
              ...props.initialValues,
              ...values,
            };

            if (props.type === 'edit' && deletedTagIDs.length > 0) {
              params.delete_tag_ids = deletedTagIDs;
            }
            await props.onFinish(params);
          }}
        >
          <h2 className="dialog-title"> {props.type==='create'?'新建客户群标签':'修改客户群标签'} </h2>
          <ProFormText
            name="name"
            label="标签组名称"
            width={'md'}
            placeholder="请输入标签组名称"
            rules={[
              {
                required: true,
                message: '标签组名称必填',
              },
            ]}
          />

          <ProFormList
            label={'标签名称'}
            name="tags"
            actionRender={(
              field: FormListFieldData,
              action: FormListOperation,
            ) => {
              const currentKey = field.name;
              const lastKey = formRef.current?.getFieldValue('tags').length - 1;
              return [
                <Tooltip key={'moveUp'} title="上移">
                  <UpCircleOutlined
                    className={'ant-pro-form-list-action-icon'}
                    onClick={() => {
                      console.log(field, currentKey);
                      if (currentKey - 1 >= 0) {
                        action.move(currentKey, currentKey - 1);
                      } else {
                        action.move(currentKey, lastKey);
                      }
                    }}
                  />
                </Tooltip>,
                <Tooltip key={'moveDown'} title="下移">
                  <DownCircleOutlined
                    className={'ant-pro-form-list-action-icon'}
                    onClick={() => {
                      console.log(field, currentKey);
                      if (currentKey + 1 <= lastKey) {
                        action.move(currentKey, currentKey + 1);
                      } else {
                        action.move(currentKey, 0);
                      }
                    }}
                  />
                </Tooltip>,
                <Tooltip key={'copy'} title="复制">
                  <CopyOutlined
                    className={'ant-pro-form-list-action-icon'}
                    onClick={() => {
                      action.add(formRef.current?.getFieldValue('tags')[currentKey]);
                    }}
                  />
                </Tooltip>,
                <Tooltip key={'remove'} title="删除">
                  <DeleteOutlined
                    className={'ant-pro-form-list-action-icon'}
                    onClick={() => {
                      if (formRef.current?.getFieldValue('tags')[currentKey]?.id) {
                        setDeletedTagIDs([
                          ...deletedTagIDs,
                          formRef.current?.getFieldValue('tags')[currentKey].id,
                        ]);
                      }
                      action.remove(currentKey);
                    }}
                  />
                </Tooltip>,
              ];
            }}
            creatorButtonProps={{
              type: 'default',
              style: {width: '128px'},
              position: 'bottom',
              creatorButtonText: '添加标签',
            }}
            creatorRecord={{
              name: '',
            }}
            rules={[
              {
                // @ts-ignore
                required: true,
                message: '标签名称必填',
              },
            ]}
          >
            <ProFormText
              name="name"
              width={'sm'}
              fieldProps={{
                allowClear: false,
                style: {
                  // width: '230px',
                },
              }}
              placeholder="请输入标签名称"
              rules={[
                {
                  required: true,
                  message: '标签名称必填',
                },
              ]}
            />
          </ProFormList>
        </ProForm>
      </Modal>

      <DepartmentSelectionModal
        visible={departmentSelectionVisible}
        setVisible={setDepartmentSelectionVisible}
        defaultCheckedDepartments={selectedDepartments}
        onFinish={(values) => {
          setSelectedDepartments(values);
        }}
        allDepartments={props.allDepartments}
      />
    </>
  );
}
Example #23
Source File: createModalForm.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
CreateModalForm: React.FC<CreateModalFormProps> = (props) => {
  const {allDepartments, initialValues} = props;
  const departmentMap = _.keyBy<DepartmentOption>(allDepartments, "ext_id");
  const [departmentSelectionVisible, setDepartmentSelectionVisible] = useState(false);
  const [selectedDepartments, setSelectedDepartments] = useState<DepartmentOption[]>([]);
  const [deletedTagExtIDs, setDeletedTagExtIDs] = useState<string[]>([]);
  const formRef = useRef<FormInstance>();
  const minOrder = props.minOrder ? props.minOrder : 10000;
  const maxOrder = props.maxOrder ? props.maxOrder : 100000;
  const itemDataToFormData = (values: CustomerTagGroupItem) => {
    const params: any = {...values}
    params.is_global = (values.department_list === undefined || values.department_list === [] || values.department_list?.includes(0)) ? True : False;
    return params;
  }

  useEffect(() => {
    if (initialValues?.department_list?.includes(0)) {
      setSelectedDepartments([])
    } else {
      setSelectedDepartments(initialValues?.department_list?.map((ext_id) => departmentMap[ext_id]) || [])
    }
    formRef?.current?.setFieldsValue(itemDataToFormData(initialValues || {}));
  }, [initialValues])

  return (
    <>
      <Modal
        {...props}
        width={568}
        className={'dialog from-item-label-100w'}
        visible={props.visible}
        onOk={() => {
          formRef.current?.submit();
        }}
        onCancel={() => {
          props.setVisible(false);
        }}
      >
        <ProForm
          submitter={{
            render: false,
          }}
          initialValues={itemDataToFormData(initialValues || {})}
          formRef={formRef}
          layout={'horizontal'}
          onFinish={async (values) => {
            const params: CustomerTagGroupItem = {
              ...props.initialValues,
              ...values,
              department_list: selectedDepartments.map((item) => item.ext_id),
            };

            if (values.is_global === True) {
              params.department_list = [0];
            }

            if (props.type === 'create') {
              if (values.order_type === 'max') {
                params.order = maxOrder + 1;
              }

              if (values.order_type === 'min') {
                params.order = minOrder - 1 >= 0 ? minOrder - 1 : 0;
              }
            }

            if (props.type === 'edit' && deletedTagExtIDs.length > 0) {
              params.remove_ext_tag_ids = deletedTagExtIDs;
            }

            await props.onFinish(params);
            setDeletedTagExtIDs([]);
          }}
        >
          <h3 className="dialog-title" style={{fontSize: 18}}>
            {' '}
            {props.type === 'edit' ? '修改标签组' : '新建标签组'}{' '}
          </h3>
          <ProFormText
            name="name"
            label="标签组名称"
            width={'md'}
            placeholder="请输入标签组名称"
            rules={[
              {
                required: true,
                message: '标签组名称必填',
              },
            ]}
          />

          <ProFormRadio.Group
            name="is_global"
            label="可见范围"
            options={[
              {
                label: '全部员工',
                value: True,
              },
              {
                label: '部门可用',
                value: False,
              },
            ]}
          />

          <ProFormDependency name={['is_global']}>
            {({is_global}) => {
              // 部门可用
              if (is_global === Disable) {
                return (
                  <>
                    <Row>
                      <ProForm.Item label={'选择可用部门'}>
                        <Button
                          icon={<PlusOutlined/>}
                          onClick={() => setDepartmentSelectionVisible(true)}
                        >
                          添加部门
                        </Button>
                      </ProForm.Item>
                    </Row>

                    <Row>
                      <Space direction={'horizontal'} wrap={true} style={{marginBottom: 6}}>
                        {selectedDepartments?.length > 0 && selectedDepartments.map((item, index) => {
                          if (!item?.id) {
                            return <div key={index}></div>
                          }
                          return (
                            <div key={item.id} className={'department-item'}>
                              <Badge
                                count={
                                  <CloseCircleOutlined
                                    onClick={() => {
                                      setSelectedDepartments(
                                        selectedDepartments.filter(
                                          (department) => department.id !== item.id,
                                        ),
                                      );
                                    }}
                                    style={{color: 'rgb(199,199,199)'}}
                                  />
                                }
                              >
                              <span className={'container'}>
                                <FolderFilled
                                  style={{
                                    color: '#47a7ff',
                                    fontSize: 20,
                                    marginRight: 6,
                                    verticalAlign: -6,
                                  }}
                                />
                                {item.name}
                              </span>
                              </Badge>
                            </div>
                          )
                        })}
                      </Space>
                    </Row>
                  </>
                );
              }

              // 全局可用
              return <></>;
            }}
          </ProFormDependency>

          {props.type === 'create' && (
            <ProFormRadio.Group
              name="order_type"
              label="默认排序"
              initialValue={'max'}
              options={[
                {
                  label: '排最前面',
                  value: 'max',
                },
                {
                  label: '排最后面',
                  value: 'min',
                },
              ]}
            />
          )}

          <ProFormList
            label={'标签名称'}
            name="tags"
            actionRender={(field: FormListFieldData, action: FormListOperation) => {
              const currentKey = field.name;
              const lastKey = formRef.current?.getFieldValue('tags').length - 1;
              return [
                <Tooltip key={'moveUp'} title="上移">
                  <UpCircleOutlined
                    className={'ant-pro-form-list-action-icon'}
                    onClick={() => {
                      if (currentKey - 1 >= 0) {
                        action.move(currentKey, currentKey - 1);
                      } else {
                        action.move(currentKey, lastKey);
                      }
                    }}
                  />
                </Tooltip>,
                <Tooltip key={'moveDown'} title="下移">
                  <DownCircleOutlined
                    className={'ant-pro-form-list-action-icon'}
                    onClick={() => {
                      if (currentKey + 1 <= lastKey) {
                        action.move(currentKey, currentKey + 1);
                      } else {
                        action.move(currentKey, 0);
                      }
                    }}
                  />
                </Tooltip>,
                <Tooltip key={'copy'} title="复制">
                  <CopyOutlined
                    className={'ant-pro-form-list-action-icon'}
                    onClick={() => {
                      action.add(formRef.current?.getFieldValue('tags')[currentKey]);
                    }}
                  />
                </Tooltip>,
                <Tooltip key={'remove'} title="删除">
                  <DeleteOutlined
                    className={'ant-pro-form-list-action-icon'}
                    onClick={() => {
                      if (formRef.current?.getFieldValue('tags')[currentKey]?.ext_id) {
                        setDeletedTagExtIDs([
                          ...deletedTagExtIDs,
                          formRef.current?.getFieldValue('tags')[currentKey].ext_id,
                        ]);
                      }
                      action.remove(currentKey);
                    }}
                  />
                </Tooltip>,
              ];
            }}
            creatorButtonProps={{
              type: 'default',
              style: {width: '128px'},
              position: 'bottom',
              creatorButtonText: '添加标签',
            }}
            creatorRecord={{
              name: '',
            }}
            rules={[
              {
                // @ts-ignore
                required: true,
                message: '标签名称必填',
              },
            ]}
          >
            <ProFormText
              name="name"
              width={'sm'}
              fieldProps={{
                allowClear: false,
                style: {
                  // width: '230px',
                },
              }}
              placeholder="请输入标签名称"
              rules={[
                {
                  required: true,
                  message: '标签名称必填',
                },
              ]}
            />
          </ProFormList>
        </ProForm>
      </Modal>

      <DepartmentSelectionModal
        visible={departmentSelectionVisible}
        setVisible={setDepartmentSelectionVisible}
        defaultCheckedDepartments={selectedDepartments}
        onFinish={(values) => {
          setSelectedDepartments(values);
        }}
        allDepartments={props.allDepartments}
      />
    </>
  );
}
Example #24
Source File: index.tsx    From Aragorn with MIT License 4 votes vote down vote up
FileManage = () => {
  const {
    state: {
      uploaderProfiles,
      configuration: { defaultUploaderProfileId }
    }
  } = useAppContext();

  const [windowHeight, setWindowHeight] = useState(window.innerHeight);

  useEffect(() => {
    function handleResize() {
      setWindowHeight(window.innerHeight);
    }
    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  const { id } = useParams<{ id: string }>();

  const [hasFileManageFeature, setHasFileManageFeature] = useState(false);

  const [uploaderProfile, setUploaderProfile] = useState({} as UploaderProfile);

  useEffect(() => {
    const currentId = id || defaultUploaderProfileId;
    setCurrentProfile(currentId as string);
  }, []);

  useEffect(() => {
    if (uploaderProfile?.id) {
      getList();
    }
  }, [uploaderProfile]);

  const [list, setList] = useState([] as ListFile[]);
  const [listLoading, setListLoading] = useState(false);

  const getList = (directoryPath?: string) => {
    setListLoading(true);
    ipcRenderer.send('file-list-get', uploaderProfile.id, directoryPath);
  };

  const [dirPath, setDirPath] = useState([] as string[]);

  useEffect(() => {
    function handleListGetReply(_, res?: FileListResponse) {
      setListLoading(false);
      if (res === undefined) {
        setHasFileManageFeature(false);
        setList([]);
        message.info(`${uploaderProfile.uploaderName}暂不支持文件管理功能`);
        return;
      }
      setHasFileManageFeature(true);
      if (res.success) {
        setList(res.data);
      } else {
        message.error(`文件列表获取失败 ${res.desc || ''}`);
      }
    }

    function handleFileDeleteReply(_, res?: DeleteFileResponse) {
      if (res === undefined) {
        return;
      }
      if (res.success) {
        message.success({ content: '文件删除成功', key: 'file-manage-delete' });
        getList(dirPath.join('/'));
      } else {
        message.error({ content: `文件删除失败 ${res.desc || ''}`, key: 'file-manage-delete' });
      }
    }

    function handleFileUploadReply() {
      getList(dirPath.join('/'));
    }

    function handleDirectoryCreateReply(_, res?: CreateDirectoryResponse) {
      if (res === undefined) {
        return;
      }
      if (res.success) {
        message.success('目录创建成功');
        setModalVisible(false);
        getList(dirPath.join('/'));
      } else {
        message.error(`目录创建失败 ${res.desc || ''}`);
      }
    }

    function handleExportReplay(_, res) {
      setExportLoading(false);
      if (res) {
        shell.showItemInFolder(res);
        setRowKeys([]);
        setSelectRows([]);
      }
    }

    ipcRenderer.on('file-list-get-reply', handleListGetReply);
    ipcRenderer.on('file-delete-reply', handleFileDeleteReply);
    ipcRenderer.on('file-upload-reply', handleFileUploadReply);
    ipcRenderer.on('directory-create-reply', handleDirectoryCreateReply);
    ipcRenderer.on('export-reply', handleExportReplay);

    return () => {
      ipcRenderer.removeListener('file-list-get-reply', handleListGetReply);
      ipcRenderer.removeListener('file-delete-reply', handleFileDeleteReply);
      ipcRenderer.removeListener('file-upload-reply', handleFileUploadReply);
      ipcRenderer.removeListener('directory-create-reply', handleDirectoryCreateReply);
      ipcRenderer.removeListener('export-reply', handleExportReplay);
    };
  }, [uploaderProfile, dirPath]);

  const handleNameClick = (record: ListFile) => {
    if (record.type === 'directory') {
      const newPath = [...dirPath, formatFileName(record.name)];
      setDirPath(newPath);
      getList(newPath.join('/'));
    } else {
      clipboard.writeText(record.url as string);
      message.success('链接已复制到粘贴板');
    }
  };

  const handlePathClick = (index: number) => {
    if (index === -1) {
      setDirPath([]);
      getList();
    } else {
      const newPath = dirPath.slice(0, index + 1);
      setDirPath(newPath);
      getList(newPath.join('/'));
    }
  };

  const setCurrentProfile = (uploaderProfileId: string) => {
    setDirPath([]);
    const uploaderProfile = uploaderProfiles.find(item => item.id === uploaderProfileId);
    setUploaderProfile(uploaderProfile as UploaderProfile);
  };

  const formatFileName = (name: string) => {
    if (dirPath.length > 0) {
      const pathPrefix = dirPath.join('/') + '/';
      return name.split(pathPrefix).pop() || '';
    } else {
      return name;
    }
  };

  const [selectRowKeys, setRowKeys] = useState([] as string[]);
  const [selectRows, setSelectRows] = useState([] as ListFile[]);

  const handleTableRowChange = (selectedRowKeys, selectedRows: ListFile[]) => {
    setRowKeys(selectedRowKeys);
    setSelectRows(selectedRows);
  };

  const handleRefresh = () => {
    getList(dirPath.join('/'));
  };

  const handleBatchDelete = () => {
    Modal.confirm({
      title: '确认删除',
      onOk: () => {
        const names = selectRows.map(item => [...dirPath, formatFileName(item.name)].join('/'));
        message.info({ content: '正在删除,请稍后...', key: 'file-manage-delete' });
        ipcRenderer.send('file-delete', uploaderProfile.id, names);
      }
    });
  };

  const handleDelete = (record: ListFile) => {
    let name = record.name;
    Modal.confirm({
      title: '确认删除',
      content: name,
      onOk: () => {
        let name = record.name;
        if (record.type === 'directory') {
          name = `${[...dirPath, record.name].join('/')}/`;
        } else {
          name = [...dirPath, formatFileName(record.name)].join('/');
        }
        message.info({ content: '正在删除,请稍后...', key: 'file-manage-delete' });
        ipcRenderer.send('file-delete', uploaderProfile.id, [name]);
      }
    });
  };

  const uploadRef = useRef<HTMLInputElement>(null);

  const handleFileUpload = (event: React.FormEvent<HTMLInputElement>) => {
    const fileList = event.currentTarget.files || [];
    const filesPath = Array.from(fileList).map(file => file.path);
    const pathPrefix = dirPath.join('/');
    ipcRenderer.send('file-upload', uploaderProfile.id, filesPath, pathPrefix);
    event.currentTarget.value = '';
  };

  const [modalVisible, setModalVisible] = useState(false);

  const [form] = Form.useForm();

  const handleCreateDirectory = () => {
    form.validateFields().then(values => {
      ipcRenderer.send('directory-create', uploaderProfile.id, values?.directoryPath || '');
    });
  };

  const handleDownload = (record: ListFile) => {
    ipcRenderer.send('file-download', record.name, record.url);
  };

  const [exportLoading, setExportLoading] = useState(false);

  const handleExport = () => {
    const data = selectRows.map(item => {
      const fileNameArr = item.name.split('.');
      fileNameArr.pop();
      return {
        name: fileNameArr.join('.'),
        url: item.url
      };
    });
    setExportLoading(true);
    ipcRenderer.send('export', data);
  };

  const columns: ColumnsType<ListFile> = [
    {
      title: '文件名',
      dataIndex: 'name',
      ellipsis: true,
      render: (val: string, record: ListFile) => (
        <div style={{ display: 'flex', alignItems: 'center' }}>
          {record.type === 'directory' ? (
            <FolderFilled style={{ fontSize: 16 }} />
          ) : (
            <FileOutlined style={{ fontSize: 16 }} />
          )}
          {record.type === 'directory' ? (
            <a
              title={val}
              onClick={() => handleNameClick(record)}
              className="table-filename"
              style={{ marginLeft: 10, overflow: 'hidden', textOverflow: 'ellipsis' }}
            >
              {formatFileName(val)}
            </a>
          ) : (
            <Popover
              placement="topLeft"
              content={() =>
                /(jpg|png|gif|jpeg)$/.test(val) ? (
                  <Image
                    style={{ maxWidth: 500 }}
                    src={record.url}
                    fallback="data:image/png;base64,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"
                  />
                ) : (
                  val
                )
              }
              trigger="hover"
            >
              <a
                title={val}
                onClick={() => handleNameClick(record)}
                className="table-filename"
                style={{ marginLeft: 10, overflow: 'hidden', textOverflow: 'ellipsis' }}
              >
                {formatFileName(val)}
              </a>
            </Popover>
          )}
        </div>
      )
    },
    {
      title: '文件大小',
      dataIndex: 'size',
      ellipsis: true,
      width: 120,
      render: val => (val ? filesize(val) : '-')
    },
    {
      title: '更新时间',
      dataIndex: 'lastModified',
      ellipsis: true,
      width: 200,
      render: val => (val ? dayjs(val).format('YYYY-MM-DD HH:mm:ss') : '-')
    },
    {
      title: '操作',
      width: 120,
      render: (_, record) => (
        <Space>
          {record.type !== 'directory' && (
            <>
              <DownloadOutlined onClick={() => handleDownload(record)} />
              <CopyOutlined onClick={() => handleNameClick(record)} />
            </>
          )}
          <DeleteOutlined onClick={() => handleDelete(record)} />
        </Space>
      )
    }
  ];

  return (
    <div className="storage-page">
      <header>
        <span>文件管理</span>
        <Divider />
      </header>
      <Space style={{ marginBottom: 10 }}>
        <Select style={{ minWidth: 120 }} value={uploaderProfile?.id} onChange={setCurrentProfile}>
          {uploaderProfiles.map(item => (
            <Select.Option key={item.name} value={item.id}>
              {item.name}
            </Select.Option>
          ))}
        </Select>
        <Button
          title="上传"
          icon={<UploadOutlined />}
          disabled={!hasFileManageFeature}
          type="primary"
          onClick={() => {
            uploadRef.current?.click();
          }}
        />
        <Button title="刷新" icon={<ReloadOutlined />} disabled={!hasFileManageFeature} onClick={handleRefresh} />
        <Button
          title="创建文件夹"
          icon={<FolderAddOutlined />}
          disabled={!hasFileManageFeature}
          onClick={() => {
            setModalVisible(true);
          }}
        />
        <Button
          title="导出"
          icon={<ExportOutlined />}
          disabled={selectRows.length === 0}
          onClick={handleExport}
          loading={exportLoading}
        />
        <Button title="删除" icon={<DeleteOutlined />} disabled={selectRows.length === 0} onClick={handleBatchDelete} />
      </Space>
      <Breadcrumb style={{ marginBottom: 10 }}>
        <Breadcrumb.Item>
          <a onClick={() => handlePathClick(-1)}>全部文件</a>
        </Breadcrumb.Item>
        {dirPath.map((item, index) => (
          <Breadcrumb.Item key={item}>
            <a onClick={() => handlePathClick(index)}>{item}</a>
          </Breadcrumb.Item>
        ))}
      </Breadcrumb>
      <div className="table-wrapper">
        <Table
          size="small"
          rowKey="name"
          scroll={{ y: windowHeight - 270 }}
          dataSource={list}
          columns={columns}
          pagination={{
            size: 'small',
            defaultPageSize: 100,
            pageSizeOptions: ['50', '100', '200'],
            hideOnSinglePage: true
          }}
          loading={listLoading}
          rowSelection={{
            onChange: handleTableRowChange,
            selectedRowKeys: selectRowKeys,
            getCheckboxProps: record => ({ disabled: record?.type === 'directory' })
          }}
        />
      </div>
      <input ref={uploadRef} type="file" multiple hidden onChange={handleFileUpload} />
      <Modal
        title="创建目录"
        visible={modalVisible}
        onCancel={() => setModalVisible(false)}
        onOk={handleCreateDirectory}
        destroyOnClose={true}
      >
        <Form form={form} preserve={false}>
          <Form.Item
            label="目录名称"
            name="directoryPath"
            rules={[{ required: true }, { pattern: domainPathRegExp, message: '目录名不能以 / 开头或结尾' }]}
          >
            <Input autoFocus />
          </Form.Item>
        </Form>
      </Modal>
    </div>
  );
}
Example #25
Source File: index.tsx    From Aragorn with MIT License 4 votes vote down vote up
Dashboard = () => {
  const {
    state: {
      uploaderProfiles,
      configuration: { defaultUploaderProfileId },
      uploadedFiles
    }
  } = useAppContext();

  const history = useHistory();

  const [selectRowKeys, setRowKeys] = useState([]);
  const [selectRows, setSelectRows] = useState([] as UploadedFileInfo[]);

  const handleProfileAdd = () => {
    history.push('/uploader');
  };

  const handleProfileClick = id => {
    if (id === defaultUploaderProfileId) {
      history.push(`/profile/${id}`);
    } else {
      ipcRenderer.send('set-default-uploader-profile', id);
    }
  };

  const handleCopy = url => {
    clipboard.writeText(url);
    message.success('已复制到粘贴板');
  };

  const handleOpen = path => {
    shell.showItemInFolder(path);
  };

  const handleTableRowChange = (selectedRowKeys, selectedRows) => {
    setRowKeys(selectedRowKeys);
    setSelectRows(selectedRows);
  };

  const handleClear = () => {
    const ids = selectRows.map(item => item.id);
    ipcRenderer.send('clear-upload-history', ids);
    setRowKeys([]);
  };

  const handleReUpload = () => {
    const data = selectRows.map(item => {
      return { id: item.uploaderProfileId, path: item.path };
    });
    ipcRenderer.send('file-reupload', data);
    setRowKeys([]);
  };

  const columns: ColumnsType<UploadedFileInfo> = [
    {
      title: '文件名',
      dataIndex: 'name',
      ellipsis: true,
      render: (val, record) => (
        <Popover
          placement="topLeft"
          content={() =>
            /(jpg|png|gif|jpeg)$/.test(val) ? (
              <Image
                style={{ maxWidth: 500 }}
                src={record.url}
                fallback="data:image/png;base64,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"
              />
            ) : (
              val
            )
          }
          trigger="hover"
        >
          <span onClick={() => handleCopy(record.url)} className="row-item">
            {val}
          </span>
        </Popover>
      )
    },
    {
      title: '类型',
      dataIndex: 'type',
      ellipsis: true,
      width: 120
    },
    {
      title: '上传器配置',
      dataIndex: 'uploaderProfileId',
      ellipsis: true,
      width: 120,
      render: val => (
        <a onClick={() => handleProfileClick(val)}>
          {uploaderProfiles.find(item => item.id === val)?.name || '未找到'}
        </a>
      )
    },
    {
      title: '状态',
      dataIndex: 'url',
      ellipsis: true,
      width: 80,
      render: val => (
        <>
          <Badge status={val ? 'success' : 'error'} />
          {val ? '成功' : '失败'}
        </>
      )
    },
    {
      title: '上传时间',
      dataIndex: 'date',
      width: 180,
      ellipsis: true,
      render: val => dayjs(val).format('YYYY-MM-DD HH:mm:ss')
    },
    {
      title: '操作',
      width: 80,
      render: (_, record) => (
        <Space>
          <FolderOpenOutlined onClick={() => handleOpen(record.path)} />
          <CopyOutlined onClick={() => handleCopy(record.url)} />
        </Space>
      )
    }
  ];

  return (
    <div className="dashboard-page">
      <header>
        <span>控制台</span>
        <Divider />
      </header>
      <main>
        <div className="profile-wrapper">
          <div className="title">上传器配置</div>
          <div className="card-wrapper">
            {uploaderProfiles.map(item => (
              <div
                key={item.id}
                className={item.id === defaultUploaderProfileId ? 'card card-active' : 'card'}
                onClick={() => handleProfileClick(item.id)}
              >
                <Box className="card-icon" />
                <span>{item.name}</span>
              </div>
            ))}
            <div className="card" onClick={handleProfileAdd}>
              <Plus className="card-icon" />
            </div>
          </div>
        </div>
        <div className="history-wrapper">
          <div className="title">最近上传</div>
          <div className="card-wrapper">
            {selectRowKeys.length > 0 && (
              <Space style={{ marginBottom: 10 }}>
                <Button icon={<DeleteOutlined />} onClick={handleClear}>
                  清除
                </Button>
                <Button icon={<UploadOutlined />} onClick={handleReUpload}>
                  重新上传
                </Button>
              </Space>
            )}
            <Table
              size="small"
              rowKey="id"
              dataSource={uploadedFiles}
              columns={columns}
              rowSelection={{ onChange: handleTableRowChange, selectedRowKeys: selectRowKeys }}
            />
          </div>
        </div>
      </main>
    </div>
  );
}
Example #26
Source File: AyFormList.tsx    From amiya with MIT License 4 votes vote down vote up
export default function AyFormList(props: AyFormListProps) {
  const { field, getFormItem, formInstant, ayFormProps } = props
  // 最少行 & 最大行
  const { min = 0, max = Infinity } = field
  // 当前行数
  const [recordNum, setRecordNum] = useState(0)

  useEffect(() => {
    setRecordNum(formInstant.getFieldValue(field.key).length)
  }, [])

  /**
   * 复制这行数据到末尾
   * @param name 实际上是当前行 index
   */
  const handleCopy = (name: number) => {
    try {
      let value = formInstant.getFieldValue(field.key)
      let newValue = [...value, value[name]]
      setRecordNum(newValue.length)
      formInstant.setFieldsValue({ [field.key || '']: newValue })
    } catch {
      console.error('复制失败')
    }
  }

  /**
   * 删除这行数据
   * @param name 实际上是当前行 index
   * @param remove 删除方法
   */
  const handleRemove = (name: number, remove: (name: number) => void) => {
    remove(name)
    setRecordNum(Number(recordNum) - 1)
  }

  /**
   * 新增一行
   * @param add 新增方法
   */
  const handleAdd = (add: (defaultValue?: any, insertIndex?: number | undefined) => void) => {
    add(field.creatorRecord || {})
    setRecordNum(Number(recordNum) + 1)
  }

  return (
    <Form.List {...field.props} name={field.key || field.label} key={field.key || field.label}>
      {(fields, { add, remove }) => (
        <>
          {fields.map(({ key, name, ...restField }) => {
            let children = field.children || []
            if (!Array.isArray(children)) {
              children = [children]
            }
            let content = getFormItem(
              children.map((child: AyFormField) => ({
                ...child,
                formItemProps: {
                  ...field.formItemProps,
                  ...restField,
                  name: [name, child.key]
                }
              })) as Array<AyFormField | AySearchTableField>,
              formInstant,
              ayFormProps,
              FORM_TYPE_LIST
            )
            return (
              <Space key={`${field.key}-${key}`} className="ay-form-list-item" align="end" {...field.spaceProps}>
                {content}
                <Space className="ay-form-list-actions">
                  {recordNum < max && (
                    <span className="ay-form-list-action" onClick={() => handleCopy(name)}>
                      <Tooltip title={locale.form.copyToEnd}>
                        <CopyOutlined />
                      </Tooltip>
                    </span>
                  )}

                  {recordNum > min && (
                    <span className="ay-form-list-action" onClick={() => handleRemove(name, remove)}>
                      <Tooltip title={locale.form.removeRow}>
                        <DeleteOutlined />
                      </Tooltip>
                    </span>
                  )}
                </Space>
              </Space>
            )
          })}
          {recordNum < max && (
            <Button type="dashed" onClick={() => handleAdd(add)} icon={<PlusOutlined />}>
              {locale.form.addItem}
            </Button>
          )}
        </>
      )}
    </Form.List>
  )
}
Example #27
Source File: index.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
Shield: React.FC = () => {
  const { t } = useTranslation();
  const history = useHistory();
  const [query, setQuery] = useState<string>('');
  const { curBusiItem } = useSelector<RootState, CommonStoreState>((state) => state.common);
  const [bgid, setBgid] = useState(undefined);
  const [clusters, setClusters] = useState<string[]>([]);
  const [currentShieldDataAll, setCurrentShieldDataAll] = useState<Array<subscribeItem>>([]);
  const [currentShieldData, setCurrentShieldData] = useState<Array<subscribeItem>>([]);
  const [loading, setLoading] = useState<boolean>(false);

  const columns: ColumnsType = [
    {
      title: t('集群'),
      dataIndex: 'cluster',
      render: (data) => {
        return <div>{data}</div>;
      },
    },
    {
      title: t('告警规则'),
      dataIndex: 'rule_name',
      render: (data) => {
        return <div>{data}</div>;
      },
    },
    {
      title: t('订阅标签'),
      dataIndex: 'tags',
      render: (text: any) => {
        return (
          <>
            {text
              ? text.map((tag, index) => {
                  return tag ? <div key={index}>{`${tag.key} ${tag.func} ${tag.func === 'in' ? tag.value.split(' ').join(', ') : tag.value}`}</div> : null;
                })
              : ''}
          </>
        );
      },
    },
    {
      title: t('告警接收组'),
      dataIndex: 'user_groups',
      render: (text: string, record: subscribeItem) => {
        return (
          <>
            {record.user_groups?.map((item) => (
              <ColorTag text={item.name} key={item.id}></ColorTag>
            ))}
          </>
        );
      },
    },

    {
      title: t('编辑人'),
      ellipsis: true,
      dataIndex: 'update_by',
    },
    {
      title: t('操作'),
      width: '128px',
      dataIndex: 'operation',
      render: (text: undefined, record: subscribeItem) => {
        return (
          <>
            <div className='table-operator-area'>
              <div
                className='table-operator-area-normal'
                style={{
                  cursor: 'pointer',
                  display: 'inline-block',
                }}
                onClick={() => {
                  curBusiItem?.id && history.push(`/alert-subscribes/edit/${record.id}`);
                }}
              >
                {t('编辑')}
              </div>
              <div
                className='table-operator-area-normal'
                style={{
                  cursor: 'pointer',
                  display: 'inline-block',
                }}
                onClick={() => {
                  curBusiItem?.id && history.push(`/alert-subscribes/edit/${record.id}?mode=clone`);
                }}
              >
                {t('克隆')}
              </div>
              <div
                className='table-operator-area-warning'
                style={{
                  cursor: 'pointer',
                  display: 'inline-block',
                }}
                onClick={() => {
                  confirm({
                    title: t('确定删除该订阅规则?'),
                    icon: <ExclamationCircleOutlined />,
                    onOk: () => {
                      dismiss(record.id);
                    },

                    onCancel() {},
                  });
                }}
              >
                {t('删除')}
              </div>
            </div>
          </>
        );
      },
    },
  ];

  useEffect(() => {
    getList();
  }, [curBusiItem]);

  useEffect(() => {
    filterData();
  }, [query, clusters, currentShieldDataAll]);

  const dismiss = (id: number) => {
    deleteSubscribes({ ids: [id] }, curBusiItem.id).then((res) => {
      refreshList();
      if (res.err) {
        message.success(res.err);
      } else {
        message.success(t('删除成功'));
      }
    });
  };

  const filterData = () => {
    const data = JSON.parse(JSON.stringify(currentShieldDataAll));
    const res = data.filter((item: subscribeItem) => {
      const tagFind = item?.tags?.find((tag) => {
        return tag.key.indexOf(query) > -1 || tag.value.indexOf(query) > -1 || tag.func.indexOf(query) > -1;
      });
      const groupFind = item?.user_groups?.find((item) => {
        return item?.name?.indexOf(query) > -1;
      });
      return (item?.rule_name?.indexOf(query) > -1 || !!tagFind || !!groupFind) && ((clusters && clusters?.indexOf(item.cluster) > -1) || clusters?.length === 0);
    });
    setCurrentShieldData(res || []);
  };

  const getList = async () => {
    if (curBusiItem.id) {
      setLoading(true);
      const { success, dat } = await getSubscribeList({ id: curBusiItem.id });
      if (success) {
        setCurrentShieldDataAll(dat || []);
        setLoading(false);
      }
    }
  };

  const refreshList = () => {
    getList();
  };

  const onSearchQuery = (e) => {
    let val = e.target.value;
    setQuery(val);
  };

  const clusterChange = (data) => {
    setClusters(data);
  };

  const busiChange = (data) => {
    setBgid(data);
  };

  return (
    <PageLayout title={t('订阅规则')} icon={<CopyOutlined />} hideCluster>
      <div className='shield-content'>
        <LeftTree
          busiGroup={{
            // showNotGroupItem: true,
            onChange: busiChange,
          }}
        ></LeftTree>
        {curBusiItem?.id ? (
          <div className='shield-index'>
            <div className='header'>
              <div className='header-left'>
                <RefreshIcon
                  className='strategy-table-search-left-refresh'
                  onClick={() => {
                    refreshList();
                  }}
                />
                <ColumnSelect onClusterChange={(e) => setClusters(e)} />
                <Input onPressEnter={onSearchQuery} className={'searchInput'} prefix={<SearchOutlined />} placeholder={t('搜索规则、标签、接收组')} />
              </div>
              <div className='header-right'>
                <Button
                  type='primary'
                  className='add'
                  ghost
                  onClick={() => {
                    history.push('/alert-subscribes/add');
                  }}
                >
                  {t('新增订阅规则')}
                </Button>
              </div>
            </div>
            <Table
              rowKey='id'
              pagination={{
                total: currentShieldData.length,
                showQuickJumper: true,
                showSizeChanger: true,
                showTotal: (total) => {
                  return `共 ${total} 条数据`;
                },
                pageSizeOptions: pageSizeOptionsDefault,
                defaultPageSize: 30,
              }}
              loading={loading}
              dataSource={currentShieldData}
              columns={columns}
            />
          </div>
        ) : (
          <BlankBusinessPlaceholder text='订阅规则' />
        )}
      </div>
    </PageLayout>
  );
}
Example #28
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 #29
Source File: EditItem.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
export default function EditItem(props: Props) {
  const { visible, onChange, value, range, id } = props;
  const { t } = useTranslation();
  const [form] = Form.useForm();
  useEffect(() => {
    value && form.setFieldsValue(value);
  }, [value]);
  const handleOk = async () => {
    await form.validateFields();
    const v: FormType = form.getFieldsValue();
    onChange(v);
  };
  const onCancel = () => {
    onChange(undefined);
  };

  const onFinish = (values) => {
    console.log('Received values of form:', values);
  };

  const handleBlur = (index) => {
    const reg = form.getFieldValue(['var', index, 'reg']);
    const expression = form.getFieldValue(['var', index, 'definition']);
    if ((!reg || new RegExp('^/(.*?)/(g?i?m?y?)$').test(reg)) && expression) {
      const formData = form.getFieldsValue();
      var newExpression = replaceExpressionVars(expression, formData, index, id);
      convertExpressionToQuery(newExpression, range).then((res) => {
        const regFilterRes = res.filter((i) => !reg || !stringToRegex(reg) || (stringToRegex(reg) as RegExp).test(i));
        if (regFilterRes.length > 0) {
          setVaraiableSelected(formData.var[index].name, regFilterRes[0], id);
        }
        // form.setFields([{ name: ['var', index, 'selected'], value: regFilterRes[0] }]);
      });
    }
  };

  return (
    <Modal title={t('大盘变量')} width={950} visible={visible} onOk={handleOk} onCancel={onCancel} wrapClassName='variable-modal'>
      <Form name='dynamic_form_nest_item' onFinish={onFinish} autoComplete='off' preserve={false} form={form}>
        <Row gutter={[6, 6]} className='tag-header'>
          <Col span={4}>{t('变量名')}</Col>
          <Col span={6}>
            {t('变量定义')}
            <QuestionCircleOutlined
              style={{ marginLeft: 5 }}
              onClick={() => window.open('https://grafana.com/docs/grafana/latest/datasources/prometheus/#query-variable', '_blank')}
            />
          </Col>
          <Col span={6}>{t('筛值正则')}</Col>
          <Col span={2}>{t('Multi')}</Col>
          <Col span={2}>{t('All Option')}</Col>
          <Col span={4}>{t('操作')}</Col>
        </Row>
        <Form.List name='var'>
          {(fields, { add, remove, move }) => (
            <>
              {fields.map(({ key, name, fieldKey, ...restField }) => (
                <Row gutter={[6, 6]} className='tag-content-item' key={key}>
                  <Col span={4}>
                    <Form.Item
                      {...restField}
                      name={[name, 'name']}
                      fieldKey={[fieldKey, 'name']}
                      rules={[
                        { required: true, message: t('请输入变量名') },
                        { pattern: /^[0-9a-zA-Z_]+$/, message: t('仅支持数字和字符下划线') },
                      ]}
                    >
                      <Input />
                    </Form.Item>
                  </Col>
                  <Col span={6}>
                    <Form.Item
                      {...restField}
                      name={[name, 'definition']}
                      fieldKey={[fieldKey, 'definition']}
                      rules={[
                        { required: true, message: t('请输入变量定义') },
                        {
                          validator(_, value) {
                            if (/^\s*label_values.+,\s*\$.+/.test(value)) {
                              return Promise.reject(new Error('label_values表达式的label不允许使用变量'));
                            }
                            return Promise.resolve();
                          },
                        },
                      ]}
                    >
                      <Input onBlur={(v) => handleBlur(name)} />
                    </Form.Item>
                  </Col>
                  <Col span={6}>
                    <Form.Item {...restField} name={[name, 'reg']} fieldKey={[fieldKey, 'reg']} rules={[{ pattern: new RegExp('^/(.*?)/(g?i?m?y?)$'), message: t('格式不对') }]}>
                      <Input placeholder='/*.hna/' onBlur={(v) => handleBlur(name)} />
                    </Form.Item>
                  </Col>
                  <Col span={2}>
                    <Form.Item {...restField} name={[name, 'multi']} fieldKey={[fieldKey, 'multi']} valuePropName='checked'>
                      <Switch />
                    </Form.Item>
                  </Col>
                  <Col span={2}>
                    <Form.Item shouldUpdate style={{ margin: 0 }}>
                      {() => {
                        return (
                          form.getFieldValue(['var', name, 'multi']) && (
                            <Form.Item {...restField} name={[name, 'allOption']} fieldKey={[fieldKey, 'allOption']} valuePropName='checked'>
                              <Switch />
                            </Form.Item>
                          )
                        );
                      }}
                    </Form.Item>
                  </Col>
                  {/* <Form.Item {...restField} name={[name, 'selected']} fieldKey={[fieldKey, 'selected']} hidden>
                    <Input />
                  </Form.Item> */}
                  <Col span={4}>
                    <Button type='link' size='small' onClick={() => move(name, name + 1)} disabled={name === fields.length - 1}>
                      <ArrowDownOutlined />
                    </Button>
                    <Button type='link' size='small' onClick={() => move(name, name - 1)} disabled={name === 0}>
                      <ArrowUpOutlined />
                    </Button>
                    <Button
                      type='link'
                      size='small'
                      onClick={() => {
                        const v = form.getFieldValue(['var', name]);
                        add({ ...v, name: 'copy_of_' + v.name });
                      }}
                    >
                      <CopyOutlined />
                    </Button>
                    <Button type='link' size='small' onClick={() => remove(name)}>
                      <DeleteOutlined />
                    </Button>
                  </Col>
                </Row>
              ))}
              <Form.Item>
                <Button type='dashed' onClick={() => add()} block icon={<PlusOutlined />}>
                  {t('新增变量')}
                </Button>
              </Form.Item>
            </>
          )}
        </Form.List>
      </Form>
    </Modal>
  );
}