react-use#useUnmount TypeScript Examples
The following examples show how to use
react-use#useUnmount.
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: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 6 votes |
MonitorChartPanel = (props: IProps) => {
// const params = routeInfoStore.useStore(s => s.params);
const { resourceType } = props;
const [listMetric] = metricsMonitorStore.useStore((s) => [s.listMetric]);
const { listMetricByResourceType: initFetch } = metricsMonitorStore.effects;
const { clearListMetrics } = metricsMonitorStore.reducers;
const [data, setData] = React.useState({});
React.useEffect(() => {
initFetch({ resourceType });
}, [initFetch, resourceType]);
useUnmount(() => {
clearListMetrics();
});
React.useEffect(() => {
const metrics = listMetric[resourceType] || {};
const list = Object.keys(metrics).map((key) => ({ key, value: metrics[key] }));
const lazyLoad = () => {
if (list.length) {
setTimeout(() => {
const { key, value } = list.shift() || {};
key && setData((prevData) => ({ ...prevData, [key]: value }));
lazyLoad();
}, 200);
}
};
lazyLoad();
}, [listMetric, resourceType]);
return (
<Holder when={isEmpty(data)}>
<PureMonitorChartPanel {...props} metrics={data} />
</Holder>
);
}
Example #2
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 6 votes |
CompSwitcher = ({ comps, children }: IProps) => {
useUnmount(() => {
commonStore.reducers.clearSlideComp();
});
const last = comps[comps.length - 1];
if (last) {
const { getComp } = last;
return getComp();
}
return children || null;
}
Example #3
Source File: record-detail.tsx From erda-ui with GNU Affero General Public License v3.0 | 6 votes |
Execute = (props: Merge<IProps, { titleOperation?: React.ReactNode }>) => {
const { pipelineId, appId, titleOperation = null } = props;
const { clearPipelineDetail } = buildStore.reducers;
useUnmount(() => {
clearPipelineDetail();
});
return (
<div>
<div className="text-base font-medium text-default-8 flex-h-center justify-between">
{`${i18n.t('dop:Pipeline Detail')}`}
{titleOperation}
</div>
<Info appId={appId} />
<PureExecute {...props} deployAuth={{ hasAuth: false }} chosenPipelineId={pipelineId} />
</div>
);
}
Example #4
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 6 votes |
EditIssueDrawerContainer = (props: IProps) => {
const { id, visible: propsVisible } = props;
const { getIssueDetail } = issueStore.effects;
const [visible, setVisible] = React.useState(false);
const [hasData, setHasData] = React.useState(false);
useUnmount(() => {
setHasData(false);
});
React.useEffect(() => {
id &&
getIssueDetail({ id: +id }).then(() => {
setHasData(true);
});
}, [id]);
React.useEffect(() => {
if (id) {
setVisible(propsVisible ? propsVisible && hasData : propsVisible);
} else {
setVisible(propsVisible);
}
}, [id, propsVisible, hasData]);
return <EditIssueDrawer {...props} visible={visible} />;
}
Example #5
Source File: data-market.tsx From erda-ui with GNU Affero General Public License v3.0 | 6 votes |
Mapper = () => {
const [outputTableList, tableAttrsList, marketBusinessScope, dataMarketPaging, tableAttrsPaging] =
dataTaskStore.useStore((s) => [
s.outputTableList,
s.tableAttrsList,
s.marketBusinessScope,
s.outputTablePaging,
s.tableAttrsPaging,
]);
const [isFetching, isFetchingTable] = useLoading(dataTaskStore, ['getOutputTables', 'getTableAttrs']);
const { getOutputTables, getTableAttrs } = dataTaskStore.effects;
const { clearOutputTables } = dataTaskStore.reducers;
useUnmount(() => {
clearOutputTables();
});
return {
outputTableList,
tableAttrsList,
tableAttrsPaging,
dataMarketPaging,
marketBusinessScope,
isFetching,
isFetchingTable,
getOutputTables,
getTableAttrs,
};
}
Example #6
Source File: data-model.tsx From erda-ui with GNU Affero General Public License v3.0 | 6 votes |
Mapper = () => {
const [businessProcessList, modelBusinessScope, dataModelPaging] = dataTaskStore.useStore((s) => [
s.businessProcessList,
s.modelBusinessScope,
s.businessProcessPaging,
]);
const [isFetching] = useLoading(dataTaskStore, ['getBusinessProcesses']);
const { getBusinessProcesses } = dataTaskStore.effects;
const { clearBusinessProcesses } = dataTaskStore.reducers;
useUnmount(() => {
clearBusinessProcesses();
});
return {
businessProcessList,
modelBusinessScope,
dataModelPaging,
isFetching,
getBusinessProcesses,
};
}
Example #7
Source File: app-variable-config.tsx From erda-ui with GNU Affero General Public License v3.0 | 6 votes |
DeployConfig = () => {
const appDetail = appStore.useStore((s) => s.detail);
const { appId } = routeInfoStore.useStore((s) => s.params);
const fullConfigs = configStore.useStore((s) => s.fullConfigs);
const envToNs = React.useRef({});
React.useEffect(() => {
if (appDetail.workspaces) {
const namespaceParams = appDetail.workspaces.map((item) => {
envToNs.current[item.workspace.toLowerCase()] = item.configNamespace;
return {
namespace_name: item.configNamespace,
decrypt: false,
};
});
configStore.getConfigs({ namespace: namespaceParams }, 'configmanage');
}
}, [appDetail.workspaces, appId, envToNs]);
useUnmount(() => {
configStore.clearConfigs();
});
return (
<VariableConfig
configType={configTypeMap.deploy}
envToNs={envToNs.current}
configs={fullConfigs}
addConfig={(data) => configStore.addConfigs(data, 'configmanage')}
updateConfig={(data) => configStore.updateConfigs(data, 'configmanage')}
deleteConfig={(data) => configStore.removeConfigWithoutDeploy(data, 'configmanage')}
importConfig={(data) => configStore.importConfigs(data, 'configmanage')}
exportConfig={configStore.exportConfigs}
uploadTip={i18n.t('dop:deploy-upload-file-tip')}
/>
);
}
Example #8
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 5 votes |
RuntimeOverView = () => {
const params = routeInfoStore.useStore((s) => s.params);
const [runtimeDetail, showRedirect, hasChange] = runtimeStore.useStore((s) => [
s.runtimeDetail,
s.showRedirect,
s.hasChange,
]);
const [loading] = useLoading(runtimeStore, ['getRuntimeDetail']);
const [state, updater] = useUpdate({
redirectVisible: false,
});
useMount(() => {
const _hasChange = !isEmpty(getLS(`${params.runtimeId}`) || getLS(`${params.runtimeId}_domain`));
runtimeStore.setHasChange(_hasChange);
});
React.useEffect(() => {
runtimeStore.toggleRedirect(false);
if (hasChange) {
const content = (
<span>
{i18n.t('runtime:configuration has changed')},{i18n.t('runtime:please')}
<span className="redeploy-tip-btn" onClick={confirmRedeploy}>
{firstCharToUpper(i18n.t('runtime:restart'))}
</span>
Runtime
</span>
);
if (hasChange) {
message.info(content, 0);
} else {
message.destroy();
}
}
}, [params.runtimeId, hasChange]);
React.useEffect(() => {
if (showRedirect && !state.redirectVisible) {
updater.redirectVisible(true);
confirm({
title: `Runtime ${i18n.t('runtime:deleted')}`,
content: i18n.t('runtime:the current runtime has been deleted, back to the pipeline?'),
onOk() {
goTo('../../../');
},
onCancel() {
updater.redirectVisible(false);
},
});
}
}, [showRedirect, state.redirectVisible, updater]);
useUnmount(() => {
runtimeStore.toggleRedirect(false);
runtimeStore.clearServiceConfig();
runtimeServiceStore.clearServiceInsMap();
// keep as last clear
runtimeStore.clearRuntimeDetail();
message.destroy();
});
const isDeleting = runtimeDetail.deleteStatus === 'DELETING';
return (
<Spin spinning={loading || isDeleting} tip={isDeleting ? `${i18n.t('runtime:deleting')}...` : undefined}>
<PureRuntimeOverView />
</Spin>
);
}
Example #9
Source File: detail.tsx From erda-ui with GNU Affero General Public License v3.0 | 5 votes |
StatisticList = ({ artifactsId, monitorKey }: { artifactsId: string; monitorKey: PUBLISHER.MonitorKey }) => {
const TODAY = 'today';
const YESTERDAY = 'yesterday';
const versionStatisticList = statisticsStore.useStore((s) => s.versionStatisticList);
const { getVersionStatistics } = statisticsStore.effects;
const { clearVersionStatistic } = statisticsStore.reducers;
const [loading] = useLoading(statisticsStore, ['getVersionStatistics']);
const [{ endAt }, updater] = useUpdate({
endAt: TODAY,
});
React.useEffect(() => {
const endAtStr = endAt === TODAY ? String(moment().valueOf()) : String(moment().startOf('day').valueOf());
monitorKey?.ai && getVersionStatistics({ artifactsId, endTime: endAtStr, ...monitorKey });
}, [artifactsId, endAt, getVersionStatistics, monitorKey]);
useUnmount(() => {
return () => clearVersionStatistic();
});
const columns: Array<ColumnProps<PUBLISHER.VersionStatistic>> = [
{
title: i18n.t('Version'),
dataIndex: 'versionOrChannel',
width: 140,
},
{
title: `${i18n.t("publisher:as of today's cumulative users")}(%)`,
dataIndex: 'totalUsers',
width: 230,
render: (text, record) => `${text}(${record.totalUsersGrowth})`,
},
{
title: i18n.t('publisher:new users'),
dataIndex: 'newUsers',
width: 180,
},
{
title: `${i18n.t('publisher:active user')}(%)`,
dataIndex: 'activeUsers',
width: 180,
render: (text, record) => `${text}(${record.activeUsersGrowth})`,
},
{
title: i18n.t('publisher:number of starts'),
width: 120,
dataIndex: 'launches',
},
{
title: i18n.t('publisher:upgrade user'),
width: 120,
dataIndex: 'upgradeUser',
},
];
return (
<>
<FilterTab
tabs={[
{ label: i18n.t('publisher:today'), value: TODAY },
{ label: i18n.t('publisher:Yesterday'), value: YESTERDAY },
]}
onChange={(val: string) => updater.endAt(val)}
defaultValue={TODAY}
className="m-2"
/>
<Table
columns={columns}
loading={loading}
dataSource={versionStatisticList.map((item, i) => ({ ...item, key: i }))}
pagination={false}
scroll={{ x: '100%' }}
/>
</>
);
}
Example #10
Source File: detail.tsx From erda-ui with GNU Affero General Public License v3.0 | 5 votes |
Detail = () => {
const [addonStatus, setAddonStatus] = React.useState('');
const [addonDetail] = middlewareDashboardStore.useStore((s) => [s.baseInfo]);
const { instanceId } = routeInfoStore.getState((s) => s.params);
const [loading] = useLoading(middlewareDashboardStore, ['getBaseInfo']);
const { getConfig, getBaseInfo } = middlewareDashboardStore.effects;
const timer = React.useRef(0 as any);
useMount(() => {
getBaseInfo(instanceId).then((res) => {
const { isOperator, instanceId: addonID, addonName, cluster: clusterName } = res;
if (isOperator) {
getConfig({ addonID });
fetchAddonStatus({ addonID, addonName, clusterName });
// 轮询状态
timer.current = setInterval(() => {
// TODO 2020/7/23 setInterval 和 setTimeout 中调用cube的effect 会导致rerender,待查原因
fetchAddonStatus({ addonID, addonName, clusterName });
}, 10000);
}
});
return () => middlewareDashboardStore.reducers.clearBaseInfo();
});
const fetchAddonStatus = (data: MIDDLEWARE_DASHBOARD.IMiddleBase) => {
getAddonStatus(data).then((res: any) => {
const { status } = res.data;
if (status !== addonStatus) {
setAddonStatus(status);
}
});
};
useUnmount(() => {
if (timer.current) {
clearInterval(timer.current);
}
});
const extraNode = React.useMemo(() => {
let node = null;
if (addonDetail.isOperator) {
const data = {
addonID: instanceId,
addonName: addonDetail.addonName,
clusterName: addonDetail.cluster,
name: addonDetail.name,
projectID: addonDetail.projectId,
projectName: addonDetail.projectName,
};
node = (
<>
<ScaleInfo data={data} />
<UpgradeInfo data={data} />
</>
);
}
return node;
}, [
addonDetail.addonName,
addonDetail.cluster,
addonDetail.isOperator,
addonDetail.name,
addonDetail.projectId,
addonDetail.projectName,
instanceId,
]);
return (
<PureBaseAddonInfo
addonDetail={{ ...addonDetail, addonStatus }}
loading={loading}
extra={extraNode}
onReload={() => {
getBaseInfo(instanceId);
}}
/>
);
}
Example #11
Source File: deploy-cluster.tsx From erda-ui with GNU Affero General Public License v3.0 | 5 votes |
DeployCluster = () => {
const deployingCluster = clusterStore.useStore((s) => s.deployingCluster);
const { killDeployCluster, deployCluster, getCurDeployCluster } = clusterStore.effects;
const { clearDeployCluster } = clusterStore.reducers;
const currentOrg = orgStore.useStore((s) => s.currentOrg);
const [logVisible, setLogVisible] = React.useState(false);
useMount(() => {
getCurDeployCluster();
});
useUnmount(() => {
clearDeployCluster();
firstVisit = true;
});
React.useEffect(() => {
if (!isEmpty(deployingCluster) && firstVisit) {
message.info(
i18n.t(
'cmp:The last deployment information has been initialized. Please click the reset button at the bottom if unnecessary.',
),
);
firstVisit = false;
}
}, [deployingCluster]);
const startDeployCluster = (postData: any) => {
deployCluster(postData).then((res: any) => {
if (res) {
setLogVisible(true);
}
});
};
return (
<div className="deploy-cluster">
<div className="deploy-info font-medium">
{i18n.t('organization')} {currentOrg.name} {i18n.t('cmp:new cluster deployment')}
<div className="deploy-operator">
<Button onClick={() => setLogVisible(true)}>{i18n.t('check log')}</Button>
<Button onClick={() => killDeployCluster()}>{i18n.t('stop deploying')}</Button>
</div>
</div>
<div className="deploy-content">
<DeployClusterForm
data={deployingCluster}
orgId={currentOrg.id}
orgName={currentOrg.name}
onSubmit={startDeployCluster}
/>
</div>
<Drawer
destroyOnClose
title={i18n.t('Deployment log')}
width="80%"
visible={logVisible}
onClose={() => setLogVisible(false)}
>
<DeployClusterLog />
</Drawer>
</div>
);
}
Example #12
Source File: service-name-select.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
export function ServiceNameSelect() {
const { ServiceMonitor } = breadcrumbStore.useStore((s) => s.infoMap.mspBreadcrumb);
const [serviceId] = serviceAnalyticsStore.useStore((s) => [s.serviceId, s.serviceName]);
const currentProject = mspStore.getState((s) => s.currentProject);
const params = routeInfoStore.useStore((s) => s.params);
const [serverListData, loading] = getServices.useState();
const { updateState } = serviceAnalyticsStore;
const serviceList = serverListData?.list || [];
const configServiceData = (key: string) => {
const service = serviceList.filter((v: MSP_SERVICES.SERVICE_LIST_ITEM) => v.id === key);
const _serviceId = service[0]?.id || serviceList[0]?.id;
const _serviceName = service[0]?.name || serviceList[0]?.name;
const [applicationId] = _serviceId?.split('_') || [];
updateState({
serviceId: _serviceId ? window.decodeURIComponent(_serviceId) : '',
serviceName: _serviceName,
applicationId: currentProject?.type === 'MSP' ? '-' : applicationId,
});
};
React.useEffect(() => {
updateState({
requestCompleted: !loading,
});
}, [loading]);
React.useEffect(() => {
getServices.fetch({
pageNo: 1,
pageSize: 1000,
tenantId: params?.terminusKey,
});
}, []);
React.useEffect(() => {
if (serviceId) {
configServiceData(serviceId);
} else if (params?.serviceId) {
configServiceData(window.decodeURIComponent(params?.serviceId));
} else if (!serviceId && serviceList?.length > 0) {
configServiceData(serviceId);
}
}, [params.serviceId, serviceId, serviceList, updateState]);
useUnmount(() => {
updateState({
serviceId: '',
serviceName: '',
applicationId: '',
});
});
const list = React.useMemo(
() =>
serviceList.map((item) => ({
...item,
value: item.id,
label: item.name,
})),
[serviceId, serviceList],
);
const handleChangeService = (serviceID: string) => {
configServiceData(serviceID);
};
return (
<div className="flex items-center service-name-select">
<div className="font-bold text-lg">{ServiceMonitor}</div>
<span className="bg-black-2 mx-4 w-px h-3" />
<div className="max-w-48">
<LoadMoreSelector
list={list}
value={serviceId}
dropdownMatchSelectWidth={false}
dropdownStyle={{ width: 300 }}
onChange={handleChangeService}
valueItemRender={(item) => {
return (
<div className="flex w-full pl-2 text-base group">
<div className="w-full flex justify-between">
<Ellipsis className="font-bold" title={item.label} />
<ErdaIcon
type="caret-down"
className="icon ml-0.5 text-default-3 group-hover:text-default"
size="14"
/>
</div>
</div>
);
}}
/>
</div>
</div>
);
}
Example #13
Source File: button.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
CP_Button = (props: CP_BUTTON.Props) => {
const { updateState, customOp, execOperation, operations, props: configProps } = props;
const {
text,
prefixIcon,
suffixIcon,
menu,
tooltip,
tipProps = {},
visible = true,
disabled: pDisabled,
disabledTip: pDisabledTip,
dropdownProps,
...rest
} = configProps || {};
const timer = React.useRef();
useUnmount(() => {
timer.current && clearTimeout(timer.current);
});
const { disabledTip, disabled, confirm } = operations?.click || {};
const onClick = () => {
if (!disabled) {
customOp?.click && customOp.click(operations?.click);
operations?.click && execOperation(operations.click);
}
};
const content = (
<div className="flex items-center">
{prefixIcon ? <IconComp type={prefixIcon} className="mr-1" /> : null}
{text}
{suffixIcon ? (
<IconComp type={suffixIcon} className="ml-1" />
) : isEmpty(menu) ? null : (
<ErdaIcon type="caret-down" size="18" className="ml-1" />
)}
</div>
);
React.useEffect(() => {
// let data drive automatic refresh or not, when autoRefresh not exist, it will stop refresh
const autoRefreshOp = operations?.autoRefresh;
timer.current && clearTimeout(timer.current);
if (autoRefreshOp) {
timer.current = setTimeout(() => {
execOperation(autoRefreshOp);
}, autoRefreshOp.duration || 5000);
}
}, [operations]);
if (!visible) return null;
if (disabled || pDisabled) {
return (
<Tooltip title={disabledTip || pDisabledTip} {...tipProps}>
<Button {...rest} disabled>
{content}
</Button>
</Tooltip>
);
}
if (!isEmpty(menu)) {
const dropdownMenu = (
<Menu
onClick={(e: any) => {
e.domEvent.stopPropagation();
const curOp = find(menu, { key: e.key });
customOp?.click && customOp.click(curOp?.operations.click);
if (curOp?.operations?.click) {
execOperation(curOp.operations.click);
}
}}
>
{map(menu, (mItem) => {
const curOp = mItem.operations?.click || {};
if (curOp.confirm && curOp.disabled !== true) {
return (
<Menu.Item key={`${fakeClick}-${mItem.key}`}>
<Popconfirm
{...tipProps}
title={curOp.confirm}
onConfirm={() => {
execOperation(curOp);
customOp?.click && customOp.click(mItem.operations.click);
}}
>
<div>{mItem.text}</div>
</Popconfirm>
</Menu.Item>
);
}
return (
<Menu.Item key={mItem.key} disabled={mItem.disabled || curOp.disabled}>
<Tooltip title={mItem.disabledTip || curOp.disabledTip}>
<div className="flex items-center">
{mItem.prefixIcon ? <IconComp type={mItem.prefixIcon} /> : null}
{mItem.text}
</div>
</Tooltip>
</Menu.Item>
);
})}
</Menu>
);
return (
<Dropdown overlay={dropdownMenu} {...dropdownProps}>
<Button {...rest}>{content}</Button>
</Dropdown>
);
}
const buttonComp = confirm ? (
<Popconfirm {...tipProps} title={confirm} onConfirm={onClick}>
<Button {...rest}>{content}</Button>
</Popconfirm>
) : (
<Button {...rest} onClick={onClick}>
{content}
</Button>
);
return tooltip ? (
<Tooltip title={tooltip} {...tipProps}>
{buttonComp}
</Tooltip>
) : (
buttonComp
);
}
Example #14
Source File: jvm-overview.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
JvmOverview = () => {
const { realInstanceId: insId } = addonStore.useStore((s) => s.addonDetail);
const { profileId } = routeInfoStore.useStore((s) => s.params);
const jvmInfo = jvmStore.useStore((s) => s.jvmInfo);
const pendingTimer = React.useRef(-1);
const failedTimer = React.useRef(-1);
const [{ isPending, isRunning, createTime, finishTime }, updater] = useUpdate({
isPending: true,
isRunning: false,
createTime: 0,
finishTime: 0,
});
useMount(() => {
rollingState();
});
useUnmount(() => {
clearTimeout(failedTimer.current);
clearTimeout(pendingTimer.current);
});
React.useEffect(() => {
if (isPending) return;
forEach(JVM_INFO_SCOPES, (scope) => {
jvmStore.getJVMInfo({
insId,
profileId,
scope,
});
});
}, [insId, isPending, profileId]);
const rollingState = React.useCallback(() => {
jvmStore.getProfileStatus({ insId, profileId }).then((res) => {
updater.createTime(res.createTime);
updater.finishTime(res.finishTime);
switch (res.state) {
case ProfileStateMap.PENDING:
pendingTimer.current = window.setTimeout(() => {
rollingState();
}, 3000);
break;
case ProfileStateMap.RUNNING:
updater.isRunning(true);
updater.isPending(false);
break;
case ProfileStateMap.FAILED:
message.error(res.message);
failedTimer.current = window.setTimeout(() => {
goTo('../');
}, 500);
break;
default:
updater.isPending(false);
break;
}
});
}, [insId, profileId, updater]);
const stopProfile = () => {
jvmStore
.stopProfile({
insId,
profileId,
})
.then(() => {
rollingState();
});
};
const getPanelBody = (data: Array<{ key: string; value: string }>) => (
<Holder when={isEmpty(data)}>
{map(data, ({ key, value }) => (
<p className="info-item">
<span className="label">{key}</span>
<span className="value">{value}</span>
</p>
))}
</Holder>
);
return (
<div className="jvm-overview">
<Spin spinning={isPending}>
<div className="p-5 mb-5 bg-white border-all">
<div className="jvm-profiler flex justify-between items-center">
<div className="profiler-info flex-1">
<p className="info-item">
<span className="label">{`${i18n.t('dop:analyze ID')}: `}</span>
<span className="value">{profileId}</span>
</p>
<p className="info-item">
<span className="label">{`${i18n.t('Creation time')}: `}</span>
<span className="value">{formatTime(createTime, 'YYYY-MM-DD HH:mm:ss')}</span>
</p>
{isRunning ? (
<p className="info-item">
<span className="label">{`${i18n.t('dop:started at')}: `}</span>
<span className="value">{fromNow(createTime)}</span>
</p>
) : (
<p className="info-item">
<span className="label">{`${i18n.t('common:End time')}: `}</span>
<span className="value">{formatTime(finishTime, 'YYYY-MM-DD HH:mm:ss')}</span>
</p>
)}
</div>
<div className="profiler-actions ml-6">
<Button type="primary" disabled={!isRunning} onClick={stopProfile}>
{i18n.t('dop:stop analysis')}
</Button>
</div>
</div>
</div>
<div className="panel block mb-5">
<div className="panel-title">{i18n.t('dop:jvm process info')}</div>
<div className="panel-body">{getPanelBody(jvmInfo.jvm_process)}</div>
</div>
<div className="flex justify-between items-center">
<div className="panel block flex-1 mr-5">
<div className="panel-title">{i18n.t('dop:jvm properties')}</div>
<div className="panel-body">{getPanelBody(jvmInfo.jvm_options)}</div>
</div>
<div className="panel block flex-1">
<div className="panel-title">{i18n.t('dop:system properties')}</div>
<div className="panel-body">{getPanelBody(jvmInfo.jvm_properties)}</div>
</div>
</div>
</Spin>
</div>
);
}
Example #15
Source File: publisher-list-v2.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
PurePublisherList = ({
list = [],
paging,
getList,
clearList,
isFetching,
onItemClick,
}: IPubliserListProps) => {
const [{ q, formVisible }, updater] = useUpdate({
q: undefined as string | undefined,
formVisible: false,
});
const { mode } = routeInfoStore.useStore((s) => s.params);
const publishOperationAuth = usePerm((s) => s.org.publisher.operation.pass);
useUnmount(clearList);
useUpdateEffect(() => {
updater.q(undefined);
}, [mode]);
useDebounce(
() => {
getList({
q,
pageNo: 1,
type: mode,
});
},
300,
[q, mode],
);
const onSubmit = ({ q: value }: { q: string }) => {
updater.q(value);
};
const goToPublisher = (item: PUBLISHER.IPublisher) => {
onItemClick(item);
};
const handlePageChange = (pageNo: number, pageSize: number) => {
getList({
q,
pageNo,
pageSize,
type: mode,
});
};
const openFormModal = () => {
updater.formVisible(true);
};
const closeFormModal = () => {
updater.formVisible(false);
};
const afterSubmitAction = (_isUpdate: boolean, data: PUBLISHER.IArtifacts) => {
const { id, type } = data;
goTo(`../${type}/${id}`);
};
const column: Array<ColumnProps<PUBLISHER.IPublisher>> = [
{
title: i18n.t('publisher:publisher content name'),
dataIndex: 'name',
width: 240,
},
{
title: i18n.t('Description'),
dataIndex: 'desc',
},
...insertWhen<ColumnProps<PUBLISHER.IPublisher>>(mode === 'LIBRARY', [
{
title: i18n.t('version number'),
width: 160,
dataIndex: 'latestVersion',
render: (text) => text || '-',
},
{
title: i18n.t('publisher:subscriptions'),
width: 120,
dataIndex: 'refCount',
render: (text) => text || 0,
},
]),
{
title: i18n.t('default:status'),
width: 120,
dataIndex: 'public',
render: (bool) => {
return (
<span className={`item-status ${bool ? 'on' : 'off'}`}>
{bool ? i18n.t('publisher:published') : i18n.t('publisher:withdrawn')}
</span>
);
},
},
];
const config = React.useMemo(
() => [
{
type: Input,
name: 'q',
customProps: {
placeholder: i18n.t('filter by {name}', { name: i18n.t('publisher:publisher content name') }),
autoComplete: 'off',
},
},
],
[],
);
return (
<Spin spinning={isFetching}>
<div className="publisher-list-section">
<TopButtonGroup>
<WithAuth pass={publishOperationAuth} tipProps={{ placement: 'bottom' }}>
<Button type="primary" onClick={() => openFormModal()}>
{i18n.t('publisher:Add')}
</Button>
</WithAuth>
</TopButtonGroup>
<ErdaTable
rowKey="id"
columns={column}
dataSource={list}
onRow={(record: PUBLISHER.IPublisher) => {
return {
onClick: () => {
goToPublisher(record);
},
};
}}
pagination={{
current: paging.pageNo,
...paging,
onChange: handlePageChange,
}}
slot={
<CustomFilter
key={mode}
config={config}
onSubmit={onSubmit}
onReset={() => {
updater.q('');
}}
/>
}
/>
<ArtifactsFormModal visible={formVisible} onCancel={closeFormModal} afterSubmit={afterSubmitAction} />
</div>
</Spin>
);
}
Example #16
Source File: detail.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
ErrorReportDetail = () => {
const [publisherItemId, query] = routeInfoStore.useStore((s) => [s.params.publisherItemId, s.query]);
const detail = errorReportStore.useStore((s) => s.errorDetail);
let tags: Obj = {};
let log = i18n.t('publisher:No data');
if (detail) {
tags = detail.tags;
tags.timestamp = detail['@timestamp'];
log = decodeURIComponent(tags.stack_trace);
}
useUnmount(() => {
errorReportStore.reducers.clearErrorDetail();
});
React.useEffect(() => {
if (query.filter) {
errorReportStore.effects.getErrorDetail({
artifactsId: publisherItemId,
filter_error: query.filter,
start: query.start,
end: query.end,
limit: 1,
ak: query.ak,
ai: query.ai,
});
}
}, [publisherItemId, query.ai, query.ak, query.end, query.filter, query.start]);
const fields = [
[i18n.t('publisher:device model'), 'md'],
[i18n.t('System version'), 'osv'],
[i18n.t('publisher:app package name'), 'dh'],
[i18n.t('publisher:app version'), 'av'],
[i18n.t('publisher:cpu architecture'), 'cpu'],
[i18n.t('publisher:Memory'), 'mem'],
[i18n.t('publisher:storage space'), 'rom'],
[i18n.t('publisher:client ID'), 'cid'],
[i18n.t('publisher:whether to jailbreak'), 'jb'],
[i18n.t('publisher:gps'), 'gps'],
[i18n.t('user ID'), 'uid'],
[i18n.t('publisher:ip address'), 'ip'],
[i18n.t('publisher:client occurrence time'), 'timestamp', 'time'],
];
const basicFieldsList = fields.map((item) => {
const [label, name, type] = item;
const typeMap = {
time: (val: string) => formatTime(val, 'YYYY-MM-DD HH:mm:ss'),
};
if (type) {
return { label, name, render: typeMap[type] };
}
return { label, name };
});
const sectionList: any[] = [
{
title: i18n.t('basic information'),
children: getInfoBlock(basicFieldsList, tags),
},
{
title: i18n.t('publisher:detailed log'),
titleExtra: (
<>
<Copy selector=".cursor-copy" />
<Button className="cursor-copy" data-clipboard-text={log} type="primary" ghost>
{i18n.t('Copy')}
</Button>
</>
),
children: (
<pre className="code-block" style={{ whiteSpace: 'pre-wrap' }}>
{log}
</pre>
),
},
];
return (
<>
<ConfigLayout sectionList={sectionList} />
</>
);
}
Example #17
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 #18
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
ApiDesign = () => {
const [
{
contentKey,
dataTypeFormData,
filterKey,
apiResourceList,
apiDataTypeList,
quotePathMap,
treeModalVisible,
apiModalVisible,
curTreeNodeData,
curApiName,
curDataType,
newTreeNode,
popVisible,
apiDetail,
},
updater,
update,
] = useUpdate({
contentKey: 'SUMMARY',
dataTypeFormData: {},
filterKey: '',
apiResourceList: [] as string[],
apiDataTypeList: [] as string[],
quotePathMap: {} as Obj,
treeModalVisible: false,
apiModalVisible: false,
curTreeNodeData: {},
curApiName: '',
curDataType: '',
newTreeNode: {} as API_SETTING.IFileTree,
popVisible: false,
apiDetail: {},
});
const { inode: inodeQuery, pinode: pinodeQuery } = routeInfoStore.useStore((s) => s.query);
React.useEffect(() => {
const [key] = contentKey.split('&DICE&');
if (key === 'RESOURCE') {
curApiName && updater.contentKey(`${key}&DICE&${curApiName}`);
} else {
curDataType && updater.contentKey(`${key}&DICE&${curDataType}`);
}
}, [curApiName, contentKey, updater, curDataType]);
const { isExternalRepo, repoConfig } = appStore.useStore((s) => s.detail);
const [openApiDoc, apiWs, apiLockState, isDocChanged, wsQuery, formErrorNum, isApiReadOnly, lockUser, docValidData] =
apiDesignStore.useStore((s) => [
s.openApiDoc,
s.apiWs,
s.apiLockState,
s.isDocChanged,
s.wsQuery,
s.formErrorNum,
s.isApiReadOnly,
s.lockUser,
s.docValidData,
]);
const {
updateOpenApiDoc,
createTreeNode,
commitSaveApi,
getApiDetail,
publishApi,
updateFormErrorNum,
resetDocValidData,
} = apiDesignStore;
const [getApiDocDetailLoading, commitSaveApiLoading, getTreeListLoading] = useLoading(apiDesignStore, [
'getApiDetail',
'commitSaveApi',
'getTreeList',
]);
useMount(() => {
window.addEventListener('beforeunload', beforeunload);
});
useUnmount(() => {
updateOpenApiDoc({});
apiWs && apiWs.close();
window.removeEventListener('beforeunload', beforeunload);
});
const changeRef = React.useRef(null as any);
React.useEffect(() => {
changeRef.current = isDocChanged;
}, [isDocChanged]);
const beforeunload = React.useCallback((e) => {
const msg = `${i18n.t('dop:not saved yet, confirm to leave')}?`;
if (changeRef.current) {
// eslint-disable-next-line no-param-reassign
(e || window.event).returnValue = msg;
}
return msg;
}, []);
const apiResourceMap = React.useMemo(() => {
const tempMap = openApiDoc?.paths || {};
const fullKeys = keys(tempMap);
let tempList = [];
if (filterKey) {
tempList = filter(keys(tempMap), (name) => name.indexOf(filterKey) > -1);
} else {
tempList = fullKeys;
}
updater.apiResourceList(tempList);
return tempMap;
}, [filterKey, openApiDoc, updater]);
const apiDataTypeMap = React.useMemo(() => {
const tempMap = openApiDoc?.components?.schemas || {};
const fullKeys = keys(tempMap);
let tempList = [];
if (filterKey) {
tempList = filter(fullKeys, (name) => name.indexOf(filterKey) > -1);
} else {
tempList = fullKeys;
}
updater.apiDataTypeList(tempList);
return tempMap;
}, [filterKey, openApiDoc, updater]);
const onCreateDoc = (values: { name: string; pinode: string }) => {
createTreeNode(values).then((res) => {
updater.newTreeNode(res);
});
updater.treeModalVisible(false);
};
const onContentChange = React.useCallback(
(contentName: string) => {
const nextHandle = () => {
updateFormErrorNum(0);
const [, name] = contentName.split('&DICE&');
updater.contentKey(contentName);
if (contentName.startsWith('RESOURCE') && name) {
updater.curApiName(name);
const tempApiDetail = get(openApiDoc, ['paths', name]) || {};
updater.apiDetail(tempApiDetail);
}
if (contentName.startsWith('DATATYPE')) {
const _fromData = apiDataTypeMap[name] || { type: 'string', example: 'Example', 'x-dice-name': name };
updater.dataTypeFormData({ ..._fromData, name });
updater.curDataType(name);
}
};
if (formErrorNum > 0) {
confirm({
title: i18n.t('dop:Are you sure to leave, with the error message not saved?'),
onOk() {
nextHandle();
},
});
} else {
nextHandle();
}
},
[apiDataTypeMap, formErrorNum, openApiDoc, updateFormErrorNum, updater],
);
const dataTypeNameMap = React.useMemo(() => {
return keys(get(openApiDoc, ['components', 'schemas']));
}, [openApiDoc]);
const apiNameMap = React.useMemo(() => {
return keys(openApiDoc.paths || {});
}, [openApiDoc]);
const onAddHandle = (addKey: IListKey) => {
let newData = {};
let newName = `/api/new${apiResourceList.length}`;
while (apiNameMap.includes(newName)) {
newName += '1';
}
let dataPath = ['paths', newName];
if (addKey === 'DATATYPE') {
newName = `NewDataType${apiDataTypeList.length}`;
newData = { type: 'string', example: 'Example', 'x-dice-name': newName };
dataPath = ['components', 'schemas', newName];
}
const tempDocDetail = produce(openApiDoc, (draft) => set(draft, dataPath, newData));
updateOpenApiDoc(tempDocDetail);
onContentChange(`${addKey}&DICE&${newName}`);
};
const onDeleteHandle = (itemKey: string) => {
const [key, name] = itemKey.split('&DICE&');
if (key === 'DATATYPE') {
const newQuoteMap = getQuoteMap(openApiDoc);
if (newQuoteMap[name]?.length) {
message.warning(i18n.t('dop:this type is referenced and cannot be deleted'));
return;
}
} else if (key === 'RESOURCE') {
const paths = keys(openApiDoc.paths);
if (paths.length === 1) {
message.warning(i18n.t('dop:at least one API needs to be kept'));
return;
}
}
const dataPath = key === 'RESOURCE' ? ['paths', name] : ['components', 'schemas', name];
const tempDocDetail = produce(openApiDoc, (draft) => {
unset(draft, dataPath);
});
updateOpenApiDoc(tempDocDetail);
onContentChange('SUMMARY');
};
// 左侧列表头部渲染
const renderPanelHead = (titleKey: IListKey) => (
<div className="list-panel-head inline-flex justify-between items-center">
<span className="font-bold">{LIST_TITLE_MAP[titleKey]}</span>
{!apiLockState && (
<ErdaIcon
type="plus"
className="mr-0 cursor-pointer"
size="16px"
onClick={(e) => {
e.stopPropagation();
onAddHandle(titleKey);
}}
/>
)}
</div>
);
// 左侧列表渲染
const renderListItem = (listKey: IListKey, name: string) => {
const apiData = apiResourceMap[name] || {};
const key = `${listKey}&DICE&${name}`;
return (
<LazyRender key={name} minHeight={listKey === 'RESOURCE' ? '58px' : '37px'}>
<div
className={`list-title ${contentKey === key ? 'list-title-active' : ''}`}
onClick={() => onContentChange(key)}
>
<div className="flex justify-between items-center">
<Ellipsis title={name}>
<div className="list-title-name w-full nowrap mr-1">{name}</div>
</Ellipsis>
<Popconfirm
title={`${i18n.t('common:confirm to delete')}?`}
onConfirm={(e: any) => {
e.stopPropagation();
onDeleteHandle(key);
}}
onCancel={(e: any) => e.stopPropagation()}
>
{!apiLockState && (
<CustomIcon
type="shanchu"
className="list-title-btn cursor-pointer"
onClick={(e) => e?.stopPropagation()}
/>
)}
</Popconfirm>
</div>
{listKey === 'RESOURCE' && (
<div className="method-list">
{map(API_METHODS, (methodKey: API_SETTING.ApiMethod) => {
const methodIconClass = !isEmpty(apiData[methodKey]) ? `method-icon-${methodKey}` : '';
return (
<Tooltip title={methodKey} key={methodKey}>
<div className={`method-icon mr-2 ${methodIconClass}`} />
</Tooltip>
);
})}
</div>
)}
</div>
</LazyRender>
);
};
// 获取所有引用的pathMap
const getQuoteMap = React.useCallback(
(data: Obj) => {
const getQuotePath = (innerData: Obj, prefixPath: Array<number | string>, pathMap: Obj) => {
const refTypePath = get(innerData, [QUOTE_PREFIX, 0, '$ref']) || innerData[QUOTE_PREFIX_NO_EXTENDED];
if (refTypePath) {
const _type = refTypePath.split('/').slice(-1)[0];
// eslint-disable-next-line no-param-reassign
!pathMap[_type] && (pathMap[_type] = []);
if (!pathMap[_type].includes(prefixPath)) {
pathMap[_type].push(prefixPath);
}
}
if (innerData?.properties) {
forEach(keys(innerData.properties), (item) => {
getQuotePath(innerData.properties[item], [...prefixPath, 'properties', item], pathMap);
});
}
if (innerData?.items) {
getQuotePath(innerData.items, [...prefixPath, 'items'], pathMap);
}
};
const tempMap = {};
const pathMap = data.paths;
forEach(keys(pathMap), (path) => {
const pathData = pathMap[path];
forEach(keys(pathData), (method) => {
const methodData = pathData[method];
const _path = ['paths', path, method];
forEach(API_MEDIA_TYPE, (mediaType) => {
// responses
const responsePath = ['responses', '200', 'content', mediaType, 'schema'];
const responseData = get(methodData, responsePath) || {};
getQuotePath(responseData, [..._path, ...responsePath], tempMap);
// requestBody;
const requestBodyPath = ['requestBody', 'content', mediaType, 'schema'];
const requestBody = get(methodData, requestBodyPath) || {};
getQuotePath(requestBody, [..._path, ...requestBodyPath], tempMap);
});
// parameters
const parametersData = methodData.parameters || [];
forEach(parametersData, (pData, index) => {
getQuotePath(pData, [..._path, 'parameters', index], tempMap);
});
});
});
// datatype中的引用
const dataTypeData = data?.components?.schemas || {};
forEach(keys(dataTypeData), (dataTypeName) => {
getQuotePath(dataTypeData[dataTypeName], ['components', 'schemas', dataTypeName], tempMap);
});
updater.quotePathMap(tempMap);
return tempMap;
},
[updater],
);
const onQuotePathMapChange = React.useCallback(
(pathMap: Obj) => {
updater.quotePathMap(pathMap);
},
[updater],
);
const onApiNameChange = React.useCallback(
(name: string) => {
updater.curApiName(name);
},
[updater],
);
const renderContent = (key: string) => {
if (key.startsWith('RESOURCE')) {
return (
<ApiResource
onQuoteChange={onQuotePathMapChange}
onApiNameChange={onApiNameChange}
quotePathMap={quotePathMap}
apiName={curApiName}
apiDetail={apiDetail}
/>
);
} else if (key.startsWith('DATATYPE')) {
return (
<DataTypeConfig
quotePathMap={quotePathMap}
dataTypeNameMap={dataTypeNameMap}
formData={dataTypeFormData}
key={dataTypeFormData?.name}
dataType={curDataType}
onQuoteNameChange={onQuotePathMapChange}
onDataTypeNameChange={(name) => updater.curDataType(name)}
isEditMode={!apiLockState}
/>
);
} else {
return <ApiSummary />;
}
};
const isDocLocked = React.useMemo(() => {
return wsQuery?.sessionID && apiLockState;
}, [apiLockState, wsQuery]);
const LockTipVisible = React.useMemo(() => isApiReadOnly || isDocLocked, [isApiReadOnly, isDocLocked]);
const docLockTip = React.useMemo(() => {
if (isApiReadOnly) {
return i18n.t('dop:protect branch, not editable');
} else if (isDocLocked) {
return lockUser + API_LOCK_WARNING;
} else {
return '';
}
}, [isApiReadOnly, isDocLocked, lockUser]);
const errorData = React.useMemo(() => {
return {
branchName: curTreeNodeData.branchName,
docName: `${curTreeNodeData.apiDocName}.yaml`,
msg: docValidData.msg,
};
}, [curTreeNodeData, docValidData]);
const onEditDocHandle = () => {
if (!apiWs) {
initApiWs({ inode: inodeQuery, pinode: pinodeQuery });
} else if (isDocLocked) {
message.warning(lockUser + API_LOCK_WARNING);
}
};
const onPublishApi = React.useCallback(
(values: any) => {
publishApi(values).then(() => {
apiWs && apiWs.close();
getApiDetail(inodeQuery as string).then((data: any) => {
getQuoteMap(data.openApiDoc);
updater.curTreeNodeData({
...curTreeNodeData,
asset: data.asset,
});
});
});
},
[apiWs, curTreeNodeData, getApiDetail, getQuoteMap, inodeQuery, publishApi, updater],
);
const onSelectDoc = React.useCallback(
(nodeData, reset) => {
if (reset) {
updateOpenApiDoc({});
resetDocValidData();
}
onContentChange('Summary');
update({
contentKey: 'SUMMARY',
curTreeNodeData: nodeData,
newTreeNode: {} as API_SETTING.IFileTree,
filterKey: '',
});
},
[onContentChange, resetDocValidData, update, updateOpenApiDoc],
);
const onToggleTreeVisible = React.useCallback(
(val: boolean) => {
updater.popVisible(val);
},
[updater],
);
const onConfirmPublish = React.useCallback(() => {
if (isDocChanged) {
confirm({
title: i18n.t('dop:The current document has not been saved. Publish the saved document?'),
onOk() {
updater.apiModalVisible(true);
},
});
} else {
updater.apiModalVisible(true);
}
}, [isDocChanged, updater]);
const showErrorDocTip = React.useMemo(() => {
return !docValidData.valid && !isDocChanged && !isEmpty(openApiDoc);
}, [docValidData.valid, isDocChanged, openApiDoc]);
return isExternalRepo === undefined ? (
<EmptyHolder relative />
) : (
<div className="api-design">
<TopButtonGroup>
<Button type="primary" onClick={() => updater.treeModalVisible(true)}>
{i18n.t('dop:New Document')}
</Button>
</TopButtonGroup>
<div className="api-design-wrap">
<div className="search-wrap mb-4 flex items-center justify-start">
<ApiDocTree
treeNodeData={curTreeNodeData}
newTreeNode={newTreeNode}
getQuoteMap={getQuoteMap}
onSelectDoc={onSelectDoc}
popVisible={popVisible}
onVisibleChange={onToggleTreeVisible}
/>
{LockTipVisible && (
<span className="ml-4">
<CustomIcon type="lock" />
{docLockTip}
</span>
)}
{showErrorDocTip && <ErrorPopover {...errorData} />}
{inodeQuery && !isEmpty(curTreeNodeData) && (
<div className="flex items-center flex-wrap justify-end flex-1">
{!apiWs || isDocLocked ? (
<WithAuth pass={!isApiReadOnly && docValidData.valid}>
<Button type="ghost" onClick={onEditDocHandle}>
{i18n.t('Edit')}
</Button>
</WithAuth>
) : (
<Button type="ghost" disabled={formErrorNum > 0} onClick={() => commitSaveApi()}>
{i18n.t('Save')}
</Button>
)}
<WithAuth pass={inodeQuery && docValidData.valid}>
<Button type="primary" className="ml-2" onClick={onConfirmPublish}>
{i18n.t('publisher:Release')}
</Button>
</WithAuth>
</div>
)}
</div>
<Spin spinning={getApiDocDetailLoading || commitSaveApiLoading || getTreeListLoading}>
{isEmpty(openApiDoc) ? (
<ErrorEmptyHolder {...errorData} isLoading={getTreeListLoading} />
) : (
<div className="api-design-content">
<div className="api-design-content-list flex flex-col justify-start">
<Input
placeholder={i18n.t('Search by keyword')}
className="mx-2 my-3 api-filter-input"
prefix={<ErdaIcon type="search1" size="14" className="mr-0.5 mt-0.5" />}
onInput={(e: React.ChangeEvent<HTMLInputElement>) => updater.filterKey(e.target.value)}
/>
<div
className={`list-title py-3 border-bottom font-bold ${
contentKey === 'SUMMARY' ? 'list-title-active' : ''
}`}
onClick={() => onContentChange('SUMMARY')}
>
{i18n.t('dop:API overview')}
</div>
<div className="panel-list">
<Collapse
accordion
bordered={false}
defaultActiveKey={['RESOURCE']}
className="api-overview-collapse"
>
<Panel header={renderPanelHead('RESOURCE')} key="RESOURCE">
{!isEmpty(apiResourceList) ? (
map(apiResourceList, (name) => renderListItem('RESOURCE', name))
) : (
<EmptyHolder relative />
)}
</Panel>
<Panel header={renderPanelHead('DATATYPE')} key="DATATYPE">
{!isEmpty(apiDataTypeList) ? (
map(apiDataTypeList, (name) => renderListItem('DATATYPE', name))
) : (
<EmptyHolder relative />
)}
</Panel>
</Collapse>
</div>
</div>
<div className="api-design-content-detail px-4 py-3">{renderContent(contentKey)}</div>
</div>
)}
</Spin>
<ApiDocAddModal
visible={treeModalVisible}
onClose={() => updater.treeModalVisible(false)}
onSubmit={onCreateDoc}
/>
<ApiPublishModal
visible={apiModalVisible}
treeNodeData={curTreeNodeData as API_SETTING.ITreeNodeData}
onSubmit={onPublishApi}
onClose={() => updater.apiModalVisible(false)}
/>
</div>
<Prompt
when={isDocChanged}
message={(location: Location) => {
if (location.pathname.endsWith('apiDesign')) {
return false;
}
return `${i18n.t('dop:not saved yet, confirm to leave')}?`;
}}
/>
</div>
);
}
Example #19
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
Pipeline = (props: IProps) => {
const {
appId,
nodeId,
pipelineId: _pipelineId,
branchExist,
projectId,
pipelineName,
appName,
setNewPipelineUsed,
projectName,
pipelineDefinitionID,
} = props;
const { getTreeNodeDetailNew } = fileTreeStore;
const [pipelineId, setPipelineId] = React.useState(_pipelineId);
const { commit } = repoStore.effects;
const [commitLoading] = useLoading(repoStore, ['commit']);
const { getPipelineDetail } = buildStore.effects;
const { clearPipelineDetail } = buildStore.reducers;
const [pipelineDetail, setPipelineDetail] = React.useState<BUILD.IPipelineDetail>();
const [nodeDetail, setNodeDetail] = React.useState<TREE.NODE>();
const [mode, setMode] = React.useState<DetailMode>(DetailMode.empty);
const [initPipeline, setInitPipeline] = React.useState(false);
const [fileChanged, setFileChanged] = React.useState(false);
// mobile_init is a special node, only allowed to run pipeline
const isMobileInit = nodeId === 'mobile_init';
useUnmount(() => {
clearPipelineDetail();
});
React.useEffect(() => {
if (pipelineId) {
getPipelineDetail({ pipelineID: +pipelineId }).then((res) => {
setPipelineDetail(res);
});
} else {
setMode(DetailMode.file);
}
}, [pipelineId, getPipelineDetail]);
React.useEffect(() => {
pipelineDetail && setInitPipeline(true);
}, [pipelineDetail]);
useUpdateEffect(() => {
initPipeline && initMode();
}, [initPipeline]);
const getNodeDetail = React.useCallback(() => {
getTreeNodeDetailNew({ id: nodeId, scope: 'project-app', scopeID: appId }).then((res) => setNodeDetail(res));
}, [nodeId, appId, getTreeNodeDetailNew]);
React.useEffect(() => {
getNodeDetail();
}, [getNodeDetail]);
const onUpdate = (ymlStr: string) => {
const fileName = nodeDetail?.name;
const { branch, path } = getBranchPath(nodeDetail);
return commit({
repoPrefix: `${projectName}/${appName}`,
branch,
message: `Update ${fileName}`,
actions: [
{
content: ymlStr,
path,
action: 'update',
pathType: 'blob',
},
],
}).then(() => {
getNodeDetail();
});
};
const initMode = () => {
const pipelineYmlContent = pipelineDetail?.ymlContent;
setMode(pipelineYmlContent ? DetailMode.execute : DetailMode.file);
};
React.useEffect(() => {
const fileYmlContent = nodeDetail?.meta?.pipelineYml;
const pipelineYmlContent = pipelineDetail?.ymlContent;
if (fileYmlContent) {
if (pipelineYmlContent) setFileChanged(fileYmlContent !== pipelineYmlContent);
if (fileYmlContent === pipelineYmlContent) setMode(DetailMode.execute);
}
}, [pipelineDetail, nodeDetail, branchExist, isMobileInit, initPipeline]);
const editAuth = { hasAuth: true };
const deployAuth = { hasAuth: true };
if (!branchExist && !pipelineId) return <EmptyHolder />;
const editPipeline = () => {
setMode(DetailMode.edit);
};
const isInApp = getIsInApp();
const extraTitle = (
<Tooltip title={i18n.t('dop:check execution history')}>
<ErdaIcon
onClick={() => {
const params = {
projectId,
query: {
// fix with base64 RFC 4648
customFilter__urlQuery: encode(`{"title":"${pipelineName}"}`).replaceAll('/', '_').replaceAll('+', '-'),
},
jumpOut: true,
};
if (isInApp) {
goTo(goTo.pages.appPipelineRecords, { ...params, appId });
} else {
goTo(goTo.pages.projectPipelineRecords, params);
}
}}
fill="black-4"
size="18"
type="jsjl"
className="cursor-pointer ml-2"
/>
</Tooltip>
);
return mode && mode !== DetailMode.execute ? (
<Editor
mode={mode}
projectId={projectId}
loading={commitLoading}
switchToExecute={() => setMode(DetailMode.execute)}
setEditMode={(v) => setMode(v)}
fileChanged={fileChanged}
setPipelineId={(v) => {
setPipelineId(v);
setNewPipelineUsed?.(true);
}}
extraTitle={extraTitle}
editAuth={editAuth.hasAuth}
pipelineDetail={pipelineDetail}
pipelineDefinitionID={pipelineDefinitionID}
deployAuth={deployAuth.hasAuth}
pipelineFileDetail={nodeDetail}
onUpdate={onUpdate}
/>
) : pipelineId ? (
<Execute
appId={appId}
setPipelineId={(v) => {
setPipelineId(v);
setNewPipelineUsed?.(true);
}}
projectId={projectId}
pipelineId={pipelineId}
switchToEditor={() => setMode(DetailMode.file)}
pipelineDefinitionID={pipelineDefinitionID}
extraTitle={extraTitle}
fileChanged={fileChanged}
deployAuth={deployAuth}
pipelineDetail={pipelineDetail}
pipelineFileDetail={nodeDetail}
editPipeline={editPipeline}
/>
) : null;
}
Example #20
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
RunDetail = (props: IProps) => {
const { runKey, scope } = props;
const [pipelineDetail] = autoTestStore.useStore((s) => [s.pipelineDetail]);
const recordRef = React.useRef(null as any);
const {
getPipelineDetail,
cancelBuild: cancelBuildCall,
runBuild: runBuildCall,
reRunFailed,
reRunEntire,
updateTaskEnv,
clearPipelineDetail,
} = autoTestStore;
const inParamsFormRef = React.useRef(null as any);
const [
{
isBlocked,
inParamsFormVis,
logVisible,
logProps,
isFetching,
snippetDetailVis,
snippetDetailProps,
resultVis,
chosenTask,
},
updater,
update,
] = useUpdate({
isFetching: false, // 自动刷新的时候不loading
startStatus: 'unstart', // unstart-未开始,ready-准备开始,start-已开始,end:执行完成或取消
logVisible: false,
logProps: {},
isBlocked: false,
inParamsFormVis: false,
snippetDetailVis: false,
snippetDetailProps: {},
resultVis: false,
chosenTask: {},
});
useUnmount(() => {
clearTimeout(tc);
clearPipelineDetail();
});
const { id: pipelineID, pipelineButton, extra, runParams, pipelineSnippetStages = [] } = pipelineDetail || {};
const onSelectPipeline = (p: PIPELINE.IPipeline) => {
if (p && p.id) {
updater.isFetching(true);
getPipelineDetail({ pipelineID: p.id }).then(() => {
updater.isFetching(false);
});
}
};
React.useEffect(() => {
clearTimeout(tc);
if (pipelineDetail && runningStatus.includes(pipelineDetail.status)) {
tc = setTimeout(() => {
getPipelineDetail({ pipelineID: pipelineDetail.id }).then(() => {
if (recordRef.current && recordRef.current.reload) {
recordRef.current.reload();
}
});
}, requestInterval);
}
}, [getPipelineDetail, pipelineDetail]);
const getSnippetNode = (node: PIPELINE.ITask, pos: number[] = []) => {
const [xPos] = pos;
let taskList = [];
if (isNumber(xPos)) {
taskList = get(pipelineSnippetStages, `[${xPos}].pipelineTasks`);
} else {
taskList = flatten(map(pipelineSnippetStages, (item) => item.pipelineTasks));
}
const nodeList = [] as PIPELINE.ITask[];
map(taskList, (subItem) => {
if (subItem.name.startsWith(`${node.name}_`)) {
nodeList.push(subItem);
}
});
return nodeList;
};
const onClickNode = (node: PIPELINE.ITask, clickTarget: string) => {
const { type } = node;
const xIndex = get(node, '_external_.xIndex');
const yIndex = get(node, '_external_.yIndex');
switch (clickTarget) {
case 'log':
if (type === 'snippet') {
const _nodeList = getSnippetNode(node, [xIndex, yIndex]);
_nodeList.length &&
update({
snippetDetailVis: true,
snippetDetailProps: {
detailType: 'log',
dataList: _nodeList,
},
});
} else {
update({
logVisible: true,
logProps: {
logId: node.extra.uuid,
title: i18n.t('msp:log details'),
customFetchAPIPrefix: `/api/apitests/pipeline/${pipelineID}/task/${node.id}/logs`,
pipelineID,
taskID: node.id,
downloadAPI: '/api/apitests/logs/actions/download',
},
});
}
break;
case 'result':
if (type === 'snippet') {
const _nodeList = getSnippetNode(node, [xIndex, yIndex]);
_nodeList.length &&
update({
snippetDetailVis: true,
snippetDetailProps: {
detailType: 'result',
dataList: _nodeList,
},
});
} else {
update({ resultVis: true, chosenTask: node });
}
break;
case 'node':
// const hasStarted = startStatus !== 'unstart';
// if (!hasStarted && pipelineDetail && pipelineDetail.status === 'Analyzed') {
// nodeClickConfirm(node);
// }
break;
default:
break;
}
};
const nodeClickConfirm = (node: PIPELINE.ITask) => {
const disabled = node.status === 'Disabled';
confirm({
title: i18n.t('OK'),
className: 'node-click-confirm',
content: i18n.t('dop:whether {action} task {name}', {
action: disabled ? i18n.t('Enable-open') : i18n.t('close'),
name: node.name,
}),
onOk: () => updateEnv({ id: node.id, disabled: !disabled }),
onCancel: noop,
});
};
const updateEnv = (info: { id: number; disabled: boolean }) => {
const { id, disabled } = info;
updateTaskEnv({ taskID: id, disabled, pipelineID: pipelineDetail.id }).then(() => {
getPipelineDetail({ pipelineID });
});
};
const beforeRunBuild = () => {
if (isEmpty(runParams)) {
// 没有入参
runBuild();
} else {
updater.inParamsFormVis(true);
}
};
const runBuild = (runPipelineParams?: any) => {
updater.startStatus('ready');
runBuildCall({ pipelineID, runPipelineParams })
.then((result: any) => {
if (result.success) {
updater.startStatus('start');
} else {
updater.startStatus('unstart');
}
})
.catch(() => updater.startStatus('unstart'));
};
const cancelBuild = () => {
cancelBuildCall({ pipelineID }).then(() => {
clearTimeout(tc);
getPipelineDetail({ pipelineID: pipelineDetail.id });
});
};
const getInParamsValue = (_pipelineParams: PIPELINE.IPipelineInParams[]) => {
const _values = {} as Obj;
map(_pipelineParams, (item: PIPELINE.IPipelineInParams) => {
if (item.value !== undefined && item.value !== null) _values[item.name] = item.value;
});
return _values;
};
const reRunPipeline = (isEntire: boolean) => {
updater.startStatus('padding');
const runPipelineParams = getInParamsValue(runParams); // 重跑:获取当前的入参值,给到新建的那个流水线;
const reRunFunc = !isEntire ? reRunFailed : reRunEntire;
reRunFunc({ pipelineID, runPipelineParams })
.then(() => {
updater.startStatus('start');
})
.catch(() => updater.startStatus('unstart'));
};
const renderReRunMenu = () => {
const { canRerunFailed, canRerun } = pipelineButton || {};
return (
<Menu>
{canRerunFailed && (
<Menu.Item>
<span
className={isBlocked ? 'disabled' : ''}
onClick={() => {
reRunPipeline(false);
}}
>{`${i18n.t('dop:rerun failed node')}(${i18n.t('dop:commit unchanged')})`}</span>
</Menu.Item>
)}
{canRerun && (
<Menu.Item>
<span
className={isBlocked ? 'disabled' : ''}
onClick={() => {
reRunPipeline(true);
}}
>{`${i18n.t('dop:rerun whole pipeline')}(${i18n.t('dop:commit unchanged')})`}</span>
</Menu.Item>
)}
</Menu>
);
};
const renderRunBtn = () => {
const { canCancel } = pipelineButton || {};
// 自动化测试此处无新建流水线概念,新建即执行,故暂时留一个cancel
return (
<IF check={canCancel}>
<DeleteConfirm
title={`${i18n.t('dop:confirm to cancel the current build')}?`}
secondTitle=""
onConfirm={() => {
cancelBuild();
}}
>
<div className="build-operator">
<Button className="mr-2">{i18n.t('dop:cancel build')}</Button>
</div>
</DeleteConfirm>
</IF>
);
};
const inParamsProps = React.useMemo(() => {
// yml数据转为表单fields
const _fields = ymlDataToFormData(runParams);
inParamsKey += 1;
return { fieldList: _fields, formData: getInParamsValue(runParams), key: inParamsKey };
}, [runParams]);
const { showMessage } = extra || {};
const hideLog = () => {
update({
logVisible: false,
logProps: {},
});
};
const closeSnippetDetail = () => {
update({
snippetDetailProps: {},
snippetDetailVis: false,
});
};
const closeResult = () => {
update({ resultVis: false, chosenTask: {} });
};
return (
<div className="pipeline-detail">
<Spin spinning={isFetching}>
<div className="info-header mb-2">
<div>
<span className="font-medium title">{i18n.t('dop:Build details')}</span>
</div>
<div className="info-header-right">
{renderRunBtn()}
<RecordList
ref={recordRef}
key={runKey}
curPipelineDetail={pipelineDetail}
onSelectPipeline={onSelectPipeline}
scope={scope}
/>
</div>
</div>
<BaseInfo data={pipelineDetail} />
{showMessage && showMessage.msg ? (
<div className="auto-test-detail-err-msg mb-2">
<div className="auto-test-err-header">
<ErdaIcon type="tishi" size="18px" className="auto-test-err-icon" />
<pre>{showMessage.msg}</pre>
</div>
<div className="auto-test-err-stack">
<ul style={{ listStyle: 'disc' }}>
{showMessage.stacks.map((stack, i) => (
<li key={`${stack}-${String(i)}`}>
<pre style={{ overflow: 'hidden', whiteSpace: 'pre-wrap' }}>{stack}</pre>
</li>
))}
</ul>
</div>
</div>
) : null}
<CasePipelineChart scope={scope} data={pipelineDetail} onClickNode={onClickNode} />
</Spin>
<FormModal
title={i18n.t('dop:please enter params')}
onCancel={() => updater.inParamsFormVis(false)}
onOk={(inParams: any) => {
runBuild(inParams);
}}
formRef={inParamsFormRef}
visible={inParamsFormVis}
{...inParamsProps}
// formData={editData}
/>
<BuildLog visible={logVisible} hideLog={hideLog} {...logProps} />
<SnippetDetail
pipelineDetail={pipelineDetail}
visible={snippetDetailVis}
onClose={closeSnippetDetail}
{...snippetDetailProps}
/>
<ResultViewDrawer visible={resultVis} onClose={closeResult} data={chosenTask} />
</div>
);
}
Example #21
Source File: file-tree.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
FileTree = (props: IProps) => {
const { scope } = props;
const scopeConfigData = scopeConfig[scope];
const [params, query] = routeInfoStore.useStore((s) => [s.params, s.query]);
const {
getCategoryById,
createTreeNode,
createRootTreeNode,
deleteTreeNode,
updateTreeNode,
moveTreeNode,
copyTreeNode,
getAncestors,
fuzzySearch,
} = fileTreeStore;
const [caseDetail] = autoTestStore.useStore((s) => [s.caseDetail]);
const [rootNode, setRootNode] = React.useState(null as null | TreeNode);
const [editVis, setEditVis] = React.useState(false);
const { getCaseDetail, clearCaseDetail } = autoTestStore;
const [editData, setEditData] = React.useState(undefined as any);
const editHookRef = React.useRef(null as any);
const projectInfo = projectStore.useStore((s) => s.info);
useUnmount(() => {
clearCaseDetail();
});
const getRootNode = React.useCallback(async () => {
const rootArray = await getCategoryById({ pinode: '0', scopeID: params.projectId, scope: scopeConfigData.scope });
if (Array.isArray(rootArray) && rootArray.length === 1) {
setRootNode(rootArray[0]);
} else {
// 根节点不存在,自动创建
const node = await createRootTreeNode({
name: projectInfo.name,
type: 'd',
pinode: '0',
scope: scopeConfigData.scope,
scopeID: params.projectId,
}); // name 待定义
setRootNode(node);
}
}, [createRootTreeNode, getCategoryById, params.projectId, projectInfo.name, scopeConfigData.scope]);
React.useEffect(() => {
if (projectInfo.name !== undefined) {
getRootNode();
}
}, [getRootNode, projectInfo]);
const folderActions = (node: TreeNode) => [
{
node: scopeConfigData.text.addFolder,
preset: 'newFolder',
},
{
node: scopeConfigData.text.addFile,
preset: 'customEdit',
func: async (nodeKey: string, _node: any, hook: any) => {
showFormModal();
editHookRef.current = {
hook,
nodeKey,
};
},
},
...insertWhen(node.key === rootNode?.key, [
{
node: i18n.t('dop:Paste'),
preset: 'paste',
},
]),
...insertWhen(node.key !== rootNode?.key, [
{
node: i18n.t('dop:Rename'),
preset: 'renameFolder',
},
{
node: i18n.t('Copy'),
preset: 'copy',
},
{
node: i18n.t('dop:Cut'),
preset: 'cut',
},
{
node: i18n.t('dop:Paste'),
preset: 'paste',
},
{
node: i18n.t('Delete'),
preset: 'delete',
},
// {
// node: i18n.t('dop:Add to Test Plan'),
// func: () => {},
// },
]),
];
const fileActions = [
{
node: i18n.t('Edit'),
preset: 'customEdit',
func: async (nodeKey: string, node: any, hook: any) => {
showFormModal(node);
editHookRef.current = {
hook,
nodeKey,
};
},
},
{
node: i18n.t('Copy'),
preset: 'copy',
},
{
node: i18n.t('dop:Cut'),
preset: 'cut',
},
{
node: i18n.t('Delete'),
preset: 'delete',
},
];
const titleOperations = [
{
preset: 'newFolder',
},
];
const onSelectNode = ({ inode, isLeaf }: { inode: string; isLeaf: boolean }) => {
if (isLeaf && inode && query.caseId !== inode) {
clearCaseDetail();
setTimeout(() => {
updateSearch({ caseId: inode });
}, 0);
}
};
const showFormModal = (node?: any) => {
setEditData(get(node, 'originData'));
setEditVis(true);
};
const onOk = (val: any) => {
if (editData) {
updateTreeNode({ ...editData, ...val }).then(() => {
if (editHookRef.current && editHookRef.current.hook) {
editHookRef.current.hook(editHookRef.current.nodeKey);
}
if (editData.inode === query.caseId) {
getCaseDetail({ id: editData.inode });
}
onClose();
});
} else {
createTreeNode({ ...val, type: 'f', pinode: editHookRef.current.nodeKey }).then((res: any) => {
if (editHookRef.current && editHookRef.current.hook) {
editHookRef.current.hook(editHookRef.current.nodeKey, true);
}
onClose();
const curInode = get(res, 'originData.inode');
if (curInode) {
setTimeout(() => {
updateSearch({ caseId: curInode });
}, 0);
}
});
}
};
const onClose = () => {
setEditVis(false);
editHookRef.current = null;
setEditData(undefined);
};
const searchNodes = (payload: { fuzzy: string }) => {
return fuzzySearch({ scope: scopeConfigData.scope, scopeID: params.projectId, recursive: true, ...payload });
};
const currentKey = get(caseDetail, 'type') === 'f' ? `leaf-${caseDetail.inode}` : undefined;
return (
<>
{rootNode && !isEmpty(rootNode) ? (
<TreeCategory
title={scopeConfigData.text.fileTreeTitle}
titleOperation={titleOperations}
onSelectNode={onSelectNode}
initTreeData={[{ ...rootNode }]}
currentKey={currentKey}
searchGroup={{ file: scopeConfigData.text.searchFile, folder: scopeConfigData.text.searchFolder }}
effects={{
moveNode: moveTreeNode,
loadData: getCategoryById,
deleteNode: async (key, isCurrentKeyDeleted) => {
await deleteTreeNode(key);
if (isCurrentKeyDeleted) {
updateSearch({ caseId: '' });
}
},
updateNode: updateTreeNode,
createNode: createTreeNode,
copyNode: copyTreeNode,
getAncestors,
fuzzySearch: searchNodes,
}}
actions={{
folderActions,
fileActions,
}}
/>
) : (
<EmptyHolder relative />
)}
<CaseEditForm editData={editData} visible={editVis} onOk={onOk} onClose={onClose} scope={scope} />
</>
);
}
Example #22
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
Config = ({
slot,
selectedApp,
onEditChange,
apiPrefix,
uploadTip = '',
namespace,
fileUploadType,
}: {
slot: React.ReactElement;
selectedApp: IApplication | null;
onEditChange: (v: boolean) => void;
uploadTip?: string;
apiPrefix: string;
fileUploadType: string;
namespace?: string;
}) => {
const { workspace: routeEnv } = routeInfoStore.useStore((s) => s.params);
const selectedEnv = routeEnv?.toUpperCase();
const [{ selectedType, editing }, updater, update] = useUpdate<IState>({
selectedType: SelectType.text,
editing: false,
});
const fullConfigs = configStore.useStore((s) => s.fullConfigs);
useUnmount(() => {
configStore.clearConfigs();
});
const checkEdit = (func: Function) => {
if (editing) {
Modal.confirm({
title: i18n.t('dop:The current application parameters have changed. Do you want to give up the modification?'),
onOk() {
func();
updater.editing(false);
},
});
} else {
func();
}
};
React.useEffect(() => {
onEditChange(editing);
}, [editing]);
React.useEffect(() => {
if (selectedApp) {
const namespaceParams = [
{
namespace_name: namespace || '',
decrypt: false,
},
];
namespace && configStore.getConfigs({ namespace: namespaceParams, appID: `${selectedApp.id}` }, apiPrefix);
}
}, [selectedApp, namespace, apiPrefix]);
const configData = React.useMemo(
() =>
namespace
? map(
fullConfigs[namespace]?.filter((item) =>
selectedType === SelectType.text
? item.type === ConfigTypeMap.kv.key
: item.type !== ConfigTypeMap.kv.key,
),
(item) => ({ ...item, namespace, appID: `${selectedApp?.id}` }),
)
: [],
[namespace, fullConfigs, selectedApp, selectedType],
);
return (
<div className="bg-white px-4 py-3 rounded-sm mt-2 flex-1 overflow-hidden flex flex-col">
<div className="flex items-center pb-2">
{slot}
<div className="w-px h-3 bg-default-1 mr-4" />
<SimpleTabs
tabs={map(ConfigTabs, (item) => ({ key: item.key, text: item.text }))}
value={selectedType}
onSelect={(v) => checkEdit(() => updater.selectedType(v as SelectType))}
/>
</div>
{selectedApp && namespace ? (
selectedType === SelectType.text ? (
<TextConfig
className="flex-1"
fullConfigData={fullConfigs[namespace]}
configData={configData}
onEditChange={(isEdit) => {
updater.editing(isEdit);
}}
updateConfig={(data) => {
const [curEncrypt, curData, batch] = Array.isArray(data)
? [data?.[0]?.encrypt, data, true]
: [data.encrypt, [data], false];
configStore.updateConfigs(
{
query: { namespace_name: namespace, encrypt: curEncrypt, appID: `${selectedApp.id}` },
configs: curData,
batch,
},
apiPrefix,
);
}}
deleteConfig={(data) =>
configStore.removeConfigWithoutDeploy(
{
key: data.key,
namespace_name: namespace,
appID: `${selectedApp.id}`,
},
apiPrefix,
)
}
/>
) : (
<OtherConfig
className="flex-1 overflow-auto"
key={`${selectedEnv}-${selectedApp?.id}`}
configData={configData}
fileUploadType={fileUploadType}
fullConfigData={fullConfigs[namespace]}
uploadTip={uploadTip}
addConfig={(data) =>
configStore.addConfigs(
{
query: { namespace_name: namespace, encrypt: data.encrypt, appID: `${selectedApp.id}` },
configs: [data],
},
apiPrefix,
)
}
deleteConfig={(data) =>
configStore.removeConfigWithoutDeploy(
{
key: data.key,
namespace_name: namespace,
appID: `${selectedApp.id}`,
},
apiPrefix,
)
}
/>
)
) : null}
</div>
);
}
Example #23
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
ApiMarketList = () => {
const [{ keyword, ...state }, updater, update] = useUpdate<IState>({
keyword: '',
visible: false,
scope: 'asset',
mode: 'add',
assetDetail: {},
showApplyModal: false,
showExportModal: false,
});
const [assetList, assetListPaging] = apiMarketStore.useStore((s) => [s.assetList, s.assetListPaging]);
const { scope } = routeInfoStore.useStore((s) => s.params) as { scope: API_MARKET.AssetScope };
const { getAssetList } = apiMarketStore.effects;
const { resetAssetList } = apiMarketStore.reducers;
const [isFetchList] = useLoading(apiMarketStore, ['getAssetList']);
useUnmount(() => {
resetAssetList();
});
const getList = (params: Pick<API_MARKET.QueryAssets, 'keyword' | 'pageNo' | 'scope' | 'pageSize'>) => {
getAssetList({
...commonQuery,
...params,
});
};
useDebounce(
() => {
getList({ keyword, pageNo: 1, scope });
},
200,
[keyword, scope],
);
const reload = () => {
getList({ keyword, pageNo: 1, scope });
};
const filterConfig = React.useMemo(
(): Field[] => [
{
label: '',
type: 'input',
outside: true,
key: 'keyword',
placeholder: i18n.t('default:Search by keyword'),
customProps: {
autoComplete: 'off',
},
},
],
[],
);
const handleSearch = (query: Record<string, any>) => {
updater.keyword(query.keyword);
};
const handleTableChange = ({ pageSize, current }: PaginationProps) => {
getList({ keyword, pageNo: current, pageSize, scope });
};
const handleManage = ({ assetID }: API_MARKET.Asset) => {
goTo(goTo.pages.apiManageAssetVersions, { scope, assetID });
};
const gotoVersion = ({ asset, latestVersion }: API_MARKET.AssetListItem) => {
goTo(goTo.pages.apiManageAssetDetail, { assetID: asset.assetID, scope, versionID: latestVersion.id });
};
const handleApply = (record: API_MARKET.Asset) => {
update({
showApplyModal: true,
assetDetail: record || {},
});
};
const closeModal = () => {
update({
visible: false,
showApplyModal: false,
assetDetail: {},
});
};
const showAssetModal = (assetScope: IScope, mode: IMode, record?: API_MARKET.Asset) => {
update({
scope: assetScope,
mode,
visible: true,
assetDetail: record || {},
});
};
const toggleExportModal = () => {
updater.showExportModal((prev: boolean) => !prev);
};
const columns: Array<ColumnProps<API_MARKET.AssetListItem>> = [
{
title: i18n.t('API name'),
dataIndex: ['asset', 'assetName'],
width: 240,
},
{
title: i18n.t('API description'),
dataIndex: ['asset', 'desc'],
},
{
title: 'API ID',
dataIndex: ['asset', 'assetID'],
width: 200,
},
{
title: i18n.t('Update time'),
dataIndex: ['asset', 'updatedAt'],
width: 200,
render: (date) => moment(date).format('YYYY-MM-DD HH:mm:ss'),
},
{
title: i18n.t('Creator'),
dataIndex: ['asset', 'creatorID'],
width: 160,
render: (text) => (
<Tooltip title={<UserInfo id={text} />}>
<UserInfo.RenderWithAvatar id={text} />
</Tooltip>
),
},
];
const actions: IActions<API_MARKET.AssetListItem> = {
render: (record) => {
const { permission, asset } = record;
const { manage, addVersion, hasAccess } = permission;
return [
{
title: i18n.t('Export'),
onClick: () => {
exportApi
.fetch({
versionID: record.latestVersion.id,
specProtocol,
})
.then(() => {
toggleExportModal();
});
},
},
{
title: i18n.t('manage'),
onClick: () => {
handleManage(asset);
},
show: manage,
},
{
title: i18n.t('add {name}', { name: i18n.t('Version') }),
onClick: () => {
showAssetModal('version', 'add', asset);
},
show: !!addVersion,
},
{
title: i18n.t('apply to call'),
onClick: () => {
handleApply(asset);
},
show: hasAccess,
},
];
},
};
return (
<div className="api-market-list">
<TopButtonGroup>
<Button onClick={toggleExportModal}>{i18n.t('Export Records')}</Button>
<Button
type="primary"
onClick={() => {
showAssetModal('asset', 'add');
}}
>
{i18n.t('default:Add Resource')}
</Button>
</TopButtonGroup>
<ErdaTable
rowKey="asset.assetID"
columns={columns}
dataSource={assetList}
pagination={{
...assetListPaging,
current: assetListPaging.pageNo,
}}
onRow={(record) => {
return {
onClick: () => {
gotoVersion(record);
},
};
}}
onChange={handleTableChange}
loading={isFetchList}
actions={actions}
slot={<ConfigurableFilter fieldsList={filterConfig} onFilter={handleSearch} />}
/>
<AssetModal
visible={state.visible}
scope={state.scope}
mode={state.mode}
formData={state.assetDetail as API_MARKET.Asset}
onCancel={closeModal}
afterSubmit={reload}
/>
<ApplyModal
visible={state.showApplyModal}
onCancel={closeModal}
dataSource={state.assetDetail as API_MARKET.Asset}
/>
<ExportRecord visible={state.showExportModal} onCancel={toggleExportModal} />
</div>
);
}
Example #24
Source File: project-list.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
ProjectList = () => {
const [list, paging] = projectStore.useStore((s) => [s.list, s.paging]);
const { getProjectList } = projectStore.effects;
const { clearProjectList } = projectStore.reducers;
const { pageNo, pageSize, total } = paging;
const [loadingList] = useLoading(projectStore, ['getProjectList']);
const orgID = orgStore.getState((s) => s.currentOrg.id);
const [searchObj, setSearchObj] = React.useState<IState>({
pageNo: 1,
pageSize,
query: '',
orderBy: 'activeTime',
asc: false,
});
const [visible, setVisible] = React.useState(false);
const [isClickExport, setIsClickExport] = React.useState(false);
useUnmount(() => {
clearProjectList();
});
const getColumnOrder = (key?: string) => {
if (key) {
return searchObj.orderBy === key ? (searchObj.asc ? 'ascend' : 'descend') : undefined;
}
return undefined;
};
const getColumns = () => {
const columns: Array<ColumnProps<PROJECT.Detail>> = [
{
title: i18n.t('project'),
dataIndex: 'displayName',
key: 'displayName',
icon: (text: string, record: PROJECT.Detail) => projectTypeMap[record.type] || projectTypeMap.DevOps,
subTitle: (text: string, record: PROJECT.Detail) => record.desc,
ellipsis: {
showTitle: false,
},
},
{
title: i18n.t('cmp:App/Member statistics'),
dataIndex: 'stats',
key: 'countApplications',
render: (stats: PROJECT.ProjectStats) => `${stats.countApplications} / ${stats.countMembers}`,
},
{
title: i18n.t('CPU quota'),
dataIndex: 'cpuQuota',
key: 'cpuQuota',
icon: <ErdaIcon type="CPU" />,
sorter: true,
sortOrder: getColumnOrder('cpuQuota'),
render: (text: string) => `${(+text || 0).toFixed(2)} Core`,
},
{
title: i18n.t('Memory quota'),
dataIndex: 'memQuota',
key: 'memQuota',
icon: <ErdaIcon type="GPU" />,
sorter: true,
sortOrder: getColumnOrder('memQuota'),
render: (text: string) => `${(+text || 0).toFixed(2)} GiB`,
},
{
title: i18n.t('Last active time'),
dataIndex: 'activeTime',
key: 'activeTime',
sorter: true,
sortOrder: getColumnOrder('activeTime'),
render: (text) => (text ? fromNow(text) : i18n.t('None')),
},
];
return columns;
};
const ProjectActions: IActions<PROJECT.Detail> = {
width: 120,
render: (record: PROJECT.Detail) => {
const { exportProject, goToEfficiencyMeasure } = {
exportProject: {
title: i18n.t('Export'),
onClick: () => {
exportProjectTemplate.fetch({ orgID, projectID: record.id }).then(() => {
message
.success(i18n.t('dop:The export task has been created, please check the progress in the record'), 4)
.then(() => {
importExportFileRecord.fetch({
orgID,
types: ['projectTemplateExport'],
pageNo: 1,
pageSize: searchObj.pageSize,
});
});
setIsClickExport(true);
setVisible(true);
});
},
},
goToEfficiencyMeasure: {
title: i18n.t('dop:Efficiency'),
onClick: () => {
goTo(`./${record.id}/measure/task`);
},
},
};
return record.type === 'MSP' ? [exportProject] : [exportProject, goToEfficiencyMeasure];
},
};
const onSearch = (query: string) => {
setSearchObj((prev) => ({ ...prev, query, pageNo: 1 }));
};
const getList = React.useCallback(
(_search?: IState) => {
getProjectList({ ...searchObj, ..._search });
},
[searchObj, getProjectList],
);
React.useEffect(() => {
getList(searchObj);
}, [searchObj, getList]);
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
setSearchObj((prev) => ({
...prev,
pageNo: pagination.current,
pageSize: pagination.pageSize,
orderBy: sorter?.field && sorter?.order ? sorter.field : undefined,
asc: sorter?.order ? sorter.order === 'ascend' : undefined,
}));
};
const addDropdownMenu = (
<Menu className="bg-default">
<Menu.Item onClick={() => goTo('./createProject')} key={'app'} className="bg-default hover:bg-white-08">
<div className="flex-h-center text-white-9">
<ErdaIcon type="plus" size={16} className="mr-1" />
{i18n.t('Add')}
</div>
</Menu.Item>
<Menu.Item onClick={() => goTo('./importProject')} key={'file'} className="bg-default hover:bg-white-08">
<div className="flex-h-center text-white-9">
<ErdaIcon type="upload" size={16} className="mr-1" />
{i18n.t('Import')}
</div>
</Menu.Item>
</Menu>
);
return (
<div className="org-project-list">
<Spin spinning={loadingList}>
<TopButtonGroup className="flex">
<Button
className="text-default-8 bg-default-06 font-medium"
onClick={() => {
setVisible(true);
}}
>
{i18n.t('Import/Export Records')}
</Button>
<Dropdown overlay={addDropdownMenu} trigger={['click']}>
<Button type={'primary'} className="bg-default flex-h-center">
{i18n.t('Add')}
<ErdaIcon type="caret-down" size="18" color="currentColor" className="ml-1 text-white-400" />
</Button>
</Dropdown>
</TopButtonGroup>
<ErdaAlert
showOnceKey="project-list"
message={i18n.t(
'Support project management such as creation and deletion, as well as project O&M of project members, quotas and more',
)}
/>
<ErdaTable
rowKey="id"
dataSource={list}
columns={getColumns()}
rowClassName={() => 'cursor-pointer'}
actions={ProjectActions}
slot={
<Filter
config={[
{
type: Input,
name: 'projectName',
customProps: {
placeholder: i18n.t('Search by project name'),
style: { width: 200 },
},
},
]}
onFilter={({ projectName }: { projectName: string }) => onSearch(projectName)}
/>
}
onRow={(record: any) => {
return {
onClick: () => {
goTo(`./${record.id}/info`);
},
};
}}
pagination={{
current: pageNo,
pageSize,
total,
showSizeChanger: true,
pageSizeOptions: PAGINATION.pageSizeOptions,
}}
onChange={handleTableChange}
/>
</Spin>
<OperationProjectRecords
visible={visible}
setVisible={setVisible}
isClickExport={isClickExport}
setIsClickExport={setIsClickExport}
/>
</div>
);
}
Example #25
Source File: issue-field-manage.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
IssueFieldManage = () => {
const { id: orgID } = orgStore.useStore((s) => s.currentOrg);
const tableData = issueFieldStore.useStore((s) => s.fieldList);
const { getFieldsByIssue, deleteFieldItem, getSpecialFieldOptions } = issueFieldStore.effects;
const { clearFieldList } = issueFieldStore.reducers;
const [{ filterData, modalVisible, formData, taskSpecialField, bugSpecialField }, updater, update] = useUpdate({
filterData: {} as Obj,
modalVisible: false,
formData: {} as ISSUE_FIELD.IFiledItem,
taskSpecialField: { ...TASK_SP_FIELD },
bugSpecialField: { ...BUG_SP_FIELD },
});
const getSpecialField = React.useCallback(() => {
getSpecialFieldOptions({ orgID, issueType: 'TASK' }).then((res) => {
updater.taskSpecialField({ ...TASK_SP_FIELD, enumeratedValues: res });
});
getSpecialFieldOptions({ orgID, issueType: 'BUG' }).then((res) => {
updater.bugSpecialField({ ...BUG_SP_FIELD, enumeratedValues: res });
});
}, [getSpecialFieldOptions, orgID, updater]);
useMount(() => {
getSpecialField();
});
const tableList = React.useMemo(() => {
const tempList = [taskSpecialField, bugSpecialField]?.filter(({ propertyName }) => {
return filterData.propertyName === undefined || propertyName?.indexOf(filterData?.propertyName) !== -1;
});
return [...tempList, ...tableData];
}, [bugSpecialField, filterData, tableData, taskSpecialField]);
const [isFetching] = useLoading(issueFieldStore, ['getFieldsByIssue']);
useUnmount(() => {
clearFieldList();
});
const fieldsList: object[] = React.useMemo(
() => [
{
label: i18n.t('dop:Field name'),
name: 'propertyName',
},
],
[],
);
const onDeleteField = React.useCallback(
async ({ propertyID, relatedIssue }) => {
if (!isEmpty(relatedIssue)) {
message.warning(
i18n.t(
'dop:This field has been referenced. If you want to delete it, please remove the reference in the corresponding issue type first.',
),
);
return;
}
await deleteFieldItem({ propertyID });
getFieldsByIssue({ ...filterData, propertyIssueType: 'COMMON', orgID });
},
[deleteFieldItem, filterData, getFieldsByIssue, orgID],
);
const columns = React.useMemo(
() => [
{
key: 'propertyName',
title: i18n.t('dop:Field name'),
width: '200',
dataIndex: 'propertyName',
},
{
key: 'required',
title: i18n.t('Required'),
dataIndex: 'required',
render: (value: boolean) => (String(value) === 'true' ? i18n.t('common:Yes') : i18n.t('common:No')),
},
{
key: 'propertyType',
title: i18n.t('Type'),
width: '200',
dataIndex: 'propertyType',
render: (t: string) => <IssueIcon type={t} withName />,
},
{
key: 'relatedIssue',
title: i18n.t('dop:Related issue type'),
width: '250',
dataIndex: 'relatedIssue',
render: (types: string[]) => {
const fullTags = () =>
map(types, (t) => (
<span key={t} className="tag-default">
{t}
</span>
));
return (
<Tooltip title={fullTags()} placement="top" overlayClassName="tags-tooltip">
{fullTags()}
</Tooltip>
);
},
},
],
[],
);
const tableAction: IActions<ISSUE_FIELD.IFiledItem> = {
render: (record) => {
return [
{
title: i18n.t('Edit'),
onClick: () => {
const { enumeratedValues = [] } = record;
const optionList = enumeratedValues
? [...enumeratedValues, { name: '', index: enumeratedValues.length }]
: [{ name: '', index: 0 }];
updater.formData({
...record,
required: record.required ? 'true' : 'false',
enumeratedValues: optionList,
});
setTimeout(() => {
updater.modalVisible(true);
});
},
},
{
title: i18n.t('Delete'),
disabled: record?.isSpecialField,
onClick: () => {
Modal.confirm({
title: `${i18n.t('common:confirm to delete')}?`,
onOk: () => {
onDeleteField(record);
},
});
},
},
];
},
};
const filterField = [
{
type: Input,
name: 'propertyName',
customProps: {
placeholder: i18n.t('filter by {name}', { name: i18n.t('dop:Field name').toLowerCase() }),
},
},
];
const onFilter = (query: Obj) => {
updater.filterData(query);
getFieldsByIssue({ ...query, propertyIssueType: 'COMMON', orgID });
};
const onClose = React.useCallback(() => {
update({
formData: {} as ISSUE_FIELD.IFiledItem,
modalVisible: false,
});
}, [update]);
const onOk = React.useCallback(() => {
formData?.isSpecialField && getSpecialField();
onClose();
getFieldsByIssue({ ...filterData, propertyIssueType: 'COMMON', orgID });
}, [filterData, formData, getFieldsByIssue, getSpecialField, onClose, orgID]);
const readonlyForm = (
<div style={{ overflow: 'hidden' }}>
<TopButtonGroup>
<WithAuth pass tipProps={{ placement: 'bottom' }}>
<Button
type="primary"
onClick={() => {
updater.modalVisible(true);
}}
>
{i18n.t('Add')}
</Button>
</WithAuth>
</TopButtonGroup>
<Table
slot={<Filter config={filterField} onFilter={onFilter} connectUrlSearch />}
actions={tableAction}
loading={isFetching}
rowKey="propertyName"
dataSource={tableList}
onReload={() => {
getFieldsByIssue({ ...filterData, propertyIssueType: 'COMMON', orgID });
}}
columns={columns}
pagination={false}
scroll={{ x: '100%' }}
/>
<IssueFieldModal visible={modalVisible} formData={formData} onOk={onOk} closeModal={onClose} />
</div>
);
return (
<SectionInfoEdit
hasAuth={false}
data={formData}
readonlyForm={readonlyForm}
fieldsList={fieldsList}
updateInfo={getFieldsByIssue}
name={i18n.t('dop:Issue field')}
desc={i18n.t('dop:Custom fields for the organization, to meet the needs of more scenarios')}
/>
);
}
Example #26
Source File: app-certificate-reference.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
AppCertificateReference = () => {
const { appId } = routeInfoStore.useStore((s) => s.params);
const { pushToConfig, getList: getRefList } = certRefStore.effects;
useUnmount(() => {
certificateStore.reducers.clearList();
});
const [state, updater, update] = useUpdate({
visible: false,
editData: {},
});
const getColumns = ({ deleteItem }: any) => {
const columns = [
{
title: i18n.t('dop:certificate name'),
dataIndex: 'name',
},
{
title: i18n.t('dop:certificate type'),
dataIndex: 'type',
render: (v: string) => CertMap[v] && CertMap[v].name,
},
{
title: i18n.t('Creation time'),
dataIndex: 'createdAt',
width: 240,
render: (text: string) => formatTime(text, 'YYYY-MM-DD HH:mm:ss'),
},
{
title: i18n.t('dop:approval status'),
dataIndex: 'status',
width: 200,
render: (text: string) => approvalStatus[text],
},
{
title: i18n.t('Operations'),
dataIndex: 'op',
width: 140,
render: (_v: any, record: APP_SETTING.CertRef) => {
return (
<div className="table-operations">
{record.status === 'approved' && (
<span
className="table-operations-btn"
onClick={() =>
update({
editData: {
enable: false,
certificateType: record.type,
...record.pushConfig,
certificateId: record.certificateId,
appId: record.appId,
},
visible: true,
})
}
>
{i18n.t('Configuration')}
</span>
)}
{record.status !== 'pending' && (
<Popconfirm
title={`${i18n.t('common:confirm to delete')}?`}
onConfirm={() =>
deleteItem({ certificateId: record.certificateId, appId: record.appId }).then(() => {
certRefStore.effects.getList({ appId });
})
}
>
<span className="table-operations-btn">{i18n.t('Remove')}</span>
</Popconfirm>
)}
</div>
);
},
},
];
return columns;
};
const getFieldsList = () => {
const fieldsList = [
{
name: 'appId',
itemProps: {
type: 'hidden',
},
initialValue: +appId,
},
{
label: i18n.t('dop:choose certificate'),
name: 'certificateId',
type: 'custom',
getComp: () => {
const getData = (q: any) => {
const { q: searchKey, ...qRest } = q;
return getCertificateList({ ...qRest, name: searchKey, appId }).then((res: any) => res.data);
};
return (
<LoadMoreSelector
getData={getData}
placeholder={i18n.t('please choose the {name}', { name: i18n.t('Certificate') })}
/>
);
},
},
];
return fieldsList;
};
const configFields = [
{
name: 'appId',
itemProps: {
type: 'hidden',
},
},
{
name: 'certificateId',
itemProps: {
type: 'hidden',
},
},
{
name: 'certificateType',
itemProps: {
type: 'hidden',
},
},
{
label: i18n.t('dop:push to variable config'),
name: 'enable',
type: 'switch',
itemProps: {
// TODO: 如何在fields变化时触发Form重渲染?
onChange: (v: boolean) => updater.editData((prev: any) => ({ ...prev, enable: v })),
},
},
...insertWhen(state.editData.enable, [
{
label: i18n.t('env'),
name: 'envs',
type: 'select',
options: WORKSPACE_LIST.map((env) => ({ name: env, value: env })),
itemProps: {
mode: 'multiple',
},
},
...(get(CertMap, `${[state.editData.certificateType]}.pushConfigField`) || []),
]),
];
const onOk = (data: any) => {
onCancel();
pushToConfig(data)
.then(() => {
getRefList({ appId });
})
.catch(() => {
// TODO 2020/4/20 请求失败后,现有FormModal会充值表单域(是否合理),导致显示逻辑不正确,需重置editData
updater.editData({});
});
};
const onCancel = () => {
update({
editData: {},
visible: false,
});
};
return (
<>
<FormModal
title={i18n.t('dop:push config')}
visible={state.visible}
formData={state.editData}
fieldsList={configFields}
onOk={onOk}
onCancel={onCancel}
/>
<CRUDTable.StoreTable<APP_SETTING.LibRef>
getColumns={getColumns as any}
getFieldsList={getFieldsList}
extraQuery={{ appId }}
store={certRefStore}
handleFormSubmit={(submitData: APP_SETTING.LibRef, { addItem, updateItem, isEdit }) => {
const data = { ...submitData, appId: +appId };
if (isEdit) {
return updateItem(data);
} else {
return addItem(data);
}
}}
tableProps={{
onReload: (pageNo: number, pageSize: number) => certificateStore.effects.getList({ pageNo, pageSize, appId }),
}}
/>
</>
);
}
Example #27
Source File: machine-manage.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
MachineManage = () => {
const [{ drawerVisible, activeMachine }, updater, update] = useUpdate({
drawerVisible: false,
activeMachine: {} as ORG_MACHINE.IMachine,
});
const [{ siteName, clusterName }] = routeInfoStore.useStore((s) => [s.query]);
const { id } = routeInfoStore.useStore((s) => s.params);
const [isFetching] = useLoading(machineManageStore, ['getGroupInfos']);
const { getGroupInfos, clearGroupInfos, offlineMachine } = machineManageStore;
const [groupInfos] = machineManageStore.useStore((s) => [s.groupInfos]);
useUnmount(() => {
clearGroupInfos();
});
const getMachineList = React.useCallback(() => {
getGroupInfos({
groups: ['cluster'],
clusters: [{ clusterName }],
filters: [
{
key: 'edge_site',
values: [siteName],
},
],
});
}, [clusterName, getGroupInfos, siteName]);
React.useEffect(() => {
getMachineList();
}, [getMachineList]);
const tableList = React.useMemo(() => {
const { machines } = groupInfos[0] || {};
return map(machines, (m) => {
return m;
});
}, [groupInfos]);
const showMonitor = (record: ORG_MACHINE.IMachine) => {
update({
drawerVisible: true,
activeMachine: record,
});
};
const offlineHandle = (record: ORG_MACHINE.IMachine) => {
offlineMachine({
siteIP: record.ip,
id: +id,
}).then(() => {
getMachineList();
});
};
const columns: Array<ColumnProps<ORG_MACHINE.IMachine>> = [
{
title: 'IP',
width: 160,
dataIndex: 'ip',
},
{
title: i18n.t('Number of instances'),
dataIndex: 'tasks',
width: 176,
sorter: (a: ORG_MACHINE.IMachine, b: ORG_MACHINE.IMachine) => Number(a.tasks) - Number(b.tasks),
},
{
title: 'CPU',
width: 120,
dataIndex: 'cpuAllocatable',
render: (_, data: ORG_MACHINE.IMachine) => {
const { cpuAllocatable, cpuUsage, cpuRequest, cpuUsagePercent, cpuDispPercent } = data;
return (
<div className="percent-row">
{DoubleProgressItem({
usedPercent: Math.ceil(cpuUsagePercent),
requestPercent: Math.ceil(cpuDispPercent),
usage: cpuUsage,
request: cpuRequest,
total: cpuAllocatable,
unit: i18n.t('core'),
})}
</div>
);
},
},
{
title: i18n.t('memory'),
width: 120,
dataIndex: 'memProportion',
render: (_, data: ORG_MACHINE.IMachine) => {
const { memAllocatable, memUsage, memRequest, memUsagePercent, memDispPercent } = data;
return (
<div className="percent-row">
{DoubleProgressItem({
usedPercent: Math.ceil(memUsagePercent),
requestPercent: Math.ceil(memDispPercent),
usage: memUsage,
request: memRequest,
total: memAllocatable,
unitType: 'STORAGE',
})}
</div>
);
},
},
{
title: <span className="main-title">{i18n.t('Label')} </span>,
dataIndex: 'labels',
className: 'machine-labels',
render: (value: string) => {
const keyArray = value?.split(',') || [];
return (
<TagsRow
labels={keyArray.map((label) => {
return { label };
})}
/>
);
},
},
{
title: i18n.t('operations'),
dataIndex: 'id',
key: 'operation',
width: 180,
fixed: 'right',
render: (_id: string, record: ORG_MACHINE.IMachine) => {
return (
<TableActions>
<span className="table-operations-btn" onClick={() => showMonitor(record)}>
{i18n.t('Machine Overview')}
</span>
<PopConfirm title={`${i18n.t('confirm to go offline')}?`} onConfirm={() => offlineHandle(record)}>
<span className="table-operations-btn">{i18n.t('msp:Offline')}</span>
</PopConfirm>
</TableActions>
);
},
},
];
const onCloseDrawer = React.useCallback(() => {
updater.drawerVisible(false);
}, [updater]);
return (
<div className="machine-table">
<Breadcrumb
separator={<ErdaIcon className="align-middle" type="right" size="14px" />}
className="path-breadcrumb mb-2"
>
<Breadcrumb.Item className="hover-active" onClick={() => goTo(goTo.pages.ecpResource)}>
{siteName}
</Breadcrumb.Item>
<Breadcrumb.Item>{i18n.t('cmp:node list')}</Breadcrumb.Item>
</Breadcrumb>
<Table
className="machine-list-table"
loading={isFetching}
rowKey="ip"
pagination={false}
bordered
columns={columns}
dataSource={tableList}
scroll={{ x: 1300 }}
/>
<Drawer
width="80%"
visible={drawerVisible}
title={i18n.t('Machine Overview')}
destroyOnClose
onClose={onCloseDrawer}
>
<MachineDetail type="insight" machineDetail={activeMachine} />
</Drawer>
</div>
);
}
Example #28
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
ClusterDashboard = () => {
const [filterGroup, groupInfos, unGroupInfo, clusterList, selectedGroups] = clusterDashboardStore.useStore((s) => [
s.filterGroup,
s.groupInfos,
s.unGroupInfo,
s.clusterList,
s.selectedGroups,
]);
const { getFilterTypes, getGroupInfos } = clusterDashboardStore.effects;
const { setSelectedGroups, clearClusterList } = clusterDashboardStore.reducers;
const [loading] = useLoading(clusterDashboardStore, ['getGroupInfos']);
const [groupContainerWidthHolder, groupContainerWidth] = useComponentWidth();
const [machineContainerWidthHolder, machineContainerWidth] = useComponentWidth();
const [groupGridClass, setGroupGridClass] = useState();
const [machineGridClass, setMachineGridClass] = useState('machine-list-ct-g4');
const [selectedFilters, setSelectedFilters] = useState();
const [selectedColour, setSelectedColour] = useState('load');
const [activeMachine, setActiveMachine] = useState({ ip: '', clusterName: '' });
const [activeMachineTab, setActiveMachineTab] = useState();
const [activeGroup, setActiveGroup] = useState('');
const [groupMap, setGroupMap] = useState({});
const [allMachines, setAllMachines] = useState();
const [activeMachineList, setActiveMachineList] = useState();
const [colorMark, setColorMark] = React.useState('load');
const [isClickState, setIsClickState] = React.useState(false);
const [isMounted, setIsMounted] = React.useState(false);
useMount(async () => {
await getFilterTypes();
setIsMounted(true);
});
useUnmount(() => {
setSelectedGroups([]);
clearClusterList();
});
useEffect(() => {
if (!isEmpty(clusterList)) {
getGroupInfos({
groups: selectedGroups,
clusters: map(clusterList, (cl: any) => ({ clusterName: cl.name })),
filters: getFilters(selectedFilters),
});
}
}, [clusterList, getGroupInfos, selectedFilters, selectedGroups]);
const unitGroups = React.useMemo(() => {
const tempList = [...selectedGroups, '', ''].slice(0, 2);
return tempList.map((item) => UNIT_MAP[item] || '');
}, [selectedGroups]);
useEffect(() => {
const groupInfoMap = reduce(
groupInfos,
(result, item) => {
let subResult = {};
if (item.groups) {
subResult = reduce(
item.groups,
(acc, subItem) => ({
...acc,
[`${item.name + unitGroups[0]}-${subItem.name + unitGroups[1]}`]: subItem,
}),
{},
);
}
return {
...result,
...subResult,
[item.name + unitGroups[0]]: item,
};
},
{},
);
setGroupMap(groupInfoMap);
}, [groupInfos, unitGroups]);
useEffect(() => {
if (isEmpty(groupInfos)) {
setAllMachines(unGroupInfo.machines || []);
} else {
let results: any[] = [];
const getAllMachines = (groups: ORG_DASHBOARD.IGroupInfo[]) => {
forEach(groups, ({ groups: subGroups, machines }: ORG_DASHBOARD.IGroupInfo) => {
if (isEmpty(subGroups)) {
results = [...results, ...(machines || [])];
} else {
getAllMachines(subGroups || []);
}
});
};
getAllMachines(groupInfos);
setAllMachines(results);
}
}, [unGroupInfo.machines, groupInfos]);
useEffect(() => {
if (!activeGroup) {
return setActiveMachineList(allMachines);
}
let machineList: any[] = [];
if (isEmpty(groupMap[activeGroup])) {
setActiveMachineList(machineList);
return;
}
const { groups, machines } = groupMap[activeGroup];
if (!isEmpty(groups)) {
machineList = reduce(groups, (result, { machines: subMachines }) => [...result, ...subMachines], []);
} else {
machineList = machines || [];
}
setActiveMachineList(machineList);
}, [activeGroup, allMachines, groupMap]);
useEffect(() => {
if (groupContainerWidth !== Infinity) {
setGroupGridClass(getGroupGridClass(groupContainerWidth as number));
}
}, [groupContainerWidth]);
useEffect(() => {
if (machineContainerWidth !== Infinity) {
setMachineGridClass(getMachineGridClass(machineContainerWidth as number));
}
}, [machineContainerWidth]);
const getFilters = (filters: string[]) => {
const filterMap = {};
forEach(filters, (item) => {
const [key, value] = item.split(':');
filterMap[key] = filterMap[key] ? [...filterMap[key], value] : [value];
});
return map(filterMap, (values, key) => ({ key, values }));
};
const getMachineColourValue = ({
cpuUsage,
cpuAllocatable,
memUsage,
memAllocatable,
diskUsage,
diskTotal,
cpuRequest,
memRequest,
load5,
}: any) => {
const getPercent = (used: number, total: number) => round((used / total) * 100, 2);
const machineColourNameMap = {
load: {
name: COLOUR_MAP.load,
value: load5,
},
cpu: {
name: COLOUR_MAP.cpu,
value: getPercent(cpuUsage, cpuAllocatable),
},
mem: {
name: COLOUR_MAP.mem,
value: getPercent(memUsage, memAllocatable),
},
disk: {
name: COLOUR_MAP.disk,
value: getPercent(diskUsage, diskTotal),
},
scheduledCPU: {
name: COLOUR_MAP.scheduledCPU,
value: getPercent(cpuRequest, cpuAllocatable),
},
scheduledMEM: {
name: COLOUR_MAP.scheduledMEM,
value: getPercent(memRequest, memAllocatable),
},
};
return machineColourNameMap[selectedColour];
};
const getMachineColourClass = (machineInfo: Partial<ORG_MACHINE.IMachine>) =>
getDegreeColourClass(getMachineColourValue(machineInfo).value, selectedColour);
const handleChangeGroups = (groups: string[]) => {
if (groups.length > 2) {
message.warning(i18n.t('cmp:up to 2 optional groups'));
return;
}
setActiveGroup('');
setSelectedGroups(groups);
};
const handleChangeFilters = (filters: string[]) => {
setSelectedFilters(filters);
};
const handleActiveMachine = (record: any, key?: string) => {
setActiveMachine(record);
setActiveMachineTab(key);
};
const getMachineGroupContent = (item: ORG_DASHBOARD.IGroupInfo) => {
if (isEmpty(item)) return null;
const { name, displayName, machines, metric, groups, clusterStatus }: ORG_DASHBOARD.IGroupInfo = item;
const groupName = displayName || name;
const activeGroupItem = clusterList.find((c) => c.name === name);
const activeGroupDisplayName = activeGroupItem?.displayName || activeGroupItem?.name;
const {
machines: machineNum,
cpuUsage,
cpuAllocatable,
memUsage,
memAllocatable,
diskUsage,
diskTotal,
} = metric || {};
const isClusterGroup = selectedGroups?.[0] === 'cluster';
return (
<Holder when={isEmpty(machines) && isEmpty(groups)}>
<IF check={selectedGroups.length}>
<div className="group-header flex justify-between items-center">
<h3
className={`group-title ${isClusterGroup ? 'cluster-group' : ''}`}
onClick={() => {
isClusterGroup && goTo(goTo.pages.cmpClustersDetail, { clusterName: name });
}}
>
{activeGroupDisplayName || groupName + unitGroups[0]}
</h3>
<IF check={activeGroup}>
<span className="group-unactived-op hover-active">
<CustomIcon type="shink" />
</span>
<IF.ELSE />
<span className="group-actived-op hover-active">
<CustomIcon type="grow" />
</span>
</IF>
</div>
</IF>
<p className="group-info">
<span>
{i18n.t('Machines')}:{machineNum}
</span>
<span>CPU:{round((cpuUsage / cpuAllocatable) * 100, 2)}%</span>
<span>
{i18n.t('memory')}:{round((memUsage / memAllocatable) * 100, 2)}%
</span>
<span>
{i18n.t('Disk')}:{round((diskUsage / diskTotal) * 100, 2)}%
</span>
</p>
<IF check={!isEmpty(groups)}>
<SubMachineGroup
groups={groups}
isClusterGroup={selectedGroups?.[1] === 'cluster'}
unitGroups={unitGroups}
groupName={groupName}
activeMachine={activeMachine}
setActiveMachine={setActiveMachine}
setActiveGroup={setActiveGroup}
getMachineColourClass={getMachineColourClass}
getMachineColourValue={getMachineColourValue}
/>
<IF.ELSE />
<div
className={classnames({
'machine-list-ct': true,
[`${machineGridClass}`]: true,
'machine-actived': !!activeMachine.ip,
})}
>
{map(machines, ({ ip, clusterName, ...rest }) => {
const { name: colourName, value: colourValue } = getMachineColourValue(rest);
return (
<Tooltip
placement="bottom"
title={`${ip} (${colourName}: ${colourValue}%)`}
key={`${clusterName}-${ip}`}
>
<div
className={classnames({
'machine-item': true,
'hover-active': true,
[`${getMachineColourClass(rest)}`]: true,
active: ip === activeMachine.ip && clusterName === activeMachine.clusterName,
})}
onClick={(e) => {
e.stopPropagation();
setActiveMachine({ ip, clusterName, ...rest });
}}
>
<span
className="cancel-active"
onClick={(e) => {
e.stopPropagation();
setActiveMachine({});
}}
>
<CustomIcon type="gb" />
</span>
</div>
</Tooltip>
);
})}
</div>
</IF>
</Holder>
);
};
const getMachineGroupWrapper = (item?: ORG_DASHBOARD.IGroupInfo, gridClass?: any) => {
return (
<div
key={item && item.name ? item.name : ''}
className={classnames({
'machine-group': true,
'actived-machine-group': !!activeGroup,
'no-machine-group': isEmpty(selectedGroups) || isEmpty(item),
})}
onClick={(e) => {
if (isEmpty(selectedGroups) || isEmpty(item)) return;
e.stopPropagation();
setIsClickState(!isClickState);
setActiveGroup(activeGroup ? '' : (item && item.name ? item.name : '') + unitGroups[0]);
}}
>
<Holder when={isEmpty(item) || !gridClass}>{getMachineGroupContent(item)}</Holder>
{machineContainerWidthHolder}
</div>
);
};
const Bottom = React.useMemo(
() => (
<IF check={activeMachine.ip}>
<div className="content-title mb-2">{activeMachine.ip}</div>
<MachineTabs activeMachine={activeMachine} activeMachineTab={activeMachineTab} />
<IF.ELSE />
<GroupTabs
activedGroup={activeGroup}
machineList={activeMachineList}
onActiveMachine={handleActiveMachine}
isClickState={isClickState}
/>
</IF>
),
[activeMachine, activeMachineList, activeMachineTab, activeGroup, isClickState],
);
const handleOptionMouseEnter = (value: string, e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
setColorMark(value);
};
const handleSelectedColour = (val: string) => {
setSelectedColour(val);
setColorMark(val);
};
return (
<>
{groupContainerWidthHolder}
<Choose>
<When condition={!clusterList.length && !loading && isMounted}>
<div className="flex flex-col justify-center items-center h-full">
<div className="font-medium text-2xl mb-2">{i18n.t('cmp:quick start')}</div>
<div className="text-desc">
{interpolationComp(
i18n.t(
'cmp:no cluster currently exists, you can click <CreateClusterLink />, you can also view <DocumentationHref /> to learn more',
),
{
CreateClusterLink: (
<Link to={`${goTo.resolve.cmpClusters()}?autoOpen=true`}>{i18n.t('cmp:create cluster')}</Link>
),
DocumentationHref: (
<a href={DOC_CMP_CLUSTER_CREATE} target="__blank">
{i18n.t('documentation')}
</a>
),
},
)}
</div>
<img className="w-80 h-80" src={noClusterPng} />
</div>
</When>
<Otherwise>
<div className="cluster-dashboard-top mb-4">
<div className="filter-group-ct mb-4">
<Row gutter={20}>
<Col span={8} className="filter-item flex justify-between items-center">
<div className="filter-item-label">{i18n.t('Group')}</div>
<Select
value={selectedGroups}
placeholder={firstCharToUpper(i18n.t('cmp:no more than 2 groups'))}
className="filter-item-content"
style={{ width: '100%' }}
showArrow
allowClear
mode="multiple"
onChange={handleChangeGroups}
>
{map(
filter(filterGroup, ({ key: group }) => ['cpus', 'mem', 'cluster'].includes(group)),
({ key, name }) => (
<Option key={key}>{name}</Option>
),
)}
</Select>
</Col>
<Col span={8} className="filter-item flex justify-between items-center">
<div className="filter-item-label">{i18n.t('Filter')}</div>
<TreeSelect
className="filter-item-content"
style={{ width: '100%' }}
value={selectedFilters}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
allowClear
multiple
// treeDefaultExpandAll
placeholder={firstCharToUpper(i18n.t('cmp:input to search'))}
onChange={handleChangeFilters}
>
{map(filterGroup, ({ name, key, values, unit, prefix }: ORG_DASHBOARD.IFilterType) => (
<TreeNode className="filter-item-node" title={`${name}(${key})`} key={key} value={key} disabled>
{map(values, (subItem: any) => (
<TreeNode
value={`${key}:${subItem}`}
title={`${prefix ? `${prefix}:` : ''}${subItem} ${unit || ''}`}
key={`${key}-${subItem}`}
/>
))}
</TreeNode>
))}
</TreeSelect>
</Col>
<Col span={8} className="filter-item flex justify-between items-center">
<div className="filter-item-label">{i18n.t('Colour')}</div>
<Select
className="filter-item-content"
style={{ width: '100%' }}
value={selectedColour}
onChange={handleSelectedColour}
dropdownRender={(menu) => {
return (
<div className="colour-select-dropdown">
<div className="menu">{menu}</div>
<div className="comments">
<ul className="colour-comment-list">
{map(DEGREE_COLOUR_MAP[colorMark], (value, color) => (
<li className="colour-comment-item flex justify-between items-center" key={color}>
<span className="colour-comment-value">{value.text}</span>
<div className={`color-block ${color}`} />
</li>
))}
</ul>
</div>
</div>
);
}}
>
{map(COLOUR_MAP, (name, key) => (
<Option value={key} key={key}>
<div
onMouseEnter={(e) => {
handleOptionMouseEnter(key, e);
}}
>
{name}
</div>
</Option>
))}
</Select>
</Col>
</Row>
</div>
<Spin spinning={!groupGridClass || loading}>
<Choose>
<When condition={isEmpty(selectedGroups) || isEmpty(groupInfos)}>
<div className="machine-group-ct machine-group-ct-g1">
{getMachineGroupWrapper(unGroupInfo, groupGridClass)}
</div>
</When>
<Otherwise>
<div className={`machine-group-ct ${activeGroup ? 'machine-group-ct-g1' : groupGridClass}`}>
<Choose>
<When condition={!!activeGroup}>
{getMachineGroupWrapper(groupMap[activeGroup], groupGridClass)}
</When>
<When condition={!groupGridClass}>{getMachineGroupWrapper()}</When>
<Otherwise>{map(groupInfos, (item) => getMachineGroupWrapper(item, true))}</Otherwise>
</Choose>
</div>
</Otherwise>
</Choose>
</Spin>
</div>
<div className="cluster-dashboard-bottom">{Bottom}</div>
</Otherwise>
</Choose>
</>
);
}
Example #29
Source File: custom-label.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
CustomLabel = React.forwardRef(
({ value = emptyArr, onChange = emptyFun, labelName = i18n.t('dop:Add-label') }: IProps, ref) => {
const [labels, setLabels] = React.useState([] as string[]);
const [showInput, setShowInput] = React.useState(false);
const [inputVal, setInputVal] = React.useState(undefined);
const inputRef = React.useRef(null);
React.useEffect(() => {
const l = isEmpty(value) ? [] : isString(value) ? value.split(',') : value;
setLabels(l);
}, [value]);
useUnmount(() => {
setInputVal(undefined);
setShowInput(false);
setLabels([]);
});
React.useEffect(() => {
const curRef = inputRef && (inputRef.current as any);
if (showInput && curRef) {
curRef.focus();
}
}, [inputRef, showInput]);
const deleteLabel = (label: string) => {
const labelArr = [...labels];
remove(labelArr, (item) => item === label);
onChange(labelArr);
};
const addLabel = (e: any) => {
const label = e.target.value;
label && label.trim();
if (label) {
const exitLabel = find(labels, (item) => item === label);
!exitLabel && onChange([...labels, label]);
}
toggleShowInput();
setInputVal(undefined);
};
const toggleShowInput = () => {
setShowInput(!showInput);
};
return (
<div ref={ref} className="custom-label-comp">
{labels.map((item, i) => {
return (
<span key={`${item}_${String(i)}`} className={'tag-default'}>
<div className="flex items-center">
{item}
<ErdaIcon
className="cursor-pointer"
onClick={() => {
deleteLabel(item);
}}
size="14"
color="black-600"
type="close"
/>
</div>
</span>
);
})}
{showInput ? (
<Input
size="small"
ref={inputRef}
className="custom-label-input"
placeholder={i18n.t('please enter')}
value={inputVal}
onChange={(e: any) => setInputVal(e.target.value)}
onPressEnter={addLabel}
onBlur={addLabel}
/>
) : (
<Button
type="primary"
ghost
className="custom-label-add"
onClick={() => {
toggleShowInput();
}}
>
+ {labelName}
</Button>
)}
</div>
);
},
)