@ant-design/icons#CloseCircleOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#CloseCircleOutlined. 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: Participants.tsx    From nanolooker with MIT License 6 votes vote down vote up
Progress: React.FC<ProgressProps> = ({ isCompleted, hash }) => {
  const { theme } = React.useContext(PreferencesContext);

  return isCompleted || (hash && hash !== "0") ? (
    <Space size={6}>
      <CheckCircleTwoTone
        twoToneColor={
          theme === Theme.DARK ? TwoToneColors.RECEIVE_DARK : "#52c41a"
        }
      />
      {hash && hash !== "0" ? (
        <Link to={`/block/${hash}`}>
          <Button shape="circle" size="small" icon={<SearchOutlined />} />
        </Link>
      ) : null}
    </Space>
  ) : (
    <CloseCircleOutlined />
  );
}
Example #2
Source File: BillingSubscribed.tsx    From posthog-foss with MIT License 6 votes vote down vote up
function SubscriptionFailure(): JSX.Element {
    const { sessionId } = useValues(billingSubscribedLogic)
    return (
        <>
            <CloseCircleOutlined style={{ color: 'var(--danger)' }} className="title-icon" />
            <h2 className="subtitle">Something went wrong</h2>
            <p>
                We couldn't start your subscription. Please try again with a{' '}
                <b>different payment method or contact us</b> if the problem persists.
            </p>
            {sessionId && (
                /* Note we include PostHog Cloud specifically (app.posthog.com) in case a self-hosted user 
                ended up here for some reason. Should not happen as these should be processed by license.posthog.com */
                <Button
                    className="btn-bridge"
                    block
                    href={`https://app.posthog.com/billing/setup?session_id=${sessionId}`}
                >
                    Try again
                </Button>
            )}
            <div className="mt text-center">
                <Link to="/">
                    Go to PostHog <ArrowRightOutlined />
                </Link>
            </div>
        </>
    )
}
Example #3
Source File: index.tsx    From nanolooker with MIT License 6 votes vote down vote up
DeleteButton = (props: any) => {
  const { theme } = React.useContext(PreferencesContext);
  const [isHovered, setIsHovered] = React.useState(false);

  return (
    <div
      {...props}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
      style={{ marginLeft: "auto", cursor: "pointer" }}
    >
      {isHovered ? (
        theme === Theme.DARK ? (
          <CloseCircleFilled style={{ color: Colors.SEND_DARK }} />
        ) : (
          <CloseCircleTwoTone twoToneColor={TwoToneColors.SEND} />
        )
      ) : theme === Theme.DARK ? (
        <CloseCircleFilled style={{ color: "gray" }} />
      ) : (
        <CloseCircleOutlined style={{ color: "rgba(0, 0, 0, 0.45)" }} />
      )}
    </div>
  );
}
Example #4
Source File: index.tsx    From XFlow with MIT License 6 votes vote down vote up
AlgoIcon: React.FC<IProps> = props => {
  if (props.hide) {
    return null
  }
  switch (props.status) {
    case StatusEnum.PROCESSING:
      return <RedoOutlined spin style={{ color: '#c1cdf7', fontSize: '16px' }} />
    case StatusEnum.ERROR:
      return <CloseCircleOutlined style={{ color: '#ff4d4f', fontSize: '16px' }} />
    case StatusEnum.SUCCESS:
      return <CheckCircleOutlined style={{ color: '#39ca74cc', fontSize: '16px' }} />
    case StatusEnum.WARNING:
      return <ExclamationCircleOutlined style={{ color: '#faad14', fontSize: '16px' }} />
    case StatusEnum.DEFAULT:
      return <InfoCircleOutlined style={{ color: '#d9d9d9', fontSize: '16px' }} />
    default:
      return null
  }
}
Example #5
Source File: algo-node.tsx    From XFlow with MIT License 6 votes vote down vote up
AlgoIcon: React.FC<IProps> = props => {
  if (props.hide) {
    return null
  }
  switch (props.status) {
    case NsGraphStatusCommand.StatusEnum.PROCESSING:
      return <RedoOutlined spin style={{ color: '#c1cdf7', fontSize: '16px' }} />
    case NsGraphStatusCommand.StatusEnum.ERROR:
      return <CloseCircleOutlined style={{ color: '#ff4d4f', fontSize: '16px' }} />
    case NsGraphStatusCommand.StatusEnum.SUCCESS:
      return <CheckCircleOutlined style={{ color: '#39ca74cc', fontSize: '16px' }} />
    case NsGraphStatusCommand.StatusEnum.WARNING:
      return <ExclamationCircleOutlined style={{ color: '#faad14', fontSize: '16px' }} />
    case NsGraphStatusCommand.StatusEnum.DEFAULT:
      return <InfoCircleOutlined style={{ color: '#d9d9d9', fontSize: '16px' }} />
    default:
      return null
  }
}
Example #6
Source File: index.tsx    From imove with MIT License 6 votes vote down vote up
hijackMap: { [key: string]: any } = {
  log: {
    bgColor: '#272823',
    textColor: '#ffffff',
    borderColor: 'rgba(128, 128, 128, 0.35)',
    icon: <div className={styles.logIcon} />,
    originMethod: console.log,
  },
  info: {
    bgColor: '#272823',
    textColor: '#ffffff',
    borderColor: 'rgba(128, 128, 128, 0.35)',
    icon: <InfoCircleOutlined className={styles.logIcon} />,
    originMethod: console.info,
  },
  warn: {
    bgColor: 'rgb(51, 42, 0)',
    textColor: 'rgb(245, 211, 150)',
    borderColor: 'rgb(102, 85, 0)',
    icon: <WarningOutlined className={styles.logIcon} />,
    originMethod: console.warn,
  },
  debug: {
    bgColor: '#272823',
    textColor: 'rgb(77, 136, 255)',
    borderColor: 'rgba(128, 128, 128, 0.35)',
    icon: <BugOutlined className={styles.logIcon} />,
    originMethod: console.debug,
  },
  error: {
    bgColor: 'rgb(40, 0, 0)',
    textColor: 'rgb(254, 127, 127)',
    borderColor: 'rgb(91, 0, 0)',
    icon: <CloseCircleOutlined className={styles.logIcon} />,
    originMethod: console.error,
  },
}
Example #7
Source File: index.tsx    From covid_dashboard with MIT License 6 votes vote down vote up
render() {
        return <div className='search-box'>
            {this.state.items.length > 0 && <div className="search-result" style={{width: this.state.size === 'small' ? 300 : 450}}>
                <div className="search-control">
                    <div className="control-btn" onClick={() => this.setState({size: this.state.size === 'small' ? 'large' : 'small'})}>{this.state.size === 'small' ? <FullscreenOutlined/> : <FullscreenExitOutlined/>}</div>
                    <div className="control-btn" onClick={() => this.setState({items: []})}><CloseCircleOutlined/></div>
                </div>
                <div className="search-items" style={{maxHeight: this.state.size === 'small' ? 350 : 500}}>
                {this.state.items.map(item => {
                    return <div key={item._id} className="search-item" onClick={() => this.onClickEvent(item)}>
                        <div className="event-type" style={{background: getEventColor(item.type)}}>{_.capitalize(item.type)}</div>
                        <div className="event-time">{item.time}</div>
                        <div className="event-title">{item.title}</div>
                    </div>
                })}
                </div>
            </div>}
            <div className="search-input">
                <Input.Search placeholder={this.props.intl.formatMessage({id: "search.placeholder"})} onChange={(e) => this.search(e.target.value)} onSearch={(text) => this.search(text)} loading={this.state.loading}/><span className='close' onClick={() => this.props.onClose && this.props.onClose()}><CloseCircleOutlined/></span>
            </div>
        </div>
    }
Example #8
Source File: TeamMembers.tsx    From posthog-foss with MIT License 5 votes vote down vote up
function ActionsComponent(member: FusedTeamMemberType): JSX.Element | null {
    const { user } = useValues(userLogic)
    const { currentTeam } = useValues(teamLogic)
    const { removeMember } = useActions(teamMembersLogic)

    if (!user) {
        return null
    }

    function handleClick(): void {
        Modal.confirm({
            title: `${
                member.user.uuid == user?.uuid
                    ? 'Leave'
                    : `Remove ${member.user.first_name} (${member.user.email}) from`
            } project ${currentTeam?.name}?`,
            icon: <ExclamationCircleOutlined />,
            okText: member.user.uuid == user?.uuid ? 'Leave' : 'Remove',
            okType: 'danger',
            onOk() {
                removeMember({ member })
            },
        })
    }

    const allowDeletion =
        // You can leave, but only project admins can remove others
        ((currentTeam?.effective_membership_level &&
            currentTeam.effective_membership_level >= OrganizationMembershipLevel.Admin) ||
            member.user.uuid === user.uuid) &&
        // Only members without implicit access can leave or be removed
        member.organization_level < MINIMUM_IMPLICIT_ACCESS_LEVEL

    return allowDeletion ? (
        <a className="text-danger" onClick={handleClick} data-attr="delete-team-membership">
            {member.user.uuid !== user.uuid ? (
                <CloseCircleOutlined title="Remove from project" />
            ) : (
                <LogoutOutlined title="Leave project" />
            )}
        </a>
    ) : null
}
Example #9
Source File: index.tsx    From dashboard with Apache License 2.0 5 votes vote down vote up
EditableTag: React.FC<EditableTagProps> = (props) => {
  const { tags, setTags } = props;
  const [inputVisible, setInputVisible] = useState<boolean>(false);
  const [inputValue, setInputValue] = useState<string>('');

  return (
    <>
      <Space direction={'horizontal'} wrap={true} className={styles.tagList}>
        <Button
          icon={<PlusOutlined />}
          onClick={() => {
            setInputVisible(true);
          }}
        >
          添加
        </Button>

        {inputVisible && (
          <Input
            value={inputValue}
            onChange={(e) => setInputValue(e.currentTarget.value)}
            autoFocus={true}
            allowClear={true}
            placeholder="逗号分隔,回车保存"
            onBlur={() => {
              setInputValue('');
              setInputVisible(false);
            }}
            onPressEnter={(e) => {
              e.preventDefault();
              const params = inputValue
                .replace(',', ',')
                .split(',')
                .filter((val: any) => val);
              setTags(_.uniq<string>([...tags, ...params]));
              setInputValue('');
            }}
          />
        )}
        {tags?.map((tag) => (
          <Badge
            key={tag}
            className={styles.tagWrapper}
            count={
              <CloseCircleOutlined
                onClick={() => {
                  setTags(tags.filter((item) => tag !== item));
                }}
                style={{ color: 'rgb(199,199,199)' }}
              />
            }
          >
            <Tag className={styles.tagItem}>{tag}</Tag>
          </Badge>
        ))}
      </Space>
    </>
  );
}
Example #10
Source File: index.tsx    From visual-layout with MIT License 5 votes vote down vote up
Header: React.FC<{
  projectService: ProjectService;
  options: Options;
  setOptions: (options: Options) => void;
}> = ({ projectService, options, setOptions }) => {
  return (
    <div className={styles.header}>
      <div className={styles.pagesWarper}>
        <div className={styles.pages}>
          {Object.values(projectService.getPages()).map(({ name, id }) => {
            const style = {
              border: `1px solid ${
                id === projectService.currentId ? '#1890ff' : ''
              }`,
            };
            return (
              <div key={id} className={styles.page} style={style}>
                <span onClick={() => projectService.setPages(id)}>{name}</span>
                <Popconfirm
                  title="确定删除?"
                  onConfirm={() => projectService.delete(id)}
                  onCancel={() => {}}
                  okText="是"
                  cancelText="否"
                >
                  <div className={styles.close}>
                    <CloseCircleOutlined />
                  </div>
                </Popconfirm>
              </div>
            );
          })}
        </div>
        <div className={styles.new}>
          <Button
            onClick={() => {
              openNewBuildModal({
                projectService,
              });
            }}
          >
            新建
          </Button>
        </div>
      </div>
      <div className={styles.opr}>
        <Operation
          options={options}
          setOptions={setOptions}
          projectService={projectService}
        />
      </div>
    </div>
  );
}
Example #11
Source File: index.tsx    From jetlinks-ui-antd with MIT License 5 votes vote down vote up
GridLayout: React.FC<Props> = props => {
    const { layout, edit } = props;
    return (
        <>
            <ReactGridLayout
                onLayoutChange={(item: any) => {
                    // layoutChange(item)
                }}
                // cols={{ md: 12 }}
                // isResizable={edit}
                // isDraggable={edit}
                onDragStop={() => {
                    // setLayout([...layout])
                }}
                onResizeStop={() => {
                    // setLayout([...layout])
                }}
                className="layout"
                // layout={layout}
                rowHeight={30}
            >

                {layout.map((item: any) => (
                    <Card
                        style={{ overflow: "hidden" }}
                        key={item.i}
                        id={item.i}
                    >

                        <div style={{ position: 'absolute', right: 15, top: 5, }}>
                            <div style={{ float: 'right' }}>
                                <Fragment>
                                    {edit && (
                                        <>
                                            <Tooltip title="删除">
                                                <CloseCircleOutlined onClick={() => {
                                                    // removeCard(item)
                                                }} />
                                            </Tooltip>
                                            <Divider type="vertical" />
                                            <Tooltip title="编辑">
                                                <EditOutlined onClick={() => {
                                                    // setAddItem(true);
                                                    // setCurrent(item)
                                                }} />
                                            </Tooltip>
                                        </>)}
                                    {item.doReady &&
                                        <>
                                            <Divider type="vertical" />
                                            <Tooltip title="刷新">
                                                <SyncOutlined onClick={() => { item.doReady() }} />
                                            </Tooltip>

                                        </>}
                                </Fragment>
                            </div>
                        </div>
                        <GridCard
                            {...item}
                            productId={props.productId}
                            deviceId={props.target} />
                    </Card>))}


            </ReactGridLayout>
        </>
    )
}
Example #12
Source File: chartGroup.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
// 根据chartConfigModal配置的数据进行展示

export default function ChartGroup(props: Props) {
  const { t } = useTranslation();
  const {
    id,
    cluster,
    busiId,
    groupInfo,
    range,
    step,
    variableConfig,
    onAddChart,
    onUpdateChart,
    onCloneChart,
    onShareChart,
    onDelChartGroup,
    onDelChart,
    onUpdateChartGroup,
    onMoveUpChartGroup,
    onMoveDownChartGroup,
    moveUpEnable,
    moveDownEnable,
  } = props;
  const [chartConfigs, setChartConfigs] = useState<Chart[]>([]);
  const [Refs, setRefs] = useState<RefObject<any>[]>();
  const [layout, setLayout] = useState<Layouts>(layouts); // const [colItem, setColItem] = useState<number>(defColItem);
  const [mounted, setMounted] = useState<boolean>(false);
  useEffect(() => {
    init();
    Refs &&
      Refs.forEach((ref) => {
        const graphInstance = ref.current;
        graphInstance && graphInstance.refresh();
      });
  }, [groupInfo.updateTime, cluster]);

  const init = async () => {
    setMounted(false);
    getCharts(busiId, groupInfo.id).then(async (res) => {
      let charts = res.dat
        ? res.dat.map((item: { configs: string; id: any; weight: any; group_id: number }) => {
            let configs = item.configs ? JSON.parse(item.configs) : {};
            return { id: item.id, configs, weight: item.weight, group_id: item.group_id };
          })
        : [];
      let haveNewChart = false;
      const innerLayout = charts.map((item: { configs: { layout: { i: string; x?: number; y?: number; w: number; h: number } } }, index: string | number) => {
        if (item.configs.layout) {
          // 当Chart被删除后 layout中的i会中断,ResponsiveReactGridLayout会有问题
          item.configs.layout.i = '' + index;
          // 克隆图表后 layout 不具备 x/y 值,需要计算设置
          if (item.configs.layout.x === undefined && item.configs.layout.y === undefined) {
            haveNewChart = true;
            return getNewItemLayout(
              charts.slice(0, index).map(
                (item: {
                  configs: {
                    layout: any;
                  };
                }) => item.configs.layout,
              ),
              Number(index),
              {
                w: item.configs.layout.w,
                h: item.configs.layout.h,
              },
            );
          }
          return item.configs.layout;
        } else {
          haveNewChart = true;
          return getNewItemLayout(
            charts.slice(0, index).map(
              (item: {
                configs: {
                  layout: any;
                };
              }) => item.configs.layout,
            ),
            Number(index),
          );
        }
      });

      if (haveNewChart) {
        const { dat } = await getPerm(busiId, 'rw');
        dat &&
          updateCharts(
            busiId,
            charts.map((item, index) => {
              const { h, w, x, y, i } = innerLayout[index];
              item.configs.layout = { h, w, x, y, i };
              return item;
            }),
          );
      }

      const realLayout: Layouts = { lg: innerLayout, sm: innerLayout, md: innerLayout, xs: innerLayout, xxs: innerLayout };
      setLayout(realLayout);
      setChartConfigs(charts);
      setRefs(new Array(charts.length).fill(0).map((_) => React.createRef()));
      setMounted(true);
    });
  };

  const getNewItemLayout = function (curentLayouts: Array<Layout>, index: number, size?: { w: number; h: number }): Layout {
    const { w, h } = size || { w: unit, h: unit / 3 };
    const layoutArrayLayoutFillArray = new Array<Array<number>>();
    curentLayouts.forEach((layoutItem) => {
      if (layoutItem) {
        const { w, h, x, y } = layoutItem;
        for (let i = 0; i < h; i++) {
          if (typeof layoutArrayLayoutFillArray[i + y] === 'undefined') {
            layoutArrayLayoutFillArray[i + y] = new Array<number>(cols).fill(0);
          }

          for (let k = 0; k < w; k++) {
            layoutArrayLayoutFillArray[i + y][k + x] = 1;
          }
        }
      }
    });
    let nextLayoutX = -1;
    let nextLayoutY = -1; // 填充空行

    for (let i = 0; i < layoutArrayLayoutFillArray.length; i++) {
      if (typeof layoutArrayLayoutFillArray[i] === 'undefined') {
        layoutArrayLayoutFillArray[i] = new Array<number>(cols).fill(0);
      }
    }

    function isEmpty(i: number, j: number) {
      let flag = true;

      for (let x = i; x < i + w; x++) {
        for (let y = j; y < j + h; y++) {
          if (layoutArrayLayoutFillArray[x] && layoutArrayLayoutFillArray[x][y]) {
            flag = false;
          }
        }
      }

      return flag;
    }

    for (let i = 0; i < layoutArrayLayoutFillArray.length - 1; i++) {
      for (let j = 0; j <= cols - unit; j++) {
        if (isEmpty(i, j)) {
          nextLayoutY = i;
          nextLayoutX = j;
          break;
        }
      }
    }

    if (nextLayoutX === -1) {
      nextLayoutX = 0;
      nextLayoutY = layoutArrayLayoutFillArray.length;
    }

    return { w, h, x: nextLayoutX, y: nextLayoutY, i: '' + index };
  };

  const onLayoutChange = async (val: { h: any; w: any; x: any; y: any; i: any }[]) => {
    if (val.length === 0) return;
    let needUpdate = false;
    const { lg: lgLayout } = layout;

    for (var k = 0; k < val.length; k++) {
      const { h, w, x, y, i } = val[k];
      const { h: oldh, w: oldw, x: oldx, y: oldy, i: oldi } = lgLayout[k];
      if (h !== oldh || w !== oldw || x !== oldx || y !== oldy || i !== oldi) {
        needUpdate = true;
      }
    }
    if (!needUpdate) return;
    let currConfigs = chartConfigs.map((item, index) => {
      const { h, w, x, y, i } = val[index];
      item.configs.layout = { h, w, x, y, i };
      return item;
    });

    // setLayout({ lg: [...layout], sm: [...layout], md: [...layout], xs: [...layout], xxs: [...layout] });

    const { dat } = await getPerm(busiId, 'rw');
    dat && updateCharts(busiId, currConfigs);
  };

  const setArrange = async (colItem: number, w = cols / colItem, h = unit / 3) => {
    setMounted(false);
    let countX = 0;
    let countY = 0;
    const _lg: Layout[] = [];
    [...layout.lg].forEach((ele, index) => {
      let innerObj = { ...ele };

      if (index + 1 > colItem) {
        let c = (index + 1) / colItem;
        countY = Math.trunc(c) * h;
      }

      innerObj.w = w;
      innerObj.h = h;
      innerObj.x = countX;
      innerObj.y = countY;
      countX += innerObj.w;

      if ((index + 1) % colItem === 0) {
        countX = 0;
      }

      _lg.push(innerObj);
    });
    let currConfigs = chartConfigs.map((item, index) => {
      const { h, w, x, y, i } = _lg[index];
      item.configs.layout = { h, w, x, y, i };
      return item;
    });
    const { dat } = await getPerm(busiId, 'rw');
    dat && updateCharts(busiId, currConfigs);
    setLayout({ lg: [..._lg], sm: [..._lg], md: [..._lg], xs: [..._lg], xxs: [..._lg] });
    setChartConfigs(currConfigs);
    setMounted(true);
  };

  function handleMenuClick(e) {
    e.domEvent.stopPropagation();
    setArrange(Number(e.key));
  }

  function menu() {
    const { t } = useTranslation();
    let listArr: ReactElement[] = [];

    for (let i = 1; i <= defColItem; i++) {
      let item = (
        <Menu.Item key={i}>
          {i}
          {t('列')}
        </Menu.Item>
      );
      listArr.push(item);
    }

    return <Menu onClick={handleMenuClick}>{listArr}</Menu>;
  }

  const generateRightButton = () => {
    const { t } = useTranslation();
    return (
      <>
        <Button
          type='link'
          size='small'
          onClick={(event) => {
            event.stopPropagation();
            onAddChart(groupInfo.id);
          }}
        >
          {t('新增图表')}
        </Button>
        <Divider type='vertical' />
        <Dropdown overlay={menu()}>
          <Button
            type='link'
            size='small'
            onClick={(event) => {
              event.stopPropagation();
            }}
          >
            {t('一键规整')}
            <DownOutlined />
          </Button>
        </Dropdown>
        <Divider type='vertical' />
        <Button
          type='link'
          size='small'
          onClick={(event) => {
            event.stopPropagation();
            onUpdateChartGroup(groupInfo);
          }}
        >
          {t('修改')}
        </Button>
        <Divider type='vertical' />
        <Button
          type='link'
          size='small'
          disabled={!moveUpEnable}
          onClick={(event) => {
            event.stopPropagation();
            onMoveUpChartGroup(groupInfo);
          }}
        >
          {t('上移')}
        </Button>
        <Divider type='vertical' />
        <Button
          type='link'
          size='small'
          disabled={!moveDownEnable}
          onClick={(event) => {
            event.stopPropagation();
            onMoveDownChartGroup(groupInfo);
          }}
        >
          {t('下移')}
        </Button>
        <Divider type='vertical' />
        <Button
          type='link'
          size='small'
          onClick={(event) => {
            event.stopPropagation();
            confirm({
              title: `${t('是否删除分类')}:${groupInfo.name}`,
              onOk: async () => {
                onDelChartGroup(groupInfo.id);
              },

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

  const generateDOM = () => {
    return (
      chartConfigs &&
      chartConfigs.length > 0 &&
      chartConfigs.map((item, i) => {
        let { QL, name, legend, yplotline1, yplotline2, highLevelConfig, version } = item.configs;
        if (semver.valid(version)) {
          // 新版图表配置的版本使用语义化版本规范
          const { type } = item.configs as any;
          return (
            <div
              style={{
                border: '1px solid #e0dee2',
              }}
              key={String(i)}
            >
              <Renderer
                dashboardId={id}
                id={item.id}
                time={range}
                refreshFlag={props.refreshFlag}
                step={step}
                type={type}
                values={item.configs as any}
                variableConfig={variableConfig}
                onCloneClick={() => {
                  onCloneChart(groupInfo, item);
                }}
                onShareClick={() => {
                  onShareChart(groupInfo, item);
                }}
                onEditClick={() => {
                  onUpdateChart(groupInfo, item);
                }}
                onDeleteClick={() => {
                  confirm({
                    title: `${t('是否删除图表')}:${item.configs.name}`,
                    onOk: async () => {
                      onDelChart(groupInfo, item);
                    },
                  });
                }}
              />
            </div>
          );
        }
        const promqls = QL.map((item) =>
          variableConfig && variableConfig.var && variableConfig.var.length ? replaceExpressionVars(item.PromQL, variableConfig, variableConfig.var.length, id) : item.PromQL,
        );
        const legendTitleFormats = QL.map((item) => item.Legend);
        return (
          <div
            style={{
              border: '1px solid #e0dee2',
            }}
            key={String(i)}
          >
            <Graph
              ref={Refs![i]}
              highLevelConfig={highLevelConfig}
              data={{
                yAxis: {
                  plotLines: [
                    {
                      value: yplotline1 ? yplotline1 : undefined,
                      color: 'orange',
                    },
                    {
                      value: yplotline2 ? yplotline2 : undefined,
                      color: 'red',
                    },
                  ],
                },
                legend: legend,
                step,
                range,
                title: (
                  <Tooltip title={name}>
                    <span>{name}</span>
                  </Tooltip>
                ),
                promqls,
                legendTitleFormats,
              }}
              extraRender={(graph) => {
                return (
                  <>
                    <Button
                      type='link'
                      size='small'
                      onClick={(e) => {
                        e.preventDefault();
                        window.open(item.configs.link);
                      }}
                      disabled={!item.configs.link}
                    >
                      <LinkOutlined />
                    </Button>
                    <Button
                      type='link'
                      size='small'
                      onClick={(e) => {
                        e.preventDefault();
                        onUpdateChart(groupInfo, item);
                      }}
                    >
                      <EditOutlined />
                    </Button>

                    <Button
                      type='link'
                      size='small'
                      onClick={(e) => {
                        e.preventDefault();
                        confirm({
                          title: `${t('是否删除图表')}:${item.configs.name}`,
                          onOk: async () => {
                            onDelChart(groupInfo, item);
                          },

                          onCancel() {},
                        });
                      }}
                    >
                      <CloseCircleOutlined />
                    </Button>
                  </>
                );
              }}
            />
          </div>
        );
      })
    );
  };

  const renderCharts = useCallback(() => {
    const { t } = useTranslation();
    return (
      <div
        style={{
          width: '100%',
        }}
      >
        {chartConfigs && chartConfigs.length > 0 ? (
          <ResponsiveReactGridLayout
            cols={{ lg: cols, sm: cols, md: cols, xs: cols, xxs: cols }}
            layouts={layout}
            onLayoutChange={onLayoutChange}
            measureBeforeMount={false}
            useCSSTransforms={false}
            preventCollision={false}
            isBounded={true}
            draggableHandle='.graph-header'
          >
            {generateDOM()}
          </ResponsiveReactGridLayout>
        ) : (
          <p className='empty-group-holder'>Now it is empty</p>
        )}
      </div>
    );
  }, [mounted, groupInfo.updateTime, range, variableConfig, step]);
  return (
    <div className='n9e-dashboard-group'>
      <Collapse defaultActiveKey={['0']}>
        <Panel header={<span className='panel-title'>{groupInfo.name}</span>} key='0' extra={generateRightButton()}>
          {renderCharts()}
        </Panel>
      </Collapse>
    </div>
  );
}
Example #13
Source File: Stats.tsx    From leek with Apache License 2.0 4 votes vote down vote up
function Stats(stats: any) {
  return [
    {
      number: stats.SEEN_TASKS,
      text: "Total Tasks",
      icon: <UnorderedListOutlined />,
      tooltip: "Seen tasks names",
    },
    {
      number: stats.SEEN_WORKERS,
      text: "Total Workers",
      icon: <RobotFilled />,
      tooltip: "The total offline/online and beat workers",
    },
    {
      number: stats.PROCESSED_EVENTS,
      text: "Events Processed",
      icon: <ThunderboltOutlined />,
      tooltip: "The total processed events",
    },
    {
      number: stats.PROCESSED_TASKS,
      text: "Tasks Processed",
      icon: <SyncOutlined />,
      tooltip: "The total processed tasks",
    },
    {
      number: stats.QUEUED,
      text: "Tasks Queued",
      icon: <EllipsisOutlined />,
      tooltip: "The total tasks in the queues",
    },
    {
      number: stats.RETRY,
      text: "To Retry",
      icon: <RetweetOutlined style={{ color: STATES_COLORS.RETRY }} />,
      tooltip: "Tasks that are failed and waiting for retry",
    },
    {
      number: stats.RECEIVED,
      text: "Received",
      icon: <SendOutlined style={{ color: STATES_COLORS.RECEIVED }} />,
      tooltip: "Tasks were received by a worker. but not yet started",
    },
    {
      number: stats.STARTED,
      text: "Started",
      icon: <LoadingOutlined style={{ color: STATES_COLORS.STARTED }} />,
      tooltip:
        "Tasks that were started by a worker and still active, set (task_track_started) to True on worker level to report started tasks",
    },
    {
      number: stats.SUCCEEDED,
      text: "Succeeded",
      icon: <CheckCircleOutlined style={{ color: STATES_COLORS.SUCCEEDED }} />,
      tooltip: "Tasks that were succeeded",
    },
    {
      number: stats.RECOVERED,
      text: "Recovered",
      icon: <IssuesCloseOutlined style={{ color: STATES_COLORS.RECOVERED }} />,
      tooltip: "Tasks that were succeeded after retries.",
    },
    {
      number: stats.FAILED,
      text: "Failed",
      icon: <WarningOutlined style={{ color: STATES_COLORS.FAILED }} />,
      tooltip: "Tasks that were failed",
    },
    {
      number: stats.CRITICAL,
      text: "Critical",
      icon: <CloseCircleOutlined style={{ color: STATES_COLORS.CRITICAL }} />,
      tooltip: "Tasks that were failed after max retries.",
    },
    {
      number: stats.REJECTED,
      text: "Rejected",
      icon: <RollbackOutlined style={{ color: STATES_COLORS.REJECTED }} />,
      tooltip:
        "Tasks that were rejected by workers and requeued, or moved to a dead letter queue",
    },
    {
      number: stats.REVOKED,
      text: "Revoked",
      icon: <StopOutlined style={{ color: STATES_COLORS.REVOKED }} />,
      tooltip: "Tasks that were revoked by workers, but still in the queue.",
    },
  ];
}
Example #14
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 dispatch = useDispatch();
  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<shieldItem>>([]);
  const [currentShieldData, setCurrentShieldData] = useState<Array<shieldItem>>([]);
  const [loading, setLoading] = useState<boolean>(false);

  const columns: ColumnsType = [
    {
      title: t('集群'),
      dataIndex: 'cluster',
      render: (data) => {
        return <div>{data}</div>;
      },
    },
    {
      title: t('标签'),
      dataIndex: 'tags',
      render: (text: any) => {
        return (
          <>
            {text
              ? text.map((tag, index) => {
                  return tag ? (
                    // <ColorTag text={`${tag.key} ${tag.func} ${tag.func === 'in' ? tag.value.split(' ').join(', ') : tag.value}`} key={index}>
                    // </ColorTag>
                    <div key={index} style={{ lineHeight: '16px' }}>{`${tag.key} ${tag.func} ${tag.func === 'in' ? tag.value.split(' ').join(', ') : tag.value}`}</div>
                  ) : null;
                })
              : ''}
          </>
        );
      },
    },
    {
      title: t('屏蔽原因'),
      dataIndex: 'cause',
      render: (text: string, record: shieldItem) => {
        return (
          <>
            <Tooltip placement='topLeft' title={text}>
              <div
                style={{
                  whiteSpace: 'nowrap',
                  textOverflow: 'ellipsis',
                  overflow: 'hidden',
                  lineHeight: '16px',
                }}
              >
                {text}
              </div>
            </Tooltip>
            by {record.create_by}
          </>
        );
      },
    },
    {
      title: t('屏蔽时间'),
      dataIndex: 'btime',
      render: (text: number, record: shieldItem) => {
        return (
          <div className='shield-time'>
            <div>
              {t('开始:')}
              {dayjs(record?.btime * 1000).format('YYYY-MM-DD HH:mm:ss')}
            </div>
            <div>
              {t('结束:')}
              {dayjs(record?.etime * 1000).format('YYYY-MM-DD HH:mm:ss')}
            </div>
          </div>
        );
      },
    },
    // {
    //   title: t('创建人'),
    //   ellipsis: true,
    //   dataIndex: 'create_by',
    // },
    {
      title: t('操作'),
      width: '98px',
      dataIndex: 'operation',
      render: (text: undefined, record: shieldItem) => {
        return (
          <>
            <div className='table-operator-area'>
              <div
                className='table-operator-area-normal'
                style={{
                  cursor: 'pointer',
                  display: 'inline-block',
                }}
                onClick={() => {
                  dispatch({
                    type: 'shield/setCurShieldData',
                    data: record,
                  });
                  curBusiItem?.id && history.push(`/alert-mutes/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) => {
    deleteShields({ 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: shieldItem) => {
      const tagFind = item.tags.find((tag) => {
        return tag.key.indexOf(query) > -1 || tag.value.indexOf(query) > -1 || tag.func.indexOf(query) > -1;
      });
      return (item.cause.indexOf(query) > -1 || !!tagFind) && ((clusters && clusters?.indexOf(item.cluster) > -1) || clusters?.length === 0);
    });
    setCurrentShieldData(res || []);
  };

  const getList = async () => {
    if (curBusiItem.id) {
      setLoading(true);
      const { success, dat } = await getShieldList({ 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={<CloseCircleOutlined />} 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-mutes/add');
                  }}
                >
                  {t('新增屏蔽规则')}
                </Button>
              </div>
            </div>
            <Table
              rowKey='id'
              // sticky
              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 #15
Source File: form.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
ContactWayForm: React.FC<ContactWayFormProps> = (props) => {
  const {staffs, tagGroups, initialValues, mode, ...rest} = props;
  const [staffSelectionVisible, setStaffSelectionVisible] = useState(false);
  const [selectedStaffs, setSelectedStaffs] = useState<StaffOption[]>([]);
  const [backupStaffSelectionVisible, setBackupStaffSelectionVisible] = useState(false);
  const [backupSelectedStaffs, setBackupSelectedStaffs] = useState<StaffOption[]>([]);
  const [blockNicknames, setBlockNicknames] = useState<string[]>([]);
  const [isFetchDone, setIsFetchDone] = useState(false);
  const [autoReply, setAutoReply] = useState<WelcomeMsg>({text: ''});
  const formRef = useRef<FormInstance>();
  const backupSelectRef = useRef<HTMLInputElement>(null);
  const staffSelectRef = useRef<HTMLInputElement>(null);
  const addScheduleRef = useRef<HTMLInputElement>(null);
  const [currentItem, setCurrentItem] = useState<ContactWayItem>();

  const itemDataToFormValues = (item: ContactWayItem | any): CreateContactWayParam => {
    const values: CreateContactWayParam = {
      daily_add_customer_limit: 0,
      ...item,
      id: props.itemID,
    };
    if (item.staffs) {
      setSelectedStaffs(
        item.staffs.map((staff: any) => {
          return {
            key: staff.ext_staff_id,
            label: staff.id,
            value: staff.ext_staff_id,
            ...staff,
            ext_id: staff.ext_staff_id,
          };
        }),
      );
    }

    if (item.auto_reply) {
      setAutoReply(item.auto_reply);
    }

    if (item.nickname_block_list) {
      setBlockNicknames(item.nickname_block_list);
    }

    if (item.backup_staffs) {
      setBackupSelectedStaffs(
        item.backup_staffs.map((staff: any) => {
          return {
            key: staff.ext_staff_id,
            label: staff.id,
            value: staff.ext_staff_id,
            ...staff,
            ext_id: staff.ext_staff_id,
          };
        }),
      );
    }

    const invertWeekdaysEnum = _.invert(WeekdaysEnum);

    if (item.schedules) {
      values.schedules = item.schedules.map((schedule: any) => {
        return {
          ...schedule,
          ext_staff_ids: schedule?.staffs?.map((staff: any) => staff.ext_staff_id),
          weekdays: schedule?.weekdays?.map((day: string) => invertWeekdaysEnum[day]),
          start_time: moment(schedule?.start_time, TimeLayout),
          end_time: moment(schedule?.end_time, TimeLayout),
        };
      });
    }

    // 将内置的boolean转为后端约定的数字
    Object.keys(values).forEach((key) => {
      const t = typeof values[key];
      if (
        !['schedule_enable', 'auto_skip_verify_enable'].includes(key) &&
        t === 'number' &&
        key.includes('_enable') &&
        [1, 2].includes(values[key])
      ) {
        values[key] = values[key] === True;
      }
    });

    if (!values.customer_tag_ext_ids) {
      values.customer_tag_ext_ids = [];
    }

    values.skip_verify_start_time = moment(values.skip_verify_start_time, TimeLayout);
    values.skip_verify_end_time = moment(values.skip_verify_end_time, TimeLayout);

    return values;
  };

  useEffect(() => {
    if (['edit', 'copy'].includes(mode) && props?.itemID) {
      const hide = message.loading('加载数据中');
      GetDetail(props?.itemID).then((res) => {
        hide();
        if (res.code === 0) {
          setCurrentItem(res.data);
          formRef.current?.setFieldsValue(itemDataToFormValues(res.data));
          setIsFetchDone(true);
          setAutoReply(res.data.auto_reply);
        } else {
          message.error(res.message);
        }
      });
    }
  }, [mode, props?.itemID]);

  // 校验参数
  const checkForm = (params: any): boolean => {
    if (backupSelectedStaffs.length === 0) {
      message.warning('请添加备份员工');
      backupSelectRef?.current?.focus();
      return false;
    }

    if (params.schedule_enable === Disable) {
      if (selectedStaffs.length === 0) {
        message.warning('请绑定员工');
        staffSelectRef?.current?.focus();
        return false;
      }
    }

    if (params.schedule_enable === Enable) {
      if (params.schedules.length === 0) {
        message.warning('请添加工作周期');
        addScheduleRef?.current?.focus();
        return false;
      }
    }

    return true;
  };

  // 格式化参数
  const formatParams = (origin: any): CreateContactWayParam => {
    const out = {...origin, id: props.itemID};
    // 将内置的boolean转为后端约定的数字
    Object.keys(out).forEach((key) => {
      const t = typeof out[key];
      if (t === 'boolean') {
        out[key] = out[key] ? True : False;
      }
    });

    const refStaffMap = _.keyBy(currentItem?.staffs, 'ext_staff_id');
    out.staffs = selectedStaffs.map((item): StaffParam => {
      return {
        id: refStaffMap[item.ext_id]?.id || '',
        ext_staff_id: item.ext_id,
        daily_add_customer_limit: out.daily_add_customer_limit ? out.daily_add_customer_limit : 0,
      };
    });

    const refBackupStaffMap = _.keyBy(currentItem?.backup_staffs, 'ext_staff_id');
    out.backup_staffs = backupSelectedStaffs.map((item): StaffParam => {
      return {
        id: refBackupStaffMap[item.ext_id]?.id || '',
        ext_staff_id: item.ext_id,
        daily_add_customer_limit: out.daily_add_customer_limit ? out.daily_add_customer_limit : 0,
      };
    });


    if (out.nickname_block_enable === True) {
      out.nickname_block_list = blockNicknames;
    }

    if (out.skip_verify_start_time) {
      out.skip_verify_start_time = moment(out.skip_verify_start_time, 'HH:mm')
        .format('HH:mm:ss')
        .toString();
    }

    if (out.skip_verify_end_time) {
      out.skip_verify_end_time = moment(out.skip_verify_end_time, 'HH:mm')
        .format('HH:mm:ss')
        .toString();
    }


    if (out.schedules) {
      out.schedules = out.schedules.map((schedule: any, index: number) => {
        // @ts-ignore
        const refScheduleStaffMap = _.keyBy(currentItem?.schedules[index]?.staffs, 'ext_staff_id');
        return {
          ...schedule,
          end_time: moment(schedule.end_time, DateTimeLayout).format('HH:mm:ss').toString(),
          start_time: moment(schedule.start_time, DateTimeLayout).format('HH:mm:ss').toString(),
          staffs: schedule.ext_staff_ids.map((ext_id: string) => {
            return {
              id: refScheduleStaffMap[ext_id]?.id ? refScheduleStaffMap[ext_id]?.id : '',
              daily_add_customer_limit: 0,
              ext_staff_id: ext_id,
            };
          }),
          weekdays: schedule.weekdays.map((day: string) => {
            return WeekdaysEnum[day];
          }),
        };
      });
    }

    if (autoReply) {
      out.auto_reply = autoReply;
    }

    return out;
  };

  return (
    <>
      <ProForm<ContactWayItem>
        {...rest}
        layout={'horizontal'}
        labelCol={{
          md: 4,
        }}
        className={styles.content}
        formRef={formRef}
        onFinish={async (values: any) => {
          const params = formatParams(values);
          if (!checkForm(params)) {
            return false;
          }
          if (props.onFinish) {
            return props.onFinish(params);
          }
          return false;
        }}
        initialValues={{...defaultValues, ...initialValues}}
      >
        <h3>基础信息</h3>
        <Divider/>

        <div className={styles.section}>
          <ProFormText
            labelAlign={'right'}
            width={'md'}
            name='name'
            label='渠道码名称'
            placeholder='请输入名称'
            rules={[
              {
                required: true,
                message: '请输入名称',
              },
            ]}
          />

          <ProFormSelect
            width={'md'}
            name='group_id'
            label='渠道码分组'
            placeholder='请选择分组'
            request={async () => {
              const res = await QueryGroup();
              if (res.data) {
                const items = res.data.items as ContactWayGroupItem[];
                return items.map((item) => {
                  return {label: item.name, value: item.id};
                });
              }
              return [];
            }}
            rules={[
              {
                required: true,
                message: '请选择分组',
              },
            ]}
          />

          <Form.Item
            label={<span className={'form-item-required'}>备份员工</span>}
            tooltip={'此渠道码没有活跃员工时,使用此处配置的员工兜底'}
          >
            <Button
              ref={backupSelectRef}
              icon={<PlusOutlined/>}
              onClick={() => setBackupStaffSelectionVisible(true)}
            >
              添加备用员工
            </Button>
          </Form.Item>

          <Space style={{marginTop: -12, marginBottom: 24, marginLeft: 20}}>
            {backupSelectedStaffs.map((item) => (
              <div key={item.id} className={'staff-item'}>
                <Badge
                  count={
                    <CloseCircleOutlined
                      onClick={() => {
                        setBackupSelectedStaffs(
                          backupSelectedStaffs.filter((staff) => staff.id !== item.id),
                        );
                      }}
                      style={{color: 'rgb(199,199,199)'}}
                    />
                  }
                >
                  <div className={'container'}>
                    <img alt={item.name} className={'avatar'} src={item.avatar_url}/>
                    <span className={'text'}>{item.name}</span>
                  </div>
                </Badge>
              </div>
            ))}
          </Space>

          <ProFormRadio.Group
            name='schedule_enable'
            label='绑定员工'
            tooltip={'用户扫描此二维码会添加这里配置的员工'}
            options={[
              {
                label: '全天在线',
                value: Disable,
              },
              {
                label: '自动上下线',
                value: Enable,
              },
            ]}
            rules={[
              {
                required: true,
                message: '请绑定员工',
              },
            ]}
          />
          <ProFormDependency name={['schedule_enable']}>
            {({schedule_enable}) => {
              // 全天在线
              if (schedule_enable === Disable) {
                return (
                  <div style={{marginLeft: 20}}>
                    <Space className={styles.formItem} style={{marginTop: -16, marginLeft: 12}}>
                      <Button
                        ref={staffSelectRef}
                        icon={<PlusOutlined/>}
                        onClick={() => setStaffSelectionVisible(true)}
                      >
                        添加员工
                      </Button>
                      <Text type={'secondary'}>
                        同一个二维码可绑定多个员工,客户扫码后随机分配一名员工进行接待
                      </Text>
                    </Space>

                    <Space className={styles.formItem} style={{marginTop: -16, marginLeft: 12}}>
                      {selectedStaffs.map((item) => (
                        <div key={item.id} className={'staff-item'}>
                          <Badge
                            count={
                              <CloseCircleOutlined
                                onClick={() => {
                                  setSelectedStaffs(
                                    selectedStaffs.filter((staff) => staff.id !== item.id),
                                  );
                                }}
                                style={{color: 'rgb(199,199,199)'}}
                              />
                            }
                          >
                            <div className={'container'}>
                              <img alt={item.name} className={'avatar'} src={item.avatar_url}/>
                              <span className={'text'}>{item.name}</span>
                            </div>
                          </Badge>
                        </div>
                      ))}
                    </Space>
                  </div>
                );
              }

              // 自动上下线
              return (
                <div className={styles.scheduleList} style={{marginLeft: 36}}>
                  <Alert
                    className={styles.tips}
                    type='info'
                    message={
                      '自动上下线:1、可用于员工早晚班等不同上班时间段使用;2、员工在非线上时间将不再接待新客户。'
                    }
                  />
                  <ProFormList
                    name='schedules'
                    creatorButtonProps={{
                      type: 'default',
                      style: {width: '160px'},
                      position: 'bottom',
                      creatorButtonText: '添加其他工作周期',
                    }}
                    creatorRecord={{
                      ext_staff_ids: [],
                    }}
                  >
                    <ProCard className={styles.scheduleItem} ref={addScheduleRef}>
                      <ProForm.Item name={'id'} noStyle={true}>
                        <input type={'hidden'}/>
                      </ProForm.Item>
                      <Row>
                        <label className={styles.label}>选择员工:</label>
                        <ProForm.Item name={'ext_staff_ids'} style={{width: 468}}>
                          <StaffTreeSelect options={staffs} maxTagCount={4}/>
                        </ProForm.Item>
                      </Row>
                      <Row>
                        <label className={styles.label}>工作日:</label>
                        <ProFormSelect
                          mode='multiple'
                          name='weekdays'
                          width={468}
                          valueEnum={WeekdaysEnum}
                          placeholder='请选择工作日'
                          rules={[{required: true, message: '请选择工作日'}]}
                        />
                      </Row>
                      <Row>
                        <label className={styles.label}>工作时间:</label>
                        <ProFormTimePicker
                          fieldProps={{
                            format: TimeLayout,
                          }}
                          name='start_time'
                          placeholder='开始时间'
                          rules={[{required: true, message: '请选择开始时间'}]}
                        />
                        <Space style={{marginLeft: 6}}> </Space>
                        <ProFormTimePicker
                          fieldProps={{
                            format: TimeLayout,
                          }}
                          name='end_time'
                          placeholder='结束时间'
                          rules={[{required: true, message: '请选择结束时间'}]}
                        />
                      </Row>
                    </ProCard>
                  </ProFormList>
                </div>
              );
            }}
          </ProFormDependency>

          <ProFormSwitch
            label={'员工添加上限'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='daily_add_customer_limit_enable'
            tooltip={'开启后员工可在侧边栏修改个人上下线状态'}
          />

          <ProFormDependency name={['daily_add_customer_limit_enable']}>
            {({daily_add_customer_limit_enable}) => {
              if (daily_add_customer_limit_enable) {
                return (
                  <div className={'sub-from-item'}>
                    <ProFormDigit
                      label={
                        <Tooltip title='员工添加上限:员工从该渠道添加的客户达到每日上限后,将自动下线当日不再接待该渠道新客户'>
                          每日最多添加
                        </Tooltip>
                      }
                      width={'md'}
                      name='daily_add_customer_limit'
                      min={0}
                      max={5000}
                    />
                  </div>
                );
              }
              return <></>;
            }}
          </ProFormDependency>

          <ProFormSwitch
            label={'员工自行上下线'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='staff_control_enable'
            tooltip={'开启后员工可在侧边栏修改个人上下线状态'}
          />

          <ProFormSwitch
            label={'客户标签:'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='auto_tag_enable'
            tooltip={
              <>
                通过此渠道码添加的客户,将自动打上此处配置的标签。您还可以转到
                <Link to={'/staff-admin/customer-management/customer-tag'}> 管理客户标签</Link>
              </>
            }
          />

          <ProFormDependency name={['auto_tag_enable']}>
            {({auto_tag_enable}) => {
              if (auto_tag_enable) {
                return (
                  <div className={'sub-from-item'}>
                    <ProForm.Item
                      name={'customer_tag_ext_ids'}
                      label={"客户标签"}
                      rules={[{required: true, message: '请选择客户标签'}]}
                    >
                      <CustomerTagSelect isEditable={true} allTagGroups={tagGroups} maxTagCount={6}
                                         style={{maxWidth: 468}}/>
                    </ProForm.Item>
                  </div>
                );
              }
              return '';
            }}
          </ProFormDependency>

          <ProFormSwitch
            label={'客户备注:'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='customer_remark_enable'
            tooltip={<>客户备注将自动附加在客户昵称之后,方便员工查看</>}
          />

          <ProFormDependency name={['customer_remark_enable']}>
            {({customer_remark_enable}) => {
              if (customer_remark_enable) {
                return (
                  <div className={'sub-from-item'}>
                    <ProFormText
                      width={'md'}
                      name='customer_remark'
                      label='客户备注'
                      placeholder='客户备注'
                      fieldProps={{
                        prefix: <span style={{color: '#666666'}}>客户昵称-</span>,
                      }}
                      rules={[{required: true, message: '请填写客户备注'}]}
                    />
                  </div>
                );
              }
              return '';
            }}
          </ProFormDependency>

          <ProFormSwitch
            label={'客户描述:'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='customer_desc_enable'
            tooltip={'开启后可为客户添加描述,将在客户画像里展示'}
          />
          <ProFormDependency name={['customer_desc_enable']}>
            {({customer_desc_enable}) => {
              if (customer_desc_enable) {
                return (
                  <div className={'sub-from-item'}>
                    <ProFormText
                      width={'md'}
                      name='customer_desc'
                      label='客户描述'
                      placeholder='客户描述'
                      rules={[{required: true, message: '请填写客户描述'}]}
                    />
                  </div>
                );
              }
              return '';
            }}
          </ProFormDependency>

          <h3 style={{marginTop: 30}}>设置欢迎语</h3>
          <Divider/>

          <Alert
            showIcon={true}
            style={{maxWidth: '680px', marginBottom: 20}}
            type='info'
            message={
              '因企业微信限制,若使用成员已在「企业微信后台」配置了欢迎语,这里的欢迎语将不会生效'
            }
          />

          <ProFormRadio.Group
            // 欢迎语类型:1,渠道欢迎语;2, 渠道默认欢迎语;3,不送欢迎语;
            name='auto_reply_type'
            label='设置欢迎语'
            tooltip={'客户添加企业之后,将自动发送此消息给客户'}
            options={[
              {
                label: '渠道欢迎语',
                value: 1,
              },
              {
                label: '渠道默认欢迎语',
                value: 2,
              },
              {
                label: '不送欢迎语',
                value: 3,
              },
            ]}
            rules={[
              {
                required: true,
                message: '请设置欢迎语',
              },
            ]}
          />

          <ProFormDependency name={['auto_reply_type']}>
            {({auto_reply_type}) => {
              if (auto_reply_type === 1) {
                // 渠道欢迎语
                return (
                  <div className={'sub-from-item'}>
                    <Form.Item
                      label={<span className={'form-item-required'}>欢迎语</span>}
                    >
                      <AutoReply welcomeMsg={autoReply} isFetchDone={isFetchDone} setWelcomeMsg={setAutoReply}/>
                    </Form.Item>
                  </div>
                );
              }
              if (auto_reply_type === 2) {
                // 渠道默认欢迎语
                return (
                  <div className={'sub-from-item'} style={{marginBottom: 32, marginLeft: 26}}>
                    <Text type={'secondary'}>
                      将发送成员已设置的欢迎语,若所选成员未设置欢迎语,则不会发送欢迎语
                    </Text>
                  </div>
                );
              }
              if (auto_reply_type === 3) {
                // 不送欢迎语
                return <div className={'sub-from-item'}></div>;
              }
              return '';
            }}
          </ProFormDependency>

          <ProFormSwitch
            label={'欢迎语屏蔽:'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='nickname_block_enable'
            tooltip={<>开启后,客户昵称中包含设定的关键词的客户不会收到欢迎语</>}
          />

          <ProFormDependency name={['nickname_block_enable']}>
            {({nickname_block_enable}) => {
              if (nickname_block_enable) {
                return (
                  <div className={'sub-from-item'} style={{marginLeft: 26, marginTop:-20}}>
                    <EditableTag tags={blockNicknames} setTags={setBlockNicknames}/>
                  </div>
                );
              }
              return '';
            }}
          </ProFormDependency>

          <h3 style={{marginTop: 30}}>功能设置</h3>
          <Divider/>

          <ProFormSwitch
            label={'自动通过好友:'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='skip_verify'
            tooltip={<>开启后,客户添加该企业微信时,无需好友验证,将会自动添加成功</>}
          />

          <ProFormDependency name={['skip_verify']}>
            {({skip_verify}) => {
              if (skip_verify) {
                return (
                  <div className={'sub-from-item'}>
                    <ProFormRadio.Group
                      // 欢迎语类型:1,渠道欢迎语;2, 渠道默认欢迎语;3,不送欢迎语;
                      name='auto_skip_verify_enable'
                      label='自动通过时段'
                      options={[
                        {
                          label: '全天开启',
                          value: 2,
                        },
                        {
                          label: '选择时间段',
                          value: 1,
                        },
                      ]}
                      rules={[
                        {
                          required: true,
                          message: '请选择时段控制',
                        },
                      ]}
                    />

                    <ProFormDependency name={['auto_skip_verify_enable']}>
                      {({auto_skip_verify_enable}) => {
                        if (auto_skip_verify_enable === 1) {
                          return (
                            <Row className={'sub-from-item'}>
                              <span style={{marginTop: 6, marginLeft: 36}}>工作时间:</span>
                              <ProFormTimePicker
                                width={'xs'}
                                name='skip_verify_start_time'
                                fieldProps={{
                                  format: TimeLayout,
                                }}
                                placeholder='开始时间'
                                rules={[{required: true, message: '请选择开始时间'}]}
                              />
                              <Space style={{marginLeft: 6}}> </Space>
                              <ProFormTimePicker
                                width={'xs'}
                                name='skip_verify_end_time'
                                fieldProps={{
                                  format: TimeLayout,
                                }}
                                placeholder='结束时间'
                                rules={[{required: true, message: '请选择结束时间'}]}
                              />
                            </Row>
                          );
                        }
                        return '';
                      }}
                    </ProFormDependency>
                  </div>
                );
              }
              return '';
            }}
          </ProFormDependency>
        </div>
      </ProForm>

      <StaffTreeSelectionModal
        visible={staffSelectionVisible}
        setVisible={setStaffSelectionVisible}
        defaultCheckedStaffs={selectedStaffs}
        onFinish={(values) => {
          setSelectedStaffs(values);
        }}
        allStaffs={staffs}
      />

      <StaffTreeSelectionModal
        visible={backupStaffSelectionVisible}
        setVisible={setBackupStaffSelectionVisible}
        defaultCheckedStaffs={backupSelectedStaffs}
        onFinish={(values: React.SetStateAction<any[]>) => {
          setBackupSelectedStaffs(values);
        }}
        allStaffs={staffs}
      />
    </>
  );
}
Example #16
Source File: form.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
CustomerMassMsgForm: React.FC<CustomerMassMsgFormProps> = (props) => {
  const {initialValues} = props;
  const staffSelectRef = useRef<HTMLInputElement>(null);
  const [selectedStaffs, setSelectedStaffs] = useState<StaffOption[]>([]);
  const [staffSelectionVisible, setStaffSelectionVisible] = useState(false);
  const [isFetchDone, setIsFetchDone] = useState(false);
  const [massMsg, setMassMsg] = useState<Msg>({text: ''});
  const [allTagGroups, setAllTagGroups] = useState<CustomerTagGroupItem[]>([]);
  const [allGroupChats, setAllGroupChats] = useState<GroupChatOption[]>([]);
  const [tagLogicalCondition, setTagLogicalCondition] = useState<'and' | 'or' | 'none'>('or');
  const [allStaffs, setAllStaffs] = useState<StaffOption[]>([]);
  const [staffMap, setStaffMap] = useState<Dictionary<StaffOption>>();

  useEffect(() => {
    QuerySimpleStaffs({page_size: 5000}).then((res) => {
      if (res.code === 0) {
        setAllStaffs(res?.data?.items || []);
        setStaffMap(_.keyBy<any>(res?.data?.items, 'ext_id'));
      } else {
        message.error(res.message);
      }
    });
  }, []);


  useEffect(() => {
    QueryCustomerTagGroups({page_size: 5000}).then((res) => {
      if (res.code === 0) {
        setAllTagGroups(res?.data?.items);
      } else {
        message.error(res.message);
      }
    });
  }, []);

  useEffect(() => {
    QueryGroupChatMainInfo({page_size: 5000}).then((res: CommonResp) => {
      if (res.code === 0) {
        setAllGroupChats(
          res.data?.items?.map((item: GroupChatMainInfoItem) => {
            return {
              key: item.ext_chat_id,
              label: item.name,
              value: item.ext_chat_id,
              ...item,
            };
          }).filter((item: { name: any; }) => item.name),
        );
      } else {
        message.error(res.message);
      }
    });
  }, []);


  const itemDataToFormValues = (item: CustomerMassMsgItem | any): CreateCustomerMassMsgParam => {
    let values: CreateCustomerMassMsgParam = {
      ...item,
      id: item.id,
      ext_staff_ids: item?.staffs?.map((staff: any) => staff.ext_id),
    };

    if (item?.ext_staff_ids) {
      setSelectedStaffs(
        item?.ext_staff_ids.map((ext_staff_id: string) => {
          let staffInfo = {};
          if (staffMap && staffMap[ext_staff_id]) {
            staffInfo = staffMap[ext_staff_id]
          }
          return {
            ...staffInfo,
            key: ext_staff_id,
            value: ext_staff_id,
          };
        }),
      );
    }

    if (item.msg) {
      setMassMsg(item.msg);
    }

    values = {
      ...values, ...{
        gender: item?.ext_customer_filter?.gender,
        date_range: [item?.ext_customer_filter?.start_time, item?.ext_customer_filter?.end_time],
        ext_group_chat_ids: item?.ext_customer_filter?.ext_group_chat_ids,
        ext_department_ids: item?.ext_customer_filter?.ext_department_ids,
        ext_tag_ids: item?.ext_customer_filter?.ext_tag_ids,
        tag_logical_condition: item?.ext_customer_filter?.tag_logical_condition,
        exclude_ext_tag_ids: item?.ext_customer_filter?.exclude_ext_tag_ids,
      }
    };

    return values;
  };

  useEffect(() => {
    if (initialValues?.id) {
      props?.formRef?.current?.setFieldsValue(itemDataToFormValues(initialValues));
      setIsFetchDone(true);
    }
  }, [initialValues, staffMap]);

  const formatParams = (values: any) => {
    const params: CreateCustomerMassMsgParam = {
      id: initialValues?.id || '',
      chat_type: 'single',
      ext_customer_filter_enable: values?.ext_customer_filter_enable,
      ext_department_ids: [],
      ext_staff_ids: selectedStaffs.map((staff) => staff.ext_id) || [],
      msg: massMsg,
      send_at: values?.send_at,
      send_type: values?.send_type,
    };
    if (params.ext_customer_filter_enable === Enable) {
      params.ext_customer_filter = {
        gender: values?.gender,
        start_time: values?.da,
        end_time: values?.end_time,
        ext_group_chat_ids: values?.ext_group_chat_ids,
        ext_department_ids: values?.ext_department_ids,
        ext_tag_ids: values?.ext_tag_ids,
        tag_logical_condition: tagLogicalCondition,
        exclude_ext_tag_ids: values?.exclude_ext_tag_ids,
      };

      if (values?.date_range) {
        [params.ext_customer_filter.start_time, params.ext_customer_filter.end_time] = values?.date_range;
      }
    }
    return params;
  };

  const checkForm = (values: any): boolean => {
    if (values?.ext_staff_ids?.length === 0) {
      message.warning('请选择使用员工');
      staffSelectRef?.current?.focus();
      return false;
    }
    if (values?.msg?.text === '') {
      message.warning('请填写群发内容');
      return false;
    }
    return true;
  };

  // @ts-ignore
  return (
    <>
      <ProForm
        className={styles.content}
        labelCol={{
          md: 3,
        }}
        layout={'horizontal'}
        formRef={props.formRef}
        onFinish={async (values: any) => {
          const params = formatParams(values);
          if (!checkForm(params)) {
            return false;
          }
          console.log(params);
          return props.onFinish(params);
        }}
      >
        <>
          <h3>基础信息</h3>
          <Divider/>
          <Alert
            showIcon={true}
            style={{maxWidth: '800px', marginBottom: 20}}
            type='warning'
            message={(
              <Typography.Text style={{color: 'rgba(66,66,66,0.8)'}}>客户每个月最多接收来自同一企业的管理员的 4
                条群发消息,4条消息可在同一天发送</Typography.Text>
            )}
          />

          <ProFormRadio.Group
            name='send_type'
            label='群发时机'
            initialValue={1}
            options={[
              {
                label: '立即发送',
                value: 1,
              },
              {
                label: '定时发送',
                value: 2,
              },
            ]}
            rules={[
              {
                required: true,
                message: '请选择群发时机',
              },
            ]}
          />

          <ProFormDependency name={['send_type']}>
            {({send_type}) => {
              if (send_type === 2) {
                return (
                  <ProFormDateTimePicker
                    name='send_at'
                    label='发送时间'
                    rules={[
                      {
                        required: true,
                        message: '请选择发送时间',
                      },
                    ]}
                  />
                );
              }
              return '';
            }}
          </ProFormDependency>

          <Form.Item
            label={<span className={'form-item-required'}>群发账号</span>}
          >
            <Button
              ref={staffSelectRef}
              icon={<PlusOutlined/>}
              onClick={() => setStaffSelectionVisible(true)}
            >
              选择员工
            </Button>
          </Form.Item>

          <Space wrap={true} style={{marginTop: -12, marginBottom: 24, marginLeft: 20}}>
            {selectedStaffs.map((item) => (
              <div key={item.id} className={'staff-item'}>
                <Badge
                  count={
                    <CloseCircleOutlined
                      onClick={() => {
                        setSelectedStaffs(
                          selectedStaffs.filter((staff) => staff.id !== item.id),
                        );
                      }}
                      style={{color: 'rgb(199,199,199)'}}
                    />
                  }
                >
                  <div className={'container'}>
                    <img alt={item.name} className={'avatar'} src={item.avatar_url}/>
                    <span className={'text'}>{item.name}</span>
                  </div>
                </Badge>
              </div>
            ))}
          </Space>

          <ProFormRadio.Group
            name='ext_customer_filter_enable'
            label='目标客户'
            initialValue={2}
            options={[
              {
                label: '全部客户',
                value: Disable,
              },
              {
                label: '筛选客户',
                value: Enable,
              },
            ]}
            rules={[
              {
                required: true,
                message: '请选择目标客户',
              },
            ]}
          />
          <ProFormDependency name={['ext_customer_filter_enable']}>
            {({ext_customer_filter_enable}) => {
              if (ext_customer_filter_enable === Enable) {
                return (
                  <div className={styles.multiFormItemSection}>
                    <ProFormRadio.Group
                      name='gender'
                      label='性别'
                      initialValue={0}
                      options={[
                        {
                          label: '全部',
                          value: 0,
                        },
                        {
                          label: '仅男粉丝',
                          value: 1,
                        },
                        {
                          label: '仅女粉丝',
                          value: 2,
                        },
                        {
                          label: '未知性别',
                          value: 3,
                        },
                      ]}
                    />

                    <ProForm.Item label={'所在群聊'} name={'ext_group_chat_ids'}>
                      <GroupChatSelect
                        placeholder={'请选择群聊'}
                        allGroupChats={allGroupChats}
                        maxTagCount={4}/>
                    </ProForm.Item>

                    <ProFormDateRangePicker name='date_range' label='添加时间'/>

                    <ProForm.Item label={'目标客户'} name={'ext_tag_ids'}>
                      <CustomerTagSelect
                        withLogicalCondition={true}
                        logicalCondition={tagLogicalCondition}
                        setLogicalCondition={setTagLogicalCondition}
                        placeholder={'按标签选择客户'}
                        allTagGroups={allTagGroups}
                        maxTagCount={4}/>
                    </ProForm.Item>

                    <ProForm.Item label={'排除客户'} name={'exclude_ext_tag_ids'}>
                      <CustomerTagSelect
                        placeholder={'按标签排除客户'}
                        allTagGroups={allTagGroups}
                        maxTagCount={4}/>
                    </ProForm.Item>

                  </div>
                );
              }
              return '';
            }}
          </ProFormDependency>

          <h3>群发设置</h3>
          <Divider/>

          <Form.Item
            label={<span className={'form-item-required'}>群发内容</span>}
            style={{marginBottom: 36}}
          >
            <AutoReply welcomeMsg={massMsg} isFetchDone={isFetchDone} setWelcomeMsg={setMassMsg}/>
          </Form.Item>

        </>
      </ProForm>

      <StaffTreeSelectionModal
        visible={staffSelectionVisible}
        setVisible={setStaffSelectionVisible}
        defaultCheckedStaffs={selectedStaffs}
        onFinish={(values) => {
          setSelectedStaffs(values);
        }}
        allStaffs={allStaffs}
      />
    </>
  );
}
Example #17
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 #18
Source File: form.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
CustomerWelcomeMsgForm: React.FC<CustomerWelcomeMsgFormProps> = (props) => {
  const { staffs, initialValues } = props;
  const staffSelectRef = useRef<HTMLInputElement>(null);
  const [selectedStaffs, setSelectedStaffs] = useState<StaffOption[]>([]);
  const [staffSelectionVisible, setStaffSelectionVisible] = useState(false);
  const [isFetchDone, setIsFetchDone] = useState(false);
  const [welcomeMsg, setWelcomeMsg] = useState<WelcomeMsg>({ text: '' });

  const itemDataToFormValues = (item: CustomerWelcomeMsgItem | any): CreateCustomerWelcomeMsgParam => {
    const values: CreateCustomerWelcomeMsgParam = {
      ...item,
      id: item.id,
      ext_staff_ids: item?.staffs?.map((staff: any) => staff.ext_id),
    };

    if (item?.staffs) {
      setSelectedStaffs(
        item?.staffs.map((staff: any) => {
          return {
            key: staff.ext_id,
            label: staff.id,
            value: staff.ext_id,
            ...staff,
            ext_id: staff.ext_id,
          };
        }),
      );
    }

    if (item.welcome_msg) {
      setWelcomeMsg(item.welcome_msg);
    }

    return values;
  };

  useEffect(() => {
    if (initialValues?.id) {
      props?.formRef?.current?.setFieldsValue(itemDataToFormValues(initialValues));
      setIsFetchDone(true);
    }
  }, [initialValues]);

  const formatParams = (values: any) => {
    const params = { ...values };
    params.ext_staff_ids = selectedStaffs.map((staff) => staff.ext_id) || [];
    params.enable_time_period_msg = Disable;
    params.id = initialValues?.id;
    params.welcome_msg = welcomeMsg;
    return params;
  };

  const checkForm = (values: any): boolean => {
    if (values.ext_staff_ids.length === 0) {
      message.warning('请选择使用员工');
      staffSelectRef?.current?.focus();
      return false;
    }
    if (values.welcome_msg.text === '') {
      message.warning('请填写欢迎语内容');
      return false;
    }
    return true;
  };

  // @ts-ignore
  return (
    <>
      <ProForm
        className={styles.content}
        labelCol={{
          md: 3,
        }}
        layout={'horizontal'}
        formRef={props.formRef}
        onFinish={async (values: any) => {
          const params = formatParams(values);
          if (!checkForm(params)) {
            return false;
          }
          return props.onFinish(params);
        }}
      >
        <>
          <h3>基础信息</h3>
          <Divider />
          <Alert
            showIcon={true}
            style={{ maxWidth: '800px', marginBottom: 20 }}
            type='info'
            message={(
              <span> 1.渠道码设置的欢迎语有更高优先级;2.同一用户设置多个欢迎语时,系统使用最新的一个</span>
            )}
          />

          <ProFormText
            name='name'
            label='欢迎语名称'
            width='md'
            rules={[
              {
                required: true,
                message: '请输入欢迎语名称!',
              },
            ]}
          />

          <Form.Item
            label={<span className={'form-item-required'}>使用员工</span>}
            tooltip={'此处设置的员工的所有新客户好友将配置此处的欢迎语'}
          >
            <Button
              ref={staffSelectRef}
              icon={<PlusOutlined />}
              onClick={() => setStaffSelectionVisible(true)}
            >
              选择员工
            </Button>
          </Form.Item>

          <Space wrap={true} style={{ marginTop: -12, marginBottom: 24, marginLeft: 20 }}>
            {selectedStaffs.map((item) => (
              <div key={item.id} className={'staff-item'}>
                <Badge
                  count={
                    <CloseCircleOutlined
                      onClick={() => {
                        setSelectedStaffs(
                          selectedStaffs.filter((staff) => staff.id !== item.id),
                        );
                      }}
                      style={{ color: 'rgb(199,199,199)' }}
                    />
                  }
                >
                  <div className={'container'}>
                    <img alt={item.name} className={'avatar'} src={item.avatar_url} />
                    <span className={'text'}>{item.name}</span>
                  </div>
                </Badge>
              </div>
            ))}
          </Space>

          <h3>欢迎语设置</h3>
          <Divider />

          <Form.Item
            label={<span className={'form-item-required'}>欢迎语</span>}
          >
            <AutoReply enableQuickInsert={true} welcomeMsg={welcomeMsg} isFetchDone={isFetchDone} setWelcomeMsg={setWelcomeMsg} />
          </Form.Item>

        </>
      </ProForm>

      <StaffTreeSelectionModal
        visible={staffSelectionVisible}
        setVisible={setStaffSelectionVisible}
        defaultCheckedStaffs={selectedStaffs}
        onFinish={(values) => {
          setSelectedStaffs(values);
        }}
        allStaffs={staffs}
      />
    </>
  );
}
Example #19
Source File: GroupModal.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
GroupModal = (props: GroupModalProps, ref: any) => {
  const {initialValues, visible, setVisible, allDepartments} = props
  const [selectedDepartments, setSelectedDepartments] = useState<DepartmentOption[]>([]);
  const [departmentSelectionVisible, setDepartmentSelectionVisible] = useState(false);
  const formRef = React.useRef<FormInstance>();
  const departmentMap = _.keyBy<DepartmentOption>(allDepartments, "ext_id");
  const itemDataToFormData = (values: ScriptGroup.Item) => {
    const params: any = {...values}
    params.is_global = values.departments?.includes(0) ? True : False;
    return params;
  }

  useImperativeHandle(ref, () => {
    return {
      open: (item?: ScriptGroup.Item) => {
        setTimeout(() => {
          formRef.current?.setFieldsValue(itemDataToFormData(item as ScriptGroup.Item))
          if (item?.departments?.includes(0)) {
            setSelectedDepartments([])
          } else {
            setSelectedDepartments(item?.departments?.map((ext_id) => departmentMap[ext_id]) || [])
          }
        }, 100)
        setVisible(true)
      },
      close: () => {
        formRef.current?.resetFields()
        setSelectedDepartments([])
        setVisible(false)
      }
    }
  })
  return (
    <>
      <Modal
        {...props}
        width={568}
        className={'dialog'}
        visible={visible}
        onOk={() => {
          if(formRef.current?.getFieldValue('is_global')===True){
            formRef.current?.submit();
          }
          if(formRef.current?.getFieldValue('is_global')===False){
            if(selectedDepartments?.length>0){
              formRef.current?.submit();
            }else{
              message.warning('请选择部门')
            }
          }
        }}
        onCancel={() => {
          setVisible(false)
          formRef.current?.resetFields()
        }}
      >
        <ProForm
          submitter={{
            render: false,
          }}
          formRef={formRef}
          // initialValues={initialValues}
          layout={'horizontal'}
          onFinish={async (values) => {
            const params: any = {
              ...initialValues,
              ...values,
              departments: selectedDepartments.map((item) => item.ext_id),
            };
            if (values.is_global === True) {
              params.departments = [0];
            }

            await props.onFinish(params, params.id ? 'update' : 'create');
          }}
        >
          <h3 className="dialog-title" style={{fontSize: 18}}>
            {initialValues?.id ? '修改分组' : '添加分组'}
          </h3>
          <ProFormText
            name="name"
            label="分组名称"
            width={'md'}
            placeholder="请输入分组名称"
            rules={[
              {
                required: true,
                message: '分组名称必填',
              },
            ]}
          />

          <ProFormRadio.Group
            name="is_global"
            label="可见范围"
            initialValue={True}
            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}/>
                          }
                          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>


        </ProForm>
      </Modal>
      <DepartmentSelectionModal
        visible={departmentSelectionVisible}
        setVisible={setDepartmentSelectionVisible}
        defaultCheckedDepartments={selectedDepartments}
        onFinish={(values) => {
          setSelectedDepartments(values);
        }}
        allDepartments={props.allDepartments}
      />
    </>

  )
}
Example #20
Source File: WidgetActionDropdown.tsx    From datart with Apache License 2.0 4 votes vote down vote up
WidgetActionDropdown: React.FC<WidgetActionDropdownProps> = memo(
  ({ widget }) => {
    const { editing: boardEditing } = useContext(BoardContext);

    const widgetAction = useWidgetAction();
    const dataChart = useContext(WidgetChartContext)!;
    const t = useI18NPrefix(`viz.widget.action`);
    const menuClick = useCallback(
      ({ key }) => {
        widgetAction(key, widget);
      },
      [widgetAction, widget],
    );
    const getAllList = useCallback(() => {
      const allWidgetActionList: WidgetActionListItem<widgetActionType>[] = [
        {
          key: 'refresh',
          label: t('refresh'),
          icon: <SyncOutlined />,
        },
        {
          key: 'fullScreen',
          label: t('fullScreen'),
          icon: <FullscreenOutlined />,
        },
        {
          key: 'edit',
          label: t('edit'),
          icon: <EditOutlined />,
        },
        {
          key: 'delete',
          label: t('delete'),
          icon: <DeleteOutlined />,
          danger: true,
        },

        {
          key: 'info',
          label: t('info'),
          icon: <InfoOutlined />,
        },
        {
          key: 'lock',
          label: t('lock'),
          icon: <LockOutlined />,
        },

        {
          key: 'makeLinkage',
          label: t('makeLinkage'),
          icon: <LinkOutlined />,
          divider: true,
        },
        {
          key: 'closeLinkage',
          label: t('closeLinkage'),
          icon: <CloseCircleOutlined />,
          danger: true,
        },
        {
          key: 'makeJump',
          label: t('makeJump'),
          icon: <BranchesOutlined />,
          divider: true,
        },
        {
          key: 'closeJump',
          label: t('closeJump'),
          icon: <CloseCircleOutlined />,
          danger: true,
        },
      ];
      return allWidgetActionList;
    }, [t]);
    const actionList = useMemo(() => {
      return (
        getWidgetActionList({
          allList: getAllList(),
          widget,
          boardEditing,
          chartGraphId: dataChart?.config?.chartGraphId,
        }) || []
      );
    }, [boardEditing, dataChart?.config?.chartGraphId, getAllList, widget]);
    const dropdownList = useMemo(() => {
      const menuItems = actionList.map(item => {
        return (
          <React.Fragment key={item.key}>
            {item.divider && <Menu.Divider />}
            <Menu.Item
              danger={item.danger}
              icon={item.icon}
              disabled={item.disabled}
              key={item.key}
            >
              {item.label}
            </Menu.Item>
          </React.Fragment>
        );
      });

      return <Menu onClick={menuClick}>{menuItems}</Menu>;
    }, [actionList, menuClick]);
    if (actionList.length === 0) {
      return null;
    }
    return (
      <Dropdown
        className="widget-tool-dropdown"
        overlay={dropdownList}
        placement="bottomCenter"
        trigger={['click']}
        arrow
      >
        <Button icon={<EllipsisOutlined />} type="link" />
      </Dropdown>
    );
  },
)
Example #21
Source File: Graph.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
export default function Graph(props: IProps) {
  const { metric, match, range, step, onClose } = props;
  const newGroups = _.map(
    _.filter(match.dimensionLabels, (item) => !_.isEmpty(item.value)),
    'label',
  );
  const [refreshFlag, setRefreshFlag] = useState(_.uniqueId('refreshFlag_'));
  const [calcFunc, setCalcFunc] = useState('');
  const [comparison, setComparison] = useState<string[]>([]);
  const [aggrFunc, setAggrFunc] = useState('avg');
  const [aggrGroups, setAggrGroups] = useState<string[]>(newGroups);
  const [labels, setLabels] = useState<string[]>([]);
  const [series, setSeries] = useState<any[]>([]);
  const [highLevelConfig, setHighLevelConfig] = useState({
    shared: true,
    sharedSortDirection: 'desc',
    legend: true,
    util: 'none',
    colorRange: colors[0].value,
    reverseColorOrder: false,
    colorDomainAuto: true,
    colorDomain: [],
    chartheight: 300,
  });
  const [chartType, setChartType] = useState('line');
  const [reduceFunc, setReduceFunc] = useState('last');
  const lineGraphProps = {
    custom: {
      drawStyle: 'lines',
      fillOpacity: 0,
      stack: 'hidden',
      lineInterpolation: 'smooth',
    },
    options: {
      legend: {
        displayMode: highLevelConfig.legend ? 'list' : 'hidden',
      },
      tooltip: {
        mode: highLevelConfig.shared ? 'all' : 'single',
        sort: highLevelConfig.sharedSortDirection,
      },
      standardOptions: {
        util: highLevelConfig.util,
      },
    },
  };
  const hexbinGraphProps = {
    custom: {
      calc: reduceFunc,
      colorRange: highLevelConfig.colorRange,
      reverseColorOrder: highLevelConfig.reverseColorOrder,
      colorDomainAuto: highLevelConfig.colorDomainAuto,
      colorDomain: highLevelConfig.colorDomain,
    },
    options: {
      standardOptions: {
        util: highLevelConfig.util,
      },
    },
  };
  const graphStandardOptions = {
    line: <LineGraphStandardOptions highLevelConfig={highLevelConfig} setHighLevelConfig={setHighLevelConfig} />,
    hexbin: <HexbinGraphStandardOptions highLevelConfig={highLevelConfig} setHighLevelConfig={setHighLevelConfig} />,
  };

  useEffect(() => {
    setAggrGroups(newGroups);
  }, [JSON.stringify(newGroups)]);

  useEffect(() => {
    const matchStr = getMatchStr(match);
    getLabels(`${metric}${matchStr}`, range).then((res) => {
      setLabels(res);
    });
  }, [refreshFlag, JSON.stringify(match), JSON.stringify(range)]);

  useEffect(() => {
    getQueryRange({
      metric,
      match: getMatchStr(match),
      range,
      step,
      aggrFunc,
      aggrGroups,
      calcFunc,
      comparison,
    }).then((res) => {
      setSeries(res);
    });
  }, [refreshFlag, metric, JSON.stringify(match), JSON.stringify(range), step, calcFunc, comparison, aggrFunc, aggrGroups]);

  return (
    <Card
      size='small'
      style={{ marginBottom: 10 }}
      title={metric}
      className='n9e-metric-views-metrics-graph'
      extra={
        <Space>
          <Space size={0} style={{ marginRight: 10 }}>
            <LineChartOutlined
              className={classNames({
                'button-link-icon': true,
                active: chartType === 'line',
              })}
              onClick={() => {
                setChartType('line');
              }}
            />
            <Divider type='vertical' />
            <HexbinIcon
              className={classNames({
                'button-link-icon': true,
                active: chartType === 'hexbin',
              })}
              onClick={() => {
                setChartType('hexbin');
              }}
            />
          </Space>
          <Popover placement='left' content={graphStandardOptions[chartType]} trigger='click' autoAdjustOverflow={false} getPopupContainer={() => document.body}>
            <a>
              <SettingOutlined />
            </a>
          </Popover>
          <a>
            <SyncOutlined
              onClick={() => {
                setRefreshFlag(_.uniqueId('refreshFlag_'));
              }}
            />
          </a>
          <a>
            <ShareAltOutlined
              onClick={() => {
                const curCluster = localStorage.getItem('curCluster');
                const dataProps = {
                  type: 'timeseries',
                  version: '2.0.0',
                  name: metric,
                  step,
                  range,
                  ...lineGraphProps,
                  targets: _.map(
                    getExprs({
                      metric,
                      match: getMatchStr(match),
                      aggrFunc,
                      aggrGroups,
                      calcFunc,
                      comparison,
                    }),
                    (expr) => {
                      return {
                        expr,
                      };
                    },
                  ),
                };
                setTmpChartData([
                  {
                    configs: JSON.stringify({
                      curCluster,
                      dataProps,
                    }),
                  },
                ]).then((res) => {
                  const ids = res.dat;
                  window.open('/chart/' + ids);
                });
              }}
            />
          </a>
          <a>
            <CloseCircleOutlined onClick={onClose} />
          </a>
        </Space>
      }
    >
      <div>
        <Space>
          <div>
            计算函数:
            <Dropdown
              overlay={
                <Menu onClick={(e) => setCalcFunc(e.key === 'clear' ? '' : e.key)} selectedKeys={[calcFunc]}>
                  <Menu.Item key='rate_1m'>rate_1m</Menu.Item>
                  <Menu.Item key='rate_5m'>rate_5m</Menu.Item>
                  <Menu.Item key='increase_1m'>increase_1m</Menu.Item>
                  <Menu.Item key='increase_5m'>increase_5m</Menu.Item>
                  <Menu.Divider></Menu.Divider>
                  <Menu.Item key='clear'>clear</Menu.Item>
                </Menu>
              }
            >
              <a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
                {calcFunc || '无'} <DownOutlined />
              </a>
            </Dropdown>
          </div>
          <div>
            环比:
            {comparison.map((ag) => (
              <Tag
                key={ag}
                closable
                onClose={() => {
                  setComparison(_.without(comparison, ag));
                }}
              >
                {ag}
              </Tag>
            ))}
            <Dropdown
              overlay={
                <Menu
                  onClick={(e) => {
                    if (comparison.indexOf(e.key) === -1) {
                      setComparison([...comparison, e.key]);
                    } else {
                      setComparison(_.without(comparison, e.key));
                    }
                  }}
                  selectedKeys={comparison}
                >
                  <Menu.Item key='1d'>1d</Menu.Item>
                  <Menu.Item key='7d'>7d</Menu.Item>
                </Menu>
              }
              overlayStyle={{ maxHeight: 400, overflow: 'auto' }}
            >
              <a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
                <PlusCircleOutlined />
              </a>
            </Dropdown>
          </div>
          <div>
            聚合函数:
            <Dropdown
              overlay={
                <Menu onClick={(e) => setAggrFunc(e.key)} selectedKeys={[aggrFunc]}>
                  <Menu.Item key='sum'>sum</Menu.Item>
                  <Menu.Item key='avg'>avg</Menu.Item>
                  <Menu.Item key='max'>max</Menu.Item>
                  <Menu.Item key='min'>min</Menu.Item>
                </Menu>
              }
            >
              <a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
                {aggrFunc} <DownOutlined />
              </a>
            </Dropdown>
          </div>
          {aggrFunc ? (
            <div className='graph-config-inner-item'>
              聚合维度:
              {aggrGroups.map((ag) => (
                <Tag
                  key={ag}
                  closable
                  onClose={() => {
                    setAggrGroups(_.without(aggrGroups, ag));
                  }}
                >
                  {ag}
                </Tag>
              ))}
              <Dropdown
                overlay={
                  <Menu
                    onClick={(e) => {
                      if (aggrGroups.indexOf(e.key) === -1) {
                        setAggrGroups([...aggrGroups, e.key]);
                      } else {
                        setAggrGroups(_.without(aggrGroups, e.key));
                      }
                    }}
                    selectedKeys={aggrGroups}
                  >
                    {_.map(
                      _.filter(labels, (n) => n !== '__name__'),
                      (ag) => (
                        <Menu.Item key={ag}>{ag}</Menu.Item>
                      ),
                    )}
                  </Menu>
                }
                overlayStyle={{ maxHeight: 400, overflow: 'auto' }}
              >
                <a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
                  <PlusCircleOutlined />
                </a>
              </Dropdown>
            </div>
          ) : null}
          {chartType === 'hexbin' && (
            <div>
              取值计算:
              <Dropdown
                overlay={
                  <Menu onClick={(e) => setReduceFunc(e.key)} selectedKeys={[reduceFunc]}>
                    {_.map(calcsOptions, (val, key) => {
                      return <Menu.Item key={key}>{val.name}</Menu.Item>;
                    })}
                  </Menu>
                }
              >
                <a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
                  {calcsOptions[reduceFunc]?.name} <DownOutlined />
                </a>
              </Dropdown>
            </div>
          )}
        </Space>
      </div>
      <div>
        {chartType === 'line' && <Timeseries inDashboard={false} values={lineGraphProps as any} series={series} />}
        {chartType === 'hexbin' && (
          <div style={{ padding: '20px 0 0 0', height: highLevelConfig.chartheight }}>
            <Hexbin values={hexbinGraphProps as any} series={series} />
          </div>
        )}
      </div>
    </Card>
  );
}
Example #22
Source File: panel.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
Panel: React.FC<PanelProps> = ({ metrics, defaultPromQL, removePanel }) => {
  const { t } = useTranslation();
  const { clusters } = useSelector<CommonRootState, CommonStoreState>((state) => state.common);
  const curPanelTab = useRef<PanelType>(PanelType.Table);
  const graphRef = useRef(null);
  const inputValue = useRef(defaultPromQL);
  const lastEndTime = useRef<number | null>(null);
  const abortInFlightFetch = useRef<(() => void) | null>(null);

  // 公共状态
  const [queryStats, setQueryStats] = useState<QueryStats | null>(null);
  const [chartType, setChartType] = useState<ChartType>(ChartType.Line);
  const [optionsRecord, setOptionsRecord] = useState<PanelOptions>({
    type: PanelType.Table,
    range: { num: 1, unit: 'hour', description: t('小时') },
    endTime: null,
    resolution: null,
    stacked: false,
    showExemplars: false,
  });
  const [errorContent, setErrorContent] = useState<string>('');
  // Table 相关状态
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [vectorData, setVectorData] = useState<VectorDataType | null>(null);

  // 更新输入框表达式内容
  function handleExpressionChange(value: string) {
    inputValue.current = value;
  }

  function handleTabChange(type: PanelType) {
    if (optionsRecord.type !== type) {
      // 同步 Table 页时间戳和 Graph 时间选择组件的时间
      if (type === PanelType.Graph && lastEndTime.current !== optionsRecord.endTime) {
        if (optionsRecord.endTime === null) {
          setOptions({ range: { num: 1, unit: 'hour', description: 'hour' } });
        } else {
          setOptions({
            range: {
              start: optionsRecord.endTime - 60 * 60,
              end: optionsRecord.endTime,
            },
          });
        }
      } else {
        lastEndTime.current = optionsRecord.endTime;
      }

      curPanelTab.current = type;
      setOptions({ type });
      setQueryStats(null);
    }
  }

  // 获取请求的结束时间戳
  function getEndTime(endTime = optionsRecord.endTime): number {
    return endTime === null ? moment().unix() : endTime;
  }

  // 更新时间戳
  function handleTimestampChange(endTime: Moment | null): void {
    setOptions({ endTime: endTime ? endTime?.unix() : null });
  }

  function setOptions(opts: Partial<PanelOptions>): void {
    setOptionsRecord((optionsRecord) => ({ ...optionsRecord, ...opts }));
  }

  // 图表选中时间改变,触发更新
  function handleGraphDateChange(range: Range) {
    const prevRange = optionsRecord.range;
    if (isAbsoluteRange(range) ? !_.isEqual(prevRange, range) : range.num !== (prevRange as RelativeRange).num || range.unit !== (prevRange as RelativeRange).unit) {
      const endTime = range.hasOwnProperty('unit') ? null : (range as AbsoluteRange).end;
      setOptions({
        endTime,
        range,
      });
    }
  }

  // 图表请求完成,回填请求信息
  function onGraphRequestCompleted(newQueryStats: QueryStats) {
    if (curPanelTab.current === PanelType.Graph) {
      setQueryStats(newQueryStats);
    }
  }

  // 触发语句查询
  function executeQuery(isExecute: boolean = true) {
    const expr = inputValue.current;
    if (!isExecute || expr === '' || clusters.length === 0) return;

    // 模式下直接调用图表组件的刷新方法
    if (curPanelTab.current === PanelType.Graph) {
      setQueryStats(null);
      graphRef.current && (graphRef.current as any).refresh();
      return;
    }
    // 存储查询历史
    // this.props.onExecuteQuery(expr);
    // 设置外部 options 参数
    // if (this.props.optionsRecord.expr !== expr) {
    //   this.setOptions({ expr });
    // }

    // 如果正在进行上一个请求,那么终止
    if (abortInFlightFetch.current) {
      abortInFlightFetch.current();
      abortInFlightFetch.current = null;
    }

    // 设置一个新的请求控制器
    const abortController = new AbortController();
    abortInFlightFetch.current = () => abortController.abort();
    setIsLoading(true);
    setQueryStats(null);
    const queryStart = Date.now();

    // 初始化参数
    const endTime = getEndTime();
    const params: URLSearchParams = new URLSearchParams({
      query: expr,
    });

    // 设置请求需要的参数
    let path = 'query';
    params.append('time', endTime.toString());

    prometheusAPI(path, params, {
      cache: 'no-store',
      credentials: 'same-origin',
      signal: abortController.signal,
    })
      .then((res: any) => {
        abortInFlightFetch.current = null;
        setIsLoading(false);
        if (curPanelTab.current === PanelType.Graph) {
          return;
        }
        if (res.hasOwnProperty('status') && res.status === 'success') {
          var tooLong = false;
          var LENGTH = 10000;
          var maxLength = 0;
          let { resultType, result } = res.data;
          if (result) {
            if (result.length > LENGTH) {
              tooLong = true;
              maxLength = result.length;
              result = result.slice(LENGTH);
            }
            result.forEach((item) => {
              if (item.values && item.values.length > LENGTH) {
                tooLong = true;
                if (item.values.length > maxLength) {
                  maxLength = item.values.length;
                }
                item.values = item.values.slice(LENGTH);
              }
            });
          }
          if (tooLong) {
            setErrorContent(`Warning:Fetched ${maxLength} metrics, only displaying first ${LENGTH}`);
          } else {
            setErrorContent('');
          }
          if (resultType === 'scalar' || resultType === 'string') {
            setVectorData({ resultType, result: [result] });
          } else {
            setVectorData({ resultType, result });
          }
          setQueryStats({
            loadTime: Date.now() - queryStart,
            resultSeries: res.data.result.length,
          });
        } else {
          setVectorData(null);
          setErrorContent(res?.error || '');
        }
      })
      .catch((error) => {
        setIsLoading(false);
      });
  }

  // 请求发生错误时,展示错误信息
  function onErrorOccured(errorArr: ErrorInfoType[]) {
    if (errorArr.length) {
      const errInfo = errorArr[0].error;
      setErrorContent(errInfo);
    } else {
      setErrorContent('');
    }
  }

  // 当时间戳变更时,重新获取数据
  useEffect(() => {
    optionsRecord.type === PanelType.Table && executeQuery();
  }, [optionsRecord.endTime, clusters]);

  // 切换标签到 Table 时获取数据
  useEffect(() => {
    optionsRecord.type === PanelType.Table && executeQuery();
  }, [optionsRecord.type, clusters]);

  return (
    <div className='panel'>
      {/* 输入框 */}
      <ExpressionInput
        queryHistory={['abs(go_gc_duration_seconds)']}
        value={inputValue.current}
        onExpressionChange={handleExpressionChange}
        metricNames={metrics}
        isLoading={isLoading}
        executeQuery={executeQuery}
      />
      {errorContent && <Alert className='error-alert' message={errorContent} type='error' />}
      {!isLoading && !errorContent && queryStats && <QueryStatsView {...queryStats} />}
      <Tabs className='panel-tab-box' type='card' activeKey={optionsRecord.type} onChange={handleTabChange}>
        <TabPane tab='Table' key='table'>
          <div className='table-timestamp'>
            Timestamp:{' '}
            <DatePicker
              placeholder='Evaluation time'
              showTime
              showNow={false}
              disabledDate={(current) => current > moment()}
              value={optionsRecord.endTime ? moment(optionsRecord.endTime * 1000) : null}
              onChange={handleTimestampChange}
            />
          </div>
          <List
            className='table-list'
            size='small'
            bordered
            loading={isLoading}
            dataSource={vectorData ? vectorData.result : []}
            renderItem={(item) => {
              const { metric, value, values } = item;
              return (
                <List.Item>
                  <div className='list-item-content'>
                    <div className='left'>{getListItemContent(vectorData?.resultType, metric)}</div>
                    {vectorData?.resultType === 'scalar' || vectorData?.resultType === 'string' ? (
                      item[1]
                    ) : (
                      <div>
                        {value && value.length > 1 && <div className='right'>{value[1] || '-'}</div>}
                        {values && values.length > 0 && (
                          <div className='right'>
                            {values.map((value) => {
                              return (
                                <>
                                  {value && value.length > 1 && (
                                    <span style={{ display: 'inline-block' }}>
                                      {value[1]} @{value[0]}
                                    </span>
                                  )}
                                  <br />
                                </>
                              );
                            })}
                          </div>
                        )}
                      </div>
                    )}
                  </div>
                </List.Item>
              );
            }}
          />
        </TabPane>
        <TabPane tab='Graph' key='graph'>
          {/* 操作栏 */}
          <div className='graph-operate-box'>
            <div className='left'>
              <Space>
                <DateRangePicker placement='bottomRight' value={optionsRecord.range} onChange={handleGraphDateChange} />
                <Resolution onChange={(v: number) => setOptions({ resolution: v })} initialValue={optionsRecord.resolution} />
                <Radio.Group
                  options={[
                    { label: <LineChartOutlined />, value: ChartType.Line },
                    { label: <AreaChartOutlined />, value: ChartType.StackArea },
                  ]}
                  onChange={(e) => {
                    e.preventDefault();
                    setChartType(e.target.value);
                  }}
                  value={chartType}
                  optionType='button'
                  buttonStyle='solid'
                />
              </Space>
            </div>
          </div>
          {/* 图 */}
          <div>
            {optionsRecord.type === PanelType.Graph && (
              <Graph
                ref={graphRef}
                showHeader={true}
                isShowRefresh={false}
                data={{
                  step: optionsRecord.resolution,
                  range: optionsRecord.range,
                  promqls: [inputValue],
                  chartType: chartType,
                }}
                onErrorOccured={onErrorOccured}
                onRequestCompleted={onGraphRequestCompleted}
              />
            )}
          </div>
        </TabPane>
      </Tabs>
      <span className='remove-panel-btn' onClick={removePanel}>
        <CloseCircleOutlined />
      </span>
    </div>
  );
}
Example #23
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 #24
Source File: GeoDataResolver.tsx    From jitsu with MIT License 4 votes vote down vote up
function GeoDataResolver() {
  const services = useServices()

  const [saving, setSaving] = useState(false)
  const [testingConnection, setTestingConnection] = useState(false)
  const [formDisabled, setFormDisabled] = useState(false)

  const [form] = useForm<GeoDataResolverFormValues>()

  const {
    error: loadingError,
    data: formConfig,
    setData: setFormConfig,
  } = useLoaderAsObject<MaxMindConfig>(async () => {
    const response = await services.backendApiClient.get(
      `/configurations/${geoDataResolversCollection}?id=${services.activeProject.id}`
    )

    let config = {
      license_key: response.maxmind?.license_key,
      enabled: response.maxmind?.enabled,
      editions: [],
    }

    //set statuses or load
    if (response.maxmind?.enabled && response.maxmind?._statuses) {
      config.editions = response.maxmind._statuses
    } else {
      const response = await ApplicationServices.get().backendApiClient.get(
        withQueryParams("/geo_data_resolvers/editions", { project_id: services.activeProject.id }),
        { proxy: true }
      )
      config.editions = response.editions
    }

    form.setFieldsValue({
      license_key: config.license_key,
      enabled: config.enabled,
    })

    setFormDisabled(!config.enabled)

    return config
  }, [])

  const submit = async () => {
    setSaving(true)
    let formValues = form.getFieldsValue()
    try {
      if (formValues.enabled) {
        await testConnection(true)
      }
      await save()
    } catch (error) {
      actionNotification.error(error.message || error)
    } finally {
      setSaving(false)
    }
  }

  const save = async () => {
    let formValues = form.getFieldsValue()

    let config = {
      maxmind: {
        enabled: formValues.enabled,
        license_key: formValues.license_key,
        _statuses: formValues.enabled ? formConfig.editions : null,
      },
    }

    await services.backendApiClient.post(
      `/configurations/${geoDataResolversCollection}?id=${services.activeProject.id}`,
      Marshal.toPureJson(config)
    )

    let anyConnected =
      formConfig.editions.filter(editionStatus => {
        return editionStatus.main.status === "ok" || editionStatus.analog?.status === "ok"
      }).length > 0

    if (!formValues.enabled || anyConnected) {
      actionNotification.success("Settings saved!")
    }

    if (formValues.enabled && !anyConnected) {
      actionNotification.warn(
        `Settings have been saved, but there is no available MaxMind database for this license key. Geo Resolution won't be applied to your JSON events`
      )
    }
  }

  const testConnection = async (hideMessage?: boolean) => {
    setTestingConnection(true)

    let formValues = form.getFieldsValue()

    try {
      const response = await ApplicationServices.get().backendApiClient.post(
        withQueryParams("/geo_data_resolvers/test", { project_id: services.activeProject.id }),
        { maxmind_url: formValues.license_key },
        {
          proxy: true,
        }
      )

      if (response.message) throw new Error(response.message)

      //enrich state
      let currentFormConfig = formConfig
      currentFormConfig.editions = response.editions
      setFormConfig(currentFormConfig)

      //show notification
      if (!hideMessage) {
        let anyConnected =
          formConfig.editions.filter(editionStatus => {
            return editionStatus.main.status === "ok" || editionStatus.analog?.status === "ok"
          }).length > 0

        if (anyConnected) {
          actionNotification.success("Successfully connected!")
        } else {
          actionNotification.error("Connection failed: there is no available MaxMind database for this license key")
        }
      }
    } catch (error) {
      if (!hideMessage) {
        handleError(error, "Connection failed")
      }
    } finally {
      setTestingConnection(false)
    }
  }

  const databaseStatusesRepresentation = (dbStatus: any) => {
    let body = <>-</>
    if (dbStatus) {
      let icon = (
        <Tooltip title="Not connected yet">
          <ClockCircleOutlined className="text-secondaryText" />
        </Tooltip>
      )

      if (dbStatus.status === "ok") {
        icon = (
          <Tooltip title="Successfully connected">
            <CheckCircleOutlined className="text-success" />
          </Tooltip>
        )
      } else if (dbStatus.status === "error") {
        icon = (
          <Tooltip title={dbStatus.message}>
            <CloseCircleOutlined className="text-error" />
          </Tooltip>
        )
      }

      body = (
        <>
          {dbStatus.name}: {icon}
        </>
      )
    }

    return body
  }

  if (loadingError) {
    return <CenteredError error={loadingError} />
  } else if (!formConfig) {
    return <CenteredSpin />
  }

  return (
    <div className="flex justify-center w-full">
      <div className="w-full pt-8 px-4" style={{ maxWidth: "1000px" }}>
        <p>
          Jitsu uses <a href="https://www.maxmind.com/">MaxMind</a> databases for geo resolution. There are two families
          of MaxMind databases: <b>GeoIP2</b> and <b>GeoLite2</b>. After setting a license key{" "}
          <b>all available MaxMind databases, which the license key has access</b>, will be downloaded and used for
          enriching incoming events. For using a certain database add{" "}
          <CodeInline>{"?edition_id=<database type>"}</CodeInline> to MaxMind License Key value. For example:{" "}
          <CodeInline>{"M10sDzWKmnDYUBM0?edition_id=GeoIP2-City,GeoIP2-ISP"}</CodeInline>.
        </p>

        <div className="w-96 flex-wrap flex justify-content-center">
          <Table
            pagination={false}
            columns={[
              {
                title: (
                  <>
                    Database{" "}
                    <Tooltip title="Paid MaxMind Database">
                      <QuestionCircleOutlined className="label-with-tooltip_question-mark" />
                    </Tooltip>
                  </>
                ),
                dataIndex: "main",
                key: "name",
                render: databaseStatusesRepresentation,
              },
              {
                title: (
                  <>
                    Analog{" "}
                    <Tooltip title="Free MaxMind Database analog. Usually it is less accurate than paid version. It is downloaded only if paid one is unavailable.">
                      <QuestionCircleOutlined className="label-with-tooltip_question-mark" />
                    </Tooltip>
                  </>
                ),
                dataIndex: "analog",
                key: "name",
                render: databaseStatusesRepresentation,
              },
            ]}
            dataSource={formConfig.editions}
          />
        </div>

        <br />
        <Form form={form} onFinish={submit}>
          <FormLayout>
            <FormField
              label="Enabled"
              tooltip={
                <>
                  If enabled - Jitsu downloads <a href="https://www.maxmind.com/en/geoip2-databases">GeoIP Databases</a>{" "}
                  with your license key and enriches incoming JSON events with location based data. Read more
                  information about{" "}
                  <a href="https://jitsu.com/docs/other-features/geo-data-resolution">Geo data resolution</a>.
                </>
              }
              key="enabled"
            >
              <Form.Item name="enabled" valuePropName="checked">
                <Switch
                  onChange={value => {
                    setFormDisabled(!value)
                  }}
                  size="default"
                />
              </Form.Item>
            </FormField>
            <FormField
              label="MaxMind License Key"
              tooltip={
                <>
                  Your MaxMind licence key. Obtain a new one in your <a href="https://www.maxmind.com/">Account</a>{" "}
                  {"->"} Manage License Keys. Jitsu downloads all available MaxMind databases with your license key. If
                  you would like to enrich events JSON with the only certain MaxMind DB data{": "}
                  specify license key with the format:{" "}
                  {"<license_key>?edition_id=<comma separated editions like: GeoIP2-City,GeoIP2-ISP>"}. If you use{" "}
                  <a href="https://cloud.jitsu.com/">Jitsu.Cloud</a> and MaxMind isn't set - free GeoLite2-City and
                  GeoLite2-ASN MaxMind databases are applied. Read more about{" "}
                  <a href="https://dev.maxmind.com/geoip/geolite2-free-geolocation-data?lang=en">
                    free MaxMind databases
                  </a>
                  .{" "}
                </>
              }
              key="license_key"
            >
              <Form.Item name="license_key">
                <Input
                  disabled={formDisabled}
                  size="large"
                  name="license_key"
                  placeholder="for example: M10sDzWKmnDYUBM0"
                  required={true}
                />
              </Form.Item>
            </FormField>
            <FormActions>
              <Button
                size="large"
                className="mr-3"
                type="dashed"
                loading={testingConnection}
                onClick={() => testConnection()}
                icon={<ApiOutlined />}
                disabled={formDisabled}
              >
                Test connection
              </Button>
              <Button loading={saving} htmlType="submit" size="large" type="primary">
                Save
              </Button>
            </FormActions>
          </FormLayout>
        </Form>
      </div>
    </div>
  )
}
Example #25
Source File: CreatePostModal.tsx    From foodie with MIT License 4 votes vote down vote up
CreatePostModal: React.FC<IProps> = (props) => {
    const [description, setDescription] = useState('');
    const [privacy, setPrivacy] = useState('public');
    const isLoadingCreatePost = useSelector((state: IRootReducer) => state.loading.isLoadingCreatePost);
    const { imageFile, onFileChange, clearFiles, removeImage } = useFileHandler<IImage[]>('multiple', []);

    const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
        const val = e.target.value;
        setDescription(val);
    };

    const handlePrivacyChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
        const val = e.target.value;
        setPrivacy(val);
    }

    const onSubmit = () => {
        if (description) {
            const formData = new FormData();
            formData.set('description', description);
            formData.set('privacy', privacy);

            if (imageFile.length !== 0) {
                imageFile.forEach((image) => {
                    if (image.file) formData.append('photos', image.file);
                });
            }

            props.dispatchCreatePost(formData);
            toast('Creating post...');
            setDescription('');
            clearFiles();
            props.closeModal();
        }
    };

    return (
        <Modal
            isOpen={props.isOpen}
            onAfterOpen={props.onAfterOpen}
            onRequestClose={props.closeModal}
            contentLabel="Create Post"
            className="modal"
            // shouldCloseOnOverlayClick={!isDeleting}
            overlayClassName="modal-overlay"
        >
            <div className="relative">
                <div
                    className="absolute right-2 top-2 p-1 rounded-full flex items-center justify-center cursor-pointer hover:bg-gray-200 dark:hover:bg-indigo-1100"
                    onClick={props.closeModal}
                >
                    <CloseOutlined className="p-2  outline-none text-gray-500 dark:text-white" />
                </div>
                <div className="w-full laptop:w-40rem p-4 laptop:px-8">
                    <h2 className="dark:text-white">Create Post</h2>
                    <select
                        className="!py-1 !text-sm w-32 dark:bg-indigo-1100 dark:text-white dark:border-gray-800"
                        id="privacy"
                        name="privacy"
                        onChange={handlePrivacyChange}
                        value={privacy}
                    >
                        <option value="public">Public</option>
                        <option value="follower">Follower</option>
                        <option value="private">Only Me</option>
                    </select>
                    <br />
                    <br />
                    <div className="flex flex-col">
                        <textarea
                            className="dark:bg-indigo-1100 dark:text-white dark:!border-gray-800"
                            cols={3}
                            id="post"
                            name="post"
                            onChange={handleDescriptionChange}
                            placeholder="What's on your mind?"
                            rows={3}
                            readOnly={isLoadingCreatePost}
                            value={description}
                        />
                        <div className="flex items-center">
                            {/* --- UPLOAD OPTIONS */}
                            <div className="flex items-center flex-grow">
                                <input
                                    multiple
                                    type="file"
                                    hidden
                                    accept="image/*"
                                    onChange={onFileChange}
                                    readOnly={isLoadingCreatePost}
                                    id="photos"
                                />
                                <label
                                    className="inline-flex items-center cursor-pointer justify-start border-gray-200 text-gray-400 py-2 text-xs"
                                    htmlFor="photos"
                                >
                                    <div
                                        className="group flex items-center justify-center w-10 h-10 border-2 border-dashed border-gray-400 hover:border-indigo-700"
                                        title="Upload photo"
                                    >
                                        <FileImageOutlined className="text-xl text-gray-400 hover:text-indigo-700" />
                                    </div>
                                </label>
                            </div>
                            {/* ---- POST BUTTON --- */}
                            <div className="flex justify-end">
                                <button onClick={onSubmit} disabled={isLoadingCreatePost}>Create Post</button>
                            </div>
                        </div>
                        {/*  ---- IMAGES PREVIEWS LIST ----- */}
                        <div className="flex items-center space-x-2">
                            {imageFile && imageFile.map((image) => (
                                <div
                                    className="w-14 h-14 !bg-cover !bg-no-repeat relative"
                                    key={image.id}
                                    style={{
                                        background: `#f7f7f7 url(${image.url})`
                                    }}
                                >
                                    <CloseCircleOutlined
                                        className="p-2 absolute top-0 left-0 right-0 bottom-0 margin-auto text-3xl text-white hover:bg-red-600 cursor-pointer outline-none opacity-75 hover:opacity-100"
                                        onClick={() => removeImage(image.id)}
                                    />
                                </div>
                            ))}
                        </div>
                    </div>
                </div>
            </div>
        </Modal>
    );
}
Example #26
Source File: index.tsx    From jetlinks-ui-antd with MIT License 4 votes vote down vote up
Visualization: React.FC<Props> = props => {

    const [addItem, setAddItem] = useState(false);
    const [edit, setEdit] = useState<boolean>(false);

    const [layout, setLayout] = useState<any>([]);
    const [current, setCurrent] = useState<any>();

    const removeCard = (item: any) => {
        const temp = layout.findIndex((it: any) => item.i === it.i);
        layout.splice(temp, 1);
        setLayout([...layout]);
    }

    const tempRef = useRef<any>();


    useEffect(() => {
        let subscription: any;
        apis.visualization.getLayout({
            target: props.target,
            type: props.type
        }).then(response => {
            if (response.status === 200) {

                const currentLayout = response.result.metadata === "" ? [] : JSON.parse(response.result.metadata);
                subscription = rxjs.from(currentLayout).pipe(map((item: any) => {
                    const temp = item;
                    const tempProps = {
                        // item: item,
                        ready: (onReady: Function) => {
                            temp.doReady = onReady;
                        },
                        ref: tempRef,
                        onLoad: () => tempRef.current.onLoad(),
                        edit: (doEdit: Function) => {
                            temp.doEdit = doEdit;
                        },
                        complate: {},
                        loading: {},
                        hide: {},
                        onEvent: {},
                        id: item.i,
                    };
                    temp.props = tempProps;
                    return item;
                }))
                    .pipe(toArray())
                    .subscribe(
                        item => {
                            // console.log(item, 'tiem')
                            setLayout(item);
                        },
                        () => { message.error('error') },
                        () => { }
                    )
            }
        }).catch(() => {
            message.error('加载数据错误');
            setLayout([]);
        })

        return () => subscription && subscription.unsubscribe();
    }, []);


    const saveLayout = () => {

        apis.visualization.saveOrUpdate({
            metadata: JSON.stringify(layout),
            type: props.type,
            target: props.target,
            name: props.name,
            id: `${props.type}:${props.target}`
        } as VisualizationItem).then(response => {
            if (response.status === 200) {
                message.success('保存成功');
            }
        }).catch(() => {
            message.error('保存失败!');
        })
    }

    const layoutChange = (currnetLayout: any[]) => {
        const newLayout = layout.map((item: any) => {
            const temp = currnetLayout.find(i => i.i === item.i);
            return { ...item, ...temp, };
        });
        setLayout(newLayout);
    }


    const saveLayoutItem = (item: any) => {
        const id = randomString(8);
        if (current) {
            const index = layout.findIndex((i: any) => i.i === current.i);
            current.config = item;
            layout[index] = current;
            setLayout(layout);
        } else {
            setLayout([{
                i: id,
                x: 0,
                y: Infinity,
                config: item,
                h: 5,
                w: 5,
            }, ...layout]);
        }
        setAddItem(false);
    }

    const renderGridLayout = () =>
        (
            <>
                <ReactGridLayout
                    onLayoutChange={(item: any) => {
                        layoutChange(item)
                    }}
                    // cols={{ md: 12 }}
                    isResizable={edit}
                    isDraggable={edit}
                    onDragStop={() => {
                        setLayout([...layout])
                    }}
                    onResizeStop={() => {
                        setLayout([...layout])
                    }}
                    className="layout"
                    layout={layout}
                    rowHeight={30}
                >
                    {layout.map((item: any) => (
                        <Card
                            style={{ overflow: "hidden" }}
                            key={item.i}
                            id={item.i}
                        >

                            <div style={{ position: 'absolute', right: 15, top: 5, }}>
                                <div style={{ float: 'right' }}>
                                    <Fragment>
                                        {edit && (
                                            <>
                                                <Tooltip title="删除">
                                                    <CloseCircleOutlined onClick={() => removeCard(item)} />
                                                </Tooltip>
                                                <Divider type="vertical" />
                                                <Tooltip title="编辑">
                                                    <EditOutlined onClick={() => {
                                                        setAddItem(true);
                                                        setCurrent(item)
                                                    }} />
                                                </Tooltip>
                                            </>)}
                                        {item.doReady &&
                                            <>
                                                <Divider type="vertical" />
                                                <Tooltip title="刷新">
                                                    <SyncOutlined onClick={() => { item.doReady() }} />
                                                </Tooltip>

                                            </>}
                                    </Fragment>
                                </div>
                            </div>
                            <GridCard
                                {...item}
                                productId={props.productId}
                                deviceId={props.target} />
                        </Card>))}

                </ReactGridLayout>
            </>
        )


    return (
        <>
            {layout.length > 0 ? renderGridLayout() : (
                <Button
                    style={{ width: '300px', height: '200px' }}
                    type="dashed"
                    onClick={() => {
                        setCurrent(undefined);
                        setEdit(true)
                        setAddItem(true);
                    }}
                >
                    <Icon type="plus" />
                   新增
                </Button>
            )}

            <div className={styles.optionGroup}>
                {edit ?
                    <div style={{ float: 'right' }}>

                        <Tooltip title="新增" style={{ float: 'right' }}>
                            <Button
                                type="danger"
                                shape="circle"
                                size="large"
                                onClick={() => {
                                    setCurrent(undefined);
                                    setAddItem(true)
                                }}
                            >
                                <PlusOutlined />
                            </Button>
                        </Tooltip>
                        <div style={{ float: 'right', marginLeft: 10 }}>
                            <Tooltip title="保存" >
                                <Button
                                    type="primary"
                                    shape="circle"
                                    size="large"
                                    onClick={() => {
                                        setEdit(false);
                                        saveLayout();
                                    }}
                                >
                                    <SaveOutlined />
                                </Button>
                            </Tooltip>
                        </div>
                    </div> :
                    <div style={{ textAlign: 'center' }}>
                        <Tooltip title="编辑" >
                            <Button
                                type="danger"
                                shape="circle"
                                size="large"
                                onClick={() => setEdit(true)}
                            >
                                <EditOutlined />
                            </Button>
                        </Tooltip>
                    </div>
                }
            </div>
            {addItem && (
                <AddItem
                    close={() => { setAddItem(false) }}
                    metaData={props.metaData}
                    current={current}
                    save={(item: any) => {
                        saveLayoutItem(item);
                    }}
                />
            )}
        </ >

    )
}
Example #27
Source File: index.tsx    From ql with MIT License 4 votes vote down vote up
Crontab = () => {
  const columns = [
    {
      title: '任务名',
      dataIndex: 'name',
      key: 'name',
      align: 'center' as const,
      render: (text: string, record: any) => (
        <span>{record.name || record._id}</span>
      ),
    },
    {
      title: '任务',
      dataIndex: 'command',
      key: 'command',
      width: '40%',
      align: 'center' as const,
      render: (text: string, record: any) => {
        return (
          <span
            style={{
              textAlign: 'left',
              width: '100%',
              display: 'inline-block',
              wordBreak: 'break-all',
            }}
          >
            {text}
          </span>
        );
      },
    },
    {
      title: '任务定时',
      dataIndex: 'schedule',
      key: 'schedule',
      align: 'center' as const,
    },
    {
      title: '状态',
      key: 'status',
      dataIndex: 'status',
      align: 'center' as const,
      render: (text: string, record: any) => (
        <>
          {(!record.isDisabled || record.status !== CrontabStatus.idle) && (
            <>
              {record.status === CrontabStatus.idle && (
                <Tag icon={<ClockCircleOutlined />} color="default">
                  空闲中
                </Tag>
              )}
              {record.status === CrontabStatus.running && (
                <Tag
                  icon={<Loading3QuartersOutlined spin />}
                  color="processing"
                >
                  运行中
                </Tag>
              )}
              {record.status === CrontabStatus.queued && (
                <Tag icon={<FieldTimeOutlined />} color="default">
                  队列中
                </Tag>
              )}
            </>
          )}
          {record.isDisabled === 1 && record.status === CrontabStatus.idle && (
            <Tag icon={<CloseCircleOutlined />} color="error">
              已禁用
            </Tag>
          )}
        </>
      ),
    },
    {
      title: '操作',
      key: 'action',
      align: 'center' as const,
      render: (text: string, record: any, index: number) => (
        <Space size="middle">
          {record.status === CrontabStatus.idle && (
            <Tooltip title="运行">
              <a
                onClick={() => {
                  runCron(record, index);
                }}
              >
                <PlayCircleOutlined />
              </a>
            </Tooltip>
          )}
          {record.status !== CrontabStatus.idle && (
            <Tooltip title="停止">
              <a
                onClick={() => {
                  stopCron(record, index);
                }}
              >
                <PauseCircleOutlined />
              </a>
            </Tooltip>
          )}
          <Tooltip title="日志">
            <a
              onClick={() => {
                setLogCron({ ...record, timestamp: Date.now() });
              }}
            >
              <FileTextOutlined />
            </a>
          </Tooltip>
          <MoreBtn key="more" record={record} index={index} />
        </Space>
      ),
    },
  ];

  const [width, setWidth] = useState('100%');
  const [marginLeft, setMarginLeft] = useState(0);
  const [marginTop, setMarginTop] = useState(-72);
  const [value, setValue] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [editedCron, setEditedCron] = useState();
  const [searchText, setSearchText] = useState('');
  const [isLogModalVisible, setIsLogModalVisible] = useState(false);
  const [logCron, setLogCron] = useState<any>();
  const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [pageSize, setPageSize] = useState(20);

  const getCrons = () => {
    setLoading(true);
    request
      .get(`${config.apiPrefix}crons?searchValue=${searchText}`)
      .then((data: any) => {
        setValue(
          data.data.sort((a: any, b: any) => {
            const sortA = a.isDisabled ? 4 : a.status;
            const sortB = b.isDisabled ? 4 : b.status;
            return CrontabSort[sortA] - CrontabSort[sortB];
          }),
        );
      })
      .finally(() => setLoading(false));
  };

  const addCron = () => {
    setEditedCron(null as any);
    setIsModalVisible(true);
  };

  const editCron = (record: any, index: number) => {
    setEditedCron(record);
    setIsModalVisible(true);
  };

  const delCron = (record: any, index: number) => {
    Modal.confirm({
      title: '确认删除',
      content: (
        <>
          确认删除定时任务{' '}
          <Text style={{ wordBreak: 'break-all' }} type="warning">
            {record.name}
          </Text>{' '}
          吗
        </>
      ),
      onOk() {
        request
          .delete(`${config.apiPrefix}crons`, { data: [record._id] })
          .then((data: any) => {
            if (data.code === 200) {
              message.success('删除成功');
              const result = [...value];
              result.splice(index + pageSize * (currentPage - 1), 1);
              setValue(result);
            } else {
              message.error(data);
            }
          });
      },
      onCancel() {
        console.log('Cancel');
      },
    });
  };

  const runCron = (record: any, index: number) => {
    Modal.confirm({
      title: '确认运行',
      content: (
        <>
          确认运行定时任务{' '}
          <Text style={{ wordBreak: 'break-all' }} type="warning">
            {record.name}
          </Text>{' '}
          吗
        </>
      ),
      onOk() {
        request
          .put(`${config.apiPrefix}crons/run`, { data: [record._id] })
          .then((data: any) => {
            if (data.code === 200) {
              const result = [...value];
              result.splice(index + pageSize * (currentPage - 1), 1, {
                ...record,
                status: CrontabStatus.running,
              });
              setValue(result);
            } else {
              message.error(data);
            }
          });
      },
      onCancel() {
        console.log('Cancel');
      },
    });
  };

  const stopCron = (record: any, index: number) => {
    Modal.confirm({
      title: '确认停止',
      content: (
        <>
          确认停止定时任务{' '}
          <Text style={{ wordBreak: 'break-all' }} type="warning">
            {record.name}
          </Text>{' '}
          吗
        </>
      ),
      onOk() {
        request
          .put(`${config.apiPrefix}crons/stop`, { data: [record._id] })
          .then((data: any) => {
            if (data.code === 200) {
              const result = [...value];
              result.splice(index + pageSize * (currentPage - 1), 1, {
                ...record,
                pid: null,
                status: CrontabStatus.idle,
              });
              setValue(result);
            } else {
              message.error(data);
            }
          });
      },
      onCancel() {
        console.log('Cancel');
      },
    });
  };

  const enabledOrDisabledCron = (record: any, index: number) => {
    Modal.confirm({
      title: `确认${record.isDisabled === 1 ? '启用' : '禁用'}`,
      content: (
        <>
          确认{record.isDisabled === 1 ? '启用' : '禁用'}
          定时任务{' '}
          <Text style={{ wordBreak: 'break-all' }} type="warning">
            {record.name}
          </Text>{' '}
          吗
        </>
      ),
      onOk() {
        request
          .put(
            `${config.apiPrefix}crons/${
              record.isDisabled === 1 ? 'enable' : 'disable'
            }`,
            {
              data: [record._id],
            },
          )
          .then((data: any) => {
            if (data.code === 200) {
              const newStatus = record.isDisabled === 1 ? 0 : 1;
              const result = [...value];
              result.splice(index + pageSize * (currentPage - 1), 1, {
                ...record,
                isDisabled: newStatus,
              });
              setValue(result);
            } else {
              message.error(data);
            }
          });
      },
      onCancel() {
        console.log('Cancel');
      },
    });
  };

  const MoreBtn: React.FC<{
    record: any;
    index: number;
  }> = ({ record, index }) => (
    <Dropdown
      arrow
      trigger={['click']}
      overlay={
        <Menu onClick={({ key }) => action(key, record, index)}>
          <Menu.Item key="edit" icon={<EditOutlined />}>
            编辑
          </Menu.Item>
          <Menu.Item
            key="enableordisable"
            icon={
              record.isDisabled === 1 ? (
                <CheckCircleOutlined />
              ) : (
                <StopOutlined />
              )
            }
          >
            {record.isDisabled === 1 ? '启用' : '禁用'}
          </Menu.Item>
          {record.isSystem !== 1 && (
            <Menu.Item key="delete" icon={<DeleteOutlined />}>
              删除
            </Menu.Item>
          )}
        </Menu>
      }
    >
      <a>
        <EllipsisOutlined />
      </a>
    </Dropdown>
  );

  const action = (key: string | number, record: any, index: number) => {
    switch (key) {
      case 'edit':
        editCron(record, index);
        break;
      case 'enableordisable':
        enabledOrDisabledCron(record, index);
        break;
      case 'delete':
        delCron(record, index);
        break;
      default:
        break;
    }
  };

  const handleCancel = (cron?: any) => {
    setIsModalVisible(false);
    if (cron) {
      handleCrons(cron);
    }
  };

  const onSearch = (value: string) => {
    setSearchText(value);
  };

  const handleCrons = (cron: any) => {
    const index = value.findIndex((x) => x._id === cron._id);
    const result = [...value];
    if (index === -1) {
      result.push(cron);
    } else {
      result.splice(index, 1, {
        ...cron,
      });
    }
    setValue(result);
  };

  const getCronDetail = (cron: any) => {
    request
      .get(`${config.apiPrefix}crons/${cron._id}`)
      .then((data: any) => {
        console.log(value);
        const index = value.findIndex((x) => x._id === cron._id);
        console.log(index);
        const result = [...value];
        result.splice(index, 1, {
          ...cron,
          ...data.data,
        });
        setValue(result);
      })
      .finally(() => setLoading(false));
  };

  const onSelectChange = (selectedIds: any[]) => {
    setSelectedRowIds(selectedIds);
  };

  const rowSelection = {
    selectedRowIds,
    onChange: onSelectChange,
    selections: [
      Table.SELECTION_ALL,
      Table.SELECTION_INVERT,
      Table.SELECTION_NONE,
    ],
  };

  const delCrons = () => {
    Modal.confirm({
      title: '确认删除',
      content: <>确认删除选中的定时任务吗</>,
      onOk() {
        request
          .delete(`${config.apiPrefix}crons`, { data: selectedRowIds })
          .then((data: any) => {
            if (data.code === 200) {
              message.success('批量删除成功');
              setSelectedRowIds([]);
              getCrons();
            } else {
              message.error(data);
            }
          });
      },
      onCancel() {
        console.log('Cancel');
      },
    });
  };

  const operateCrons = (operationStatus: number) => {
    Modal.confirm({
      title: `确认${OperationName[operationStatus]}`,
      content: <>确认{OperationName[operationStatus]}选中的定时任务吗</>,
      onOk() {
        request
          .put(`${config.apiPrefix}crons/${OperationPath[operationStatus]}`, {
            data: selectedRowIds,
          })
          .then((data: any) => {
            if (data.code === 200) {
              getCrons();
            } else {
              message.error(data);
            }
          });
      },
      onCancel() {
        console.log('Cancel');
      },
    });
  };

  const onPageChange = (page: number, pageSize: number | undefined) => {
    setCurrentPage(page);
    setPageSize(pageSize as number);
    localStorage.setItem('pageSize', pageSize + '');
  };

  useEffect(() => {
    if (logCron) {
      localStorage.setItem('logCron', logCron._id);
      setIsLogModalVisible(true);
    }
  }, [logCron]);

  useEffect(() => {
    getCrons();
  }, [searchText]);

  useEffect(() => {
    if (document.body.clientWidth < 768) {
      setWidth('auto');
      setMarginLeft(0);
      setMarginTop(0);
    } else {
      setWidth('100%');
      setMarginLeft(0);
      setMarginTop(-72);
    }
    setPageSize(parseInt(localStorage.getItem('pageSize') || '20'));
  }, []);

  return (
    <PageContainer
      className="ql-container-wrapper crontab-wrapper"
      title="定时任务"
      extra={[
        <Search
          placeholder="请输入名称或者关键词"
          style={{ width: 'auto' }}
          enterButton
          loading={loading}
          onSearch={onSearch}
        />,
        <Button key="2" type="primary" onClick={() => addCron()}>
          添加定时
        </Button>,
      ]}
      header={{
        style: {
          padding: '4px 16px 4px 15px',
          position: 'sticky',
          top: 0,
          left: 0,
          zIndex: 20,
          marginTop,
          width,
          marginLeft,
        },
      }}
    >
      {selectedRowIds.length > 0 && (
        <div style={{ marginBottom: 16 }}>
          <Button type="primary" style={{ marginBottom: 5 }} onClick={delCrons}>
            批量删除
          </Button>
          <Button
            type="primary"
            onClick={() => operateCrons(0)}
            style={{ marginLeft: 8, marginBottom: 5 }}
          >
            批量启用
          </Button>
          <Button
            type="primary"
            onClick={() => operateCrons(1)}
            style={{ marginLeft: 8, marginRight: 8 }}
          >
            批量禁用
          </Button>
          <Button
            type="primary"
            style={{ marginRight: 8 }}
            onClick={() => operateCrons(2)}
          >
            批量运行
          </Button>
          <Button type="primary" onClick={() => operateCrons(3)}>
            批量停止
          </Button>
          <span style={{ marginLeft: 8 }}>
            已选择
            <a>{selectedRowIds?.length}</a>项
          </span>
        </div>
      )}
      <Table
        columns={columns}
        pagination={{
          hideOnSinglePage: true,
          current: currentPage,
          onChange: onPageChange,
          pageSize: pageSize,
          showSizeChanger: true,
          defaultPageSize: 20,
          showTotal: (total: number, range: number[]) =>
            `第 ${range[0]}-${range[1]} 条/总共 ${total} 条`,
        }}
        dataSource={value}
        rowKey="_id"
        size="middle"
        scroll={{ x: 768 }}
        loading={loading}
        rowSelection={rowSelection}
      />
      <CronLogModal
        visible={isLogModalVisible}
        handleCancel={() => {
          getCronDetail(logCron);
          setIsLogModalVisible(false);
        }}
        cron={logCron}
      />
      <CronModal
        visible={isModalVisible}
        handleCancel={handleCancel}
        cron={editedCron}
      />
    </PageContainer>
  );
}
Example #28
Source File: Icon.tsx    From html2sketch with MIT License 4 votes vote down vote up
IconSymbol: FC = () => {
  return (
    <Row>
      {/*<CaretUpOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/1.CaretUpOutlined'}*/}
      {/*/>*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.MailOutlined'}*/}
      {/*/>*/}
      {/*<StepBackwardOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
      {/*/>*/}
      {/*<StepForwardOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
      {/*/>*/}
      <StepForwardOutlined />
      <ShrinkOutlined />
      <ArrowsAltOutlined />
      <DownOutlined />
      <UpOutlined />
      <LeftOutlined />
      <RightOutlined />
      <CaretUpOutlined />
      <CaretDownOutlined />
      <CaretLeftOutlined />
      <CaretRightOutlined />
      <VerticalAlignTopOutlined />
      <RollbackOutlined />
      <FastBackwardOutlined />
      <FastForwardOutlined />
      <DoubleRightOutlined />
      <DoubleLeftOutlined />
      <VerticalLeftOutlined />
      <VerticalRightOutlined />
      <VerticalAlignMiddleOutlined />
      <VerticalAlignBottomOutlined />
      <ForwardOutlined />
      <BackwardOutlined />
      <EnterOutlined />
      <RetweetOutlined />
      <SwapOutlined />
      <SwapLeftOutlined />
      <SwapRightOutlined />
      <ArrowUpOutlined />
      <ArrowDownOutlined />
      <ArrowLeftOutlined />
      <ArrowRightOutlined />
      <LoginOutlined />
      <LogoutOutlined />
      <MenuFoldOutlined />
      <MenuUnfoldOutlined />
      <BorderBottomOutlined />
      <BorderHorizontalOutlined />
      <BorderInnerOutlined />
      <BorderOuterOutlined />
      <BorderLeftOutlined />
      <BorderRightOutlined />
      <BorderTopOutlined />
      <BorderVerticleOutlined />
      <PicCenterOutlined />
      <PicLeftOutlined />
      <PicRightOutlined />
      <RadiusBottomleftOutlined />
      <RadiusBottomrightOutlined />
      <RadiusUpleftOutlined />
      <RadiusUprightOutlined />
      <FullscreenOutlined />
      <FullscreenExitOutlined />
      <QuestionOutlined />
      <PauseOutlined />
      <MinusOutlined />
      <PauseCircleOutlined />
      <InfoOutlined />
      <CloseOutlined />
      <ExclamationOutlined />
      <CheckOutlined />
      <WarningOutlined />
      <IssuesCloseOutlined />
      <StopOutlined />
      <EditOutlined />
      <CopyOutlined />
      <ScissorOutlined />
      <DeleteOutlined />
      <SnippetsOutlined />
      <DiffOutlined />
      <HighlightOutlined />
      <AlignCenterOutlined />
      <AlignLeftOutlined />
      <AlignRightOutlined />
      <BgColorsOutlined />
      <BoldOutlined />
      <ItalicOutlined />
      <UnderlineOutlined />
      <StrikethroughOutlined />
      <RedoOutlined />
      <UndoOutlined />
      <ZoomInOutlined />
      <ZoomOutOutlined />
      <FontColorsOutlined />
      <FontSizeOutlined />
      <LineHeightOutlined />
      <SortAscendingOutlined />
      <SortDescendingOutlined />
      <DragOutlined />
      <OrderedListOutlined />
      <UnorderedListOutlined />
      <RadiusSettingOutlined />
      <ColumnWidthOutlined />
      <ColumnHeightOutlined />
      <AreaChartOutlined />
      <PieChartOutlined />
      <BarChartOutlined />
      <DotChartOutlined />
      <LineChartOutlined />
      <RadarChartOutlined />
      <HeatMapOutlined />
      <FallOutlined />
      <RiseOutlined />
      <StockOutlined />
      <BoxPlotOutlined />
      <FundOutlined />
      <SlidersOutlined />
      <AndroidOutlined />
      <AppleOutlined />
      <WindowsOutlined />
      <IeOutlined />
      <ChromeOutlined />
      <GithubOutlined />
      <AliwangwangOutlined />
      <DingdingOutlined />
      <WeiboSquareOutlined />
      <WeiboCircleOutlined />
      <TaobaoCircleOutlined />
      <Html5Outlined />
      <WeiboOutlined />
      <TwitterOutlined />
      <WechatOutlined />
      <AlipayCircleOutlined />
      <TaobaoOutlined />
      <SkypeOutlined />
      <FacebookOutlined />
      <CodepenOutlined />
      <CodeSandboxOutlined />
      <AmazonOutlined />
      <GoogleOutlined />
      <AlipayOutlined />
      <AntDesignOutlined />
      <AntCloudOutlined />
      <ZhihuOutlined />
      <SlackOutlined />
      <SlackSquareOutlined />
      <BehanceSquareOutlined />
      <DribbbleOutlined />
      <DribbbleSquareOutlined />
      <InstagramOutlined />
      <YuqueOutlined />
      <AlibabaOutlined />
      <YahooOutlined />
      <RedditOutlined />
      <SketchOutlined />
      <AccountBookOutlined />
      <AlertOutlined />
      <ApartmentOutlined />
      <ApiOutlined />
      <QqOutlined />
      <MediumWorkmarkOutlined />
      <GitlabOutlined />
      <MediumOutlined />
      <GooglePlusOutlined />
      <AppstoreAddOutlined />
      <AppstoreOutlined />
      <AudioOutlined />
      <AudioMutedOutlined />
      <AuditOutlined />
      <BankOutlined />
      <BarcodeOutlined />
      <BarsOutlined />
      <BellOutlined />
      <BlockOutlined />
      <BookOutlined />
      <BorderOutlined />
      <BranchesOutlined />
      <BuildOutlined />
      <BulbOutlined />
      <CalculatorOutlined />
      <CalendarOutlined />
      <CameraOutlined />
      <CarOutlined />
      <CarryOutOutlined />
      <CiCircleOutlined />
      <CiOutlined />
      <CloudOutlined />
      <ClearOutlined />
      <ClusterOutlined />
      <CodeOutlined />
      <CoffeeOutlined />
      <CompassOutlined />
      <CompressOutlined />
      <ContactsOutlined />
      <ContainerOutlined />
      <ControlOutlined />
      <CopyrightCircleOutlined />
      <CopyrightOutlined />
      <CreditCardOutlined />
      <CrownOutlined />
      <CustomerServiceOutlined />
      <DashboardOutlined />
      <DatabaseOutlined />
      <DeleteColumnOutlined />
      <DeleteRowOutlined />
      <DisconnectOutlined />
      <DislikeOutlined />
      <DollarCircleOutlined />
      <DollarOutlined />
      <DownloadOutlined />
      <EllipsisOutlined />
      <EnvironmentOutlined />
      <EuroCircleOutlined />
      <EuroOutlined />
      <ExceptionOutlined />
      <ExpandAltOutlined />
      <ExpandOutlined />
      <ExperimentOutlined />
      <ExportOutlined />
      <EyeOutlined />
      <FieldBinaryOutlined />
      <FieldNumberOutlined />
      <FieldStringOutlined />
      <DesktopOutlined />
      <DingtalkOutlined />
      <FileAddOutlined />
      <FileDoneOutlined />
      <FileExcelOutlined />
      <FileExclamationOutlined />
      <FileOutlined />
      <FileImageOutlined />
      <FileJpgOutlined />
      <FileMarkdownOutlined />
      <FilePdfOutlined />
      <FilePptOutlined />
      <FileProtectOutlined />
      <FileSearchOutlined />
      <FileSyncOutlined />
      <FileTextOutlined />
      <FileUnknownOutlined />
      <FileWordOutlined />
      <FilterOutlined />
      <FireOutlined />
      <FlagOutlined />
      <FolderAddOutlined />
      <FolderOutlined />
      <FolderOpenOutlined />
      <ForkOutlined />
      <FormatPainterOutlined />
      <FrownOutlined />
      <FunctionOutlined />
      <FunnelPlotOutlined />
      <GatewayOutlined />
      <GifOutlined />
      <GiftOutlined />
      <GlobalOutlined />
      <GoldOutlined />
      <GroupOutlined />
      <HddOutlined />
      <HeartOutlined />
      <HistoryOutlined />
      <HomeOutlined />
      <HourglassOutlined />
      <IdcardOutlined />
      <ImportOutlined />
      <InboxOutlined />
      <InsertRowAboveOutlined />
      <InsertRowBelowOutlined />
      <InsertRowLeftOutlined />
      <InsertRowRightOutlined />
      <InsuranceOutlined />
      <InteractionOutlined />
      <KeyOutlined />
      <LaptopOutlined />
      <LayoutOutlined />
      <LikeOutlined />
      <LineOutlined />
      <LinkOutlined />
      <Loading3QuartersOutlined />
      <LoadingOutlined />
      <LockOutlined />
      <MailOutlined />
      <ManOutlined />
      <MedicineBoxOutlined />
      <MehOutlined />
      <MenuOutlined />
      <MergeCellsOutlined />
      <MessageOutlined />
      <MobileOutlined />
      <MoneyCollectOutlined />
      <MonitorOutlined />
      <MoreOutlined />
      <NodeCollapseOutlined />
      <NodeExpandOutlined />
      <NodeIndexOutlined />
      <NotificationOutlined />
      <NumberOutlined />
      <PaperClipOutlined />
      <PartitionOutlined />
      <PayCircleOutlined />
      <PercentageOutlined />
      <PhoneOutlined />
      <PictureOutlined />
      <PoundCircleOutlined />
      <PoundOutlined />
      <PoweroffOutlined />
      <PrinterOutlined />
      <ProfileOutlined />
      <ProjectOutlined />
      <PropertySafetyOutlined />
      <PullRequestOutlined />
      <PushpinOutlined />
      <QrcodeOutlined />
      <ReadOutlined />
      <ReconciliationOutlined />
      <RedEnvelopeOutlined />
      <ReloadOutlined />
      <RestOutlined />
      <RobotOutlined />
      <RocketOutlined />
      <SafetyCertificateOutlined />
      <SafetyOutlined />
      <ScanOutlined />
      <ScheduleOutlined />
      <SearchOutlined />
      <SecurityScanOutlined />
      <SelectOutlined />
      <SendOutlined />
      <SettingOutlined />
      <ShakeOutlined />
      <ShareAltOutlined />
      <ShopOutlined />
      <ShoppingCartOutlined />
      <ShoppingOutlined />
      <SisternodeOutlined />
      <SkinOutlined />
      <SmileOutlined />
      <SolutionOutlined />
      <SoundOutlined />
      <SplitCellsOutlined />
      <StarOutlined />
      <SubnodeOutlined />
      <SyncOutlined />
      <TableOutlined />
      <TabletOutlined />
      <TagOutlined />
      <TagsOutlined />
      <TeamOutlined />
      <ThunderboltOutlined />
      <ToTopOutlined />
      <ToolOutlined />
      <TrademarkCircleOutlined />
      <TrademarkOutlined />
      <TransactionOutlined />
      <TrophyOutlined />
      <UngroupOutlined />
      <UnlockOutlined />
      <UploadOutlined />
      <UsbOutlined />
      <UserAddOutlined />
      <UserDeleteOutlined />
      <UserOutlined />
      <UserSwitchOutlined />
      <UsergroupAddOutlined />
      <UsergroupDeleteOutlined />
      <VideoCameraOutlined />
      <WalletOutlined />
      <WifiOutlined />
      <BorderlessTableOutlined />
      <WomanOutlined />
      <BehanceOutlined />
      <DropboxOutlined />
      <DeploymentUnitOutlined />
      <UpCircleOutlined />
      <DownCircleOutlined />
      <LeftCircleOutlined />
      <RightCircleOutlined />
      <UpSquareOutlined />
      <DownSquareOutlined />
      <LeftSquareOutlined />
      <RightSquareOutlined />
      <PlayCircleOutlined />
      <QuestionCircleOutlined />
      <PlusCircleOutlined />
      <PlusSquareOutlined />
      <MinusSquareOutlined />
      <MinusCircleOutlined />
      <InfoCircleOutlined />
      <ExclamationCircleOutlined />
      <CloseCircleOutlined />
      <CloseSquareOutlined />
      <CheckCircleOutlined />
      <CheckSquareOutlined />
      <ClockCircleOutlined />
      <FormOutlined />
      <DashOutlined />
      <SmallDashOutlined />
      <YoutubeOutlined />
      <CodepenCircleOutlined />
      <AliyunOutlined />
      <PlusOutlined />
      <LinkedinOutlined />
      <AimOutlined />
      <BugOutlined />
      <CloudDownloadOutlined />
      <CloudServerOutlined />
      <CloudSyncOutlined />
      <CloudUploadOutlined />
      <CommentOutlined />
      <ConsoleSqlOutlined />
      <EyeInvisibleOutlined />
      <FileGifOutlined />
      <DeliveredProcedureOutlined />
      <FieldTimeOutlined />
      <FileZipOutlined />
      <FolderViewOutlined />
      <FundProjectionScreenOutlined />
      <FundViewOutlined />
      <MacCommandOutlined />
      <PlaySquareOutlined />
      <OneToOneOutlined />
      <RotateLeftOutlined />
      <RotateRightOutlined />
      <SaveOutlined />
      <SwitcherOutlined />
      <TranslationOutlined />
      <VerifiedOutlined />
      <VideoCameraAddOutlined />
      <WhatsAppOutlined />

      {/*</Col>*/}
    </Row>
  );
}
Example #29
Source File: CorrelationMatrix.tsx    From posthog-foss with MIT License 4 votes vote down vote up
export function CorrelationMatrix(): JSX.Element {
    const { insightProps } = useValues(insightLogic)
    const logic = funnelLogic(insightProps)
    const { correlationsLoading, funnelCorrelationDetails, parseDisplayNameForCorrelation, correlationMatrixAndScore } =
        useValues(logic)
    const { setFunnelCorrelationDetails, openCorrelationPersonsModal } = useActions(logic)

    const actor = funnelCorrelationDetails?.result_type === FunnelCorrelationResultsType.Events ? 'event' : 'property'
    const action =
        funnelCorrelationDetails?.result_type === FunnelCorrelationResultsType.Events
            ? 'performed event'
            : 'have property'

    let displayName = <></>

    if (funnelCorrelationDetails) {
        const { first_value, second_value } = parseDisplayNameForCorrelation(funnelCorrelationDetails)
        displayName = (
            <>
                <PropertyKeyInfo value={first_value} />
                {second_value !== undefined && (
                    <>
                        {' :: '}
                        <PropertyKeyInfo value={second_value} disablePopover />
                    </>
                )}
            </>
        )
    }

    const { correlationScore, truePositive, falsePositive, trueNegative, falseNegative, correlationScoreStrength } =
        correlationMatrixAndScore

    const scoreIcon =
        correlationScoreStrength === 'strong' ? (
            <CheckCircleFilled style={{ color: 'var(--success)' }} />
        ) : correlationScoreStrength === 'moderate' ? (
            <MinusCircleOutlined style={{ color: 'var(--warning)' }} />
        ) : (
            <CloseCircleOutlined style={{ color: 'var(--danger)' }} />
        )

    const dismiss = (): void => setFunnelCorrelationDetails(null)

    return (
        <Modal
            className="correlation-matrix"
            visible={!!funnelCorrelationDetails}
            onCancel={dismiss}
            destroyOnClose
            footer={<Button onClick={dismiss}>Dismiss</Button>}
            width={600}
            title="Correlation details"
        >
            <div className="correlation-table-wrapper">
                {correlationsLoading ? (
                    <div className="mt text-center">
                        <Spinner size="lg" />
                    </div>
                ) : funnelCorrelationDetails ? (
                    <>
                        <p className="text-muted-alt mb">
                            The table below displays the correlation details for users who {action} <b>{displayName}</b>
                            .
                        </p>
                        <table>
                            <thead>
                                <tr className="table-title">
                                    <td colSpan={3}>Results matrix</td>
                                </tr>
                                <tr>
                                    <td>
                                        {funnelCorrelationDetails?.result_type === FunnelCorrelationResultsType.Events
                                            ? 'Performed event'
                                            : 'Has property'}
                                    </td>
                                    <td>Success</td>
                                    <td>Dropped off</td>
                                </tr>
                            </thead>
                            <tbody>
                                <tr>
                                    <td className="horizontal-header">Yes</td>
                                    <td>
                                        <Tooltip
                                            title={`True positive (TP) - Percentage of users who ${action} and completed the funnel.`}
                                        >
                                            <div className="percentage">
                                                {truePositive
                                                    ? percentage(truePositive / (truePositive + falsePositive))
                                                    : '0.00%'}
                                            </div>
                                        </Tooltip>
                                        {truePositive === 0 ? (
                                            '0 users'
                                        ) : (
                                            <Link
                                                onClick={() => {
                                                    openCorrelationPersonsModal(funnelCorrelationDetails, true)
                                                }}
                                            >
                                                {pluralize(truePositive, 'user', undefined, true, true)}
                                            </Link>
                                        )}
                                    </td>
                                    <td>
                                        <div className="percentage">
                                            <Tooltip
                                                title={`False negative (FN) - Percentage of users who ${action} and did not complete the funnel.`}
                                            >
                                                {falseNegative
                                                    ? percentage(falseNegative / (falseNegative + trueNegative))
                                                    : '0.00%'}
                                            </Tooltip>
                                        </div>
                                        {falseNegative === 0 ? (
                                            '0 users'
                                        ) : (
                                            <Link
                                                onClick={() => {
                                                    openCorrelationPersonsModal(funnelCorrelationDetails, false)
                                                }}
                                            >
                                                {pluralize(falseNegative, 'user', undefined, true, true)}
                                            </Link>
                                        )}
                                    </td>
                                </tr>
                                <tr>
                                    <td className="horizontal-header">No</td>
                                    <td>
                                        <div className="percentage">
                                            <Tooltip
                                                title={`False positive (FP) - Percentage of users who did not ${action} and completed the funnel.`}
                                            >
                                                {falsePositive
                                                    ? percentage(falsePositive / (truePositive + falsePositive))
                                                    : '0.00%'}
                                            </Tooltip>
                                        </div>
                                        {pluralize(falsePositive, 'user', undefined, true, true)}
                                    </td>
                                    <td>
                                        <div className="percentage">
                                            <Tooltip
                                                title={`True negative (TN) - Percentage of users who did not ${action} and did not complete the funnel.`}
                                            >
                                                {trueNegative
                                                    ? percentage(trueNegative / (falseNegative + trueNegative))
                                                    : '0.00%'}
                                            </Tooltip>
                                        </div>
                                        {pluralize(trueNegative, 'user', undefined, true, true)}
                                    </td>
                                </tr>
                                <tr>
                                    <td className="horizontal-header" />
                                    <td>
                                        <b>100%</b>
                                    </td>
                                    <td>
                                        <b>100%</b>
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                        <div className="mt text-center">
                            {capitalizeFirstLetter(funnelCorrelationDetails?.result_type || '')} <b>{displayName}</b>{' '}
                            has a{' '}
                            {funnelCorrelationDetails?.correlation_type === FunnelCorrelationType.Success ? (
                                <b className="text-success">
                                    positive{' '}
                                    <Tooltip
                                        title={`Positive correlation means this ${actor} is correlated with a successful conversion.`}
                                    >
                                        <InfoCircleOutlined className="cursor-pointer" />
                                    </Tooltip>
                                </b>
                            ) : (
                                <b className="text-danger">
                                    negative{' '}
                                    <Tooltip
                                        title={`Negative correlation means this ${actor} is correlated with an unsuccessful conversion (user dropped off).`}
                                    >
                                        <InfoCircleOutlined className="cursor-pointer" />
                                    </Tooltip>
                                </b>
                            )}{' '}
                            correlation score of{' '}
                            <b
                                style={{
                                    color:
                                        correlationScoreStrength === 'strong'
                                            ? 'var(--success)'
                                            : correlationScoreStrength === 'moderate'
                                            ? 'var(--warning)'
                                            : 'var(--danger)',
                                }}
                            >
                                <Tooltip title={`This ${actor} has ${correlationScoreStrength} correlation.`}>
                                    <span style={{ cursor: 'pointer' }}>
                                        {scoreIcon} {correlationScore.toFixed(3)}
                                    </span>
                                </Tooltip>
                            </b>
                        </div>
                    </>
                ) : (
                    <div>
                        <InlineMessage type="danger">
                            We could not load the details for this correlation value. Please recreate your funnel and
                            try again.
                        </InlineMessage>
                    </div>
                )}
            </div>
        </Modal>
    )
}