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 |
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 |
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 |
{ Item: TimeLineItem } = Timeline
Example #4
Source File: detail-modal.tsx From erda-ui with GNU Affero General Public License v3.0 | 5 votes |
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 |
{ Item: TimelineItem } = Timeline
Example #6
Source File: activity.tsx From erda-ui with GNU Affero General Public License v3.0 | 5 votes |
TimelineItem = Timeline.Item
Example #7
Source File: activity.tsx From erda-ui with GNU Affero General Public License v3.0 | 5 votes |
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 |
{ Item: TimelineItem } = Timeline
Example #9
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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>
);
}