@ant-design/icons#EditOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#EditOutlined. 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: UnControlledTableHeaderPanel.tsx    From datart with Apache License 2.0 6 votes vote down vote up
EditableLabel: FC<{
  label: string;
  editable?: Boolean;
  onChange: (value: string) => void;
}> = memo(({ label, editable = true, onChange }) => {
  const [isEditing, setIsEditing] = useState(false);

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

  return <StyledEditableLabel>{render()}</StyledEditableLabel>;
})
Example #2
Source File: CommandInput.tsx    From posthog-foss with MIT License 6 votes vote down vote up
export function CommandInput(): JSX.Element {
    const { input, isSqueak, activeFlow } = useValues(commandPaletteLogic)
    const { setInput } = useActions(commandPaletteLogic)

    return (
        <div className="palette__row">
            {isSqueak ? (
                <img src={PostHogIcon} className="palette__icon" />
            ) : activeFlow ? (
                <activeFlow.icon className="palette__icon" /> ?? <EditOutlined className="palette__icon" />
            ) : (
                <SearchOutlined className="palette__icon" />
            )}
            <input
                className="palette__display palette__input ph-no-capture"
                autoFocus
                value={input}
                onChange={(event) => {
                    setInput(event.target.value)
                }}
                placeholder={activeFlow?.instruction ?? 'What would you like to do? Try some suggestions…'}
                data-attr="command-palette-input"
            />
        </div>
    )
}
Example #3
Source File: BasicUnControlledTabPanel.tsx    From datart with Apache License 2.0 6 votes vote down vote up
EditableTabHeader: FC<{
  label: string;
  rowKey: string;
  editable?: Boolean;
  onChange: (key: string, value: string) => void;
}> = memo(({ label, rowKey, editable = true, onChange }) => {
  const [isEditing, setIsEditing] = useState(false);

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

  return render();
})
Example #4
Source File: MoreOption.spec.tsx    From next-basics with GNU General Public License v3.0 6 votes vote down vote up
describe("MoreOption", () => {
  it("should work", () => {
    const wrapper = shallow(<MoreOption />);
    expect(wrapper.find("span").text()).toEqual("20 ");
  });

  it("should work with props", () => {
    const mockClickFn = jest.fn();
    const wrapper = mount(
      <MoreOption itemsCount={10} onChange={mockClickFn} />
    );

    wrapper.find(EditOutlined).simulate("click");

    wrapper.find(InputNumber).invoke("onChange")(100);

    wrapper.find(InputNumber).invoke("onBlur")(null);

    expect(mockClickFn).toBeCalledWith(100);
  });
});
Example #5
Source File: index.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
TagFilter: React.ForwardRefRenderFunction<any, ITagFilterProps> = ({ isOpen = false, value, onChange, editable = true, cluster, range, id, onOpenFire }, ref) => {
  const { t } = useTranslation();
  const [editing, setEditing] = useState<boolean>(isOpen);
  const [varsMap, setVarsMap] = useState<{ string?: string | string[] | undefined }>({});
  const [data, setData] = useState<FormType>();
  const handleEditClose = (v: FormType) => {
    if (v) {
      onChange(v, true);
      setData(v);
    }
    setEditing(false);
  };

  useEffect(() => {
    value && setData(value);
  }, [value]);

  const handleVariableChange = (index: number, v: string | string[], options) => {
    const newData = data ? { var: _.cloneDeep(data.var) } : { var: [] };
    const newDataWithOptions = data ? { var: _.cloneDeep(data.var) } : { var: [] };
    // newData.var[index].selected = v;
    setVaraiableSelected(newData.var[index].name, v, id);
    setVarsMap((varsMap) => ({ ...varsMap, [`$${newData.var[index].name}`]: v }));
    options && (newDataWithOptions.var[index].options = options);
    setData(newData);
    onChange(newData, false, newDataWithOptions);
  };

  return (
    <div className='tag-area'>
      <div className={classNames('tag-content', 'tag-content-close')}>
        {data?.var && data?.var.length > 0 && (
          <>
            {data.var.map((expression, index) => (
              <DisplayItem
                expression={expression}
                index={index}
                data={data.var}
                onChange={handleVariableChange}
                cluster={cluster}
                range={range}
                key={index}
                id={id}
                varsMap={varsMap}
              ></DisplayItem>
            ))}
            {editable && (
              <EditOutlined
                className='icon'
                onClick={() => {
                  setEditing(true);
                  onOpenFire && onOpenFire();
                }}
              ></EditOutlined>
            )}
          </>
        )}
        {(data ? data?.var.length === 0 : true) && editable && (
          <div
            className='add-variable-tips'
            onClick={() => {
              setEditing(true);
              onOpenFire && onOpenFire();
            }}
          >
            {t('添加大盘变量')}
          </div>
        )}
      </div>
      <EditItem visible={editing} onChange={handleEditClose} value={data} range={range} id={id} />
    </div>
  );
}
Example #6
Source File: PluginField.tsx    From posthog-foss with MIT License 5 votes vote down vote up
export function PluginField({
    value,
    onChange,
    fieldConfig,
}: {
    value?: any
    onChange?: (value: any) => void
    fieldConfig: PluginConfigSchema
}): JSX.Element {
    const [editingSecret, setEditingSecret] = useState(false)
    if (
        fieldConfig.secret &&
        !editingSecret &&
        value &&
        (value === SECRET_FIELD_VALUE || value.name === SECRET_FIELD_VALUE)
    ) {
        return (
            <Button
                icon={<EditOutlined />}
                onClick={() => {
                    onChange?.(fieldConfig.default || '')
                    setEditingSecret(true)
                }}
            >
                Reset secret {fieldConfig.type === 'attachment' ? 'attachment' : 'field'}
            </Button>
        )
    }

    return fieldConfig.type === 'attachment' ? (
        <UploadField value={value} onChange={onChange} />
    ) : fieldConfig.type === 'string' ? (
        <Input value={value} onChange={onChange} autoFocus={editingSecret} className="ph-no-capture" />
    ) : fieldConfig.type === 'choice' ? (
        <Select dropdownMatchSelectWidth={false} value={value} className="ph-no-capture" onChange={onChange} showSearch>
            {fieldConfig.choices.map((choice) => (
                <Select.Option value={choice} key={choice}>
                    {choice}
                </Select.Option>
            ))}
        </Select>
    ) : (
        <strong style={{ color: 'var(--danger)' }}>
            Unknown field type "<code>{fieldConfig.type}</code>".
            <br />
            You may need to upgrade PostHog!
        </strong>
    )
}
Example #7
Source File: Tile.tsx    From ant-extensions with MIT License 5 votes vote down vote up
Tile: React.FC<ITileConfig> = React.memo((item) => {
  const { isEditing, editWidget, renderWidget, findWidget } = useContext(Context);
  const style = useMemo(
    () => ({
      color: item.color || "inherit"
    }),
    [item.color]
  );

  const [expanded, setExpanded] = useState(false);

  const widget = useMemo(() => findWidget(item.widgetId), [findWidget, item.widgetId]);

  return (
    <Item item={item} expanded={!isEditing && expanded}>
      <div className="ant-ext-pm__tileHead">
        <span style={style}>{item.iconCls && <i className={item.iconCls} />}</span>
        <label style={style}>{item.title}</label>
        <div>
          {item.info && (
            <Tooltip
              overlay={<pre dangerouslySetInnerHTML={{ __html: item.info }} />}
              overlayClassName="ant-ext-pm__tileInfo"
            >
              <button>
                <InfoCircleOutlined />
              </button>
            </Tooltip>
          )}
          {!isEditing && item.expandable && (
            <button className="ant-ext-pm__tileExpander" onClick={() => setExpanded(!expanded)}>
              {expanded ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
            </button>
          )}
          {isEditing && (
            <button onClick={() => editWidget(item.widgetId)}>
              <EditOutlined />
            </button>
          )}
        </div>
      </div>
      <div className="ant-ext-pm__tileBody">
        {!isEditing && renderWidget(item.widgetId)}
        {isEditing && widget && (
          <div style={{ placeSelf: "center", textAlign: "center" }}>
            {widget.icon}
            <div>{widget.title}</div>
          </div>
        )}
      </div>
    </Item>
  );
})
Example #8
Source File: InputConfirm.tsx    From iot-center-v2 with MIT License 5 votes vote down vote up
InputConfirm: React.FC<TInputConfirmProps> = (props) => {
  const {value, onValueChange, tooltip} = props
  const [newValue, setNewValue] = useState('')
  const [isEditing, setIsEditing] = useState(false)
  const [loading, setLoading] = useState(false)

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

  return (
    <Row>
      <Col flex="auto">
        {!isEditing ? (
          value ?? ''
        ) : (
          <Tooltip title={tooltip ?? ''}>
            <Input
              value={newValue}
              onChange={(e) => setNewValue(e.target.value)}
              style={{width: '100%'}}
            />
          </Tooltip>
        )}
      </Col>
      <Col>
        {!isEditing ? (
          <Tooltip title={'Edit'} key="edit">
            <Button
              size="small"
              type="text"
              icon={<EditOutlined />}
              onClick={() => setIsEditing(true)}
            ></Button>
          </Tooltip>
        ) : (
          <>
            <Tooltip title={loading ? '' : 'Cancel'} color="red" key="cancel">
              <Button
                size="small"
                type="text"
                disabled={loading}
                icon={<CloseOutlined />}
                onClick={() => {
                  setIsEditing(false)
                }}
                danger
              ></Button>
            </Tooltip>
            <Tooltip title={loading ? '' : 'Save'} color="green">
              <Button
                size="small"
                type="text"
                disabled={loading}
                loading={loading}
                style={{color: 'green'}}
                icon={<CheckOutlined />}
                onClick={async () => {
                  try {
                    setLoading(true)
                    await (onValueChange ?? (() => undefined))(newValue)
                  } finally {
                    setIsEditing(false)
                    setLoading(false)
                  }
                }}
              ></Button>
            </Tooltip>
          </>
        )}
      </Col>
    </Row>
  )
}
Example #9
Source File: node.tsx    From imove with MIT License 5 votes vote down vote up
nodeMenuConfig = [
  {
    key: 'copy',
    title: '复制',
    icon: <CopyOutlined />,
    handler: shortcuts.copy.handler,
  },
  {
    key: 'delete',
    title: '删除',
    icon: <DeleteOutlined />,
    handler: shortcuts.delete.handler,
  },
  {
    key: 'rename',
    title: '编辑文本',
    icon: <EditOutlined />,
    showDividerBehind: true,
    handler() {
      // TODO
    },
  },
  {
    key: 'bringToTop',
    title: '置于顶层',
    icon: <XIcon type={'icon-bring-to-top'} />,
    handler: shortcuts.bringToTop.handler,
  },
  {
    key: 'bringToBack',
    title: '置于底层',
    icon: <XIcon type={'icon-bring-to-bottom'} />,
    showDividerBehind: true,
    handler: shortcuts.bringToBack.handler,
  },
  {
    key: 'editCode',
    title: '编辑代码',
    icon: <FormOutlined />,
    disabled(flowChart: Graph) {
      return getSelectedNodes(flowChart).length !== 1;
    },
    handler(flowChart: Graph) {
      flowChart.trigger('graph:editCode');
    },
  },
  {
    key: 'executeCode',
    title: '执行代码',
    icon: <CodeOutlined />,
    disabled(flowChart: Graph) {
      return getSelectedNodes(flowChart).length !== 1;
    },
    handler(flowChart: Graph) {
      flowChart.trigger('graph:runCode');
    },
  },
]
Example #10
Source File: index.tsx    From 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 #11
Source File: Children.tsx    From ui-visualization with MIT License 5 votes vote down vote up
Children = (props: Props) => {

    const childLayout = [
        { i: 'd', x: 6, y: 0, w: 3, h: 2, minW: 2, maxW: 4 },
        { i: 'e', x: 8, y: 0, w: 1, h: 2 }
    ]
    const [edit, setEdit] = useState<boolean>(false);

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

    return (
        <Fragment>

            <GridLayout
                className="layout"
                layout={layout}
                cols={12}
                rowHeight={30}
                isDraggable={edit}
                isResizable={edit}
                width={1200}>
                {
                    layout.map(i => <div key={i.i} style={{ backgroundColor: '#69E2D4' }}>{i.i}</div>)
                }
            </GridLayout>
            <div className={styles.layoutOption}>
                {edit ?
                    <div style={{ float: 'right' }}>

                        <Tooltip title="新增" style={{ float: 'right' }}>
                            <Button
                                type="danger"
                                shape="circle"
                                size="large"
                                onClick={() => {
                                    layout.push({ i: Math.random(), x: 8, y: 7, w: 4, h: 4 })
                                    setLayout([...layout]);
                                }}
                            >
                                <PlusOutlined />
                            </Button>
                        </Tooltip>
                        <div style={{ float: 'right', marginLeft: 10 }}>
                            <Tooltip title="保存" >
                                <Button
                                    type="primary"
                                    shape="circle"
                                    size="large"
                                    onClick={() => {
                                        setEdit(false)
                                    }}
                                >
                                    <SaveOutlined />
                                </Button>
                            </Tooltip>
                        </div>
                    </div> :
                    <div style={{ float: 'right', textAlign: 'center' }}>
                        <Tooltip title="编辑" >
                            <Button
                                type="danger"
                                shape="circle"
                                size="large"
                                onClick={() => {
                                    setEdit(true)
                                    props.edit()
                                }}
                            >
                                <EditOutlined />
                            </Button>
                        </Tooltip>
                    </div>
                }
            </div>
        </Fragment >
    )
}
Example #12
Source File: index.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
export default function index(props: IProps) {
  const { editable = true, value } = props;
  return (
    <Space align='baseline'>
      <Dropdown
        overlay={
          <Menu>
            {_.isEmpty(value) ? (
              <div style={{ textAlign: 'center' }}>暂无数据</div>
            ) : (
              _.map(value, (item, idx) => {
                return (
                  <Menu.Item key={idx}>
                    <a href={item.url} target={item.targetBlank ? '_blank' : '_self'}>
                      {item.title}
                    </a>
                  </Menu.Item>
                );
              })
            )}
          </Menu>
        }
      >
        <Button>
          大盘链接 <DownOutlined />
        </Button>
      </Dropdown>
      {editable && (
        <EditOutlined
          style={{ fontSize: 18 }}
          className='icon'
          onClick={() => {
            Edit({
              initialValues: value,
              onOk: (newValue) => {
                props.onChange(newValue);
              },
            });
          }}
        />
      )}
    </Space>
  );
}
Example #13
Source File: index.tsx    From visual-layout with MIT License 5 votes vote down vote up
Project: React.FC<{
  project: ProjectObject;
  appService: AppService;
  setVisible: (visible: boolean) => void;
}> = ({ project, appService, setVisible }) => {
  const operation = [
    {
      key: 'EditOutlined',
      icon: (
        <div
          className={styles.item}
          onClick={() => {
            appService.set(project.id);
            setVisible(false);
          }}
        >
          <EditOutlined />
        </div>
      ),
    },
    {
      key: 'ArrowDownOutlined',
      icon: (
        <div
          className={styles.item}
          onClick={() => {
            exportCode(appService.project);
          }}
        >
          <ArrowDownOutlined />
        </div>
      ),
    },
    {
      key: 'DeleteOutlined',
      icon: (
        <Popconfirm
          title="确定删除项目"
          onConfirm={() => appService.delete(project.id)}
          onCancel={() => {}}
          okText="是"
          cancelText="否"
        >
          <div className={styles.item}>
            <DeleteOutlined style={{ color: 'red' }} />
          </div>
        </Popconfirm>
      ),
    },
  ];

  const isSelect = appService.project.id === project.id;

  return (
    <div className={styles.container}>
      <div className={styles.operation}>
        <span className={isSelect ? styles.select : ''} />
        {operation.map(({ key, icon }) => (
          <div key={key}>{icon}</div>
        ))}
      </div>
      <div className={styles.info}>
        <div>
          <span>项目名: </span>
          <span>{project.name ? project.name : '--'}</span>
        </div>
        <div>
          <span>项目描述: </span>
          <span>{project.description ? project.description : '--'}</span>
        </div>
      </div>
    </div>
  );
}
Example #14
Source File: CommentOptions.tsx    From foodie with MIT License 5 votes vote down vote up
CommentOptions: React.FC<IProps> = (props) => {
    const [isOpen, setIsOpen] = useState(false);
    const isOpenRef = useRef(isOpen);
    const dispatch = useDispatch();

    useEffect(() => {
        document.addEventListener('click', handleClickOutside);

        return () => {
            document.removeEventListener('click', handleClickOutside);
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    useEffect(() => {
        isOpenRef.current = isOpen;
    }, [isOpen]);

    const handleClickOutside = (e: Event) => {
        const option = (e.target as HTMLDivElement).closest(`#comment_${props.comment.id}`);

        if (!option && isOpenRef.current) {
            setIsOpen(false);
        }
    }

    const toggleOpen = () => {
        setIsOpen(!isOpen);
    }

    const onClickDelete = () => {
        dispatch(setTargetComment(props.comment));
        props.openDeleteModal();
    }

    const onClickEdit = () => {
        setIsOpen(false);

        props.onClickEdit();
        dispatch(setTargetComment(props.comment));
    }

    return (
        <div className="relative z-10" id={`comment_${props.comment.id}`}>
            <div
                className="p-2 rounded-full flex items-center justify-center cursor-pointer hover:bg-gray-200 dark:text-white dark:hover:bg-indigo-1100"
                onClick={toggleOpen}
            >
                <EllipsisOutlined style={{ fontSize: '20px' }} />
            </div>
            {isOpen && (
                <div className=" w-56 flex flex-col bg-white dark:bg-indigo-1000 rounded-md shadow-lg overflow-hidden absolute top-8 right-3 border border-gray-200 dark:border-gray-800 divide-y divide-gray-100 dark:divide-gray-800">
                    {props.comment.isOwnComment && (
                        <h4
                            className="p-4 flex items-center hover:bg-indigo-700 hover:text-white cursor-pointer dark:text-white"
                            onClick={onClickEdit}
                        >
                            <EditOutlined className="mr-4" />
                            Edit Comment
                        </h4>
                    )}
                    <h4
                        className="p-4 flex items-center hover:bg-indigo-700 hover:text-white cursor-pointer dark:text-white"
                        onClick={onClickDelete}
                    >
                        <DeleteOutlined className="mr-4" />
                        Delete Comment
                </h4>
                </div>
            )}
        </div>
    );
}
Example #15
Source File: TriggerData.tsx    From leek with Apache License 2.0 5 votes vote down vote up
function TriggerData(props) {
  return [
    {
      title: "ID",
      dataIndex: "id",
      key: "id",
      render: (id) => {
        return (
          <Space direction="horizontal">
            <SlackOutlined /> <Text strong>{id}</Text>
          </Space>
        );
      },
    },
    {
      title: "Status",
      dataIndex: "enabled",
      key: "ENABLED",
      render: (enabled) => {
        return enabled ? (
          <Tag color="green">Enabled</Tag>
        ) : (
          <Tag color="red">Disabled</Tag>
        );
      },
    },
    {
      title: "States",
      dataIndex: "states",
      key: "states",
      render: (states) => {
        return states && states.length > 0 ? (
          states.map((state, key) => <TaskState state={state} key={key} />)
        ) : (
          <Tag>{"All STATES"}</Tag>
        );
      },
    },
    {
      title: "Environments",
      dataIndex: "envs",
      key: "envs",
      render: (envs) => {
        return envs && envs.length > 0 ? (
          envs.map((env, key) => <Tag key={key}>{env}</Tag>)
        ) : (
          <Tag>{"All ENVS"}</Tag>
        );
      },
    },
    {
      title: "Actions",
      dataIndex: "id",
      key: "id",
      render: (id, record) => {
        return (
          <Space direction="horizontal">
            <Button
              onClick={() => props.handleEditTrigger(id, record)}
              size="small"
              loading={props.triggersModifying}
              icon={<EditOutlined />}
            />
            <Button
              onClick={() => props.handleDeleteTrigger(id)}
              size="small"
              type="primary"
              ghost
              danger
              loading={props.triggersModifying}
              icon={<DeleteOutlined />}
            />
          </Space>
        );
      },
    },
  ];
}
Example #16
Source File: ApiKeyCard.tsx    From jitsu with MIT License 5 votes vote down vote up
export function ApiKeyCard({ apiKey: key, showDocumentation }: ApiKeyCardProps) {
  const services = useServices()
  const [loading, setLoading] = useState(false)
  const rotateKey = async (key: ApiKey, type: "jsAuth" | "serverAuth"): Promise<string> => {
    let newKey = apiKeysStore.generateApiToken(type === "jsAuth" ? "js" : "s2s")
    await flowResult(apiKeysStore.patch(key.uid, { [type]: newKey }))
    actionNotification.info("New key has been generated and saved")
    return newKey
  }

  let deleteAction = async () => {
    confirmDelete({
      entityName: "api key",
      action: async () => {
        setLoading(true)
        try {
          await flowResult(apiKeysStore.delete(key.uid))
        } catch (error) {
          handleError(error, "Unable to delete API key at this moment, please try later.")
        } finally {
          setLoading(false)
        }
      },
    })
  }
  let editLink = generatePath(apiKeysRoutes.editExact, {
    projectId: services.activeProject.id,
    id: key.uid.replace(".", "-"),
  })
  return (
    <ConnectionCard
      loading={loading}
      title={APIKeyUtil.getDisplayName(key)}
      icon={apiKeysReferenceMap.js.icon}
      deleteAction={deleteAction}
      editAction={editLink}
      menuOverlay={
        <Menu>
          <Menu.Item icon={<EditOutlined />}>
            <NavLink to={editLink}>Edit</NavLink>
          </Menu.Item>
          <Menu.Item icon={<DeleteOutlined />} onClick={deleteAction}>
            Delete
          </Menu.Item>
        </Menu>
      }
      rename={async newName => {
        await flowResult(apiKeysStore.patch(key.uid, { comment: newName }))
      }}
      subtitle={<a onClick={showDocumentation}>Show connection instructions→</a>}
      status={
        <>
          <div className="text-xs">
            <div className="flex flex-nowrap items-center">
              <span className="inline-block whitespace-nowrap w-16 text-xxs">Server Key</span>{" "}
              <SecretKey rotateKey={() => rotateKey(key, "serverAuth")}>{key.serverAuth}</SecretKey>
            </div>
            <div className="flex flex-nowrap items-center pt-2">
              <span className="inline-block whitespace-nowrap w-16 text-xxs">JS Key</span>
              <SecretKey rotateKey={() => rotateKey(key, "jsAuth")}>{key.jsAuth}</SecretKey>
            </div>
          </div>
        </>
      }
    />
  )
}
Example #17
Source File: NewActionButton.tsx    From posthog-foss with MIT License 5 votes vote down vote up
export function NewActionButton(): JSX.Element {
    const [visible, setVisible] = useState(false)
    const [appUrlsVisible, setAppUrlsVisible] = useState(false)

    return (
        <>
            <Button type="primary" icon={<PlusOutlined />} onClick={() => setVisible(true)} data-attr="create-action">
                New Action
            </Button>
            <Modal
                visible={visible}
                style={{ cursor: 'pointer' }}
                onCancel={() => {
                    setVisible(false)
                    setAppUrlsVisible(false)
                }}
                title="Create new action"
                footer={[
                    appUrlsVisible && (
                        <Button key="back-button" onClick={() => setAppUrlsVisible(false)}>
                            Back
                        </Button>
                    ),
                    <Button
                        key="cancel-button"
                        onClick={() => {
                            setVisible(false)
                            setAppUrlsVisible(false)
                        }}
                    >
                        Cancel
                    </Button>,
                ]}
            >
                {!appUrlsVisible && (
                    <Row gutter={2} justify="space-between">
                        <Col xs={11}>
                            <Card
                                title="Inspect element on your site"
                                onClick={() => setAppUrlsVisible(true)}
                                size="small"
                            >
                                <div style={{ textAlign: 'center', fontSize: 40 }}>
                                    <SearchOutlined />
                                </div>
                            </Card>
                        </Col>
                        <Col xs={11}>
                            <Card
                                title="From event or pageview"
                                onClick={() => {
                                    router.actions.push(urls.createAction())
                                }}
                                size="small"
                            >
                                <div style={{ textAlign: 'center', fontSize: 40 }} data-attr="new-action-pageview">
                                    <EditOutlined />
                                </div>
                            </Card>
                        </Col>
                    </Row>
                )}
                {appUrlsVisible && <AuthorizedUrlsTable />}
            </Modal>
        </>
    )
}
Example #18
Source File: CourseAdminPanel.tsx    From office-hours with GNU General Public License v3.0 5 votes vote down vote up
export default function CourseAdminPanel({
  defaultPage,
  courseId,
}: CourseAdminPageProps): ReactElement {
  const profile = useProfile();
  const [currentSettings, setCurrentSettings] = useState(
    defaultPage || CourseAdminOptions.CHECK_IN
  );

  const router = useRouter();

  return (
    <Row>
      <Col span={4} style={{ textAlign: "center" }}>
        <SettingsPanelAvatar avatarSize={20} />
        <CenteredText>
          Welcome back
          <br />
          {profile?.firstName} {profile?.lastName}
          {!profile?.photoURL && (
            <Tooltip
              title={
                "You should consider uploading a profile picture to make yourself more recognizable to students"
              }
            >
              <span>
                <QuestionCircleOutlined
                  style={{ marginLeft: "5px" }}
                  onClick={() => {
                    router.push(`/settings?cid=${courseId}`);
                  }}
                />
              </span>
            </Tooltip>
          )}
        </CenteredText>
        <Menu
          defaultSelectedKeys={[currentSettings]}
          onClick={(e) => setCurrentSettings(e.key as CourseAdminOptions)}
          style={{ background: "#f8f9fb", paddingTop: "20px" }}
        >
          <Menu.Item key={CourseAdminOptions.CHECK_IN} icon={<EditOutlined />}>
            TA Check In/Out Times
          </Menu.Item>
          <Menu.Item key={CourseAdminOptions.OVERRIDES} icon={<BellOutlined />}>
            Course Overrides
          </Menu.Item>
        </Menu>
      </Col>
      <VerticalDivider />
      <Space direction="vertical" size={40} style={{ flexGrow: 1 }}>
        <Col span={20}>
          {currentSettings === CourseAdminOptions.CHECK_IN && (
            <TACheckInCheckOutTimes courseId={courseId} />
          )}
          {currentSettings === CourseAdminOptions.OVERRIDES && (
            <CourseOverrideSettings courseId={courseId} />
          )}
        </Col>
      </Space>
    </Row>
  );
}
Example #19
Source File: FlowNameView.tsx    From Protoman with MIT License 5 votes vote down vote up
FlowNameView: React.FunctionComponent<Props> = ({}) => {
  const dispatch = useDispatch();

  const [draftName, setDraftName] = React.useState('');

  const [isEditingName, setIsEditingName] = React.useState(false);
  const startEditing = (): void => setIsEditingName(true);
  const stopEditing = (): void => setIsEditingName(false);

  const [isInvalidName, setIsInvalidName] = React.useState(false);

  const collection = useSelector(selectCurrentCol);
  const flowName = useSelector((s: AppState) => s.currentFlow);

  React.useEffect(() => {
    if (flowName) {
      setDraftName(flowName);
    }
  }, [flowName]);

  function checkName(newName: string): boolean {
    return newName === flowName || !collection?.flows?.map(([n]): string => n)?.includes(newName);
  }

  return (
    <TitleWrapper>
      {isEditingName ? (
        <Form.Item
          validateStatus={isInvalidName ? 'error' : ''}
          style={{ margin: 0 }}
          help={isInvalidName ? 'Invalid Name' : ''}
        >
          <TitleInput
            value={draftName}
            onChange={(e): void => {
              setIsInvalidName(!checkName(e.target.value));
              setDraftName(e.target.value);
            }}
            onKeyDown={(e): void => {
              switch (e.keyCode) {
                case 27: // esc
                  setDraftName(name);
                  stopEditing();
                  break;
                case 13: // enter
                  if (!isInvalidName) {
                    dispatch(changeFlowName(draftName));
                    stopEditing();
                  }
              }
            }}
          />
        </Form.Item>
      ) : (
        <>
          <Title>{draftName}</Title>
          <Button shape="circle" size="small" onClick={startEditing} style={{ marginLeft: 4 }}>
            <EditOutlined />
          </Button>
        </>
      )}
    </TitleWrapper>
  );
}
Example #20
Source File: detail.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
export default function DashboardDetail() {
  const refreshRef = useRef<{ closeRefresh: Function }>();
  const { t } = useTranslation();
  const { id, busiId } = useParams<URLParam>();
  const [groupForm] = Form.useForm();
  const history = useHistory();
  const Ref = useRef<any>(null);
  const { clusters } = useSelector<CommonRootState, CommonStoreState>((state) => state.common);
  const localCluster = localStorage.getItem('curCluster');
  const [curCluster, setCurCluster] = useState<string>(localCluster || clusters[0]);
  if (!localCluster && clusters.length > 0) {
    setCurCluster(clusters[0]);
    localStorage.setItem('curCluster', clusters[0]);
  }
  const [dashboard, setDashboard] = useState<Dashboard>({
    create_by: '',
    favorite: 0,
    id: 0,
    name: '',
    tags: '',
    update_at: 0,
    update_by: '',
  });
  const [step, setStep] = useState<number | null>(null);
  const [titleEditing, setTitleEditing] = useState(false);
  const [chartGroup, setChartGroup] = useState<Group[]>([]);
  const [variableConfig, setVariableConfig] = useState<VariableType>();
  const [variableConfigWithOptions, setVariableConfigWithOptions] = useState<VariableType>();
  const [dashboardLinks, setDashboardLinks] = useState<ILink[]>();
  const [groupModalVisible, setGroupModalVisible] = useState(false);
  const [chartModalVisible, setChartModalVisible] = useState(false);
  const [chartModalInitValue, setChartModalInitValue] = useState<Chart | null>();
  const [range, setRange] = useState<Range>({
    start: 0,
    end: 0,
  });
  const [refreshFlag, setRefreshFlag] = useState(_.uniqueId('refreshFlag_'));
  const { run } = useThrottleFn(
    () => {
      if ('start' in range && range.start && range.end) {
        const diff = range.end - range.start;
        const now = moment().unix();
        setRange({
          end: now,
          start: now - diff,
        });
      } else if ('unit' in range && range.unit) {
        const newRefreshFlag = _.uniqueId('refreshFlag_');
        setRange({
          ...range,
          refreshFlag: newRefreshFlag,
        });
        setRefreshFlag(newRefreshFlag);
      }
      init(false);
    },
    { wait: 1000 },
  );

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

  const init = (needUpdateVariable = true) => {
    getSingleDashboard(busiId, id).then((res) => {
      setDashboard(res.dat);
      if (res.dat.configs) {
        const configs = JSON.parse(res.dat.configs);
        setVariableConfig(configs);
        setVariableConfigWithOptions(configs);
        setDashboardLinks(configs.links);
      }
    });
    getChartGroup(busiId, id).then((res) => {
      let arr = res.dat || [];
      setChartGroup(
        arr
          .sort((a, b) => a - b)
          .map((item) => {
            item.updateTime = Date.now(); // 前端拓展一个更新时间字段,用来主动刷新ChartGroup
            return item;
          }),
      );
    });
  };

  const handleDateChange = (e) => {
    setRange(e);
  };

  const handleEdit = () => {
    setTitleEditing(!titleEditing);
  };

  const handleModifyTitle = async (e) => {
    await updateSingleDashboard(busiId, id, { ...dashboard, name: e.target.value });
    // await init();
    setDashboard({ ...dashboard, name: e.target.value });
    setTitleEditing(false);
  };

  const handleAddChart = (gid: number) => {
    groupId = gid;
    editor({
      visible: true,
      variableConfig: variableConfigWithOptions,
      cluster: curCluster,
      busiId,
      groupId,
      id,
      initialValues: {
        type: 'timeseries',
        targets: [
          {
            refId: 'A',
            expr: '',
          },
        ],
      },
      onOK: () => {
        handleChartConfigVisibleChange(true);
      },
    });
    // setChartModalVisible(true);
  }; //group是为了让detail组件知道当前需要刷新的是哪个chartGroup,item是为了获取待编辑的信息

  const handleUpdateChart = (group: Group, item: Chart) => {
    groupId = group.id;
    setChartModalInitValue(item);

    if (semver.valid(item.configs.version)) {
      editor({
        visible: true,
        variableConfig,
        cluster: curCluster,
        busiId,
        groupId,
        id,
        initialValues: {
          ...item.configs,
          id: item.id,
        },
        onOK: () => {
          handleChartConfigVisibleChange(true);
        },
      });
    } else {
      setChartModalVisible(true);
    }
  };

  const handleDelChart = async (group: Group, item: Chart) => {
    groupId = group.id;
    await removeChart(busiId, item.id as any);
    refreshUpdateTimeByChartGroupId();
  };

  const handleCloneChart = async (group: Group, item: Chart) => {
    groupId = group.id;
    const configsClone = _.cloneDeep(item.configs);
    configsClone.layout = {
      w: configsClone.layout.w,
      h: configsClone.layout.h,
    };
    await createChart(busiId, {
      configs: JSON.stringify(configsClone),
      weight: 0,
      group_id: groupId,
    });
    refreshUpdateTimeByChartGroupId();
  };

  const handleShareChart = async (group: Group, item: any) => {
    const serielData = {
      dataProps: {
        ...item.configs,
        targets: _.map(item.configs.targets, (target) => {
          const realExpr = variableConfigWithOptions ? replaceExpressionVars(target.expr, variableConfigWithOptions, variableConfigWithOptions.var.length, id) : target.expr;
          return {
            ...target,
            expr: realExpr,
          };
        }),
        step,
        range,
      },
      curCluster: localStorage.getItem('curCluster'),
    };
    SetTmpChartData([
      {
        configs: JSON.stringify(serielData),
      },
    ]).then((res) => {
      const ids = res.dat;
      window.open('/chart/' + ids);
    });
  };

  const handleAddOrUpdateChartGroup = async () => {
    await groupForm.validateFields();
    let obj = groupForm.getFieldsValue();

    if (isAddGroup) {
      let weightArr = chartGroup.map((item) => item.weight);
      let weight = Math.max(...weightArr) + 1;
      await createChartGroup(busiId, { ...obj, weight, dashboard_id: Number(id) });
    } else {
      let group = chartGroup.find((item) => item.id === groupId);
      await updateChartGroup(busiId, [{ dashboard_id: Number(id), ...group, ...obj }]);
    }

    init();
    isAddGroup = true;
    setGroupModalVisible(false);
  };

  const handleUpdateChartGroup = (group: Group) => {
    groupId = group.id;
    isAddGroup = false;
    groupForm.setFieldsValue({ name: group.name });
    setGroupModalVisible(true);
  };

  const handleMoveUpChartGroup = async (group: Group) => {
    const { weight } = group;
    let lessWeightGroup = chartGroup.find((item) => item.weight === weight - 1);
    if (!lessWeightGroup) return;
    lessWeightGroup.weight = weight;
    group.weight = weight - 1;
    await updateChartGroup(busiId, [lessWeightGroup, group]);
    init();
  };

  const handleMoveDownChartGroup = async (group: Group) => {
    const { weight } = group;
    let lessWeightGroup = chartGroup.find((item) => item.weight === weight + 1);
    if (!lessWeightGroup) return;
    lessWeightGroup.weight = weight;
    group.weight = weight + 1;
    await updateChartGroup(busiId, [lessWeightGroup, group]);
    init();
  };

  const handleDelChartGroup = async (id: number) => {
    await delChartGroup(busiId, id);
    message.success(t('删除分组成功'));
    init();
    setGroupModalVisible(false);
  };

  const refreshUpdateTimeByChartGroupId = () => {
    let groupIndex = chartGroup.findIndex((item) => item.id === groupId);
    if (groupIndex < 0) return;
    let newChartGroup = [...chartGroup];
    newChartGroup[groupIndex].updateTime = Date.now();
    setChartGroup(newChartGroup);
  };

  const handleChartConfigVisibleChange = (b) => {
    setChartModalVisible(false);
    setChartModalInitValue(null);
    b && refreshUpdateTimeByChartGroupId();
  };

  const handleVariableChange = (value, b, valueWithOptions) => {
    let dashboardConfigs: any = {};
    try {
      if (dashboard.configs) {
        dashboardConfigs = JSON.parse(dashboard.configs);
      }
    } catch (e) {
      console.error(e);
    }
    dashboardConfigs.var = value.var;
    b && updateSingleDashboard(busiId, id, { ...dashboard, configs: JSON.stringify(dashboardConfigs) });
    setVariableConfig(dashboardConfigs);
    valueWithOptions && setVariableConfigWithOptions(valueWithOptions);
  };

  const stopAutoRefresh = () => {
    refreshRef.current?.closeRefresh();
  };
  const clusterMenu = (
    <Menu selectedKeys={[curCluster]}>
      {clusters.map((cluster) => (
        <Menu.Item
          key={cluster}
          onClick={(_) => {
            setCurCluster(cluster);
            localStorage.setItem('curCluster', cluster);
            init();
          }}
        >
          {cluster}
        </Menu.Item>
      ))}
    </Menu>
  );
  return (
    <PageLayout
      customArea={
        <div className='dashboard-detail-header'>
          <div className='dashboard-detail-header-left'>
            <RollbackOutlined className='back' onClick={() => history.push('/dashboards')} />
            {titleEditing ? <Input ref={Ref} defaultValue={dashboard.name} onPressEnter={handleModifyTitle} /> : <div className='title'>{dashboard.name}</div>}
            {!titleEditing ? (
              <EditOutlined className='edit' onClick={handleEdit} />
            ) : (
              <>
                <Button size='small' style={{ marginRight: 5, marginLeft: 5 }} onClick={() => setTitleEditing(false)}>
                  取消
                </Button>
                <Button
                  size='small'
                  type='primary'
                  onClick={() => {
                    handleModifyTitle({ target: { value: Ref.current.state.value } });
                  }}
                >
                  保存
                </Button>
              </>
            )}
          </div>
          <div className='dashboard-detail-header-right'>
            <Space>
              <div style={{ display: 'flex', alignItems: 'center' }}>
                集群:
                <Dropdown overlay={clusterMenu}>
                  <Button>
                    {curCluster} <DownOutlined />
                  </Button>
                </Dropdown>
              </div>
              <DateRangePicker onChange={handleDateChange} />
              <Resolution onChange={(v) => setStep(v)} initialValue={step} />
              <Refresh onRefresh={run} ref={refreshRef} />
            </Space>
          </div>
        </div>
      }
    >
      <div className='dashboard-detail-content'>
        <div className='dashboard-detail-content-header'>
          <div className='variable-area'>
            <VariableConfig onChange={handleVariableChange} value={variableConfig} cluster={curCluster} range={range} id={id} onOpenFire={stopAutoRefresh} />
          </div>
          <DashboardLinks
            value={dashboardLinks}
            onChange={(v) => {
              let dashboardConfigs: any = {};
              try {
                if (dashboard.configs) {
                  dashboardConfigs = JSON.parse(dashboard.configs);
                }
              } catch (e) {
                console.error(e);
              }
              dashboardConfigs.links = v;
              updateSingleDashboard(busiId, id, {
                ...dashboard,
                configs: JSON.stringify(dashboardConfigs),
              });
              setDashboardLinks(v);
            }}
          />
        </div>

        <div className='charts'>
          {chartGroup.map((item, i) => (
            <ChartGroup
              id={id}
              cluster={curCluster}
              busiId={busiId}
              key={i}
              step={step}
              groupInfo={item}
              onAddChart={handleAddChart}
              onUpdateChart={handleUpdateChart}
              onCloneChart={handleCloneChart}
              onShareChart={handleShareChart}
              onUpdateChartGroup={handleUpdateChartGroup}
              onMoveUpChartGroup={handleMoveUpChartGroup}
              onMoveDownChartGroup={handleMoveDownChartGroup}
              onDelChart={handleDelChart}
              onDelChartGroup={handleDelChartGroup}
              range={range}
              refreshFlag={refreshFlag}
              variableConfig={variableConfigWithOptions!}
              moveUpEnable={i > 0}
              moveDownEnable={i < chartGroup.length - 1}
            />
          ))}
          <Button
            block
            icon={<PlusOutlined />}
            style={{
              paddingRight: 0,
            }}
            onClick={() => {
              groupForm.setFieldsValue({ name: '' });
              setGroupModalVisible(true);
            }}
          >
            {t('新增图表分组')}
          </Button>
        </div>
      </div>
      <Modal
        title={isAddGroup ? t('新建分组') : t('更新分组名称')}
        visible={groupModalVisible}
        onOk={handleAddOrUpdateChartGroup}
        onCancel={() => {
          setGroupModalVisible(false);
        }}
      >
        <Form {...layout} form={groupForm}>
          <Form.Item
            label={t('分组名称')}
            name='name'
            rules={[
              {
                required: true,
                message: t('请输入名称'),
              },
            ]}
          >
            <Input />
          </Form.Item>
        </Form>
      </Modal>

      {chartModalVisible && (
        <ChartConfigModal
          id={id}
          cluster={curCluster}
          busiId={busiId}
          initialValue={chartModalInitValue}
          groupId={groupId}
          show={chartModalVisible}
          onVisibleChange={handleChartConfigVisibleChange}
          variableConfig={variableConfigWithOptions}
        />
      )}
    </PageLayout>
  );
}
Example #21
Source File: StudentBanner.tsx    From office-hours with GNU General Public License v3.0 4 votes vote down vote up
export default function StudentBanner({
  queueId,
  editQuestion,
  leaveQueue,
}: StudentBannerProps): ReactElement {
  const { studentQuestion, studentQuestionIndex } = useStudentQuestion(queueId);
  const isQueueOnline = useQueue(queueId).queue?.room.startsWith("Online");

  switch (studentQuestion?.status) {
    case "Drafting":
      return (
        <Banner
          titleColor="#faad14"
          contentColor="#ffd666"
          title="Please finish writing your question"
          content="Your spot in queue has been temporarily reserved. Please finish describing your question to receive help and finish joining the queue."
          buttons={
            <>
              <Tooltip title="Delete Draft">
                <BannerButton
                  icon={<DeleteRowOutlined />}
                  onClick={leaveQueue}
                />
              </Tooltip>
              <Tooltip title="Finish Draft">
                <BannerButton
                  data-cy="edit-question"
                  icon={<EditOutlined />}
                  onClick={async () => {
                    editQuestion();
                  }}
                />
              </Tooltip>
            </>
          }
        />
      );
    case "Queued":
      return (
        <Banner
          titleColor="#3684C6"
          contentColor="#ABD4F3"
          title={
            <span>
              You are{" "}
              <BoldNumber>{toOrdinal(studentQuestionIndex + 1)}</BoldNumber> in
              queue
            </span>
          }
          buttons={
            <>
              <LeaveQueueButton leaveQueue={leaveQueue} />
              <Tooltip title="Edit Question">
                <BannerButton
                  data-cy="edit-question"
                  icon={<EditOutlined />}
                  onClick={editQuestion}
                />
              </Tooltip>
            </>
          }
          content={<QuestionDetailRow studentQuestion={studentQuestion} />}
        />
      );
    case "Helping":
      return (
        <Banner
          titleColor="#66BB6A"
          contentColor="#82C985"
          title={
            <span>
              <BoldNumber>{studentQuestion.taHelped.name}</BoldNumber> is coming
              to help you
            </span>
          }
          buttons={
            <>
              <LeaveQueueButton leaveQueue={leaveQueue} />
              {isQueueOnline && (
                <Tooltip title="Open Teams DM">
                  <BannerButton
                    icon={<TeamOutlined />}
                    onClick={() => {
                      window.open(
                        `https://teams.microsoft.com/l/chat/0/0?users=${studentQuestion.taHelped.email}`
                      );
                    }}
                  />
                </Tooltip>
              )}
            </>
          }
          content={
            <Bullets>
              <li>Please be dressed appropriately</li>
              <li>Be respectful of the TA’s time</li>
              <li>Come prepared with your question!</li>
            </Bullets>
          }
        />
      );
    case "ReQueueing":
      return (
        <Banner
          titleColor="#66BB6A"
          contentColor="#82C985"
          title={<span>Are you ready to re-join the queue?</span>}
          buttons={
            <>
              <LeaveQueueButton leaveQueue={leaveQueue} />
              <Tooltip title="Rejoin Queue">
                <Button
                  shape="circle"
                  style={{
                    marginLeft: "16px",
                    border: 0,
                  }}
                  icon={<UndoOutlined />}
                  onClick={async () => {
                    await API.questions.update(studentQuestion.id, {
                      status: OpenQuestionStatus.PriorityQueued,
                    });
                  }}
                  type="primary"
                  data-cy="re-join-queue"
                  size="large"
                />
              </Tooltip>
            </>
          }
          content={
            <Bullets>
              <li>Have you finished doing what the TA has told you?</li>
              <li>
                Once you hit requeue, you will be placed at the top of the queue
              </li>
            </Bullets>
          }
        />
      );
    case "PriorityQueued":
      return (
        <Banner
          titleColor="#3684C6"
          contentColor="#ABD4F3"
          title={
            <PriorityQueuedBanner>
              You are now in a priority queue, you will be helped soon. <br />
              <span style={{ fontSize: 16 }}>
                You were last helped by{" "}
                <span style={{ fontWeight: "bold" }}>
                  {studentQuestion.taHelped.name}
                </span>
                .
              </span>
            </PriorityQueuedBanner>
          }
          buttons={
            <>
              <LeaveQueueButton leaveQueue={leaveQueue} />
              <Tooltip title="Edit Question">
                <BannerButton
                  data-cy="edit-question"
                  icon={<EditOutlined />}
                  onClick={editQuestion}
                />
              </Tooltip>
            </>
          }
          content={<QuestionDetailRow studentQuestion={studentQuestion} />}
        />
      );
    default:
      return <div />;
  }
}
Example #22
Source File: List.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
export default function List(props: IProps) {
  const [list, setList] = useState([]);
  const [active, setActive] = useState<number>();
  const [search, setSearch] = useState('');
  const [refreshFlag, setRefreshFlag] = useState(_.uniqueId('refreshFlag_'));
  const { profile } = useSelector<AccountRootState, accountStoreState>((state) => state.account);
  useEffect(() => {
    const defaultMetricViewId = localStorage.getItem('metric-view-id') !== null ? Number(localStorage.getItem('metric-view-id')) : null;
    getList().then((res) => {
      setList(res);
      let curId;
      if (!defaultMetricViewId || !_.find(res, { id: defaultMetricViewId })) {
        curId = _.get(_.head(res), 'id');
      } else {
        curId = defaultMetricViewId;
      }
      if (curId) {
        setActive(curId);
        const curItem = _.find(res, { id: curId });
        let configs = {} as IMatch;
        try {
          configs = JSON.parse(curItem.configs);
          configs.id = curId;
          configs.refreshFlag = refreshFlag;
        } catch (e) {
          console.error(e);
        }
        props.onSelect({
          ...configs,
        });
      }
    });
  }, [refreshFlag]);

  return (
    <div className='n9e-metric-views-list'>
      <div className='n9e-metric-views-list-header'>
        <div className='metric-page-title'>快捷视图列表</div>
        <a>
          <PlusSquareOutlined
            onClick={() => {
              Form({
                admin: profile.admin,
                action: 'add',
                visible: true,
                range: props.range,
                onOk: (record) => {
                  localStorage.setItem('metric-view-id', record.id);
                  setRefreshFlag(_.uniqueId('refreshFlag_'));
                },
              });
            }}
          />
        </a>
      </div>
      <Input
        prefix={<SearchOutlined />}
        value={search}
        onChange={(e) => {
          setSearch(e.target.value);
        }}
      />
      <div className='n9e-metric-views-list-content'>
        {_.isEmpty(list)
          ? '暂无数据'
          : _.map(
              _.filter(list, (item) => {
                if (search) {
                  let result = true;
                  try {
                    const reg = new RegExp(search, 'gi');
                    result = reg.test(item.name);
                  } catch (e) {
                    console.log(e);
                  }
                  return result;
                }
                return true;
              }),
              (item) => {
                return (
                  <div
                    className={classNames({
                      'n9e-metric-views-list-content-item': true,
                      active: item.id === active,
                    })}
                    key={item.id}
                    onClick={() => {
                      setActive(item.id);
                      localStorage.setItem('metric-view-id', item.id);
                      const curItem = _.find(list, { id: item.id });
                      let configs = {} as IMatch;
                      try {
                        configs = JSON.parse(curItem.configs);
                        configs.id = item.id;
                      } catch (e) {
                        console.error(e);
                      }
                      props.onSelect({
                        ...configs,
                      });
                    }}
                  >
                    <span className='name'>{item.name}</span>
                    {item.cate === 1 || profile.admin ? (
                      <span>
                        {item.cate === 0 && (
                          <span className='n9e-metric-views-list-content-item-cate' style={{ color: '#ccc' }}>
                            公开
                          </span>
                        )}
                        <div className='n9e-metric-views-list-content-item-opes'>
                          <EditOutlined
                            onClick={(e) => {
                              e.stopPropagation();
                              let configs = {} as any;
                              try {
                                configs = JSON.parse(item.configs);
                                configs.dynamicLabels = _.map(configs.dynamicLabels, 'label');
                                configs.dimensionLabels = _.map(configs.dimensionLabels, 'label');
                              } catch (e) {
                                console.error(e);
                              }
                              const initialValues = {
                                id: item.id,
                                name: item.name,
                                cate: item.cate === 0,
                                ...configs,
                              };
                              Form({
                                admin: profile.admin,
                                action: 'edit',
                                visible: true,
                                range: props.range,
                                initialValues,
                                onOk: () => {
                                  localStorage.setItem('metric-view-id', item.id);
                                  setRefreshFlag(_.uniqueId('refreshFlag_'));
                                },
                              });
                            }}
                          />
                          <DeleteOutlined
                            onClick={(e) => {
                              e.stopPropagation();
                              Modal.confirm({
                                title: '是否要删除?',
                                onOk: () => {
                                  deleteMetricView({
                                    ids: [item.id],
                                  }).then(() => {
                                    message.success('删除成功');
                                    setRefreshFlag(_.uniqueId('refreshFlag_'));
                                  });
                                },
                              });
                            }}
                          />
                          <Tooltip title='导出配置' placement='right'>
                            <ExportOutlined
                              onClick={() => {
                                Export({
                                  visible: true,
                                  data: item.configs,
                                });
                              }}
                            />
                          </Tooltip>
                        </div>
                      </span>
                    ) : (
                      <span style={{ color: '#ccc' }}>公开</span>
                    )}
                  </div>
                );
              },
            )}
      </div>
    </div>
  );
}
Example #23
Source File: CollectionCell.tsx    From Protoman with MIT License 4 votes vote down vote up
CollectionCell: React.FunctionComponent<Props> = ({ collectionName }) => {
  const dispatch = useDispatch();

  const collection = useSelector((s: AppState) => getByKey(s.collections, collectionName));
  const collectionNames = useSelector(selectColNames);

  const [menuVisible, setMenuVisible] = React.useState(false);
  const [isEditingName, setIsEditingName] = React.useState(false);
  const [isInvalidName, setIsInvalidName] = React.useState(false);
  const [draftName, setDraftName] = React.useState(collectionName);

  React.useEffect(() => {
    setDraftName(collectionName);
  }, [collectionName]);

  function showMenu(): void {
    setMenuVisible(true);
  }

  function hideMenu(): void {
    setMenuVisible(false);
  }

  function startEditing(): void {
    setIsEditingName(true);
    hideMenu();
  }

  function stopEditing(): void {
    setIsEditingName(false);
  }

  const collectionSize = Object.keys(collection?.flows || {}).length;

  function handleDelete(): void {
    if (collectionNames.length > 1) {
      dispatch(deleteCollection(collectionName));
    } else {
      message.error("Can't delete the last collection");
    }
    hideMenu();
  }

  function checkName(newName: string): boolean {
    return validateCollectionName(newName, collectionName, collectionNames);
  }

  function handleNameChange(newName: string): void {
    if (checkName(newName)) {
      dispatch(changeCollectionName(collectionName, newName));
    }
  }

  function handleOpenFM(): void {
    dispatch(openFM(collectionName));
    hideMenu();
  }

  function validateFlowName(flowName: string): boolean {
    return !collection?.flows?.map(([n]) => n)?.includes(flowName);
  }

  function handleCreate(): void {
    const tmpName = 'Request';
    let tmpNameIdx = 1;
    while (!validateFlowName(`${tmpName}${tmpNameIdx}`)) tmpNameIdx++;
    dispatch(createFlow(collectionName, `${tmpName}${tmpNameIdx}`));
    hideMenu();
  }

  function handleExport(): void {
    if (collection) {
      // error display is done in index.ts
      exportCollection(collectionName, collection);
    }
  }

  const menu = (
    <>
      <Button type="link" onClick={prevent(handleOpenFM)}>
        <FilePptOutlined />
        Manage .proto files
      </Button>
      <Separator />
      <Button type="link" onClick={prevent(handleCreate)}>
        <PlusOutlined />
        New Request
      </Button>
      <Separator />
      <Button type="link" onClick={prevent(startEditing)}>
        <EditOutlined />
        Edit Name
      </Button>
      <Separator />
      <Button type="link" onClick={prevent(handleExport)}>
        <ExportOutlined />
        Export Collection
      </Button>
      <Separator />
      <Button type="link" danger onClick={prevent(handleDelete)}>
        <DeleteOutlined />
        Delete Collection
      </Button>
    </>
  );

  return (
    <Popover
      placement="rightTop"
      content={menu}
      visible={menuVisible}
      trigger="contextMenu"
      onVisibleChange={setMenuVisible}
    >
      <TableData onContextMenu={prevent(showMenu)}>
        {isEditingName ? (
          <Form.Item
            validateStatus={isInvalidName ? 'error' : ''}
            style={{ margin: 0 }}
            help={isInvalidName ? 'Invalid Name' : ''}
          >
            <TitleInput
              value={draftName}
              onChange={(e): void => {
                setIsInvalidName(!checkName(e.target.value));
                setDraftName(e.target.value);
              }}
              onKeyDown={(e): void => {
                switch (e.keyCode) {
                  case 27: // esc
                    setDraftName(collectionName);
                    stopEditing();
                    break;
                  case 13: // enter
                    if (!isInvalidName) {
                      handleNameChange(draftName);
                      stopEditing();
                    }
                }
              }}
              onClick={prevent(e => e)}
            />
          </Form.Item>
        ) : (
          <Title>{draftName}</Title>
        )}
        <Description>
          {collectionSize} {collectionSize === 1 ? 'entry' : 'entries'}
        </Description>
      </TableData>
    </Popover>
  );
}
Example #24
Source File: FolderTree.tsx    From datart with Apache License 2.0 4 votes vote down vote up
export function FolderTree({
  selectedId,
  treeData,
  i18nPrefix,
}: FolderTreeProps) {
  const tg = useI18NPrefix('global');
  const dispatch = useDispatch();
  const history = useHistory();
  const orgId = useSelector(selectOrgId);
  const loading = useSelector(selectVizListLoading);
  const vizsData = useSelector(selectVizs);
  const { showSaveForm } = useContext(SaveFormContext);
  const saveAsViz = useSaveAsViz();

  useEffect(() => {
    dispatch(getFolders(orgId));
  }, [dispatch, orgId]);

  const redirect = useCallback(
    tabKey => {
      if (tabKey) {
        history.push(`/organizations/${orgId}/vizs/${tabKey}`);
      } else {
        history.push(`/organizations/${orgId}/vizs`);
      }
    },
    [history, orgId],
  );

  const menuSelect = useCallback(
    (_, { node }) => {
      if (node.relType !== 'FOLDER') {
        history.push(`/organizations/${orgId}/vizs/${node.relId}`);
      }
    },
    [history, orgId],
  );

  const archiveViz = useCallback(
    ({ id: folderId, relId, relType }) =>
      () => {
        let id = folderId;
        let archive = false;
        let msg = tg('operation.deleteSuccess');

        if (['DASHBOARD', 'DATACHART'].includes(relType)) {
          id = relId;
          archive = true;
          msg = tg('operation.archiveSuccess');
        }
        dispatch(
          deleteViz({
            params: { id, archive },
            type: relType,
            resolve: () => {
              message.success(msg);
              dispatch(removeTab({ id, resolve: redirect }));
            },
          }),
        );
      },
    [dispatch, redirect, tg],
  );

  const moreMenuClick = useCallback(
    node =>
      ({ key, domEvent }) => {
        domEvent.stopPropagation();
        switch (key) {
          case 'info':
            showSaveForm({
              vizType: node.relType,
              type: CommonFormTypes.Edit,
              visible: true,
              initialValues: { ...node, parentId: node.parentId || void 0 },
              onSave: (values, onClose) => {
                let index = node.index;
                if (isParentIdEqual(node.parentId, values.parentId)) {
                  index = getInsertedNodeIndex(values, vizsData);
                }

                dispatch(
                  editFolder({
                    folder: {
                      ...values,
                      parentId: values.parentId || null,
                      index,
                    },
                    resolve: onClose,
                  }),
                );
              },
            });
            break;
          case 'delete':
            break;
          case 'saveAs':
            saveAsViz(node.relId, node.relType);
            break;
          default:
            break;
        }
      },
    [dispatch, showSaveForm, vizsData, saveAsViz],
  );

  const renderTreeTitle = useCallback(
    node => {
      const { isFolder, title, path, relType } = node;

      return (
        <TreeTitle>
          <h4>{`${title}`}</h4>
          <CascadeAccess
            module={ResourceTypes.Viz}
            path={path}
            level={PermissionLevels.Manage}
          >
            <Popup
              trigger={['click']}
              placement="bottom"
              content={
                <Menu
                  prefixCls="ant-dropdown-menu"
                  selectable={false}
                  onClick={moreMenuClick(node)}
                >
                  <MenuListItem
                    key="info"
                    prefix={<EditOutlined className="icon" />}
                  >
                    {tg('button.info')}
                  </MenuListItem>

                  {!isFolder && (
                    <MenuListItem
                      key="saveAs"
                      prefix={<CopyFilled className="icon" />}
                    >
                      {tg('button.saveAs')}
                    </MenuListItem>
                  )}

                  <MenuListItem
                    key="delete"
                    prefix={<DeleteOutlined className="icon" />}
                  >
                    <Popconfirm
                      title={`${
                        relType === 'FOLDER'
                          ? tg('operation.deleteConfirm')
                          : tg('operation.archiveConfirm')
                      }`}
                      onConfirm={archiveViz(node)}
                    >
                      {relType === 'FOLDER'
                        ? tg('button.delete')
                        : tg('button.archive')}
                    </Popconfirm>
                  </MenuListItem>
                </Menu>
              }
            >
              <span className="action" onClick={stopPPG}>
                <MoreOutlined />
              </span>
            </Popup>
          </CascadeAccess>
        </TreeTitle>
      );
    },
    [moreMenuClick, archiveViz, tg],
  );

  const onDrop = info => {
    onDropTreeFn({
      info,
      treeData,
      callback: (id, parentId, index) => {
        dispatch(
          editFolder({
            folder: {
              id,
              parentId,
              index: index,
            },
            resolve: () => {},
          }),
        );
      },
    });
  };
  return (
    <Tree
      loading={loading}
      treeData={treeData}
      titleRender={renderTreeTitle}
      onSelect={menuSelect}
      onDrop={onDrop}
      {...(selectedId && { selectedKeys: [selectedId] })}
      defaultExpandAll
      draggable
    />
  );
}
Example #25
Source File: index.tsx    From amiya with MIT License 4 votes vote down vote up
export default function Demo() {
  return (
    <AySearchTable
      api={listApi}
      rowKey="id"
      title="员工列表"
      dialogFormExtend={{ addApi, updateApi }}
      deleteApi={deleteApi}
      ctrl={ctrl}
    >
      <AyFields>
        <AyField
          title="头像"
          key="avatar"
          width={70}
          renderType="image"
          props={{
            width: 60,
            height: 60
          }}
          dialog={{
            title: '选择头像',
            type: 'card-group',
            tooltip: '选择一个喜欢的头像',
            required: true,
            cardStyle: {
              padding: 2
            },
            options: [
              require('../images/avatar1.jpg'),
              require('../images/avatar2.jpg'),
              require('../images/avatar3.jpg'),
              require('../images/avatar4.jpg'),
              require('../images/avatar5.jpg')
            ].map(src => {
              return {
                label: <img src={src} width="50" height="50" />,
                value: src
              }
            })
          }}
        />
        <AyField title="用户名称" key="nickname" search dialog={{ required: true }} />
        <AyField title="登录账号" key="username" search dialog={{ required: true }} />
        <AyField
          title="所属角色"
          key="character"
          type="select"
          search
          renderType="group"
          split="\"
          size={0}
          tooltip="当前绑定的角色"
          after={
            <AyButton tooltip="编辑角色" type="link" icon={<EditOutlined />} onClick={() => message.info('未实现')} />
          }
          options={[
            { label: '超级管理员', value: 1 },
            { label: '财务', value: 2 },
            { label: '运营', value: 3 }
          ]}
        />
        <AyField title="邮箱地址" key="email" dialog />
        <AyField
          title="第三方绑定"
          key="linkAccount"
          renderType="group"
          after={
            <div>
              <a onClick={() => message.info('未实现')}>添加绑定</a>
            </div>
          }
          options={[
            { label: <WeiboCircleOutlined style={{ color: '#d52c2b', fontSize: 20 }} />, value: 'weibo' },
            { label: <QqOutlined style={{ color: '#333', fontSize: 20 }} />, value: 'qq' },
            { label: <WechatOutlined style={{ color: '#03dc6c', fontSize: 20 }} />, value: 'wechat' },
            { label: <TwitterOutlined style={{ color: '#1d9bf0', fontSize: 20 }} />, value: 'twitter' }
          ]}
        />
        <AyField
          title="在职状态"
          key="status"
          type="select"
          search
          renderType="status"
          dialog={{
            type: 'radio-group',
            required: true,
            defaultValue: 1,
            optionType: 'button'
          }}
          options={[
            { label: '在职', value: 1, color: 'green' },
            { label: '离职', value: 2, color: 'red' },
            { label: '退休', value: 3, color: 'cyan' },
            { label: '停薪', value: 4, color: 'purple' }
          ]}
        />
      </AyFields>
      <Space size="large">
        <Text type="secondary">初始登录密码:111111</Text>
        <AyAction action="add">新增员工</AyAction>
      </Space>
    </AySearchTable>
  )
}
Example #26
Source File: MainOperator.tsx    From yakit with GNU Affero General Public License v3.0 4 votes vote down vote up
Main: React.FC<MainProp> = (props) => {
    const [engineStatus, setEngineStatus] = useState<"ok" | "error">("ok")
    const [status, setStatus] = useState<{ addr: string; isTLS: boolean }>()
    const [collapsed, setCollapsed] = useState(false)
    const [hideMenu, setHideMenu] = useState(false)

    const [loading, setLoading] = useState(false)
    const [menuItems, setMenuItems] = useState<MenuItemGroup[]>([])
    const [routeMenuData, setRouteMenuData] = useState<MenuDataProps[]>(RouteMenuData)

    const [notification, setNotification] = useState("")

    const [pageCache, setPageCache] = useState<PageCache[]>([
        {
            verbose: "MITM",
            route: Route.HTTPHacker,
            singleNode: ContentByRoute(Route.HTTPHacker),
            multipleNode: []
        }
    ])
    const [currentTabKey, setCurrentTabKey] = useState<string>(Route.HTTPHacker)

    // 系统类型
    const [system, setSystem] = useState<string>("")
    useEffect(() => {
        ipcRenderer.invoke('fetch-system-name').then((res) => setSystem(res))
    }, [])

    // yakit页面关闭是否二次确认提示
    const [winCloseFlag, setWinCloseFlag] = useState<boolean>(true)
    const [winCloseShow, setWinCloseShow] = useState<boolean>(false)
    useEffect(() => {
        ipcRenderer
            .invoke("get-value", WindowsCloseFlag)
            .then((flag: any) => setWinCloseFlag(flag === undefined ? true : flag))
    }, [])

    // 获取自定义菜单
    const updateMenuItems = () => {
        setLoading(true)
        // Fetch User Defined Plugins
        ipcRenderer
            .invoke("GetAllMenuItem", {})
            .then((data: { Groups: MenuItemGroup[] }) => {
                setMenuItems(data.Groups)
            })
            .catch((e: any) => failed("Update Menu Item Failed"))
            .finally(() => setTimeout(() => setLoading(false), 300))
        // Fetch Official General Plugins
        ipcRenderer
            .invoke("QueryYakScript", {
                Pagination: genDefaultPagination(1000),
                IsGeneralModule: true,
                Type: "yak"
            } as QueryYakScriptRequest)
            .then((data: QueryYakScriptsResponse) => {
                const tabList: MenuDataProps[] = cloneDeep(RouteMenuData)
                for (let item of tabList) {
                    if (item.subMenuData) {
                        if (item.key === Route.GeneralModule) {
                            const extraMenus: MenuDataProps[] = data.Data.map((i) => {
                                return {
                                    icon: <EllipsisOutlined/>,
                                    key: `plugin:${i.Id}`,
                                    label: i.ScriptName,
                                } as unknown as MenuDataProps
                            })
                            item.subMenuData.push(...extraMenus)
                        }
                        item.subMenuData.sort((a, b) => a.label.localeCompare(b.label))
                    }
                }
                setRouteMenuData(tabList)
            })
    }
    useEffect(() => {
        updateMenuItems()
        ipcRenderer.on("fetch-new-main-menu", (e) => {
            updateMenuItems()
        })

        return () => {
            ipcRenderer.removeAllListeners("fetch-new-main-menu")
        }
    }, [])

    useEffect(() => {
        if (engineStatus === "error") props.onErrorConfirmed && props.onErrorConfirmed()
    }, [engineStatus])

    // 整合路由对应名称
    const pluginKey = (item: PluginMenuItem) => `plugin:${item.Group}:${item.YakScriptId}`;
    const routeKeyToLabel = new Map<string, string>();
    routeMenuData.forEach(k => {
        (k.subMenuData || []).forEach(subKey => {
            routeKeyToLabel.set(`${subKey.key}`, subKey.label)
        })

        routeKeyToLabel.set(`${k.key}`, k.label)
    })
    menuItems.forEach((k) => {
        k.Items.forEach((value) => {
            routeKeyToLabel.set(pluginKey(value), value.Verbose)
        })
    })

    // Tabs Bar Operation Function
    const getCacheIndex = (route: string) => {
        const targets = pageCache.filter((i) => i.route === route)
        return targets.length > 0 ? pageCache.indexOf(targets[0]) : -1
    }
    const addTabPage = useMemoizedFn(
        (route: Route, nodeParams?: { time?: string; node: ReactNode; isRecord?: boolean }) => {
            const filterPage = pageCache.filter((i) => i.route === route)
            const filterPageLength = filterPage.length

            if (singletonRoute.includes(route)) {
                if (filterPageLength > 0) {
                    setCurrentTabKey(route)
                } else {
                    const tabName = routeKeyToLabel.get(route) || `${route}`
                    setPageCache([
                        ...pageCache,
                        {
                            verbose: tabName,
                            route: route,
                            singleNode: ContentByRoute(route),
                            multipleNode: []
                        }
                    ])
                    setCurrentTabKey(route)
                }
            } else {
                if (filterPageLength > 0) {
                    const tabName = routeKeyToLabel.get(route) || `${route}`
                    const tabId = `${route}-[${randomString(49)}]`
                    const time = new Date().getTime().toString()
                    const node: multipleNodeInfo = {
                        id: tabId,
                        verbose: `${tabName}-[${filterPage[0].multipleNode.length + 1}]`,
                        node: nodeParams && nodeParams.node ? nodeParams?.node || <></> : ContentByRoute(route),
                        time: nodeParams && nodeParams.node ? nodeParams?.time || time : time
                    }
                    const pages = pageCache.map((item) => {
                        if (item.route === route) {
                            item.multipleNode.push(node)
                            item.multipleCurrentKey = tabId
                            return item
                        }
                        return item
                    })
                    setPageCache([...pages])
                    setCurrentTabKey(route)
                    if (nodeParams && !!nodeParams.isRecord) addFuzzerList(nodeParams?.time || time)
                } else {
                    const tabName = routeKeyToLabel.get(route) || `${route}`
                    const tabId = `${route}-[${randomString(49)}]`
                    const time = new Date().getTime().toString()
                    const node: multipleNodeInfo = {
                        id: tabId,
                        verbose: `${tabName}-[1]`,
                        node: nodeParams && nodeParams.node ? nodeParams?.node || <></> : ContentByRoute(route),
                        time: nodeParams && nodeParams.node ? nodeParams?.time || time : time
                    }
                    setPageCache([
                        ...pageCache,
                        {
                            verbose: tabName,
                            route: route,
                            singleNode: undefined,
                            multipleNode: [node],
                            multipleCurrentKey: tabId
                        }
                    ])
                    setCurrentTabKey(route)
                    if (nodeParams && !!nodeParams.isRecord) addFuzzerList(nodeParams?.time || time)
                }
            }
        }
    )
    const menuAddPage = useMemoizedFn((route: Route) => {
        if (route === "ignore") return

        if (route === Route.HTTPFuzzer) {
            const time = new Date().getTime().toString()
            addTabPage(Route.HTTPFuzzer, {
                time: time,
                node: ContentByRoute(Route.HTTPFuzzer, undefined, {
                    system: system,
                    order: time
                }),
                isRecord: true
            })
        } else addTabPage(route as Route)
    })
    const removePage = (route: string) => {
        const targetIndex = getCacheIndex(route)

        if (targetIndex > 0 && pageCache[targetIndex - 1]) {
            const targetCache = pageCache[targetIndex - 1]
            setCurrentTabKey(targetCache.route)
        }
        if (targetIndex === 0 && pageCache[targetIndex + 1]) {
            const targetCache = pageCache[targetIndex + 1]
            setCurrentTabKey(targetCache.route)
        }
        if (targetIndex === 0 && pageCache.length === 1) setCurrentTabKey("")

        setPageCache(pageCache.filter((i) => i.route !== route))

        if (route === Route.HTTPFuzzer) delFuzzerList(1)
    }
    const updateCacheVerbose = (id: string, verbose: string) => {
        const index = getCacheIndex(id)
        if (index < 0) return

        pageCache[index].verbose = verbose
        setPageCache([...pageCache])
    }
    const setMultipleCurrentKey = useMemoizedFn((key: string, type: Route) => {
        const arr = pageCache.map(item => {
            if (item.route === type) {
                item.multipleCurrentKey = key
                return item
            }
            return item
        })
        setPageCache([...arr])
    })
    const removeMultipleNodePage = useMemoizedFn((key: string, type: Route) => {
        const removeArr: multipleNodeInfo[] = pageCache.filter(item => item.route === type)[0]?.multipleNode || []
        if (removeArr.length === 0) return
        const nodes = removeArr.filter(item => item.id === key)
        const time = nodes[0].time

        let index = 0
        for (let i in removeArr) {
            if (removeArr[i].id === key) {
                index = +i
                break
            }
        }

        if (index === 0 && removeArr.length === 1) {
            removePage(type)
            return
        }

        let current = ""
        let filterArr: multipleNodeInfo[] = []
        if (index > 0 && removeArr[index - 1]) {
            current = removeArr[index - 1].id
            filterArr = removeArr.filter(item => item.id !== key)
        }
        if (index === 0 && removeArr[index + 1]) {
            current = removeArr[index + 1].id
            filterArr = removeArr.filter(item => item.id !== key)
        }

        if (current) {
            const arr = pageCache.map(item => {
                if (item.route === type) {
                    item.multipleNode = [...filterArr]
                    item.multipleCurrentKey = current
                    return item
                }
                return item
            })
            setPageCache([...arr])
            if (type === Route.HTTPFuzzer) delFuzzerList(2, time)
        }
    })
    const removeOtherMultipleNodePage = useMemoizedFn((key: string, type: Route) => {
        const removeArr: multipleNodeInfo[] = pageCache.filter(item => item.route === type)[0]?.multipleNode || []
        if (removeArr.length === 0) return
        const nodes = removeArr.filter(item => item.id === key)
        const time = nodes[0].time

        const arr = pageCache.map(item => {
            if (item.route === type) {
                item.multipleNode = [...nodes]
                item.multipleCurrentKey = key
                return item
            }
            return item
        })
        setPageCache([...arr])
        if (type === Route.HTTPFuzzer) delFuzzerList(3, time)
    })

    // 全局记录鼠标坐标位置(为右键菜单提供定位)
    const coordinateTimer = useRef<any>(null)
    useEffect(() => {
        document.onmousemove = (e) => {
            const {screenX, screenY, clientX, clientY, pageX, pageY} = e
            if (coordinateTimer.current) {
                clearTimeout(coordinateTimer.current)
                coordinateTimer.current = null
            }
            coordinateTimer.current = setTimeout(() => {
                coordinate.screenX = screenX
                coordinate.screenY = screenY
                coordinate.clientX = clientX
                coordinate.clientY = clientY
                coordinate.pageX = pageX
                coordinate.pageY = pageY
            }, 50);
        }
    }, [])

    // 全局注册快捷键功能
    const documentKeyDown = useMemoizedFn((e: any) => {
        // ctrl + w 关闭tab页面
        if (e.code === "KeyW" && (e.ctrlKey || e.metaKey)) {
            e.preventDefault()
            if (pageCache.length === 0) return

            setLoading(true)
            removePage(currentTabKey)
            setTimeout(() => setLoading(false), 300);
            return
        }
    })
    useEffect(() => {
        document.onkeydown = documentKeyDown
    }, [])

    // fuzzer本地缓存
    const fuzzerList = useRef<Map<string, fuzzerInfoProp>>(new Map<string, fuzzerInfoProp>())
    const saveFuzzerList = debounce(() => {
        const historys: fuzzerInfoProp[] = []
        fuzzerList.current.forEach((value) => historys.push(value))
        historys.sort((a, b) => +a.time - +b.time)
        const filters = historys.filter(item => (item.request || "").length < 1000000 && (item.request || "").length > 0)
        ipcRenderer.invoke("set-value", FuzzerCache, JSON.stringify(filters.slice(-5)))
    }, 500)
    const fetchFuzzerList = useMemoizedFn(() => {
        setLoading(true)
        fuzzerList.current.clear()
        ipcRenderer
            .invoke("get-value", FuzzerCache)
            .then((res: any) => {
                const cache = JSON.parse(res || "[]")

                for (let item of cache) {
                    const time = new Date().getTime().toString()
                    fuzzerList.current.set(time, {...item, time: time})
                    addTabPage(Route.HTTPFuzzer, {
                        time: time,
                        node:
                            ContentByRoute(
                                Route.HTTPFuzzer,
                                undefined,
                                {
                                    isHttps: item.isHttps || false,
                                    request: item.request || "",
                                    fuzzerParams: item,
                                    system: system,
                                    order: time
                                }
                            )
                    })
                }
            })
            .catch((e) => console.info(e))
            .finally(() => setTimeout(() => setLoading(false), 300))
    })
    const addFuzzerList = (key: string, request?: string, isHttps?: boolean) => {
        fuzzerList.current.set(key, {request, isHttps, time: key})
    }
    const delFuzzerList = (type: number, key?: string) => {
        if (type === 1) fuzzerList.current.clear()
        if (type === 2 && key) if (fuzzerList.current.has(key)) fuzzerList.current.delete(key)
        if (type === 3 && key) {
            const info = fuzzerList.current.get(key)
            if (info) {
                fuzzerList.current.clear()
                fuzzerList.current.set(key, info)
            }
        }
        saveFuzzerList()
    }
    const updateFuzzerList = (key: string, param: fuzzerInfoProp) => {
        fuzzerList.current.set(key, param)
        saveFuzzerList()
    }
    useEffect(() => {
        ipcRenderer.on("fetch-fuzzer-setting-data", (e, res: any) => updateFuzzerList(res.key, JSON.parse(res.param)))
        // 开发环境不展示fuzzer缓存
        ipcRenderer.invoke("is-dev").then((flag) => {
            if (!flag) fetchFuzzerList()
            // fetchFuzzerList()
        })
        return () => ipcRenderer.removeAllListeners("fetch-fuzzer-setting-data")
    }, [])

    // 加载补全
    useEffect(() => {
        ipcRenderer.invoke("GetYakitCompletionRaw").then((data: { RawJson: Uint8Array }) => {
            const completionJson = Buffer.from(data.RawJson).toString("utf8")
            setCompletions(JSON.parse(completionJson) as CompletionTotal)
            // success("加载 Yak 语言自动补全成功 / Load Yak IDE Auto Completion Finished")
        })
    }, [])

    useEffect(() => {
        ipcRenderer.invoke("yakit-connect-status").then((data) => {
            setStatus(data)
        })

        ipcRenderer.on("client-engine-status-ok", (e, reason) => {
            if (engineStatus !== "ok") setEngineStatus("ok")
        })
        ipcRenderer.on("client-engine-status-error", (e, reason) => {
            if (engineStatus === "ok") setEngineStatus("error")
        })

        const updateEngineStatus = () => {
            ipcRenderer
                .invoke("engine-status")
                .catch((e: any) => {
                    setEngineStatus("error")
                })
                .finally(() => {
                })
        }
        let id = setInterval(updateEngineStatus, 3000)
        return () => {
            ipcRenderer.removeAllListeners("client-engine-status-error")
            ipcRenderer.removeAllListeners("client-engine-status-ok")
            clearInterval(id)
        }
    }, [])

    useHotkeys("Ctrl+Alt+T", () => {
    })

    useEffect(() => {
        ipcRenderer.invoke("query-latest-notification").then((e: string) => {
            setNotification(e)

            if (e) {
                success(
                    <>
                        <Space direction={"vertical"}>
                            <span>来自于 yaklang.io 的通知</span>
                            <Button
                                type={"link"}
                                onClick={() => {
                                    showModal({
                                        title: "Notification",
                                        content: (
                                            <>
                                                <MDEditor.Markdown source={e}/>
                                            </>
                                        )
                                    })
                                }}
                            >
                                点击查看
                            </Button>
                        </Space>
                    </>
                )
            }
        })
    }, [])

    // 新增数据对比页面
    useEffect(() => {
        ipcRenderer.on("main-container-add-compare", (e, params) => {
            const newTabId = `${Route.DataCompare}-[${randomString(49)}]`;
            const verboseNameRaw = routeKeyToLabel.get(Route.DataCompare) || `${Route.DataCompare}`;
            addTabPage(Route.DataCompare, {node: ContentByRoute(Route.DataCompare, undefined, {system: system})})

            // 区分新建对比页面还是别的页面请求对比的情况
            ipcRenderer.invoke("created-data-compare")
        })

        return () => {
            ipcRenderer.removeAllListeners("main-container-add-compare")
        }
    }, [pageCache])

    // Global Sending Function(全局发送功能|通过发送新增功能页面)
    const addFuzzer = useMemoizedFn((res: any) => {
        const {isHttps, request} = res || {}
        if (request) {
            const time = new Date().getTime().toString()
            addTabPage(Route.HTTPFuzzer, {
                time: time,
                node:
                    ContentByRoute(
                        Route.HTTPFuzzer,
                        undefined,
                        {
                            isHttps: isHttps || false,
                            request: request || "",
                            system: system,
                            order: time
                        }
                    )
            })
            addFuzzerList(time, request || "", isHttps || false)
        }
    })
    const addScanPort = useMemoizedFn((res: any) => {
        const {URL = ""} = res || {}
        if (URL) {
            addTabPage(Route.Mod_ScanPort, {
                node: ContentByRoute(Route.Mod_ScanPort, undefined, {scanportParams: URL})
            })
        }
    })
    const addBrute = useMemoizedFn((res: any) => {
        const {URL = ""} = res || {}
        if (URL) {
            addTabPage(Route.Mod_Brute, {
                node: ContentByRoute(Route.Mod_Brute, undefined, {bruteParams: URL})
            })
        }
    })
    // 发送到专项漏洞检测modal-show变量
    const [bugTestShow, setBugTestShow] = useState<boolean>(false)
    const [bugList, setBugList] = useState<BugInfoProps[]>([])
    const [bugTestValue, setBugTestValue] = useState<BugInfoProps[]>([])
    const [bugUrl, setBugUrl] = useState<string>("")
    const addBugTest = useMemoizedFn((type: number, res?: any) => {
        const {URL = ""} = res || {}

        if (type === 1 && URL) {
            setBugUrl(URL)
            ipcRenderer.invoke("get-value", CustomBugList)
                .then((res: any) => {
                    setBugList(res ? JSON.parse(res) : [])
                    setBugTestShow(true)
                })
                .catch(() => {
                })
        }
        if (type === 2) {
            const filter = pageCache.filter(item => item.route === Route.PoC)
            if (filter.length === 0) {
                addTabPage(Route.PoC)
                setTimeout(() => {
                    ipcRenderer.invoke("send-to-bug-test", {type: bugTestValue, data: bugUrl})
                    setBugTestValue([])
                    setBugUrl("")
                }, 300);
            } else {
                ipcRenderer.invoke("send-to-bug-test", {type: bugTestValue, data: bugUrl})
                setCurrentTabKey(Route.PoC)
                setBugTestValue([])
                setBugUrl("")
            }

        }
    })
    const addYakRunning = useMemoizedFn((res: any) => {
        const {name = "", code = ""} = res || {}
        const filter = pageCache.filter(item => item.route === Route.YakScript)

        if (!name || !code) return false

        if ((filter || []).length === 0) {
            addTabPage(Route.YakScript)
            setTimeout(() => {
                ipcRenderer.invoke("send-to-yak-running", {name, code})
            }, 300);
        } else {
            ipcRenderer.invoke("send-to-yak-running", {name, code})
            setCurrentTabKey(Route.YakScript)
        }
    })
    useEffect(() => {
        ipcRenderer.on("fetch-send-to-tab", (e, res: any) => {
            const {type, data = {}} = res
            if (type === "fuzzer") addFuzzer(data)
            if (type === "scan-port") addScanPort(data)
            if (type === "brute") addBrute(data)
            if (type === "bug-test") addBugTest(1, data)
            if (type === "plugin-store") addYakRunning(data)
        })

        return () => {
            ipcRenderer.removeAllListeners("fetch-send-to-tab")
        }
    }, [])

    // Tabs Bar 组件
    const closeAllCache = useMemoizedFn(() => {
        Modal.confirm({
            title: "确定要关闭所有 Tabs?",
            content: "这样将会关闭所有进行中的进程",
            onOk: () => {
                delFuzzerList(1)
                setPageCache([])
            }
        })
    })
    const closeOtherCache = useMemoizedFn((route: string) => {
        Modal.confirm({
            title: "确定要关闭除此之外所有 Tabs?",
            content: "这样将会关闭所有进行中的进程",
            onOk: () => {
                const arr = pageCache.filter((i) => i.route === route)
                setPageCache(arr)
                if (route === Route.HTTPFuzzer) delFuzzerList(1)
            }
        })
    })
    const bars = (props: any, TabBarDefault: any) => {
        return (
            <TabBarDefault
                {...props}
                children={(barNode: React.ReactElement) => {
                    return (
                        <DropdownMenu
                            menu={{
                                data: [
                                    {key: "all", title: "关闭所有Tabs"},
                                    {key: "other", title: "关闭其他Tabs"}
                                ]
                            }}
                            dropdown={{trigger: ["contextMenu"]}}
                            onClick={(key) => {
                                switch (key) {
                                    case "all":
                                        closeAllCache()
                                        break
                                    case "other":
                                        closeOtherCache(barNode.key as Route)
                                        break
                                    default:
                                        break
                                }
                            }}
                        >
                            {barNode}
                        </DropdownMenu>
                    )
                }}
            />
        )
    }

    return (
        <Layout className="yakit-main-layout">
            <AutoSpin spinning={loading}>
                <Header className="main-laytou-header">
                    <Row>
                        <Col span={8}>
                            <Space>
                                <div style={{marginLeft: 18, textAlign: "center", height: 60}}>
                                    <Image src={YakLogoBanner} preview={false} width={130} style={{marginTop: 6}}/>
                                </div>
                                <Divider type={"vertical"}/>
                                <YakVersion/>
                                <YakitVersion/>
                                {!hideMenu && (
                                    <Button
                                        style={{marginLeft: 4, color: "#207ee8"}}
                                        type={"ghost"}
                                        ghost={true}
                                        onClick={(e) => {
                                            setCollapsed(!collapsed)
                                        }}
                                        icon={collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>}
                                    />
                                )}
                                <Button
                                    style={{marginLeft: 4, color: "#207ee8"}}
                                    type={"ghost"}
                                    ghost={true}
                                    onClick={(e) => {
                                        updateMenuItems()
                                    }}
                                    icon={<ReloadOutlined/>}
                                />
                            </Space>
                        </Col>
                        <Col span={16} style={{textAlign: "right", paddingRight: 28}}>
                            <PerformanceDisplay/>
                            <RiskStatsTag professionalMode={true}/>
                            <Space>
                                {/* {status?.isTLS ? <Tag color={"green"}>TLS:通信已加密</Tag> : <Tag color={"red"}>通信未加密</Tag>} */}
                                {status?.addr && <Tag color={"geekblue"}>{status?.addr}</Tag>}
                                {/* <Tag color={engineStatus === "ok" ? "green" : "red"}>Yak 引擎状态:{engineStatus}</Tag> */}
                                <ReversePlatformStatus/>
                                <Dropdown forceRender={true} overlay={<Menu>
                                    <Menu.Item key={"update"}>
                                        <AutoUpdateYakModuleButton/>
                                    </Menu.Item>
                                    <Menu.Item key={"reverse-global"}>
                                        <ConfigGlobalReverseButton/>
                                    </Menu.Item>
                                </Menu>} trigger={["click"]}>
                                    <Button icon={<SettingOutlined/>}>
                                        配置
                                    </Button>
                                </Dropdown>
                                <Button type={"link"} danger={true} icon={<PoweroffOutlined/>} onClick={() => {
                                    if (winCloseFlag) setWinCloseShow(true)
                                    else {
                                        success("退出当前 Yak 服务器成功")
                                        setEngineStatus("error")
                                    }
                                }}/>
                            </Space>
                        </Col>
                    </Row>
                </Header>
                <Content
                    style={{
                        margin: 12,
                        backgroundColor: "#fff",
                        overflow: "auto"
                    }}
                >
                    <Layout style={{height: "100%", overflow: "hidden"}}>
                        {!hideMenu && (
                            <Sider
                                style={{backgroundColor: "#fff", overflow: "auto"}}
                                collapsed={collapsed}
                            >
                                <Spin spinning={loading}>
                                    <Space
                                        direction={"vertical"}
                                        style={{
                                            width: "100%"
                                        }}
                                    >
                                        <Menu
                                            theme={"light"}
                                            style={{}}
                                            selectedKeys={[]}
                                            mode={"inline"}
                                            onSelect={(e) => {
                                                if (e.key === "ignore") return

                                                const flag = pageCache.filter(item => item.route === (e.key as Route)).length === 0
                                                if (flag) menuAddPage(e.key as Route)
                                                else setCurrentTabKey(e.key)
                                            }}
                                        >
                                            {menuItems.map((i) => {
                                                if (i.Group === "UserDefined") {
                                                    i.Group = "社区插件"
                                                }
                                                return (
                                                    <Menu.SubMenu icon={<EllipsisOutlined/>} key={i.Group}
                                                                  title={i.Group}>
                                                        {i.Items.map((item) => {
                                                            if (item.YakScriptId > 0) {
                                                                return (
                                                                    <MenuItem icon={<EllipsisOutlined/>}
                                                                              key={`plugin:${item.Group}:${item.YakScriptId}`}>
                                                                        <Text
                                                                            ellipsis={{tooltip: true}}>{item.Verbose}</Text>
                                                                    </MenuItem>
                                                                )
                                                            }
                                                            return (
                                                                <MenuItem icon={<EllipsisOutlined/>}
                                                                          key={`batch:${item.Group}:${item.Verbose}:${item.MenuItemId}`}>
                                                                    <Text
                                                                        ellipsis={{tooltip: true}}>{item.Verbose}</Text>
                                                                </MenuItem>
                                                            )

                                                        })}
                                                    </Menu.SubMenu>
                                                )
                                            })}
                                            {(routeMenuData || []).map((i) => {
                                                if (i.subMenuData) {
                                                    return (
                                                        <Menu.SubMenu icon={i.icon} key={i.key} title={i.label}>
                                                            {(i.subMenuData || []).map((subMenu) => {
                                                                return (
                                                                    <MenuItem icon={subMenu.icon} key={subMenu.key}
                                                                              disabled={subMenu.disabled}>
                                                                        <Text
                                                                            ellipsis={{tooltip: true}}>{subMenu.label}</Text>
                                                                    </MenuItem>
                                                                )
                                                            })}
                                                        </Menu.SubMenu>
                                                    )
                                                }
                                                return (
                                                    <MenuItem icon={i.icon} key={i.key} disabled={i.disabled}>
                                                        {i.label}
                                                    </MenuItem>
                                                )
                                            })}
                                        </Menu>
                                    </Space>
                                </Spin>
                            </Sider>
                        )}
                        <Content style={{
                            overflow: "hidden",
                            backgroundColor: "#fff",
                            marginLeft: 12,
                            height: "100%",
                            display: "flex",
                            flexFlow: "column"
                        }}>
                            <div style={{
                                padding: 12,
                                paddingTop: 8,
                                overflow: "hidden",
                                flex: "1",
                                display: "flex",
                                flexFlow: "column"
                            }}>
                                {pageCache.length > 0 ? (
                                    <Tabs
                                        style={{display: "flex", flex: "1"}}
                                        tabBarStyle={{marginBottom: 8}}
                                        className='main-content-tabs yakit-layout-tabs'
                                        activeKey={currentTabKey}
                                        onChange={setCurrentTabKey}
                                        size={"small"}
                                        type={"editable-card"}
                                        renderTabBar={(props, TabBarDefault) => {
                                            return bars(props, TabBarDefault)
                                        }}
                                        hideAdd={true}
                                        onTabClick={(key, e) => {
                                            const divExisted = document.getElementById("yakit-cursor-menu")
                                            if (divExisted) {
                                                const div: HTMLDivElement = divExisted as HTMLDivElement
                                                const unmountResult = ReactDOM.unmountComponentAtNode(div)
                                                if (unmountResult && div.parentNode) {
                                                    div.parentNode.removeChild(div)
                                                }
                                            }
                                        }}
                                    >
                                        {pageCache.map((i) => {
                                            return (
                                                <Tabs.TabPane
                                                    forceRender={true}
                                                    key={i.route}
                                                    tab={i.verbose}
                                                    closeIcon={
                                                        <Space>
                                                            <Popover
                                                                trigger={"click"}
                                                                title={"修改名称"}
                                                                content={
                                                                    <>
                                                                        <Input
                                                                            size={"small"}
                                                                            defaultValue={i.verbose}
                                                                            onBlur={(e) => updateCacheVerbose(i.route, e.target.value)}
                                                                        />
                                                                    </>
                                                                }
                                                            >
                                                                <EditOutlined className='main-container-cion'/>
                                                            </Popover>
                                                            <CloseOutlined
                                                                className='main-container-cion'
                                                                onClick={() => removePage(i.route)}
                                                            />
                                                        </Space>
                                                    }
                                                >
                                                    <div
                                                        style={{
                                                            overflowY: NoScrollRoutes.includes(i.route)
                                                                ? "hidden"
                                                                : "auto",
                                                            overflowX: "hidden",
                                                            height: "100%",
                                                            maxHeight: "100%"
                                                        }}
                                                    >
                                                        {i.singleNode ? (
                                                            i.singleNode
                                                        ) : (
                                                            <MainTabs
                                                                currentTabKey={currentTabKey}
                                                                tabType={i.route}
                                                                pages={i.multipleNode}
                                                                currentKey={i.multipleCurrentKey || ""}
                                                                isShowAdd={true}
                                                                setCurrentKey={(key, type) => {
                                                                    setMultipleCurrentKey(key, type as Route)
                                                                }}
                                                                removePage={(key, type) => {
                                                                    removeMultipleNodePage(key, type as Route)
                                                                }}
                                                                removeOtherPage={(key, type) => {
                                                                    removeOtherMultipleNodePage(key, type as Route)
                                                                }}
                                                                onAddTab={() => menuAddPage(i.route)}
                                                            ></MainTabs>
                                                        )}
                                                    </div>
                                                </Tabs.TabPane>
                                            )
                                        })}
                                    </Tabs>
                                ) : (
                                    <></>
                                )}
                            </div>
                        </Content>
                    </Layout>
                </Content>
            </AutoSpin>

            <Modal
                visible={winCloseShow}
                onCancel={() => setWinCloseShow(false)}
                footer={[
                    <Button key='link' onClick={() => setWinCloseShow(false)}>
                        取消
                    </Button>,
                    <Button key='back' type='primary' onClick={() => {
                        success("退出当前 Yak 服务器成功")
                        setEngineStatus("error")
                    }}>
                        退出
                    </Button>
                ]}
            >
                <div style={{height: 40}}>
                    <ExclamationCircleOutlined style={{fontSize: 22, color: "#faad14"}}/>
                    <span style={{fontSize: 18, marginLeft: 15}}>提示</span>
                </div>
                <p style={{fontSize: 15, marginLeft: 37}}>是否要退出yakit操作界面,一旦退出,界面内打开内容除fuzzer页外都会销毁</p>
                <div style={{marginLeft: 37}}>
                    <Checkbox
                        defaultChecked={!winCloseFlag}
                        value={!winCloseFlag}
                        onChange={() => {
                            setWinCloseFlag(!winCloseFlag)
                            ipcRenderer.invoke("set-value", WindowsCloseFlag, false)
                        }}
                    ></Checkbox>
                    <span style={{marginLeft: 8}}>不再出现该提示信息</span>
                </div>
            </Modal>
            <Modal
                visible={bugTestShow}
                onCancel={() => setBugTestShow(false)}
                footer={[
                    <Button key='link' onClick={() => setBugTestShow(false)}>
                        取消
                    </Button>,
                    <Button key='back' type='primary' onClick={() => {
                        if ((bugTestValue || []).length === 0) return failed("请选择类型后再次提交")
                        addBugTest(2)
                        setBugTestShow(false)
                    }}>
                        确定
                    </Button>
                ]}
            >
                <ItemSelects
                    item={{
                        label: "专项漏洞类型",
                        style: {marginTop: 20}
                    }}
                    select={{
                        allowClear: true,
                        data: BugList.concat(bugList) || [],
                        optText: "title",
                        optValue: "key",
                        value: (bugTestValue || [])[0]?.key,
                        onChange: (value, option: any) => setBugTestValue(value ? [{
                            key: option?.key,
                            title: option?.title
                        }] : [])
                    }}
                ></ItemSelects>
            </Modal>
        </Layout>
    )
}
Example #27
Source File: DestinationStatistics.tsx    From jitsu with MIT License 4 votes vote down vote up
DestinationStatistics: React.FC<CommonDestinationPageProps> = () => {
  const history = useHistory()
  const services = useServices()
  const params = useParams<StatisticsPageParams>()
  const destination = destinationsStore.list.find(d => d._id === params.id)
  const destinationUid = destination?._uid
  const destinationReference = destinationsStore.getDestinationReferenceById(destinationUid)
  const statisticsService = useMemo<IStatisticsService>(
    () => new StatisticsService(services.backendApiClient, services.activeProject.id, true),
    []
  )

  const isSelfHosted = services.features.environment !== "jitsu_cloud"

  // Events last 30 days
  const lastMonthPushEvents = useLoaderAsObject<CombinedStatisticsDatePoint[]>(
    monthlyDataLoader(destinationUid, destinationReference, "push", statisticsService),
    [destinationUid]
  )
  const lastMonthPullEvents = useLoaderAsObject<CombinedStatisticsDatePoint[]>(
    monthlyDataLoader(destinationUid, destinationReference, "pull", statisticsService),
    [destinationUid]
  )

  // Last 24 hours
  const lastDayPushEvents = useLoaderAsObject<CombinedStatisticsDatePoint[]>(
    hourlyDataLoader(destinationUid, destinationReference, "push", statisticsService),
    [destinationUid]
  )
  const lastDayPullEvents = useLoaderAsObject<CombinedStatisticsDatePoint[]>(
    hourlyDataLoader(destinationUid, destinationReference, "pull", statisticsService),
    [destinationUid]
  )

  const somethingIsLoading =
    lastMonthPushEvents.isLoading ||
    lastMonthPullEvents.isLoading ||
    lastDayPushEvents.isLoading ||
    lastDayPullEvents.isLoading

  useEffect(() => {
    currentPageHeaderStore.setBreadcrumbs(
      { title: "Destinations", link: projectRoute(destinationPageRoutes.root) },
      {
        title: (
          <PageHeader
            title={destinationReference ? params.id : "Destination Not Found"}
            icon={destinationReference?.ui.icon}
            mode={destinationReference ? "statistics" : null}
          />
        ),
      }
    )
  }, [])

  return destinationReference ? (
    <>
      <div className="flex flex-row space-x-2 justify-end mb-4">
        <Button
          type="ghost"
          icon={<EditOutlined />}
          size="large"
          onClick={() =>
            history.push(
              projectRoute(destinationPageRoutes.editExact, {
                id: params.id,
              })
            )
          }
        >
          {"Edit Destination"}
        </Button>
        <Button
          type="ghost"
          icon={<UnorderedListOutlined />}
          size="large"
          onClick={() => history.push(projectRoute(destinationPageRoutes.root))}
        >
          {"Destinations List"}
        </Button>
      </div>
      {isSelfHosted && (
        <Row>
          <span className={`text-secondaryText mb-4`}>
            Jitsu 1.37 brought an update that enables for serving more fine-grained statistics data. The new charts will
            not show the events processed by the previous versions of Jitsu.
          </span>
        </Row>
      )}
      <Row gutter={16} className={"mb-4"}>
        <Col span={12}>
          <Card title="Incoming events (last 30 days)" bordered={false} className="w-full" loading={somethingIsLoading}>
            <StatisticsChart data={lastMonthPushEvents.data || []} granularity={"day"} />
          </Card>
        </Col>
        <Col span={12}>
          <Card
            title="Incoming events (last 24 hours)"
            bordered={false}
            className="w-full"
            loading={somethingIsLoading}
          >
            <StatisticsChart data={lastDayPushEvents.data || []} granularity={"hour"} />
          </Card>
        </Col>
      </Row>
      {destinationReference.syncFromSourcesStatus === "supported" && (
        <Row gutter={16}>
          <Col span={12}>
            <Card
              title="Rows synchronized from sources (last 30 days)"
              bordered={false}
              className="w-full"
              loading={somethingIsLoading}
            >
              <StatisticsChart
                data={lastMonthPullEvents.data || []}
                granularity={"day"}
                dataToDisplay={["success", "skip"]}
              />
            </Card>
          </Col>
          <Col span={12}>
            <Card
              title="Rows synchronized from sources (last 24 hours)"
              bordered={false}
              className="w-full"
              loading={somethingIsLoading}
            >
              <StatisticsChart
                data={lastDayPullEvents.data || []}
                granularity={"hour"}
                dataToDisplay={["success", "skip"]}
              />
            </Card>
          </Col>
        </Row>
      )}
    </>
  ) : (
    <DestinationNotFound destinationId={params.id} />
  )
}
Example #28
Source File: CarBrowserCards.tsx    From jmix-frontend with Apache License 2.0 4 votes vote down vote up
CarBrowserCards = observer((props: EntityListProps<Car>) => {
  const {
    entityList,
    onEntityListChange,
    onSelectEntity,
    disabled: readOnlyMode
  } = props;
  const onOpenScreenError = useOpenScreenErrorCallback();
  const onEntityDelete = useEntityDeleteCallback();
  const {
    items,
    count,
    executeListQuery,
    listQueryResult: { loading, error },
    handleDeleteBtnClick,
    handleCreateBtnClick,
    handleEditBtnClick,
    handlePaginationChange,
    goToParentScreen,
    entityListState
  } = useEntityList<Car>({
    listQuery: SCR_CAR_LIST,
    entityName: ENTITY_NAME,
    routingPath: ROUTING_PATH,
    entityList,
    onEntityListChange,
    onPagination: saveHistory,
    onEntityDelete,
    onOpenScreenError
  });

  const getEntityCardsActions = useMemo(() => {
    if (readOnlyMode) {
      return () => [];
    }

    return onSelectEntity
      ? (e: EntityInstance<Car>) => [
          <Button
            htmlType="button"
            type="primary"
            onClick={() => {
              onSelectEntity(e);
              goToParentScreen();
            }}
          >
            <span>
              <FormattedMessage id="common.selectEntity" />
            </span>
          </Button>
        ]
      : (e: EntityInstance<Car>) => [
          <EntityPermAccessControl entityName={ENTITY_NAME} operation="delete">
            <DeleteOutlined
              role={"button"}
              key="delete"
              onClick={(event?: React.MouseEvent) =>
                handleDeleteBtnClick(event, e.id)
              }
            />
          </EntityPermAccessControl>,
          <EntityPermAccessControl entityName={ENTITY_NAME} operation="update">
            <EditOutlined
              role={"button"}
              key="edit"
              onClick={(event?: React.MouseEvent) =>
                handleEditBtnClick(event, e.id)
              }
            />
          </EntityPermAccessControl>
        ];
  }, [
    onSelectEntity,
    handleDeleteBtnClick,
    handleEditBtnClick,
    goToParentScreen,
    readOnlyMode
  ]);

  if (error != null) {
    console.error(error);
    return <RetryDialog onRetry={executeListQuery} />;
  }

  if (loading || items == null) {
    return <Spinner />;
  }

  return (
    <div className={styles.narrowLayout}>
      <div style={{ marginBottom: "12px" }}>
        {(entityList != null || onSelectEntity != null) && (
          <Tooltip title={<FormattedMessage id="common.back" />}>
            <Button
              htmlType="button"
              style={{ margin: "0 12px 12px 0" }}
              icon={<LeftOutlined />}
              onClick={goToParentScreen}
              key="back"
              type="default"
              shape="circle"
            />
          </Tooltip>
        )}
        {onSelectEntity == null && !readOnlyMode && (
          <EntityPermAccessControl entityName={ENTITY_NAME} operation="create">
            <span>
              <Button
                htmlType="button"
                type="primary"
                icon={<PlusOutlined />}
                onClick={handleCreateBtnClick}
              >
                <span>
                  <FormattedMessage id="common.create" />
                </span>
              </Button>
            </span>
          </EntityPermAccessControl>
        )}
      </div>

      {items == null || items.length === 0 ? (
        <p>
          <FormattedMessage id="management.browser.noItems" />
        </p>
      ) : null}
      {items.map((e: EntityInstance<Car>) => (
        <Card
          title={e._instanceName}
          key={e.id ? toIdString(e.id) : undefined}
          style={{ marginBottom: "12px" }}
          actions={getEntityCardsActions(e)}
        >
          {getFields(e).map(p => (
            <EntityProperty
              entityName={ENTITY_NAME}
              propertyName={p}
              value={e[p]}
              key={p}
            />
          ))}
        </Card>
      ))}

      <div style={{ margin: "12px 0 12px 0", float: "right" }}>
        <Paging
          paginationConfig={entityListState.pagination ?? {}}
          onPagingChange={handlePaginationChange}
          total={count}
        />
      </div>
    </div>
  );
})
Example #29
Source File: detail.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
CustomerDetail: React.FC = () => {
  const queryFormRef = useRef<FormInstance>();
  const actionRef = useRef<ActionType>();
  const [customerDetail, setCustomerDetail] = useState<CustomerItem>()
  const [staffMap, setStaffMap] = useState<Dictionary<StaffOption>>({});
  const [allCustomerTagGroups, setAllCustomerTagGroups] = useState<CustomerTagGroupItem[]>([]);
  const [defaultCustomerTags, setDefaultCustomerTags] = useState<CustomerTag[]>([]);
  const [defaultInternalTagsIds, setDefaultInternalTagsIds] = useState<string []>([])
  const [personalTagModalVisible, setPersonalTagModalVisible] = useState(false)
  const [customerTagModalVisible, setCustomerTagModalVisible] = useState(false)
  const [internalTagList, setInternalTagList] = useState<InternalTags.Item[]>([])
  const [internalTagListMap, setInternalTagListMap] = useState<Dictionary<InternalTags.Item>>({});
  const [initialEvents, setInitialEvents] = useState<CustomerEvents.Item[]>([])
  const [currentTab, setCurrentTab] = useState('survey')
  const [basicInfoDisplay, setBasicInfoDisplay] = useState({} as any)// 展示哪些基本信息
  const [basicInfoValues, setBasicInfoValues] = useState({} as any) // 基本信息取值
  const [remarkValues, setRemarkValues] = useState<Remark[]>([])
  const [reloadCusDataTimesTamp, setReloadCusDataTimesTamp] = useState(Date.now)
  const [formRef] = Form.useForm()

  const params = new URLSearchParams(window.location.search);
  const currentStaff = localStorage.getItem('extStaffAdminID') as string
  const extCustomerID = params.get('ext_customer_id') || "";
  if (!extCustomerID) {
    message.error('传入参数请带上ID');
  }

  const extStaff = () => {
    const staffs: StaffItem[] = [];
    customerDetail?.staff_relations?.forEach((staff_relation) => {
      // @ts-ignore
      const staff = staffMap[staff_relation.ext_staff_id];
      if (staff) {
        staffs.push(staff);
      }
    });
    return staffs;
  }

  const getCustomerDetail = () => {
    const hide = message.loading("加载数据中");
    GetCustomerDetail(extCustomerID).then(res => {
      hide();
      if (res?.code !== 0) {
        message.error("获取客户详情失败");
        return;
      }
      setCustomerDetail(res?.data);
      const cusTags: any[] = [];
      const interTagsIds: any[] = []
      res?.data?.staff_relations?.forEach((relation: any) => {
        if (relation.ext_staff_id === currentStaff) {
          relation.customer_staff_tags?.forEach((tag: any) => {
            cusTags.push({...tag, name: tag.tag_name, ext_id: tag.ext_tag_id});
          });
          relation.internal_tags?.forEach((tagId: string) => {
            interTagsIds.push(tagId);
          })
        }

      });
      setDefaultCustomerTags(cusTags)
      setDefaultInternalTagsIds(interTagsIds)
    }).catch(() => {
      hide();
    })
  }

  const getInternalTags = () => {
    QueryInternalTags({page_size: 5000, ext_staff_id: currentStaff}).then(res => {
      if (res?.code === 0) {
        setInternalTagList(res?.data?.items)
        setInternalTagListMap(_.keyBy(res?.data?.items, 'id'))
      } else {
        message.error(res?.message)
      }
    })
  }

  const getCustomerRemark = () => { // 自定义信息id-key
    QueryCustomerRemark().then(res => {
      if (res?.code === 0) {
        console.log('QueryCustomerRemark', res.data)
      } else {
        message.error(res?.message)
      }
    })
  }

  const getBasicInfoDisplay = () => {
    GetCustomerBasicInfoDisplay().then(res => {
      if (res?.code === 0) {
        const displayData = res?.data
        delete displayData.id
        delete displayData.ext_corp_id
        delete displayData.created_at
        delete displayData.updated_at
        delete displayData.deleted_at
        setBasicInfoDisplay(displayData || {})
      } else {
        message.error(res?.message)
      }
    })
  }

  const getBasicInfoAndRemarkValues = () => {
    GetBasicInfoAndRemarkValues({
      ext_customer_id: extCustomerID,
      ext_staff_id: currentStaff,
    }).then(res => {
      if (res?.code === 0) {
        const resData = res?.data
        delete resData.id
        delete resData.ext_corp_id
        delete resData.ext_creator_id
        delete resData.ext_customer_id
        delete resData.ext_staff_id
        delete resData.created_at
        delete resData.updated_at
        delete resData.deleted_at
        delete resData.remark_values
        setBasicInfoValues(resData)
        setRemarkValues(res?.data?.remark_values || [])
      }
    })
  }

  const updateBasicInfoAndRemark = (basicInfoParams: any) => {
    UpdateBasicInfoAndRemark({
      ext_staff_id: currentStaff,
      ext_customer_id: extCustomerID,
      ...basicInfoParams
    }).then(res => {
      if (res?.code === 0) {
        message.success('客户信息更新成功')
        setReloadCusDataTimesTamp(Date.now)
      } else {
        message.error('客户信息更新失败')
      }
    })
  }

  useEffect(() => {
    getInternalTags()
    getCustomerDetail()
    getCustomerRemark()
    getBasicInfoDisplay()
    getBasicInfoAndRemarkValues()
  }, [reloadCusDataTimesTamp])

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

  useEffect(() => {
    QuerySimpleStaffs({page_size: 5000}).then((res) => {
      if (res.code === 0) {
        const staffs = res?.data?.items?.map((item: SimpleStaffInterface) => {
          return {
            label: item.name,
            value: item.ext_id,
            ...item,
          };
        }) || [];
        setStaffMap(_.keyBy<StaffOption>(staffs, 'ext_id'));
      } else {
        message.error(res.message);
      }
    });
  }, []);

  useEffect(() => {
    QueryCustomerEvents({
      ext_customer_id: extCustomerID,
      ext_staff_id: currentStaff,
      page_size: 5
    }).then(res => {
      console.log('QueryCustomerEventsQueryCustomerEvents', res)
      setInitialEvents(res?.data?.items || [])
    })
  }, [])

  useEffect(() => {
    formRef.setFieldsValue(basicInfoValues)
  }, [basicInfoValues])

  return (
    <PageContainer
      fixedHeader
      onBack={() => history.go(-1)}
      backIcon={<LeftOutlined/>}
      header={{
        title: '客户详情',
      }}
    >
      <ProCard>
        <Descriptions title="客户信息" column={1}>
          <Descriptions.Item>
            <div className={'customer-info-field'}>
              <div><img src={customerDetail?.avatar} alt={customerDetail?.name} style={{borderRadius: 5}}/></div>
              <div style={{fontSize: 16, marginLeft: 10}}>
                <p>{customerDetail?.name}</p>
                {customerDetail?.corp_name && (
                  <p style={{color: '#eda150', marginTop: 10}}>@{customerDetail?.corp_name}</p>
                )}
                {customerDetail?.type === 1 && (
                  <p style={{
                    color: '#5ec75d',
                    fontSize: '13px'
                  }}>@微信</p>
                )}
              </div>
            </div>
          </Descriptions.Item>
          <div>
            <div style={{width: 70, display: 'inline-block'}}>企业标签:</div>
            <div className={styles.tagContainer}>
              <Space direction={'horizontal'} wrap={true}>
                {
                  defaultCustomerTags?.length > 0 && defaultCustomerTags?.map((tag) =>
                    <Tag
                      key={tag?.id}
                      className={'tag-item selected-tag-item'}
                    >
                      {tag?.name}
                    </Tag>
                  )}
              </Space>
            </div>
            <Button
              key='addCusTags'
              icon={<EditOutlined/>}
              type={'link'}
              onClick={() => {
                setCustomerTagModalVisible(true);
              }}
            >
              编辑
            </Button>
          </div>

          <div>
            <div style={{width: 70, display: 'inline-block'}}>个人标签:</div>
            <div className={styles.tagContainer}>
              <Space direction={'horizontal'} wrap={true}>
                {
                  defaultInternalTagsIds?.length > 0 && defaultInternalTagsIds.map(id => internalTagListMap[id])?.map((tag) =>
                    <Tag
                      key={tag?.id}
                      className={'tag-item selected-tag-item'}
                    >
                      {tag?.name}
                      <span>
                     </span>
                    </Tag>
                  )}
              </Space>
            </div>
            <Button
              key='addInternalTags'
              icon={<EditOutlined/>}
              type={'link'}
              onClick={() => {
                setPersonalTagModalVisible(true);
              }}
            >
              编辑
            </Button>
          </div>
        </Descriptions>
      </ProCard>

      <ProCard
        tabs={{
          onChange: (activeKey: string) => setCurrentTab(activeKey),
          activeKey: currentTab
        }}
        style={{marginTop: 25}}
      >
        <ProCard.TabPane key="survey" tab="客户概况">
          <div className={styles.survey}>
            <div className={styles.cusSurveyLeft}>
              <div>
                <Descriptions title={<div><ContactsFilled/>&nbsp;&nbsp;添加客服信息</div>} layout="vertical" bordered
                              column={4}>
                  <Descriptions.Item label="所属员工">
                    <CollapsedStaffs limit={2} staffs={extStaff()}/>
                  </Descriptions.Item>

                  <Descriptions.Item label="客户来源">
                       <span>
                         {customerDetail?.staff_relations?.map((para) => {
                           return (`${addWayEnums[para.add_way || 0]}\n`);
                         })}
                       </span>
                  </Descriptions.Item>
                  <Descriptions.Item label="添加时间">
                    <Space>
                      {customerDetail?.staff_relations?.map((para) => {
                        return (
                          <div className={styles.staffTag}
                               dangerouslySetInnerHTML={{
                                 __html: moment(para.createtime)
                                   .format('YYYY-MM-DD HH:mm')
                                   .split(' ')
                                   .join('<br />'),
                               }}
                          />
                        );
                      })}
                    </Space>
                  </Descriptions.Item>
                  <Descriptions.Item label="更新时间">
                    <div
                      dangerouslySetInnerHTML={{
                        __html: moment(customerDetail?.updated_at)
                          .format('YYYY-MM-DD HH:mm')
                          .split(' ')
                          .join('<br />'),
                      }}
                    />
                  </Descriptions.Item>
                </Descriptions>
              </div>
              <Form form={formRef} onFinish={(values) => {
                console.log('ooooooooooooooovaluesvalues', values)
                const basicInfoParams = {...values}
                updateBasicInfoAndRemark(basicInfoParams)
              }}>
                <div style={{paddingTop: 20}} className={styles.baseInfoContainer}>
                  <Descriptions
                    title={<div><BookFilled/>&nbsp;&nbsp;基本信息</div>}
                    bordered
                    column={2}
                    size={'small'}
                  >
                    {
                      Object.keys(basicInfoDisplay).map(key => {
                        return <Descriptions.Item label={basicInfo[key]}>
                          <TableInput name={key} />
                        </Descriptions.Item>
                      })
                    }
                  </Descriptions>
                </div>
                {
                  remarkValues.length > 0 && <div style={{paddingTop: 20}} className={styles.customInfoContainer}>
                    <Descriptions
                      title={<div><EditFilled/>&nbsp;&nbsp;自定义信息</div>}
                      bordered
                      column={2}
                      size={'small'}
                    >
                      <Descriptions.Item label="sfdsf">
                        <TableInput name={'aa'}/>
                      </Descriptions.Item>
                      <Descriptions.Item label="违法的">
                        <TableInput name={'bb'}/>
                      </Descriptions.Item>
                      <Descriptions.Item label="sdf434">
                        <TableInput name={'cc'}/>
                      </Descriptions.Item>
                      <Descriptions.Item label="yjkyujy">
                        <TableInput name={'dd'}/>
                      </Descriptions.Item>
                    </Descriptions>
                  </div>
                }
                <div style={{display: 'flex', justifyContent: 'center', marginTop: 40}}>
                  <Space>
                    <Button onClick={() => formRef.setFieldsValue(basicInfoValues)}>重置</Button>
                    <Button type={"primary"} onClick={() => {
                      formRef.submit()
                    }}>提交</Button>
                  </Space>
                </div>
              </Form>
            </div>

            <div className={styles.cusSurveyRight}>
              <div className={styles.eventsTitle}>
                <span className={styles.titleText}><SoundFilled/>&nbsp;&nbsp;客户动态</span>
                <a onClick={() => setCurrentTab('events')} style={{fontSize: 12}}>查看更多<RightOutlined/></a>
              </div>
              <Events data={initialEvents.filter(elem => elem !== null)} simpleRender={true} staffMap={staffMap}
                      extCustomerID={extCustomerID}/>
            </div>
          </div>

        </ProCard.TabPane>

        <ProCard.TabPane key="events" tab="客户动态">
          <Events staffMap={staffMap} extCustomerID={extCustomerID}/>
        </ProCard.TabPane>

        <ProCard.TabPane key="room" tab="所在群聊">
          <ProTable<GroupChatItem>
            search={false}
            formRef={queryFormRef}
            actionRef={actionRef}
            className={'table'}
            scroll={{x: 'max-content'}}
            columns={columns}
            rowKey="id"
            toolBarRender={false}
            bordered={false}
            tableAlertRender={false}
            dateFormatter="string"
            request={async (originParams: any, sort, filter) => {
              return ProTableRequestAdapter(
                originParams,
                sort,
                filter,
                QueryCustomerGroupsList,
              );
            }}
          />
        </ProCard.TabPane>

        <ProCard.TabPane key="chat" tab="聊天记录">
          {
            setStaffMap[currentStaff]?.enable_msg_arch === 1 ? <Button
                key={'chatSession'}
                type={"link"}
                icon={<ClockCircleOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
                onClick={() => {
                  window.open(`/staff-admin/corp-risk-control/chat-session?staff=${currentStaff}`)
                }}
              >
                聊天记录查询
              </Button>
              :
              <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={<span>员工暂未开启消息存档</span>}/>
          }
        </ProCard.TabPane>

      </ProCard>

      <CustomerTagSelectionModal
        type={'customerDetailEnterpriseTag'}
        isEditable={true}
        withLogicalCondition={false}
        width={'630px'}
        visible={customerTagModalVisible}
        setVisible={setCustomerTagModalVisible}
        defaultCheckedTags={defaultCustomerTags}
        onFinish={(selectedTags) => {
          const removeAry = _.difference(defaultCustomerTags.map(dt => dt.ext_id), selectedTags.map(st => st.ext_id))
          UpdateCustomerTags({
            // @ts-ignore
            add_ext_tag_ids: selectedTags.map((tag) => tag.ext_id),
            ext_customer_ids: [extCustomerID],
            ext_staff_id: currentStaff,
            // @ts-ignore
            remove_ext_tag_ids: removeAry
          }).then(() => {
            getCustomerDetail()
          })
        }}
        allTagGroups={allCustomerTagGroups}
      />

      <InternalTagModal
        width={560}
        allTags={internalTagList}
        allTagsMap={internalTagListMap}
        setAllTags={setInternalTagList}
        visible={personalTagModalVisible}
        setVisible={setPersonalTagModalVisible}
        defaultCheckedTagsIds={defaultInternalTagsIds}
        reloadTags={getInternalTags}
        onFinish={(selectedTags) => {
          console.log('selectedTags', selectedTags)
          const removeAry = _.difference(defaultInternalTagsIds, selectedTags.map(st => st.id))
          CustomerInternalTags({
            // @ts-ignore
            add_ext_tag_ids: selectedTags.map((tag) => tag.id),
            ext_customer_id: extCustomerID,
            ext_staff_id: currentStaff,
            // @ts-ignore
            remove_ext_tag_ids: removeAry
          }).then(() => {
            getCustomerDetail()
          })
        }
        }
      />
    </PageContainer>
  );
}