You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
autoflow-web-console/src/components/templates/run/executions/ViewComponent.vue

314 lines
9.4 KiB

<script setup lang="ts">
import IconDeleteBtn from "@/components/atoms/button/IconDeleteBtn.vue";
import IconModifyBtn from "@/components/atoms/button/IconModifyBtn.vue";
// import FormComponent from "@/components/device/FormComponent.vue";
9 months ago
import { computed, onMounted, ref, watch } from "vue";
11 months ago
// const store = commonStore();
11 months ago
9 months ago
const props = defineProps<{
experimentInfo: any;
}>();
11 months ago
9 months ago
const emit = defineEmits<{ (e: "close"): void }>();
const history = computed(() =>
(props.experimentInfo.raw?.state_history ?? [])
.slice()
.sort(
(a: any, b: any) =>
new Date(a.update_time).getTime() - new Date(b.update_time).getTime(),
),
);
9 months ago
// 히스토리에서 각 단계의 기록 찾기
const hPending = computed(() =>
history.value.find((h) => (h.state || "").toUpperCase() === "PENDING"),
);
const hRunning = computed(() =>
history.value.find((h) => (h.state || "").toUpperCase() === "RUNNING"),
);
const hTerminal = computed(() => {
const t = history.value
.slice()
.reverse()
.find((h) =>
["SUCCEEDED", "FAILED"].includes((h.state || "").toUpperCase()),
);
return t ?? null;
});
9 months ago
// 3단계 고정 스텝 정의
const steps = computed(() => {
const lastLabel = (hTerminal.value?.state || "COMPLETED").toUpperCase();
9 months ago
return [
{
9 months ago
key: "PENDING",
label: "PENDING",
active: !!(hPending.value || hRunning.value || hTerminal.value),
color: "primary",
icon: "mdi-clock-outline",
ts: hPending.value?.update_time,
},
{
9 months ago
key: "RUNNING",
label: "RUNNING",
active: !!(hRunning.value || hTerminal.value),
color: "info",
icon: "mdi-progress-clock",
ts: hRunning.value?.update_time,
},
{
9 months ago
key: "TERMINAL",
label: ["SUCCEEDED", "FAILED"].includes(lastLabel)
? lastLabel
: "COMPLETED",
active: !!hTerminal.value,
color:
lastLabel === "FAILED"
? "error"
: lastLabel === "SUCCEEDED"
? "success"
: "surface-variant",
icon:
lastLabel === "FAILED"
? "mdi-close"
: lastLabel === "SUCCEEDED"
? "mdi-check"
: "mdi-dots-horizontal",
ts: hTerminal.value?.update_time,
},
];
9 months ago
});
9 months ago
// 고정 3스텝 기준 위치/세그먼트
const nSteps = 3;
const activeIndex = computed(
() => steps.value.map((s) => s.active).lastIndexOf(true), // -1이면 아무것도 진행X
);
const leftPct = (i: number) => (i / (nSteps - 1)) * 100;
const segWidthPct = () => 100 / (nSteps - 1);
9 months ago
// 유틸
const fmt = (iso?: string) => (iso ? new Date(iso).toLocaleString() : "—");
</script>
<template>
11 months ago
<v-container fluid class="h-100 pa-5 d-flex flex-column align-center">
<v-card
flat
class="bg-shades-transparent d-flex flex-column justify-center w-100"
>
<v-card flat class="bg-shades-transparent w-100">
<v-card-item class="text-h5 font-weight-bold pt-0 pa-5 pl-0">
<div class="d-flex flex-row justify-start align-center">
11 months ago
<div class="text-primary">View Details</div>
</div>
</v-card-item>
</v-card>
11 months ago
<v-card flat class="bordered-box mb-6 w-100 rounded-lg pa-8">
<v-card-title class="grey lighten-4 py-2 px-4">
9 months ago
<span class="font-weight-bold">Execution Information</span>
11 months ago
</v-card-title>
11 months ago
<v-card-text class="px-6 pb-6 pt-4">
<v-row align="center" class="py-2">
9 months ago
<v-col cols="3" class="text-h6 font-weight-bold">Name</v-col>
<v-col cols="9" class="pa-2">{{ props.experimentInfo.name }}</v-col>
11 months ago
</v-row>
<VDivider class="my-2" />
11 months ago
<v-row align="center" class="py-2">
<v-col cols="3" class="text-h6 font-weight-bold">Status</v-col>
<v-col cols="9" class="pa-2">
9 months ago
<v-icon
v-if="props.experimentInfo.status === 'Succeeded'"
color="green"
11 months ago
>mdi-check-circle</v-icon
>
9 months ago
<v-icon
v-else-if="props.experimentInfo.status === 'Failed'"
color="red"
11 months ago
>mdi-close-circle</v-icon
>
9 months ago
<v-icon v-else color="grey">mdi-loading</v-icon>
{{ props.experimentInfo.status }}
</v-col>
11 months ago
</v-row>
<VDivider class="my-2" />
9 months ago
11 months ago
<v-row align="center" class="py-2">
9 months ago
<v-col cols="3" class="text-h6 font-weight-bold">Duration</v-col>
<v-col cols="9" class="pa-2">{{
props.experimentInfo.duration
}}</v-col>
</v-row>
<VDivider class="my-2" />
<v-row align="center" class="py-2">
<v-col cols="3" class="text-h6 font-weight-bold"
>Experiment ID</v-col
>
<v-col cols="9" class="pa-2">{{
props.experimentInfo.experiment
}}</v-col>
11 months ago
</v-row>
<VDivider class="my-2" />
9 months ago
11 months ago
<v-row align="center" class="py-2">
9 months ago
<v-col cols="3" class="text-h6 font-weight-bold">Workflow</v-col>
<v-col cols="9" class="pa-2">{{
props.experimentInfo.workflow
}}</v-col>
11 months ago
</v-row>
<VDivider class="my-2" />
9 months ago
11 months ago
<v-row align="center" class="py-2">
<v-col cols="3" class="text-h6 font-weight-bold">Start Time</v-col>
9 months ago
<v-col cols="9" class="pa-2">{{
props.experimentInfo.startTime
}}</v-col>
11 months ago
</v-row>
<VDivider class="my-2" />
<v-row align="center" class="py-2">
<v-col cols="3" class="text-h6 font-weight-bold"
>Registry Status</v-col
>
<v-col cols="9" class="pa-2">{{
9 months ago
props.experimentInfo.registryStatus
11 months ago
}}</v-col>
</v-row>
<VDivider class="my-2" />
9 months ago
<!-- 🔹 state_history 출력 -->
<!-- 🔹 State History -->
<v-row align="center" class="py-6">
<v-col cols="3" class="text-h6 font-weight-bold"
>State History</v-col
>
<v-col cols="9" class="pa-2">
<div class="history-rail">
<!-- 기본(회색) 레일 -->
<div class="history-rail__line" />
<!-- 진행 세그먼트 2 고정 -->
<template v-for="i in 2" :key="'seg-' + i">
<div
class="history-rail__seg"
:style="{
left: `calc(${leftPct(i - 1)}% + var(--dot-size)/2)`,
width: `calc(${segWidthPct()}% - var(--dot-size))`,
background:
i - 1 < activeIndex
? 'var(--color-active)'
: 'var(--color-idle)',
}"
/>
</template>
<!-- /아이콘 + 라벨/시간 : 3 고정 -->
<template v-for="(s, i) in steps" :key="'dot-' + s.key">
<div
class="history-rail__dot"
:style="{ left: leftPct(i) + '%' }"
>
<v-avatar
:color="s.active ? s.color : 'surface-variant'"
size="28"
class="elev-1"
>
<v-icon size="18">{{ s.icon }}</v-icon>
</v-avatar>
</div>
<div
class="history-rail__label"
:style="{ left: leftPct(i) + '%' }"
>
<v-chip
size="small"
:color="s.active ? s.color : undefined"
variant="tonal"
class="mb-1 text-uppercase font-weight-medium"
>
{{ s.label }}
</v-chip>
<div class="text-caption text-medium-emphasis">
{{ fmt(s.ts) }}
</div>
</div>
</template>
</div>
</v-col>
</v-row>
11 months ago
</v-card-text>
9 months ago
11 months ago
<v-sheet class="d-flex justify-end mb-2">
9 months ago
<v-btn color="primary" @click="emit('close')">Back to List</v-btn>
11 months ago
</v-sheet>
</v-card>
</v-card>
</v-container>
</template>
<style scoped>
9 months ago
:root {
--dot-size: 28px;
11 months ago
}
9 months ago
/* 레일 컨테이너: 한 줄 정렬 + 여백 */
.history-rail {
position: relative;
width: 40%;
padding: 12px 0 34px; /* 위: 점 여유 / 아래: 라벨 공간 */
--color-rail: rgba(255, 255, 255, 0.12);
--color-idle: rgba(255, 255, 255, 0.12);
--color-active: rgb(
98,
0,
238
); /* theme primary(보라) 느낌, 필요시 바꿔도 OK */
11 months ago
}
9 months ago
/* 가로 기준선 */
.history-rail__line {
position: absolute;
left: 0;
right: 0;
top: 14px; /* 점 중앙과 자연스럽게 맞춤 */
height: 4px;
background: var(--color-rail);
border-radius: 2px;
11 months ago
}
9 months ago
/* 진행 세그먼트(점~점 사이) */
.history-rail__seg {
position: absolute;
top: 14px;
height: 4px;
border-radius: 2px;
transition:
width 240ms ease,
background 240ms ease;
}
/* 점 아이콘 */
.history-rail__dot {
position: absolute;
top: 0; /* 컨테이너 안에서 수직 정렬 */
transform: translateX(-50%);
}
/* 라벨+시간(점 아래) */
.history-rail__label {
position: absolute;
top: 28px;
transform: translateX(-50%);
text-align: center;
white-space: nowrap;
}
</style>