diff --git a/pkg/harvester/pages/c/_cluster/vm-migration/index.vue b/pkg/harvester/pages/c/_cluster/vm-migration/index.vue index 1c71623c..503bf302 100644 --- a/pkg/harvester/pages/c/_cluster/vm-migration/index.vue +++ b/pkg/harvester/pages/c/_cluster/vm-migration/index.vue @@ -38,6 +38,65 @@ const allPlans = computed(() => store.getters[`${ inStore.value }/all`](HCI.FORK const allNetworkMaps = computed(() => store.getters[`${ inStore.value }/all`](HCI.FORKLIFT_NETWORK_MAP)); const allStorageMaps = computed(() => store.getters[`${ inStore.value }/all`](HCI.FORKLIFT_STORAGE_MAP)); +const escapeHtml = (value = '') => String(value) + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); + +const getPipelineStepMeta = (step = {}) => { + const phase = String(step.phase || '').toLowerCase(); + const hasProgress = typeof step.progress?.completed === 'number' && typeof step.progress?.total === 'number' && step.progress.total > 0; + const progressPct = hasProgress ? Math.round((step.progress.completed / step.progress.total) * 1000) / 10 : null; + + if (phase === 'completed' || phase === 'succeeded') { + return { + icon: '✓', + color: 'var(--success)', + text: '' + }; + } + + if (phase === 'running' || phase === 'inprogress' || phase === 'progressing' || phase === 'started' || (hasProgress && progressPct > 0)) { + return { + icon: '↻', + color: 'var(--primary)', + text: progressPct !== null ? `${ step.phase || t('generic.inProgress') } (${ progressPct }%)` : (step.phase || t('generic.inProgress')) + }; + } + + if (phase === 'failed' || phase === 'error') { + return { + icon: '✕', + color: 'var(--error)', + text: step.phase || t('harvester.addons.vmMigration.dashboard.progress.failed') + }; + } + + return { + icon: '○', + color: 'var(--muted)', + text: '' + }; +}; + +const formatPipelineTooltip = (pipeline = []) => { + if (!pipeline.length) { + return `