Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@
"{name} {action} successfully.": "{name} {action} successfully.",
"Image Repository": "Image Repository",
"Image Registry": "Image Registry",
"Optionally select a configured Registry resource. If none is selected, offline installation loads packaged images on each node.": "Optionally select a configured Registry resource. If none is selected, offline installation loads packaged images on each node.",
"Select a configured Registry resource. Its address and access settings will be used to pull offline installation images, and components inherit this Registry by default.": "Select a configured Registry resource. Its address and access settings will be used to pull offline installation images, and components inherit this Registry by default.",
"Optionally select a configured Registry resource for image pulls. If none is selected, images are pulled from the official registries.": "Optionally select a configured Registry resource for image pulls. If none is selected, images are pulled from the official registries.",
"Select a configured Registry resource for image pulls. If none is selected, online installation uses the official registries.": "Select a configured Registry resource for image pulls. If none is selected, online installation uses the official registries.",
Expand Down
1 change: 1 addition & 0 deletions src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@
"{name} {action} successfully.": "{name} {action} 成功。",
"Image Repository": "镜像仓库",
"Image Registry": "镜像仓库(Registry)",
"Optionally select a configured Registry resource. If none is selected, offline installation loads packaged images on each node.": "可选:选择已配置的 Registry 资源;未选择时离线安装会在各节点加载安装包中的镜像。",
"Select a configured Registry resource. Its address and access settings will be used to pull offline installation images, and components inherit this Registry by default.": "请选择已配置的 Registry 资源。系统将使用该资源的地址及访问配置拉取离线安装镜像,组件默认继承此 Registry。",
"Optionally select a configured Registry resource for image pulls. If none is selected, images are pulled from the official registries.": "可选:选择已配置的 Registry 资源用于拉取镜像;未选择时使用官方镜像仓库。",
"Select a configured Registry resource for image pulls. If none is selected, online installation uses the official registries.": "请选择已配置的 Registry 资源用于拉取镜像;未选择时在线安装使用官方镜像仓库。",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import classNames from 'classnames';
import {
CaretRightOutlined,
CaretDownOutlined,
ExclamationCircleFilled,
CheckCircleFilled,
CloseCircleFilled,
} from '@ant-design/icons';
Expand All @@ -44,44 +43,40 @@ function LogItemContent(props) {
}

const LogItem = observer((props) => {
const { nodeStatus, logStore, runtime, nodes, index } = props;
const { operationStore } = useRootStore();
const step = operationStore.currentNodesByStep;
const { nodeStatus, taskName, logStore, runtime, nodes, index } = props;
const inputEl = useRef();

const basicStepFinished = !!operationStore.currentNodesByStep?.status;
logStore.isStepFinished = basicStepFinished;
const taskFinished = ['Succeeded', 'Failed', 'TimedOut', 'Canceled'].includes(
nodeStatus
);
logStore.isStepFinished = taskFinished;
const { logdata, isExpand, isLoading, cumulativeSize, isStepFinished } =
logStore;

const stateIcons = (errIgnore, status) => {
if (errIgnore && status === 'failed') {
return <ExclamationCircleFilled style={{ color: '#fadb14' }} />;
} else if (status === 'successful') {
if (status === 'Succeeded') {
return <CheckCircleFilled style={{ color: '#57E39B' }} />;
} else if (status === 'failed') {
} else if (['Failed', 'TimedOut', 'Canceled'].includes(status)) {
return <CloseCircleFilled style={{ color: '#EB354D' }} />;
}
return '';
};

const params = {
operation: operationStore.currentOperation.name,
node: nodes.id,
step: step.id,
taskName,
offset: cumulativeSize,
};

useEffect(() => {
if (index === 0) {
if (index === 0 && taskName) {
const fn = async () => {
await logStore.fetchStepLog(params);
inputEl.current?.scrollIntoView({ block: 'end' });
};

fn();
}
}, [step.id]);
}, [taskName]);

useInterval(
() => {
Expand All @@ -91,10 +86,12 @@ const LogItem = observer((props) => {
};
fn();
},
isExpand && !isStepFinished ? 2000 : null
isExpand && !isStepFinished && taskName ? 2000 : null
);

const toggleExpand = async () => {
if (!taskName) return;

if (!isExpand) {
logStore.getStepLog(params);
} else {
Expand All @@ -107,10 +104,10 @@ const LogItem = observer((props) => {
<div className={styles.LogItem} ref={inputEl}>
<div className={classNames(styles.LogItem__title)} onClick={toggleExpand}>
{isExpand ? <CaretDownOutlined /> : <CaretRightOutlined />}
{`${nodes.ipv4} (${nodes.id})`}
{`${nodes.name || nodes.ipv4 || nodes.id}`}
<span className={styles.logitem_status}>
<span>{runtime || ''}</span>
{stateIcons(step.errIgnore, nodeStatus)}
{stateIcons(false, nodeStatus)}
</span>
</div>
<LogItemContent
Expand All @@ -125,39 +122,38 @@ const LogItem = observer((props) => {
function RightLogContent() {
const { operationStore } = useRootStore();
const { currentNodesByStep, activeStepIndex } = operationStore;
const logStores = useRef(new Map());

return useMemo(() => {
// resolve
if (currentNodesByStep?.status) {
return (
<div className={styles.right}>
{currentNodesByStep?.status?.map(
({ node, status, startAt, endAt }, index) => (
<LogItem
key={node}
logStore={new LogStore()}
nodeStatus={status}
runtime={formatSeconds(startAt, endAt)}
nodes={currentNodesByStep.nodes[index]}
index={index}
/>
)
)}
</div>
);
}

// pending
const nodes = currentNodesByStep?.nodes || [];
const statuses = currentNodesByStep?.status || [];
return (
<div className={styles.right}>
{currentNodesByStep?.nodes?.map((item, index) => (
<LogItem
key={index}
logStore={new LogStore()}
nodes={item}
index={index}
/>
))}
{nodes.map((item, index) => {
const taskStatus = statuses[index] || {};
const taskName = taskStatus.taskName || item.taskName;
const storeKey =
taskName ||
`${item.uid || item.name || item.id || 'node'}-${index}`;
if (!logStores.current.has(storeKey)) {
logStores.current.set(storeKey, new LogStore());
}
return (
<LogItem
key={index}
logStore={logStores.current.get(storeKey)}
nodeStatus={taskStatus.status || 'Pending'}
taskName={taskName}
runtime={
taskStatus.startAt
? formatSeconds(taskStatus.startAt, taskStatus.endAt)
: ''
}
nodes={item}
index={index}
/>
);
})}
</div>
);
}, [currentNodesByStep, activeStepIndex]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import { ViewAction } from 'containers/Action';
import { rootStore } from 'stores';
import WebsocketStore from 'stores/websocket';
import { getToken } from 'utils/localStorage';
import { APIVERSION } from 'utils/constants';
import ObjectMapper from 'utils/object.mapper';
import { findLastIndex, findLast } from 'lodash';

import RightLogContent from './RightLogContent';
import LeftSteps from './LeftSteps';
Expand Down Expand Up @@ -49,22 +49,35 @@ export default class PipelineLog extends ViewAction {

this.store.reset();
this.websocket = new WebsocketStore();
this.rawOperation = null;
this.taskTimer = null;
this.refreshing = false;
this.disposer = null;
}

componentDidMount() {
this.store.list.silent = true;
this.loadOperation();
this.initWebsocket();
this.taskTimer = setInterval(this.refreshTasks, 2000);
}

componentWillUnmount() {
this.store.list.silent = false;
this.websocket.close();
this.disposer?.();
if (this.taskTimer) {
clearInterval(this.taskTimer);
this.taskTimer = null;
}
}

initWebsocket = () => {
const token = getToken();
this.websocket.watch(
`api/core.kubeclipper.io/v1/operations?fieldSelector=metadata.name=${this.item.name}&watch=true&token=${token}`
`api/operations.kubeclipper.io/v1alpha1/operations?fieldSelector=metadata.name=${encodeURIComponent(
this.item.name
)}&watch=true&token=${encodeURIComponent(token)}`
);

this.disposer = reaction(
Expand All @@ -73,56 +86,72 @@ export default class PipelineLog extends ViewAction {
message = toJS(message);
const types = ['ADDED', 'MODIFIED', 'DELETED'];
if (types.includes(message.type)) {
const result = ObjectMapper.operations(message.object);
this.store.status = result.status;

this.generateStatusBySteps(result.operationSteps);
this.store.currentOperation = result;
this.store.operationSteps = result.operationSteps;
this.updateOperation(message.object);
}
}
);
};

// 计算 step status
generateStatusBySteps(operationSteps) {
operationSteps.forEach((step, index) => {
if (!step.status) {
operationSteps[index].stepStatus = 'warning';
} else {
operationSteps[index].stepStatus = 'success';
for (const node of step.status) {
if (node.status === 'failed') {
if (step.errignore) {
operationSteps[index].stepStatus = 'warning';
} else {
operationSteps[index].stepStatus = 'failed';
}
break;
}
}
}
});
loadOperation = async () => {
try {
const operation = await request.get(
`${APIVERSION.operations}/operations/${encodeURIComponent(
this.item.name
)}`
);
await this.updateOperation(operation);
} catch (error) {
// The operation list already rendered the row. Keep the dialog usable
// while the watch connection retries after a transient read failure.
// eslint-disable-next-line no-console
console.log(error);
}
};

refreshTasks = async () => {
if (!this.rawOperation || this.refreshing) return;

this.refreshing = true;
try {
const tasks = await this.fetchTasks(this.rawOperation);
await this.updateOperation(this.rawOperation, tasks);
} catch (error) {
// Task creation/status updates are eventually consistent with the
// operation watch. The next refresh will retry without closing the log.
// eslint-disable-next-line no-console
console.log(error);
} finally {
this.refreshing = false;
}
};

const lastIndex = findLastIndex(operationSteps, (item) => item.stepID);
const lastStep = findLast(operationSteps, (item) => item.stepID);
fetchTasks = async (operation) => {
const uid = operation?.metadata?.uid;
if (!uid) return [];

const msgPending = ['running'];
// const msgResolve = ['successful', 'failed'];
const result = await request.get(
`${APIVERSION.operations}/operationtasks`,
{ fieldSelector: `spec.operationRef.uid=${uid}` }
);
return result?.items || [];
};

if (
lastIndex !== operationSteps.length - 1 &&
msgPending.includes(this.store.status)
) {
const processSteps = operationSteps[lastIndex + 1];
processSteps.frontStatus = 'pending';
processSteps.stepStatus = 'processing';
updateOperation = async (operation, tasks) => {
if (!operation) return;

this.activeByStep(processSteps, lastIndex + 1);
} else {
this.activeByStep(lastStep, lastIndex);
}
}
this.rawOperation = operation;
const operationTasks = tasks || (await this.fetchTasks(operation));
const result = ObjectMapper.operations(operation, operationTasks);
const { operationSteps } = result;

this.store.status = result.status;
this.store.currentOperation = result;
this.store.operationSteps = operationSteps;

const activeIndex = operationSteps.findIndex((step) => !step.isComplete);
const index = activeIndex === -1 ? operationSteps.length - 1 : activeIndex;
this.activeByStep(operationSteps[index], index);
};

activeByStep = async (step, index) => {
this.store.currentNodesByStep = step;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,17 @@ export default class Index extends ConfirmAction {
isFailedStatus = (item) => {
const firstPage = operationStore.list.page;
const firstData = operationStore.list.data[0];
const { id, status } = item;
const { id, phase } = item;
if (!firstData) return false;

if (
firstPage === 1 &&
firstData.id === id &&
firstData.status === 'failed' &&
['Failed', 'TimedOut'].includes(phase) &&
!this.notAllowAction.includes(firstData.operationName)
) {
return true;
} else if (firstData.id === id && status === 'termination') {
} else if (firstData.id === id && phase === 'Canceled') {
return true;
}

Expand All @@ -71,6 +73,6 @@ export default class Index extends ConfirmAction {
onSubmit = (data) => {
const { id } = data;

return operationStore.retry({ id });
return operationStore.retry({ id }, data);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default class Index extends ConfirmAction {
}

isDisableStatus(item) {
return item.status === 'running';
return ['Pending', 'Running'].includes(item.phase);
}

allowedCheckFunc = (item) => this.isDisableStatus(item);
Expand All @@ -49,6 +49,6 @@ export default class Index extends ConfirmAction {
onSubmit = (data) => {
const { id } = data;

return operationStore.stop({ id });
return operationStore.stop({ id }, data);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function OperationList() {
name: 'operationName',
},
],
transitionStatusList: ['running'],
transitionStatusList: ['Pending', 'Running'],
actionConfigs,
store,
detail: clusterStore.detail,
Expand Down
Loading
Loading