antd#Timeline TypeScript Examples

The following examples show how to use antd#Timeline. 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: YakitLogFormatter.tsx    From yakit with GNU Affero General Public License v3.0 6 votes vote down vote up
YakitLogViewers = React.memo((props: YakitLogViewersProp) => {
    return <Timeline pending={!props.finished} reverse={true}>
        {(props.data || []).map(e => {
            return <Timeline.Item color={LogLevelToCode(e.level)}>
                <YakitLogFormatter data={e.data} level={e.level} timestamp={e.timestamp} onlyTime={props.onlyTime}/>
            </Timeline.Item>
        })}
    </Timeline>
})
Example #2
Source File: basic.tsx    From yakit with GNU Affero General Public License v3.0 5 votes vote down vote up
AutoUpdateYakModuleViewer: React.FC<AutoUpdateYakModuleViewerProp> = (props) => {
    const [end, setEnd] = useState(false);
    const [error, setError] = useState("");
    const [msg, setMsgs] = useState<ExecResultMessage[]>([]);

    useEffect(() => {
        const messages: ExecResultMessage[] = []
        ipcRenderer.on("client-auto-update-yak-module-data", (e, data: ExecResult) => {
            if (data.IsMessage) {
                try {
                    let obj: ExecResultMessage = JSON.parse(Buffer.from(data.Message).toString("utf8"));
                    messages.unshift(obj)
                } catch (e) {

                }
            }
        });
        ipcRenderer.on("client-auto-update-yak-module-end", (e) => {
            setEnd(true)

        });
        ipcRenderer.on("client-auto-update-yak-module-error", (e, msg: any) => {
            setError(`${msg}`)
        });
        ipcRenderer.invoke("auto-update-yak-module")
        let id = setInterval(() => setMsgs([...messages]), 1000)
        return () => {
            clearInterval(id);
            ipcRenderer.removeAllListeners("client-auto-update-yak-module-data")
            ipcRenderer.removeAllListeners("client-auto-update-yak-module-error")
            ipcRenderer.removeAllListeners("client-auto-update-yak-module-end")
        }
    }, [])

    return <Card title={"自动更新进度"}>
        <Space direction={"vertical"} style={{width: "100%"}} size={12}>
            {error && <Alert type={"error"} message={error}/>}
            {end && <Alert type={"info"} message={"更新进程已结束"}/>}
            <Timeline pending={!end} style={{marginTop: 20}}>
                {(msg || []).filter(i => i.type === "log").map(i => i.content as ExecResultLog).map(e => {
                    return <Timeline.Item color={LogLevelToCode(e.level)}>
                        <YakitLogFormatter data={e.data} level={e.level} timestamp={e.timestamp}/>
                    </Timeline.Item>
                })}
            </Timeline>
        </Space>
    </Card>;
}
Example #3
Source File: detail-modal.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
{ Item: TimeLineItem } = Timeline
Example #4
Source File: detail-modal.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
DetailModal = ({ visible, onCancel, dataSource }: IProps) => {
  const { getOperationRecord } = apiAccessStore.effects;
  const { clearOperationRecord } = apiAccessStore.reducers;
  const [records] = apiAccessStore.useStore((s) => [s.operationRecord]);
  const { client, contract } = { ...defaultData, ...dataSource };
  React.useEffect(() => {
    if (visible) {
      getOperationRecord({ clientID: client.id, contractID: contract.id });
    } else {
      clearOperationRecord();
    }
  }, [clearOperationRecord, client.id, contract.id, getOperationRecord, visible]);
  const fields = [
    {
      label: i18n.t('Creator'),
      value: <UserInfo id={get(client, 'creatorID')} />,
    },
    {
      label: i18n.t('client number'),
      value: get(client, 'clientID'),
    },
  ];
  return (
    <Modal
      title={get(client, 'name')}
      visible={visible}
      onCancel={onCancel}
      destroyOnClose
      footer={null}
      className="client-detail-modal"
      width={960}
    >
      <DetailsPanel
        baseInfoConf={{
          title: i18n.t('basic information'),
          panelProps: {
            fields,
          },
        }}
      />
      <div className="p-4 record-list">
        <div className="title text-base text-normal font-medium mb-2">{i18n.t('approval record')}</div>
        {records.length ? (
          <Timeline>
            {records.map(({ createdAt, action, creatorID, id }) => {
              return (
                <TimeLineItem key={id}>
                  <span className="mr-4">{moment(createdAt).format('YYYY-MM-DD HH:mm:ss')}</span>
                  {creatorID ? <span className="mr-4">{<UserInfo id={creatorID} />}</span> : null}
                  <span>{action}</span>
                </TimeLineItem>
              );
            })}
          </Timeline>
        ) : (
          <div className="no-data">
            <EmptyHolder />
          </div>
        )}
      </div>
    </Modal>
  );
}
Example #5
Source File: repo-commit.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
{ Item: TimelineItem } = Timeline
Example #6
Source File: activity.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
TimelineItem = Timeline.Item
Example #7
Source File: activity.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
TimelineActivity = ({
  list,
  userMap,
  isLoading = false,
  hasMore = false,
  loadMore = noop,
  CustomCard,
}: IProps) => {
  // 依据每一条动态的 timeStamp 区分出时间范围
  const groupActivityList = groupBy(
    map(list, ({ timeStamp, ...rest }) => ({ ...rest, timeStamp, timeRange: moment(timeStamp).format('YYYY-MM-DD') })),
    'timeRange',
  );
  const ranges = Object.keys(groupActivityList);

  return (
    <Timeline className="activity-timeline" pending={isLoading ? `${i18n.t('dop:loading')}...` : false}>
      {map(ranges, (range, i) => {
        return (
          <TimelineItem key={i}>
            <div className="time tc2">{range}</div>
            <div className="list">
              {map(groupActivityList[range], (activity) => {
                const { id } = activity;
                const props = { activity, key: id, userMap };
                const Comp = CustomCard || ActiveCard;
                return <Comp {...props} key={id} />;
              })}
            </div>
          </TimelineItem>
        );
      })}
      <IF check={hasMore && !isLoading}>
        <TimelineItem key="key-load" className="load-more">
          <a onClick={() => loadMore()}>{i18n.t('load more')}</a>
        </TimelineItem>
        <ELSE />
        <TimelineItem />
      </IF>
    </Timeline>
  );
}
Example #8
Source File: version-list.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
{ Item: TimelineItem } = Timeline
Example #9
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
DeployContent = ({
  projectId,
  appId,
  env: propsEnv,
  onCountChange,
  isAppDeploy,
}: {
  projectId: string;
  appId: string;
  env: string;
  isAppDeploy: boolean;
  onCountChange: (count: number) => void;
}) => {
  const [
    {
      detailDrawerVisible,
      addDrawerVisible,
      searchValue,
      logVisible,
      logData,
      deployDetail,
      selectedOrder,
      selectedRelease,
      modes,
    },
    updater,
    update,
  ] = useUpdate<IState>({
    detailDrawerVisible: false,
    addDrawerVisible: false,
    logVisible: false,
    logData: undefined,
    searchValue: '',
    deployDetail: undefined,
    selectedRelease: undefined,
    modes: [],
    selectedOrder: '',
  });
  const env = propsEnv?.toUpperCase();

  const timer = React.useRef<number>();
  const isAutoLoaing = React.useRef(false);
  const reloadRef = React.useRef<{ reload: () => void }>();

  const [deployOrdersData, loading] = getDeployOrders.useState();
  const deployOrders = React.useMemo(() => deployOrdersData?.list || [], [deployOrdersData]);

  const reloadRuntime = () => {
    if (reloadRef.current && reloadRef.current.reload) {
      reloadRef.current.reload();
    }
  };

  const getDeployOrdersFunc = React.useCallback(
    (_query?: { q: string }) => {
      clearInterval(timer.current);
      getDeployOrders.fetch({
        q: searchValue,
        ..._query,
        pageNo: 1,
        pageSize: 100,
        projectID: projectId,
        workspace: env,
      });
      timer.current = setInterval(() => {
        isAutoLoaing.current = true;
        getDeployOrders
          .fetch({
            q: searchValue,
            ..._query,
            pageNo: 1,
            pageSize: 100,
            projectID: projectId,
            workspace: env,
          })
          .then(() => {
            isAutoLoaing.current = false;
          });
      }, 1000 * 30);
    },
    [projectId, env, searchValue],
  );

  const debounceChange = React.useRef(debounce(getDeployOrdersFunc, 600));

  useEffectOnce(() => {
    getDeployOrdersFunc();

    return () => {
      clearInterval(timer.current);
    };
  });

  useUpdateEffect(() => {
    debounceChange.current({ q: searchValue });
  }, [searchValue]);

  const getDeployDetailFunc = React.useCallback(
    (deploymentOrderId: string) => {
      getDeployOrderDetail.fetch({ deploymentOrderId }).then((res) => {
        if (res.data) {
          update({
            deployDetail: res.data,
            detailDrawerVisible: true,
          });
        }
      });
    },
    [update],
  );

  const inParams = {
    projectId,
    appId,
    // deployId: selectedOrder,
    env,
  };

  const deployOrderOpMap = React.useMemo(
    () => ({
      start: (deploymentOrderID: string) => (
        <Button
          type="primary"
          size="small"
          onClick={(e) => {
            e.stopPropagation();
            startDeploy.fetch({ deploymentOrderID }).then(() => {
              reloadRuntime();
              getDeployOrdersFunc();
              deployDetail && getDeployDetailFunc(deployDetail.id);
            });
          }}
        >
          {i18n.t('dop:Start Deployment')}
        </Button>
      ),
      restart: (deploymentOrderID: string) => (
        <Button
          type="primary"
          size="small"
          onClick={(e) => {
            e.stopPropagation();
            startDeploy.fetch({ deploymentOrderID }).then(() => {
              getDeployOrdersFunc();
              deployDetail && getDeployDetailFunc(deployDetail.id);
            });
          }}
        >
          {i18n.t('dop:Restart Deployment')}
        </Button>
      ),
      cancel: (deploymentOrderID: string) => (
        <Button
          size="small"
          onClick={(e) => {
            e.stopPropagation();
            cancelDeploy.fetch({ deploymentOrderID, force: true }).then(() => {
              getDeployOrdersFunc();
              deployDetail && getDeployDetailFunc(deployDetail.id);
            });
          }}
        >
          {i18n.t('dop:cancel deploying')}
        </Button>
      ),
    }),
    [getDeployOrdersFunc, getDeployDetailFunc, deployDetail],
  );

  const userMap = useUserMap();
  const cards = React.useMemo(() => {
    return deployOrders.map((item) => {
      const curUser = userMap[item.operator];
      const curStatus = deployOrderStatusMap[item.status];
      const typeStatusMap = {
        project: { status: 'processing', text: i18n.t('project'), showDot: false },
        application: { status: 'success', text: i18n.t('App'), showDot: false },
      };
      return {
        id: item.id,
        title: item.name,
        time: item.createdAt,
        operator: curUser?.nick || curUser?.name || item.operator,
        titleState: [{ status: curStatus?.status, onlyDot: true }, typeStatusMap[item.releaseInfo?.type]],
        textMeta: [
          {
            mainText: item.applicationStatus,
            subText: i18n.t('App'),
            subTip: firstCharToUpper(i18n.t('dop:deploy succeeded applications count / applications count')),
          },

          { mainText: item.releaseInfo?.version || item.releaseInfo?.id, subText: i18n.t('Artifacts') },
        ],
        icon: (
          <ErdaIcon type="id" size="20" disableCurrent />
          // <Avatar src={curUser?.avatar} size="small" className="mr-1">
          //   {curUser?.nick ? getAvatarChars(curUser.nick) : i18n.t('None')}
          // </Avatar>
        ),
        buttonOperation: item.type !== 'PIPELINE' ? deployOrderOpMap[curStatus.op]?.(item.id) : undefined,
      };
    });
  }, [userMap, deployOrders, deployOrderOpMap]);

  const curDetailStatus =
    deployDetail?.type !== 'PIPELINE' && deployDetail?.status && deployOrderStatusMap[deployDetail?.status];
  const closeAddDrawer = () => {
    update({
      addDrawerVisible: false,
      selectedRelease: undefined,
      modes: [],
    });
  };
  const scenarioKey = isAppDeploy ? 'app-runtime' : 'project-runtime';
  return (
    <>
      <div className="flex flex-1 mt-2 overflow-hidden">
        <div className="bg-white flex-1 overflow-hidden">
          <DiceConfigPage
            scenarioKey={scenarioKey}
            scenarioType={scenarioKey}
            // useMock={useMock}
            // forceMock
            ref={reloadRef}
            inParams={inParams}
            customProps={{
              list: {
                props: isAppDeploy
                  ? {}
                  : {
                      whiteHead: true,
                      whiteFooter: true,
                    },
                op: {
                  onStateChange: (data: { total: number }) => {
                    onCountChange(data?.total);
                  },
                  clickItem: (op: { serverData?: { logId: string; appId: string } }, extra: { action: string }) => {
                    const { logId, appId: _appId } = op.serverData || {};
                    if (extra.action === 'clickTitleState' && logId && _appId) {
                      update({
                        logVisible: true,
                        logData: {
                          detailLogId: logId,
                          applicationId: _appId,
                        },
                      });
                    }
                  },
                },
              },
              page: {
                props: {
                  className: 'h-full',
                },
              },
            }}
          />
        </div>
        {isAppDeploy ? null : (
          <div className="bg-white flex">
            <div className="w-[320px] bg-default-02 rounded-sm flex flex-col">
              <div className="px-4 flex justify-between items-center mt-2">
                <span className="text-default-8 font-medium">{i18n.t('dop:Deployment records')}</span>
                <Button
                  size="small"
                  className="text-default-4 hover:text-default-8 flex items-center"
                  onClick={() => updater.addDrawerVisible(true)}
                >
                  <ErdaIcon type="plus" />
                </Button>
              </div>
              <div className="mt-2 px-4">
                <Input
                  size="small"
                  className="bg-black-06 border-none"
                  value={searchValue}
                  prefix={<ErdaIcon size="16" fill="default-3" type="search" />}
                  onChange={(e) => {
                    const { value } = e.target;
                    updater.searchValue(value);
                  }}
                  placeholder={i18n.t('dop:search by ID, person or product information')}
                />
              </div>
              <div className="mt-2 flex-1 h-0">
                <Spin
                  spinning={!isAutoLoaing.current && loading}
                  wrapperClassName="full-spin-height overflow-hidden project-deploy-orders"
                >
                  {cards.length ? (
                    <Timeline className="mt-2">
                      {cards.map((card) => {
                        const { operator, ...cardRest } = card;
                        return (
                          <Timeline.Item
                            key={card.id}
                            dot={<div className="ml-0.5 mt-1 bg-default-3 w-[8px] h-[8px] rounded-full" />}
                          >
                            <div className="text-sm text-default-6 mb-1">
                              <span className="mr-2">{operator}</span>
                              <span>{moment(card.time).format('YYYY-MM-DD HH:mm:ss')}</span>
                            </div>
                            <CardItem
                              className={'bg-white'}
                              card={cardRest}
                              onClick={() => {
                                getDeployDetailFunc(card.id);
                              }}
                            />
                          </Timeline.Item>
                        );
                      })}
                    </Timeline>
                  ) : (
                    <EmptyHolder relative />
                  )}
                </Spin>
              </div>
            </div>
          </div>
        )}
      </div>
      <Drawer
        width={'80%'}
        destroyOnClose
        title={
          <div className="flex-h-center justify-between pr-8">
            <div className="flex-h-center">
              <span>{deployDetail?.name}</span>
              {curDetailStatus ? (
                <Badge className="ml-1" status={curDetailStatus.status} text={curDetailStatus.text} />
              ) : null}
            </div>
            <div>{curDetailStatus?.op && deployOrderOpMap[curDetailStatus.op]?.(deployDetail?.id)}</div>
          </div>
        }
        visible={detailDrawerVisible}
        onClose={() => update({ detailDrawerVisible: false, deployDetail: undefined })}
      >
        <DeployDetail detail={deployDetail} />
      </Drawer>
      <Drawer
        title={
          <div className="flex-h-center">
            <span className="mr-2">{i18n.t('dop:Create deployment')}</span>
            {selectedRelease ? (
              <>
                <ErdaIcon size={20} type="id" disableCurrent className="mr-1" />
                <span>{selectedRelease.name}</span>
              </>
            ) : null}
          </div>
        }
        width={'80%'}
        destroyOnClose
        visible={addDrawerVisible}
        onClose={closeAddDrawer}
        footer={
          <div className="">
            <Button
              type="primary"
              className="mr-2"
              disabled={!selectedRelease || selectedRelease.hasFail}
              onClick={() => {
                if (selectedRelease?.type === 'PROJECT_RELEASE' && !modes.length) {
                  message.error(i18n.t('please choose the {name}', { name: i18n.t('mode') }));
                  return;
                }

                selectedRelease &&
                  createDeploy
                    .fetch({ workspace: env, id: selectedRelease.id, releaseId: selectedRelease.releaseId, modes })
                    .then(() => {
                      getDeployOrdersFunc();
                      closeAddDrawer();
                    });
              }}
            >
              {i18n.t('create')}
            </Button>
            <Button onClick={closeAddDrawer}>{i18n.t('Cancel')}</Button>
          </div>
        }
      >
        <AddDeploy
          id={selectedRelease?.id}
          onSelect={(v: { id: string; releaseId: string; name: string; hasFail: boolean; type?: string }) =>
            updater.selectedRelease(v)
          }
          onModesSelect={(v: string[]) => {
            updater.modes(v);
          }}
        />
      </Drawer>

      <Drawer visible={logVisible} width={'80%'} onClose={() => update({ logVisible: false, logData: undefined })}>
        {logData ? <DeployLog {...logData} /> : null}
      </Drawer>
    </>
  );
}
Example #10
Source File: base.tsx    From yakit with GNU Affero General Public License v3.0 4 votes vote down vote up
PluginResultUI: React.FC<PluginResultUIProp> = React.memo((props) => {
    const {loading, results, featureType = [], feature = [], progress, script, statusCards} = props;
    const [active, setActive] = useState(props.defaultConsole ? "console" : "feature-0");
    const xtermRef = useRef(null)
    const timer = useRef<any>(null)

    useEffect(() => {
        if (!xtermRef) {
            return
        }
        if (props.onXtermRef) props.onXtermRef(xtermRef);
    }, [xtermRef])

    let progressBars: { id: string, node: React.ReactNode }[] = [];
    progress.forEach((v) => {
        progressBars.push({
            id: v.id, node: <Card size={"small"} hoverable={false} bordered={true} title={`任务进度ID:${v.id}`}>
                <Progress percent={parseInt((v.progress * 100).toFixed(0))} status="active"/>
            </Card>,
        })
    })
    // progressBars = progressBars.sort((a, b) => a.id.localeCompare(b.id));

    const features: { feature: string, params: any, key: string }[] = featureType.filter(i => {
        return i.level === "json-feature"
    }).map(i => {
        try {
            let res = JSON.parse(i.data) as { feature: string, params: any, key: string };
            if (!res.key) {
                res.key = randomString(50)
            }
            return res
        } catch (e) {
            return {feature: "", params: undefined, key: ""}
        }
    }).filter(i => i.feature !== "");

    const finalFeatures = features.length > 0 ?
        features.filter((data, i) => features.indexOf(data) === i)
        : [];

    const timelineItemProps = (results || []).filter(i => {
        return !((i?.level || "").startsWith("json-feature") || (i?.level || "").startsWith("feature-"))
    }).splice(0, 25);

    return <div style={{width: "100%", height: "100%", overflow: "hidden auto"}}>
        {/* <div style={{width: "100%", height: "100%", display: "flex", flexDirection: "column", overflow: "auto"}}> */}
        {props.debugMode && props.onXtermRef && <>
            <div style={{width: "100%", height: 240}}>
                <XTerm ref={xtermRef} options={{convertEol: true, rows: 8}}
                       onResize={(r) => {
                           xtermFit(xtermRef, 50, 18)
                       }}
                       customKeyEventHandler={(e) => {
                           if (e.keyCode === 67 && (e.ctrlKey || e.metaKey)) {
                               const str = xtermRef?.current ? (xtermRef.current as any).terminal.getSelection() : ""

                               if (timer.current) {
                                   clearTimeout(timer.current)
                                   timer.current = null
                               }
                               timer.current = setTimeout(() => {
                                   ipcRenderer.invoke("copy-clipboard", str).finally(() => {
                                       timer.current = null
                                   })
                               }, 300)
                           }
                           return true
                       }}
                />
            </div>
        </>}
        {statusCards.length > 0 && <div style={{marginTop: 8, marginBottom: 8}}>
            <Row gutter={8}>
                {statusCards.map((card, cardIndex) => {
                    return <Col key={card.tag} span={8} style={{marginBottom: 8}}>
                        <Card hoverable={true} bodyStyle={{padding: 12}}>
                            <div>
                                <h2>{card.tag}</h2>
                                <div style={{display: 'flex', justifyContent: 'space-between'}}>
                                    {card.info.map((info, infoIndex) => {
                                        return <Statistic valueStyle={{
                                            color: idToColor(info.Id),
                                            textAlign: `${(infoIndex >= 1) && (card.info.length === infoIndex + 1) ? 'right' : 'left'}`
                                        }} key={info.Id} title={card.info.length > 1 ? info.Id : ''} value={info.Data}/>
                                    })}
                                </div>
                            </div>

                        </Card>
                    </Col>
                })}
            </Row>
        </div>}
        {progressBars.length > 0 && <div style={{marginTop: 4, marginBottom: 8}}>
            {progressBars.map(i => i.node)}
        </div>}
        <Tabs
            style={{flex: 1}}
            className={"main-content-tabs"}
            size={"small"}
            activeKey={active}
            onChange={activeKey => {
                setActive(activeKey)
                setTimeout(() => {
                    if (xtermRef && props.debugMode) xtermFit(xtermRef, 50, 18)
                }, 50);
            }}
        >
            {(finalFeatures || []).map((i, index) => {
                return <Tabs.TabPane
                    tab={YakitFeatureTabName(i.feature, i.params)}
                    key={`feature-${index}`}>
                    <YakitFeatureRender
                        params={i.params} feature={i.feature}
                        execResultsLog={feature || []}
                    />
                </Tabs.TabPane>
            })}
            <Tabs.TabPane tab={"基础插件信息 / 日志"} key={finalFeatures.length > 0 ? "log" : "feature-0"}>
                {<>
                    {/*<Divider orientation={"left"}>Yakit Module Output</Divider>*/}
                    <AutoCard
                        size={"small"} hoverable={true} bordered={true} title={<Space>
                        <div>
                            任务额外日志与结果
                        </div>
                        {(timelineItemProps || []).length > 0 ? formatDate(timelineItemProps[0].timestamp) : ""}
                    </Space>}
                        style={{marginBottom: 20, marginRight: 2}}
                        bodyStyle={{overflowY: "auto"}}
                    >
                        <Timeline pending={loading} style={{marginTop: 10, marginBottom: 10}}>
                            {(timelineItemProps || []).reverse().map((e, index) => {
                                return <Timeline.Item key={index} color={LogLevelToCode(e.level)}>
                                    <YakitLogFormatter data={e.data} level={e.level} timestamp={e.timestamp}
                                                       onlyTime={true}/>
                                </Timeline.Item>
                            })}
                        </Timeline>
                    </AutoCard>
                </>}
            </Tabs.TabPane>
            {!props.debugMode && props.onXtermRef && <Tabs.TabPane tab={"Console"} key={"console"}>
                <div style={{width: "100%", height: "100%"}}>
                    <CVXterm
                        ref={xtermRef}
                        options={{convertEol: true}}
                    />
                    {/* <XTerm ref={xtermRef} options={{convertEol: true, rows: 8}}
                        onResize={(r) => {
                            xtermFit(xtermRef, 50, 18)
                        }}
                        customKeyEventHandler={(e) => {
                            if (e.keyCode === 67 && (e.ctrlKey || e.metaKey)) {
                                const str = xtermRef?.current ? (xtermRef.current as any).terminal.getSelection() : ""
        
                                if (timer.current) {
                                    clearTimeout(timer.current)
                                    timer.current = null
                                }
                                timer.current = setTimeout(() => {
                                    ipcRenderer.invoke("copy-clipboard", str).finally(() => {
                                        timer.current = null
                                    })
                                }, 300)
                            }
                            return true
                        }}
                    /> */}
                </div>
            </Tabs.TabPane>}
        </Tabs>
        {/* </div> */}
    </div>
})
Example #11
Source File: ExecMessageViewer.tsx    From yakit with GNU Affero General Public License v3.0 4 votes vote down vote up
ExecResultsViewer: React.FC<ExecResultsViewerProp> = (props) => {
    const messages: ExecResultMessage[] = [];
    props.results.forEach(e => {
        if (!e.IsMessage) {
            return
        }

        try {
            const raw = e.Message
            const obj: ExecResultMessage = JSON.parse(Buffer.from(raw).toString("utf8"))
            messages.push(obj);
        } catch (e) {
            console.error(e)
        }
    })

    // 处理日志并排序
    const logs: ExecResultLog[] = messages.filter(e => e.type === "log")
        .map(i => {
            return i.content
        })
        .sort((a: any, b: any) => a.timestamp - b.timestamp) as ExecResultLog[];
    const haveCriticalResult = logs.filter(i => ["json", "success"].includes((i?.level || "").toLowerCase())).length > 0;

    // 处理进度
    const progressTable = new Map<string, number>();
    messages.forEach(e => {
        if (e.type === "progress") {
            const progress = (e.content as ExecResultProgress);
            let percent = progressTable.get(progress.id)
            if (!percent) {
                progressTable.set(progress.id, progress.progress)
            } else {
                progressTable.set(progress.id, Math.max(percent, progress.progress))
            }
        }
    })

    const full = useMemoizedFn(() => {
        let progressBars: { id: string, node: React.ReactNode }[] = [];
        progressTable.forEach((v, k) => {
            progressBars.push({
                id: k, node: <Card size={"small"} hoverable={false} bordered={true} title={`任务进度ID:${k}`}>
                    <Progress percent={parseInt((v * 100).toFixed(0))} status="active"/>
                </Card>,
            })
        })
        progressBars = progressBars.sort((a, b) => a.id.localeCompare(b.id));

        return <Space direction={"vertical"} style={{width: "100%"}}>
            {haveCriticalResult && <Alert
                style={{marginBottom: 8}}
                type={"success"}
                message={<div>
                    ATTENTION: 本 PoC 输出相对关键的信息 / There is something important from current PoC.
                </div>}
            />}
            {progressBars.map(i => i.node)}
            <Timeline pending={true}>
                {(logs || []).sort().map((e, index) => {
                    return <Timeline.Item key={index} color={LogLevelToCode(e.level)}>
                        <YakitLogFormatter data={e.data} level={e.level} timestamp={e.timestamp}/>
                    </Timeline.Item>
                })}
            </Timeline>
        </Space>
    })

    if (props.oneLine) {
        let progressOneLine: { id: string, node: React.ReactNode }[] = [];
        progressTable.forEach((v, k) => {
            progressOneLine.push({
                id: k, node: <Tag>{k}: {(v * 100).toString(0)}%</Tag>,
            })
        })
        progressOneLine = progressOneLine.sort((a, b) => a.id.localeCompare(b.id));
        const latestLogData = logs[logs.length - 1];
        const latestLog = logs.length > 0 && latestLogData && <Space>
            <Tag
                color={LogLevelToCode(latestLogData.level)}
            >{formatTime(latestLogData?.timestamp)}: {(latestLogData.level).toUpperCase()}</Tag>
            <Text style={{maxWidth: 1200}} ellipsis={{tooltip: true}} copyable={true}>{latestLogData.data}</Text>
        </Space>
        return <Card hoverable={true} bodyStyle={{
            padding: 6, margin: 0,
        }} bordered={false} onClick={e => {
            showModal({
                width: "75%",
                title: "任务进度详情",
                content: <>
                    {full()}
                </>
            })
        }}>
            <Space>
                {haveCriticalResult ? <Tag color={"red"}>HIT</Tag> : <Tag color={"gray"}>暂无结果</Tag>}
                {progressTable.size > 0 ? <Space>
                    {progressOneLine.map(i => i.node)}
                </Space> : undefined}
                {logs.length > 0 ? <>
                    {latestLog}
                </> : undefined}
            </Space>
        </Card>
    }
    return <>{full()}</>;
}
Example #12
Source File: BatchExecuteByFilter.tsx    From yakit with GNU Affero General Public License v3.0 4 votes vote down vote up
BatchExecutorResultByFilter: React.FC<BatchExecutorResultByFilterProp> = (props) => {
    const [activeTask, setActiveTask] = useState<BatchTask[]>([]);
    const allPluginTasks = useRef<Map<string, ExecBatchYakScriptResult[]>>(new Map<string, ExecBatchYakScriptResult[]>())
    const [allTasks, setAllTasks] = useState<BatchTask[]>([]);
    const [hitTasks, setHitTasks] = useState<ExecResultLog[]>([]);
    const [taskLog, setTaskLog, getTaskLog] = useGetState<TaskResultLog[]>([]);
    const allTasksMap = useCreation<Map<string, BatchTask>>(() => {
        return new Map<string, BatchTask>()
    }, [])
    const [jsonRisks, setJsonRisks] = useState<Risk[]>([]);

    const [tableContentHeight, setTableContentHeight] = useState<number>(0);
    const [activeKey, setActiveKey] = useState<string>("executing")

    useEffect(() => {
        if (props.executing && (!!allPluginTasks) && (!!allPluginTasks.current)) allPluginTasks.current.clear()
    }, [props.executing])

    // 转换task内的result数据
    const convertTask = (task: BatchTask) => {
        // @ts-ignore
        const results: ExecResult[] = task.Results.filter((item) => !!item.Result).map((item) => item.Result)

        const messages: ExecResultMessage[] = []
        for (let item of results) {
            if (!item.IsMessage) continue

            try {
                const raw = item.Message
                const obj: ExecResultMessage = JSON.parse(Buffer.from(raw).toString("utf8"))
                messages.push(obj)
            } catch (e) {
                console.error(e)
            }
        }

        return messages
    }

    useEffect(() => {
        const update = () => {
            const result: BatchTask[] = [];
            let hitResult: ExecResultLog[] = [];
            allTasksMap.forEach(value => {
                if (value.Results[value.Results.length - 1]?.Status === "end") {
                    result.push(value)
                    if (value.Results.length !== 0) {
                        const arr: ExecResultLog[] =
                            (convertTask(value)
                                .filter((e) => e.type === "log")
                                .map((i) => i.content) as ExecResultLog[])
                                .filter((i) => (i?.level || "").toLowerCase() === "json-risk")
                        if (arr.length > 0) {
                            hitResult = hitResult.concat(...arr)
                        }
                    }
                }
            })
            setAllTasks(result)
            setHitTasks(hitResult)
        }
        update()
        const id = setInterval(update, 3000)
        return () => {
            clearInterval(id)
        }
    }, [])

    useEffect(() => {
        let index = 0
        const activeTask = new Map<string, ExecBatchYakScriptResult[]>();
        ipcRenderer.on(`${props.token}-error`, async (e, exception) => {
            if (`${exception}`.includes("Cancelled on client")) {
                return
            }
            console.info("call exception")
            console.info(exception)
        })

        ipcRenderer.on(`${props.token}-data`, async (e, data: ExecBatchYakScriptResult) => {
            // 处理进度信息
            if (data.ProgressMessage) {
                if (!!props.setPercent) {
                    props.setPercent(data.ProgressPercent || 0)
                }
                return
            }

            // 处理其他任务信息
            const taskId: string = data.TaskId || "";
            if (taskId === "") return

            // 缓存内容
            let activeResult = activeTask.get(taskId);
            if (!activeResult) activeResult = []
            activeResult.push(data)
            activeTask.set(taskId, activeResult)
            // 缓存全部
            let allresult = allPluginTasks.current.get(taskId);
            if (!allresult) allresult = []
            allresult.push(data)
            allPluginTasks.current.set(taskId, allresult)

            if (data.Result && data.Result.IsMessage) {
                const info: TaskResultLog = JSON.parse(new Buffer(data.Result.Message).toString()).content
                if (info) {
                    info.key = index
                    index += 1
                    const arr: TaskResultLog[] = [...getTaskLog()]
                    if (arr.length >= 20) arr.shift()
                    arr.push(info)
                    setTaskLog([...arr])
                }
            }

            // 设置状态
            if (data.Status === "end") {
                activeTask.delete(taskId)
                return
            }

            // 看一下输出结果
            // if (data.Result && data.Result.IsMessage) {
            //     console.info(321,new Buffer(data.Result.Message).toString())
            // }
        })

        let cached = "";
        const syncActiveTask = () => {
            if (activeTask.size <= 0) setActiveTask([]);
            if (activeTask.size <= 0 && allPluginTasks.current.size <= 0) return

            const result: BatchTask[] = [];
            const tasks: string[] = [];
            activeTask.forEach(value => {
                if (value.length <= 0) return

                const first = value[0];
                const task = {
                    Target: first.Target || "",
                    ExtraParam: first.ExtraParams || [],
                    PoC: first.PoC,
                    TaskId: first.TaskId,
                    CreatedAt: first.Timestamp,
                } as BatchTask;
                task.Results = value;
                result.push(task)
                tasks.push(`${value.length}` + task.TaskId)
            })
            const allResult: BatchTask[] = [];
            allPluginTasks.current.forEach(value => {
                if (value.length <= 0) return

                const task = {
                    Target: value[0].Target || "",
                    ExtraParam: value[0].ExtraParams || [],
                    PoC: value[0].PoC,
                    TaskId: value[0].TaskId,
                    CreatedAt: value[0].Timestamp,
                } as BatchTask;
                task.Results = value;
                allResult.push(task)
            })

            const oldAllResult: BatchTask[] = []
            allTasksMap.forEach(value => oldAllResult.push(value))
            if (JSON.stringify(allResult) !== JSON.stringify(oldAllResult)) {
                allResult.forEach((value) => allTasksMap.set(value.TaskId, value))
            }

            const tasksRaw = tasks.sort().join("|")
            if (tasksRaw !== cached) {
                cached = tasksRaw
                setActiveTask(result)
            }
        }

        let id = setInterval(syncActiveTask, 300);
        return () => {
            ipcRenderer.removeAllListeners(`${props.token}-data`)
            ipcRenderer.removeAllListeners(`${props.token}-end`)
            ipcRenderer.removeAllListeners(`${props.token}-error`)
            allTasksMap.clear()
            setTaskLog([])
            setAllTasks([])
            setActiveKey("executing")
            clearInterval(id);
        }
    }, [props.token])

    useEffect(() => {
        if (hitTasks.length <= 0) {
            return
        }
        setJsonRisks(hitTasks.map(i => {
            try {
                return JSON.parse(i.data)
            } catch (e) {
                return undefined
            }
        }).filter(i => !!i))
    }, [hitTasks])

    return <div className="batch-executor-result">
        <div className="result-notice-body">
            <div className="notice-body">
                <div className="notice-body-header notice-font-in-progress">正在执行任务</div>
                <div className="notice-body-counter">{activeTask.length}</div>
            </div>
            <Divider type="vertical" className="notice-divider"/>
            <div className="notice-body">
                <div className="notice-body-header notice-font-completed">已完成任务</div>
                <div className="notice-body-counter">{allTasks.length}</div>
            </div>
            <Divider type="vertical" className="notice-divider"/>
            <div className="notice-body">
                <div className="notice-body-header notice-font-vuln">命中风险/漏洞</div>
                <div className="notice-body-counter">{jsonRisks.length}</div>
            </div>
        </div>

        <Divider style={{margin: 4}}/>

        <div className="result-table-body">
            <Tabs className="div-width-height-100 yakit-layout-tabs" activeKey={activeKey} onChange={setActiveKey}>
                <Tabs.TabPane tab="任务日志" key={"executing"}>
                    <div className="div-width-height-100" style={{overflow: "hidden"}}>
                        <Timeline className="body-time-line" pending={props.executing} reverse={true}>
                            {taskLog.map(item => {
                                return <Timeline.Item key={item.key}>
                                    <YakitLogFormatter data={item.data} level={item.level}
                                                       timestamp={item.timestamp} onlyTime={true} isCollapsed={true}/>
                                </Timeline.Item>
                            })}
                        </Timeline>
                    </div>
                </Tabs.TabPane>
                <Tabs.TabPane tab="命中风险与漏洞" key={"hitTable"}>
                    <div style={{width: "100%", height: "100%"}}>
                        <ReactResizeDetector
                            onResize={(width, height) => {
                                if (!width || !height) return
                                setTableContentHeight(height - 4)
                            }}
                            handleWidth={true} handleHeight={true} refreshMode={"debounce"} refreshRate={50}/>
                        <TableResizableColumn
                            virtualized={true}
                            sortFilter={() => {
                            }}
                            autoHeight={tableContentHeight <= 0}
                            height={tableContentHeight}
                            data={jsonRisks}
                            wordWrap={true}
                            renderEmpty={() => {
                                return <Empty className="table-empty" description="数据加载中"/>
                            }}
                            columns={[
                                {
                                    dataKey: "TitleVerbose",
                                    width: 400,
                                    resizable: true,
                                    headRender: () => "标题",
                                    cellRender: ({rowData, dataKey, ...props}: any) => {
                                        return (
                                            <div
                                                className="div-font-ellipsis"
                                                style={{width: "100%"}}
                                                title={rowData?.TitleVerbose || rowData.Title}
                                            >
                                                {rowData?.TitleVerbose || rowData.Title}
                                            </div>
                                        )
                                    }
                                },
                                {
                                    dataKey: "RiskTypeVerbose",
                                    width: 130,
                                    headRender: () => "类型",
                                    cellRender: ({rowData, dataKey, ...props}: any) => {
                                        return rowData?.RiskTypeVerbose || rowData.RiskType
                                    }
                                },
                                {
                                    dataKey: "Severity",
                                    width: 90,
                                    headRender: () => "等级",
                                    cellRender: ({rowData, dataKey, ...props}: any) => {
                                        const title = TitleColor.filter((item) => item.key.includes(rowData.Severity || ""))[0]
                                        return (
                                            <span className={title?.value || "title-default"}>
                                                {title ? title.name : rowData.Severity || "-"}
                                            </span>
                                        )
                                    }
                                },
                                {
                                    dataKey: "IP",
                                    width: 140,
                                    headRender: () => "IP",
                                    cellRender: ({rowData, dataKey, ...props}: any) => {
                                        return rowData?.IP || "-"
                                    }
                                },
                                {
                                    dataKey: "ReverseToken",
                                    headRender: () => "Token",
                                    cellRender: ({rowData, dataKey, ...props}: any) => {
                                        return rowData?.ReverseToken || "-"
                                    }
                                },
                                {
                                    dataKey: "operate",
                                    width: 90,
                                    fixed: "right",
                                    headRender: () => "操作",
                                    cellRender: ({rowData}: any) => {
                                        return (
                                            <a
                                                onClick={(e) => {
                                                    showModal({
                                                        width: "80%",
                                                        title: "详情",
                                                        content: (
                                                            <div style={{overflow: "auto"}}>
                                                                <RiskDetails info={rowData} isShowTime={false}/>
                                                            </div>
                                                        )
                                                    })
                                                }}
                                            >详情</a>
                                        )
                                    }
                                }
                            ].map(item => {
                                item["verticalAlign"] = "middle"
                                return item
                            })}
                        />
                    </div>
                </Tabs.TabPane>
            </Tabs>
        </div>
    </div>
}
Example #13
Source File: Timeline.tsx    From nanolooker with MIT License 4 votes vote down vote up
RecentTransactions: React.FC<Props> = ({ recentTransactions }) => {
  const { t } = useTranslation();
  const history = useHistory();
  const {
    theme,
    filterTransactions,
    disableLiveTransactions,
  } = React.useContext(PreferencesContext);
  const isMediumAndLower = !useMediaQuery("(min-width: 768px)");

  return (
    <Timeline className="sticky" mode={isMediumAndLower ? "left" : "alternate"}>
      {recentTransactions.map(
        ({ account, amount, hash, timestamp, alias, block: { subtype } }) => {
          const color =
            // @ts-ignore
            Colors[
              `${subtype.toUpperCase()}${theme === Theme.DARK ? "_DARK" : ""}`
            ];

          return (
            <Timeline.Item
              color={color}
              key={hash}
              className={`fadein ${subtype === "send" ? "right" : "left"}`}
            >
              <div className="first-row">
                <Tag
                  color={
                    // @ts-ignore
                    TwoToneColors[
                      `${subtype.toUpperCase()}${
                        theme === Theme.DARK ? "_DARK" : ""
                      }`
                    ]
                  }
                  className={`tag-${subtype} timeline-tag`}
                >
                  {t(`transaction.${subtype}`)}
                </Tag>
                {subtype !== "change" ? (
                  <Text style={{ color }} className="timeline-amount">
                    {amount
                      ? `Ӿ ${new BigNumber(rawToRai(amount)).toFormat()}`
                      : t("common.notAvailable")}
                  </Text>
                ) : null}
                <TimeAgo
                  locale={i18next.language}
                  datetime={timestamp}
                  live={true}
                  className="timeline-timeago color-muted"
                  style={{
                    marginLeft: subtype === "change" ? "6px" : 0,
                  }}
                />
              </div>
              {alias ? <div className="color-important">{alias}</div> : null}
              {filterTransactions || disableLiveTransactions ? (
                <>
                  <Link to={`/account/${account}`} className="color-normal">
                    {account}
                  </Link>
                  <br />
                  <Link to={`/block/${hash}`} className="color-muted">
                    {hash}
                  </Link>
                </>
              ) : (
                <>
                  <span
                    className="link color-normal"
                    // iOS has difficulties when using <a> & onClick listeners when CPS are very high,
                    // the other page onClick events becomes unresponsive, using <span> & onMouseDown instead
                    // seems to remove that limitation :shrug:
                    onMouseDown={e => {
                      e.preventDefault();
                      history.push(`/account/${account}`);
                    }}
                  >
                    {account}
                  </span>
                  <br />
                  <span
                    className="link color-muted"
                    onMouseDown={e => {
                      e.preventDefault();
                      history.push(`/block/${hash}`);
                    }}
                  >
                    {hash}
                  </span>
                </>
              )}
            </Timeline.Item>
          );
        },
      )}
    </Timeline>
  );
}
Example #14
Source File: version-list.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
VersionList = (props: IProps) => {
  const { artifacts } = props;
  const { id: artifactsId } = artifacts || {};
  const { getVersionList, setGrayAndPublish, getOnlineVersionData, getH5PackageNames } = publisherStore.effects;
  const [list, paging, onlineVersionData] = publisherStore.useStore((s) => [
    s.versionList,
    s.versionPaging,
    s.onlineVersionData,
  ]);
  const [isFetching] = useLoading(publisherStore, ['getVersionList']);
  const publishOperationAuth = usePerm((s) => s.org.publisher.operation.pass);
  const { mode, publisherItemId } = routeInfoStore.useStore((s) => s.params);
  const isMobile = mode === ArtifactsTypeMap.MOBILE.value;
  const [
    {
      pageNo,
      formModalVis,
      h5packageNames,
      curPackageName,
      curMobileType,
      grayModalVisible,
      versionGrayData,
      uploadModalVisible,
    },
    updater,
  ] = useUpdate({
    pageNo: 1,
    formModalVis: false,
    h5packageNames: [],
    curPackageName: '',
    curMobileType: 'ios' as PUBLISHER.MobileType,
    grayModalVisible: false,
    versionGrayData: {} as PUBLISHER.IVersion | Obj,
    uploadModalVisible: false,
  });
  const isH5 = curMobileType === 'h5';

  const mobileType = isMobile ? curMobileType : undefined;
  const packageName = isH5 ? curPackageName : undefined;

  useMount(() => {
    getH5PackageNames({ publishItemId: artifactsId }).then((names) => {
      updater.h5packageNames(names);
      names[0] && updater.curPackageName(names[0]);
    });
  });

  const getList = React.useCallback(
    (q?: Obj) => {
      getVersionList({
        pageNo,
        artifactsId,
        mobileType,
        packageName,
        ...q,
        pageSize: 15,
      });
      getOnlineVersionData({ publishItemId: +publisherItemId, mobileType, packageName });
    },
    [artifactsId, getOnlineVersionData, getVersionList, mobileType, packageName, pageNo, publisherItemId],
  );

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

  const loadmore = () => getList({ pageNo: paging.pageNo + 1 });

  useUnmount(() => {
    publisherStore.reducers.clearVersionList();
  });

  const openFormModal = () => {
    updater.formModalVis(true);
  };
  const closeFormModal = () => {
    updater.formModalVis(false);
  };
  const reloadVersionList = () => {
    getList({ pageNo: 1, mobileType: curMobileType });
  };

  const daySplit = {};
  list.forEach((item) => {
    const day = item.createdAt.slice(0, 10);
    daySplit[day] = daySplit[day] || [];
    daySplit[day].push(item);
  });

  const setGray = (record: PUBLISHER.IVersion) => {
    updater.versionGrayData({ ...record, versionStates: record.versionStates || 'beta' });
    updater.grayModalVisible(true);
  };

  const disableVersionConf = (record: PUBLISHER.IVersion) => {
    return onlineVersionData.length === 2 && !record.versionStates;
  };

  const onCloseGrayModal = () => {
    updater.grayModalVisible(false);
    updater.versionGrayData({});
  };

  const onAfterPublishHandle = () => {
    onCloseGrayModal();
    reloadVersionList();
    getOnlineVersionData({ publishItemId: +publisherItemId, mobileType: curMobileType, packageName });
  };

  const openGrayModal = (record: PUBLISHER.IVersion) => {
    if (isEmpty(onlineVersionData)) {
      setGrayAndPublish({
        versionStates: 'release',
        action: 'publish',
        publishItemID: +publisherItemId,
        publishItemVersionID: +record.id,
        packageName,
      }).then(() => {
        onAfterPublishHandle();
      });
      return;
    }

    if (record.public) {
      setGrayAndPublish({
        action: 'unpublish',
        publishItemID: +publisherItemId,
        publishItemVersionID: +record.id,
        versionStates: record.versionStates,
        packageName,
      }).then(() => {
        onAfterPublishHandle();
      });
    } else {
      if (isEmpty(onlineVersionData)) {
        return message.warn(
          i18n.t(
            'publisher:There is no need to set gray value when no version is published, and the official version can be published directly.',
          ),
        );
      }
      setGray(record);
    }
  };
  const versionStateRender = (record: PUBLISHER.IVersion) => {
    const { versionStates, grayLevelPercent } = record;
    if (versionStates) {
      let content: string = versionTypeDic[versionStates];
      if (versionStates === 'beta') {
        content += `:${grayLevelPercent}%`;
      }
      return <span className="tag-success ml-2">({content})</span>;
    } else {
      return null;
    }
  };

  const handleUploadSuccess = (type: PUBLISHER.OfflinePackageType) => {
    if (type === curMobileType) {
      getList();
    } else {
      updater.curMobileType(type);
    }
  };

  return (
    <div className="publisher-version-list">
      {
        // 由于后端逻辑问题,3.15先移除此按钮,
        // mode === ArtifactsTypeMap.MOBILE.value ? <Button type="primary" className="mt-2 mb-4" ghost onClick={openFormModal}>{i18n.t('publisher:add version')}</Button> : null
      }
      {isMobile && (
        <div className="flex justify-between items-center">
          <Radio.Group
            buttonStyle="solid"
            className="mb-4"
            onChange={(e) => {
              updater.curMobileType(e.target.value);
            }}
            value={curMobileType}
          >
            <Radio.Button value="ios">iOS</Radio.Button>
            <Radio.Button value="android">Android</Radio.Button>
            {curPackageName ? (
              <Dropdown
                overlay={
                  <Menu
                    onClick={(sel) => {
                      updater.curMobileType('h5');
                      updater.curPackageName(sel.key);
                    }}
                  >
                    {h5packageNames.map((n) => (
                      <Menu.Item key={n}>{n}</Menu.Item>
                    ))}
                  </Menu>
                }
              >
                <Radio.Button value="h5">
                  H5{curPackageName ? `(${curPackageName})` : null}{' '}
                  <ErdaIcon type="caret-down" className="align-middle" style={{ lineHeight: 1 }} size="18" />
                </Radio.Button>
              </Dropdown>
            ) : (
              <Radio.Button value="h5">H5</Radio.Button>
            )}
            <Radio.Button value="aab">Android App Bundle</Radio.Button>
          </Radio.Group>
          <WithAuth pass={publishOperationAuth} disableMode>
            <Button
              onClick={() => {
                updater.uploadModalVisible(true);
              }}
            >
              {i18n.t('upload offline package')}
            </Button>
          </WithAuth>
        </div>
      )}
      <Holder when={isEmpty(daySplit) && !isFetching}>
        <Timeline className="version-list">
          {map(daySplit, (items: [], day) => {
            return (
              <TimelineItem key={day}>
                <div className="mb-4 text-normal text-base mb-4">{day}</div>
                <div className="version-day-list">
                  {map(items, (record: PUBLISHER.IVersion) => {
                    const {
                      id,
                      buildId,
                      public: isPublic,
                      version,
                      createdAt,
                      meta,
                      versionStates,
                      targetMobiles,
                      resources,
                    } = record;
                    const { appName, projectName } = meta || ({} as PUBLISHER.IMeta);
                    const _targetMobiles = targetMobiles || { ios: [], android: [] };
                    const appStoreURL = get(
                      find(resources, ({ type }) => type === 'ios'),
                      'meta.appStoreURL',
                    );
                    return (
                      <div key={id} className="version-item">
                        <div className={`version-number mb-3 ${isPublic ? 'on' : 'off'}`}>
                          <ErdaIcon className="mt-1" size="16" type={isPublic ? 'yuanxingxuanzhong-fill' : 'tishi'} />
                          <span className="number">
                            V{version} ({buildId})
                          </span>
                          {versionStateRender(record)}
                        </div>
                        <div className="version-tips">
                          <ErdaIcon type="xm-2" size="16" />
                          <span className="text">{appName}</span>
                          <ErdaIcon type="yy-4" size="16" />
                          <span className="text">{projectName}</span>
                          <ErdaIcon type="shijian" size="16" />
                          <span className="text">{createdAt ? moment(createdAt).format('HH:mm:ss') : '-'}</span>
                          {curMobileType === 'ios' && appStoreURL ? (
                            <>
                              <ErdaIcon size="16" type="app" />
                              <a
                                className="nowrap app-store-url"
                                target="_blank"
                                rel="noopener noreferrer"
                                href={appStoreURL}
                              >
                                {appStoreURL}
                              </a>
                            </>
                          ) : null}
                          {isH5 && (
                            <>
                              <Popover
                                title={i18n.t('Supported iOS package versions')}
                                placement="bottom"
                                content={
                                  <div>
                                    {map(_targetMobiles.ios, (n) => (
                                      <span className="tag-default mr-1 mb-1" key={n}>
                                        {n}
                                      </span>
                                    ))}
                                  </div>
                                }
                              >
                                <span className="text">
                                  <ErdaIcon type="apple" className="align-middle mr-0.5" size="16" />{' '}
                                  {_targetMobiles.ios?.length || 0}个版本
                                </span>
                              </Popover>
                              <Popover
                                title={i18n.t('Supported Android package versions')}
                                placement="bottom"
                                content={
                                  <div>
                                    {map(_targetMobiles.android, (n) => (
                                      <span className="tag-default mr-1 mb-1" key={n}>
                                        {n}
                                      </span>
                                    ))}
                                  </div>
                                }
                              >
                                <span className="text">
                                  <ErdaIcon className="align-middle mr-0.5" type="android" size="16" />{' '}
                                  {_targetMobiles.android?.length || 0}个版本
                                </span>
                              </Popover>
                            </>
                          )}
                        </div>
                        <div className="version-op flex items-center flex-wrap justify-end">
                          <IF check={versionStates === 'beta'}>
                            <WithAuth pass={publishOperationAuth}>
                              <Button
                                className="mr-2"
                                onClick={() => {
                                  setGray(record);
                                }}
                              >
                                {i18n.t('publisher:set gray release')}
                              </Button>
                            </WithAuth>
                          </IF>
                          {record.resources.map((item) => {
                            if (item.type === 'aab') {
                              return (
                                <WithAuth pass={publishOperationAuth}>
                                  <Button disabled={disableVersionConf(record)} onClick={() => window.open(item.url)}>
                                    {i18n.t('Download')}
                                  </Button>
                                </WithAuth>
                              );
                            } else {
                              return (
                                <Popconfirm
                                  title={i18n.t('is it confirmed {action}?', {
                                    action: isPublic ? i18n.t('publisher:withdraw') : i18n.t('publisher:publish'),
                                  })}
                                  onConfirm={() => {
                                    openGrayModal(record);
                                  }}
                                >
                                  <WithAuth pass={publishOperationAuth}>
                                    <Tooltip
                                      title={
                                        disableVersionConf(record)
                                          ? i18n.t(
                                              'publisher:The official and preview version published should be withdrawn first, then other versions can be published.',
                                            )
                                          : undefined
                                      }
                                    >
                                      <Button disabled={disableVersionConf(record)}>
                                        {isPublic ? i18n.t('publisher:withdraw') : i18n.t('publisher:publish')}
                                      </Button>
                                    </Tooltip>
                                  </WithAuth>
                                </Popconfirm>
                              );
                            }
                          })}
                        </div>
                      </div>
                    );
                  })}
                </div>
              </TimelineItem>
            );
          })}
        </Timeline>
      </Holder>
      <VersionFormModal
        visible={formModalVis}
        artifacts={artifacts}
        onCancel={closeFormModal}
        afterSubmit={reloadVersionList}
      />
      <LoadMore load={loadmore} hasMore={paging.hasMore} isLoading={isFetching} />
      <GrayFormModal
        visible={grayModalVisible}
        onOk={onAfterPublishHandle}
        formData={versionGrayData}
        onCancel={onCloseGrayModal}
      />
      <UploadModal
        visible={uploadModalVisible}
        onCancel={() => {
          updater.uploadModalVisible(false);
        }}
        afterUpload={handleUploadSuccess}
      />
    </div>
  );
}
Example #15
Source File: release-select.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
ReleaseSelect = ({
  value,
  readOnly = false,
  onChange,
  renderSelectedItem = defaultRenderSelectedItem,
}: IProps) => {
  const [releaseVisible, setReleaseVisible] = React.useState<boolean>(false);
  const [groupList, setGroupList] = React.useState<Array<{ active: boolean; list: Item[] }>>([
    { active: true, list: [] },
  ]);
  const [currentGroup, setCurrentGroup] = React.useState<number>(0);
  const [selectedList, setSelectedList] = React.useState<Item[]>([]);

  const select = (selectItem: Item, checked: boolean) => {
    const groupIndex = groupList.findIndex((group) => group.list.find((item) => item.pId === selectItem.pId));
    if (groupIndex === -1 || groupIndex === currentGroup) {
      setSelectedList((prev) =>
        checked
          ? [...prev.filter((item) => item.pId !== selectItem.pId), selectItem]
          : prev.filter((item) => item.id !== selectItem.id),
      );
    } else {
      message.error(
        i18n.t('dop:this application already has release selected in {name}', {
          name: i18n.t('dop:group {index}', { index: groupIndex + 1 }),
        }),
      );
    }
  };

  const remove = (id: string) => {
    setSelectedList((prev) => prev.filter((item) => item.id !== id));
  };

  const clear = () => {
    setSelectedList([]);
  };

  const removeGroup = (index: number, e: React.MouseEvent<HTMLElement>) => {
    e.stopPropagation();
    const _groupList = groupList.filter((_item, i) => i !== index);
    setGroupList(_groupList);
    onChange?.(_groupList);
  };

  const removeResult = (i: number, id: string) => {
    const { list: _list } = groupList[i];
    const currentList = [..._list];
    const index = _list.findIndex((item) => item.id === id);
    currentList.splice(index, 1);
    groupList[i].list = currentList;
    setGroupList([...groupList]);
    onChange?.(groupList);
  };

  const onOk = () => {
    groupList[currentGroup].list = selectedList;
    setGroupList(groupList);
    onChange?.([...groupList]);
    setReleaseVisible(false);
  };

  React.useEffect(() => {
    value && setGroupList(value);
  }, [value]);

  return (
    <div className="erda-list-select">
      <Timeline className="mt-1 project-release-time-line">
        {groupList.map((group, index) => (
          <Timeline.Item
            dot={
              <div className="leading-4">
                <i
                  className={`inline-block rounded-full border-primary border-solid w-2 h-2 ${
                    group.active ? 'bg-primary' : ''
                  }`}
                  style={{ borderWidth: 1 }}
                />
              </div>
            }
          >
            <Collapse
              activeKey={group.active ? ['1'] : []}
              onChange={() => {
                groupList[index].active = !groupList[index].active;
                setGroupList([...groupList]);
              }}
              ghost
              className="time-line-collapse"
            >
              <Panel
                header={
                  <span className={`time-line-collapse-header ${group.active ? 'active' : ''}`}>
                    <span className="group-title">
                      {firstCharToUpper(i18n.t('dop:group {index}', { index: index + 1 }))}
                    </span>
                    {group.list?.length ? (
                      <span className="bg-default-1 rounded-full px-2 py-0.5 text-xs ml-1">{group.list.length}</span>
                    ) : (
                      ''
                    )}
                    {!readOnly ? (
                      <ErdaIcon
                        className="float-right mr-5 mt-1 text-default-6 remove-group"
                        type="remove"
                        size={16}
                        onClick={(e: React.MouseEvent<HTMLElement>) => removeGroup(index, e)}
                      />
                    ) : (
                      ''
                    )}
                  </span>
                }
                key="1"
              >
                {group.list?.length ? (
                  <div className="bg-default-02 p-2">
                    {group.list?.map?.((item) => (
                      <div
                        className={`erda-list-select-selected-item flex items-center p-2 rounded-sm ${
                          !readOnly ? 'hover:bg-default-04' : ''
                        }`}
                        key={item.id}
                      >
                        <div className="flex-1 pr-2 overflow-auto">{renderSelectedItem(item)}</div>
                        {!readOnly ? (
                          <ErdaIcon
                            type="close-small"
                            size={24}
                            className="erda-list-select-selected-item-delete cursor-pointer text-default-4"
                            onClick={() => removeResult(index, item.id)}
                          />
                        ) : null}
                      </div>
                    ))}
                    {!readOnly ? (
                      <div
                        className="text-center text-purple-deep cursor-pointer"
                        onClick={() => {
                          setReleaseVisible(true);
                          setCurrentGroup(index);
                          setSelectedList(group.list);
                        }}
                      >
                        {i18n.t('dop:manage the group of releases')}
                      </div>
                    ) : (
                      ''
                    )}
                  </div>
                ) : (
                  <div className="bg-default-02 py-4 flex-all-center">
                    <img src={empty} className="mr-2" />
                    <div>
                      <div className="text-lg leading-6">{i18n.t('dop:No app artifacts selected')}</div>
                      <div
                        className="text-xs text-purple-deep cursor-pointer leading-5"
                        onClick={() => {
                          setReleaseVisible(true);
                          setCurrentGroup(index);
                          setSelectedList([]);
                        }}
                      >
                        {i18n.t('dop:Click to add app artifacts')}
                      </div>
                    </div>
                  </div>
                )}
              </Panel>
            </Collapse>
          </Timeline.Item>
        ))}

        {!readOnly ? (
          <Timeline.Item
            dot={
              <div className="leading-4">
                <i
                  className="inline-block rounded-full border-primary border-solid w-2 h-2"
                  style={{ borderWidth: 1 }}
                />
              </div>
            }
          >
            <div
              className={'erda-list-select-btn px-2 leading-7 rounded-sm inline-flex items-center cursor-pointer'}
              onClick={() => setGroupList((prev) => [...prev, { active: true, list: [] }])}
            >
              <ErdaIcon type="plus" color="currentColor" size={16} className="mr-1" />
              {allWordsFirstLetterUpper(i18n.t('add {name}', { name: i18n.t('dop:group') }))}
            </div>
          </Timeline.Item>
        ) : (
          ''
        )}
      </Timeline>
      <Modal
        visible={releaseVisible}
        onCancel={() => setReleaseVisible(false)}
        wrapClassName="no-wrapper-modal"
        width={1120}
        footer={null}
      >
        <ListSelectOverlay
          selectedList={selectedList}
          select={select}
          remove={remove}
          onOk={onOk}
          onCancel={() => setReleaseVisible(false)}
          clear={clear}
          renderSelectedItem={renderSelectedItem}
        />
      </Modal>
    </div>
  );
}
Example #16
Source File: BrickTimeline.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function BrickTimeline(props: BrickTimelineProps): React.ReactElement {
  const getGeneralProps = React.useCallback(
    (item: TimelineItem): Omit<TimelineItem, "status" | "time"> =>
      pick(item, ["title", "description", "link"]),
    []
  );

  const renderTimeline = (list: ItemProps[] = []): React.ReactElement => {
    const existedDateTime: Record<string, boolean> = {};
    return (
      <Timeline
        mode={props.useBrick ? props.mode : "left"}
        className={style.brickTimeline}
      >
        {list?.map((item, index) => {
          // 根据不同时间类型统一转化为时间戳处理
          let timestamp: moment.Moment;
          if (props.timeType === "second") {
            timestamp = moment(item.time * 1000);
          } else {
            timestamp = moment(item.time);
          }

          // 判断该时间点对应的日期是否首次出现,是的话需要显示在时间轴左侧
          let showLeftDate: boolean;
          const date = moment(timestamp).format("YYYY-MM-DD");
          if (!existedDateTime[date]) {
            showLeftDate = true;
            existedDateTime[date] = true;
          }

          return (
            <Timeline.Item
              key={index}
              color={get(props.statusMap, item.status)}
            >
              {props.useBrick ? (
                <BrickAsComponent
                  useBrick={props.useBrick}
                  data={{ item, index, list: props.itemList }}
                />
              ) : props.type === "extension" ? (
                <TimelineExtensionCard
                  {...getGeneralProps(item as TimelineItem)}
                  timestamp={timestamp}
                  showLeftDate={showLeftDate}
                  onClick={props.onClick}
                  itemData={item}
                />
              ) : (
                <TimelineBaseCard
                  {...getGeneralProps(item as TimelineItem)}
                  timestamp={timestamp}
                  onClick={props.onClick}
                  itemData={item}
                />
              )}
            </Timeline.Item>
          );
        })}
      </Timeline>
    );
  };

  const getComponent = (): React.ReactElement => {
    if (props.type === "extension") {
      // 根据月份分组
      const timelineGroup = groupByMoth(props.itemList, props.timeType);
      return (
        <>
          {timelineGroup.map((item) => (
            <div key={item.groupName}>
              <div className={style.groupName}>{item.groupName}</div>
              {renderTimeline(item.list)}
            </div>
          ))}
        </>
      );
    } else {
      return renderTimeline(props.itemList);
    }
  };

  return props.showCard ? <Card>{getComponent()}</Card> : getComponent();
}
Example #17
Source File: repo-commit.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
RepoCommit = () => {
  const [info, commitPaging, list] = repoStore.useStore((s) => [s.info, s.commitPaging, s.commit]);
  const { getCommitList } = repoStore.effects;
  const { resetCommitPaging, clearListByType } = repoStore.reducers;
  const { appId } = routeInfoStore.useStore((s) => s.params);
  const [isFetching] = useLoading(repoStore, ['getCommitList']);
  const [searchValue, setSearchValue] = React.useState('');

  const branchesStr = JSON.stringify(info?.branches);

  React.useEffect(() => {
    branchesStr && getCommitList({ pageNo: 1 });
  }, [branchesStr, getCommitList]);

  React.useEffect(() => {
    return () => {
      resetCommitPaging();
      clearListByType('commit');
    };
  }, [clearListByType, resetCommitPaging]);
  const { branches = [], tags = [], refName } = info;

  const onBranchChange = (branch: string) => {
    const { before } = getSplitPathBy('commits');
    if (branches.includes(branch)) {
      // save branch info to LS
      setLS(`branch-${appId}`, branch);
    }
    if (tags.includes(branch)) {
      // save branch info to LS
      setLS(`tag-${appId}`, branch);
    }
    goTo(`${before}/${branch}`, { replace: true });
    resetCommitPaging();
    getCommitList({ branch, pageNo: 1 });
    setSearchValue('');
  };
  const load = () => {
    const { after } = getSplitPathBy('commits');
    return getCommitList({ branch: after.replace('/', '') || info.defaultBranch, pageNo: commitPaging.pageNo + 1 });
  };

  const daySplit = {};
  list.forEach((item) => {
    const day = item.author.when.slice(0, 10);
    daySplit[day] = daySplit[day] || [];
    daySplit[day].push(item);
  });
  const { branch, commitId, tag } = getInfoFromRefName(refName);
  const path = getSplitPathBy(branch.endsWith('/') ? branch : `${branch}/`).after;
  return (
    <div className="repo-commit">
      <div className="commit-nav mb-5">
        <div className="nav-left flex justify-between items-center flex-1">
          <BranchSelect
            className="mr-4"
            {...{ branches, tags, current: branch || tag || '' }}
            onChange={onBranchChange}
          >
            {branch ? (
              <>
                <span>{i18n.t('dop:branch')}:</span>
                <span className="branch-name font-bold nowrap">{branch}</span>
              </>
            ) : tag ? (
              <>
                <span>{i18n.t('tag')}:</span>
                <span className="branch-name font-bold nowrap">{tag}</span>
              </>
            ) : (
              <>
                <span>{i18n.t('Commit')}:</span>
                <span className="branch-name font-bold nowrap">{commitId}</span>
              </>
            )}
            <ErdaIcon type="caret-down" size="18px" className="mt-0.5" />
          </BranchSelect>
          <IF check={path && branch}>
            <RepoBreadcrumb splitKey="commits" path={path} />
          </IF>
        </div>
        <Input
          value={searchValue}
          className="search-input"
          placeholder={i18n.t('dop:Filter by commit message')}
          onPressEnter={() => {
            getCommitList({
              search: searchValue || undefined,
              branch: getSplitPathBy('commits').after.replace('/', '') || info.defaultBranch,
              pageNo: 1,
            });
          }}
          onChange={(e) => {
            setSearchValue(e.target.value);
          }}
        />
      </div>
      <Spin spinning={isFetching}>
        <Holder when={!list.length && !isFetching}>
          <Timeline>
            {map(daySplit, (items: [], day) => (
              <TimelineItem key={day}>
                <div className="mb-4 text-normal text-base">{day}</div>
                <div className="commit-list">{items.map(renderCommitItem)}</div>
              </TimelineItem>
            ))}
            <TimelineItem />
          </Timeline>
        </Holder>
      </Spin>
      <LoadMore key={branch || ''} threshold={500} load={load} hasMore={commitPaging.hasMore} isLoading={isFetching} />
    </div>
  );
}
Example #18
Source File: pipeline-log.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
PipelineLog = ({ isBuilding = false, resourceId, resourceType, className = '' }: IProps) => {
  const { getPipelineLog } = buildStore.effects;
  const { clearPipelineLog } = buildStore.reducers;
  const pipelineLog = buildStore.useStore((s) => s.pipelineLog);
  const [isFecthing] = useLoading(buildStore, ['getPipelineLog']);
  const [{ detailLog, detailVis }, , update] = useUpdate({
    detailLog: '',
    detailVis: false,
  });

  useEffectOnce(() => {
    return () => {
      clearTimeout(timer);
      clearPipelineLog();
    };
  });

  const getList = () => {
    clearTimeout(timer);
    if (detailVis) return;
    resourceId &&
      getPipelineLog({ resourceId, resourceType }).then(() => {
        if (isBuilding) {
          timer = setTimeout(getList, DURATION);
        }
      });
  };

  React.useEffect(() => {
    if (resourceId) {
      getList();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [resourceId]);

  useUpdateEffect(() => {
    (!detailVis || isBuilding) && delayGetList(getList); // 详情关闭,或状态改变后,启动刷新
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [detailVis, isBuilding]);

  const logOperation = [
    {
      title: isFecthing ? (
        <ErdaIcon type="loading" color="black-4" spin className="mr-1.5" />
      ) : (
        <Tooltip
          title={
            isBuilding
              ? `${i18n.t('dop:refresh every {time}, click to refresh now', {
                  time: `${DURATION / 1000} ${i18n.t('common:second(s)')}`,
                })}`
              : i18n.t('refresh')
          }
        >
          <ErdaIcon
            color="black-4"
            size="18"
            type="redo"
            className="mr-1 cursor-pointer"
            onClick={() => delayGetList(getList, 0)}
          />
        </Tooltip>
      ),
    },
  ];

  return (
    <div className={`pipeline-log ${className}`}>
      <Title title={i18n.t('Deployment log')} level={2} mt={8} showDivider={false} operations={logOperation} />
      {isEmpty(pipelineLog) ? (
        <EmptyHolder relative />
      ) : (
        <Timeline>
          {pipelineLog.map((item, index) => {
            const { occurrenceTime, humanLog, primevalLog, level } = item;
            return (
              <Timeline.Item key={`${String(index)}-${occurrenceTime}`} color={colorMap[level]}>
                <div className={'pipeline-log-time'}>
                  <div className="mb-2">{occurrenceTime}</div>
                  <div className="pipeline-log-title flex items-start">
                    <span className="flex-1">{humanLog}</span>
                    <span
                      className="text-primary cursor-pointer ml-2"
                      onClick={() => update({ detailVis: true, detailLog: primevalLog })}
                    >
                      {i18n.t('View details')}
                    </span>
                  </div>
                </div>
              </Timeline.Item>
            );
          })}
        </Timeline>
      )}
      <Drawer
        title={i18n.t('detail')}
        visible={detailVis}
        width={800}
        onClose={() => {
          update({ detailVis: false, detailLog: '' });
        }}
      >
        <div className="pipeline-log-detail">{detailLog}</div>
      </Drawer>
    </div>
  );
}
Example #19
Source File: pipeline-log.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
PipelineLog = ({ isBuilding = false, resourceId, resourceType, className = '' }: IProps) => {
  const { getPipelineLog } = buildStore.effects;
  const { clearPipelineLog } = buildStore.reducers;
  const pipelineLog = buildStore.useStore((s) => s.pipelineLog);
  const [isFetching] = useLoading(buildStore, ['getPipelineLog']);
  const [{ detailLog, detailVis }, , update] = useUpdate({
    detailLog: '',
    detailVis: false,
  });

  useEffectOnce(() => {
    return () => {
      clearTimeout(timer);
      clearPipelineLog();
    };
  });

  const getList = () => {
    clearTimeout(timer);
    if (detailVis) return;
    resourceId &&
      getPipelineLog({ resourceId, resourceType }).then(() => {
        if (isBuilding) {
          timer = setTimeout(getList, DURATION);
        }
      });
  };

  React.useEffect(() => {
    if (resourceId) {
      getList();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [resourceId]);

  useUpdateEffect(() => {
    (!detailVis || isBuilding) && delayGetList(getList); // 详情关闭,或状态改变后,启动刷新
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [detailVis, isBuilding]);

  const logOperation = [
    {
      title: isFetching ? (
        <ErdaIcon type="loading" color="black-4" spin className="mr-1.5" />
      ) : (
        <Tooltip
          title={
            isBuilding
              ? `${i18n.t('dop:refresh every {time}, click to refresh now', {
                  time: `${DURATION / 1000} ${i18n.t('common:second(s)')}`,
                })}`
              : i18n.t('refresh')
          }
        >
          <ErdaIcon
            color="black-4"
            size="18"
            type="redo"
            className="mr-1 cursor-pointer"
            onClick={() => delayGetList(getList, 0)}
          />
        </Tooltip>
      ),
    },
  ];

  return (
    <div className={`pipeline-log ${className}`}>
      <Title
        title={i18n.t('Deployment log')}
        className="my-3"
        level={2}
        showDivider={false}
        operations={logOperation}
      />
      {isEmpty(pipelineLog) ? (
        <EmptyHolder relative />
      ) : (
        <Timeline>
          {pipelineLog.map((item, index) => {
            const { occurrenceTime, humanLog, primevalLog, level } = item;
            return (
              <Timeline.Item key={`${String(index)}-${occurrenceTime}`} color={colorMap[level]}>
                <div className={'pipeline-log-time'}>
                  <div className="mb-2">{occurrenceTime}</div>
                  <div className="pipeline-log-title flex items-start">
                    <span className="flex-1">{humanLog}</span>
                    <span
                      className="text-primary cursor-pointer ml-2"
                      onClick={() => update({ detailVis: true, detailLog: primevalLog })}
                    >
                      {i18n.t('View details')}
                    </span>
                  </div>
                </div>
              </Timeline.Item>
            );
          })}
        </Timeline>
      )}
      <Drawer
        title={i18n.t('detail')}
        visible={detailVis}
        width={800}
        onClose={() => {
          update({ detailVis: false, detailLog: '' });
        }}
      >
        <div className="pipeline-log-detail">{detailLog}</div>
      </Drawer>
    </div>
  );
}
Example #20
Source File: message.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
MessageCenter = ({ show }: { show: boolean }) => {
  const params = routeInfoStore.useStore((s) => s.params);
  const { orgName = '-' } = params || {};
  const hasOrgRef = React.useRef(orgName !== '-');
  hasOrgRef.current = orgName !== '-';

  const [list, detail, msgPaging, unreadCount] = messageStore.useStore((s) => [
    s.list,
    s.detail,
    s.msgPaging,
    s.unreadCount,
  ]);
  const { getMessageList, getMessageStats, readOneMessage, clearAll } = messageStore.effects;
  const { resetDetail } = messageStore.reducers;
  const [loadingList] = useLoading(messageStore, ['getMessageList']);
  const { switchMessageCenter } = layoutStore.reducers;
  const boxRef = React.useRef<HTMLElement>();
  const loopUnreadCountTimer = React.useRef(0 as any);

  React.useEffect(() => {
    if (hasOrgRef.current) {
      getMessageStats();
      if (show) {
        getMessageList({ pageNo: 1 });
      }
    }

    return () => {
      messageStore.reducers.resetAll();
    };
  }, [getMessageList, getMessageStats, show, orgName]);

  const viewMsg = () => {
    switchMessageCenter(true);
  };

  useEffectOnce(() => {
    const cycle = 10 * 60 * 1000;
    // 每隔5min检查一次
    const interval = 5 * 60 * 1000;
    checkPermission();
    const loop = () => {
      let timers = Number(sessionStorage.getItem('message_timer') || 0);
      if (!timers) {
        timers = Date.now();
        sessionStorage.setItem('message_timer', `${timers}`);
      }
      if (loopUnreadCountTimer.current) {
        clearTimeout(loopUnreadCountTimer.current);
      }
      loopUnreadCountTimer.current = setTimeout(() => {
        const now = Date.now();
        if (now - timers > cycle) {
          sessionStorage.setItem('message_timer', `${now}`);
          if (hasOrgRef.current) {
            getMessageStats().then((res) => {
              if (res?.hasNewUnread) {
                if (show) {
                  // resetDetail();
                  getMessageList({ pageNo: 1 });
                }
                notifyMe(i18n.t('default:you have new site message, please pay attention to check'), viewMsg);
              }
            });
          }
        }
        loop();
      }, interval);
    };
    loop();
    return () => {
      clearTimeout(loopUnreadCountTimer.current);
    };
  });

  if (!show) {
    return null;
  }

  const handleClick = (item: LAYOUT.IMsg) => {
    readOneMessage(item.id, item.status === MSG_STATUS.READ);
  };

  let curDate = '';
  const groupList: Array<{ date: string; list: LAYOUT.IMsg[] }> = [];
  list.forEach((item) => {
    const date = moment(item.createdAt).format('YYYY-MM-DD');
    if (date !== curDate) {
      groupList.push({ date, list: [item] });
      curDate = date;
    } else {
      groupList[groupList.length - 1].list.push(item);
    }
  });

  const clearAllMessage = () => {
    Modal.confirm({
      title: i18n.t('confirm to read all'),
      onOk() {
        return clearAll().then(() => message.success(i18n.t('operated successfully')));
      },
    });
  };

  return (
    <div className="message-center" ref={boxRef as React.RefObject<HTMLDivElement>}>
      <div className="header">
        <CustomIcon type="arrow-left" onClick={() => layoutStore.reducers.switchMessageCenter(null)} />
        {i18n.t('Site message')}
      </div>
      <div className="content">
        <div className="summary flex justify-between">
          {i18n.t('{unreadCount} messages unread', {
            unreadCount,
          })}

          <a className="mr-6 cursor-pointer" onClick={() => clearAllMessage()}>
            {i18n.t('one key all read')}
          </a>
        </div>
        <Holder when={!list.length}>
          <Timeline>
            {map(groupList, (group) => {
              return (
                <Timeline.Item key={group.date}>
                  <div>{group.date}</div>
                  <div className="message-list">
                    {group.list.map((item) => {
                      const isUnRead = item.status === MSG_STATUS.UNREAD;
                      return (
                        <div key={item.id} className="message-item" onClick={() => handleClick(item)}>
                          <div className="message-item-content flex items-center" title={item.title}>
                            <span className="status">{isUnRead ? <Badge color="red" /> : null}</span>
                            <ErdaIcon className="mr-1" type="remind" size="16px" />
                            <span>{item.title}</span>
                          </div>
                          <div>
                            {item.unreadCount > 1 && (
                              <span className="unread-count mr-3">
                                <span className="unread-count-text">
                                  {item.unreadCount > 99 ? '99+' : item.unreadCount}
                                </span>
                              </span>
                            )}
                            <span className="message-time">{moment(item.createdAt).format('HH:mm:ss')}</span>
                          </div>
                        </div>
                      );
                    })}
                  </div>
                </Timeline.Item>
              );
            })}
          </Timeline>
          <LoadMore
            getContainer={() => boxRef.current}
            load={() => getMessageList({ pageNo: msgPaging.pageNo + 1 })}
            hasMore={msgPaging.hasMore}
            isLoading={loadingList}
          />
          <Drawer
            width="60%"
            visible={!!detail}
            title={detail && detail.title}
            onClose={() => resetDetail()}
            destroyOnClose
            className="site-message-drawer"
          >
            <MarkdownRender value={(detail && detail.content) || ''} />
          </Drawer>
        </Holder>
      </div>
    </div>
  );
}