From ac1f47a72a3c06ad999f070d615fb418d7cfe250 Mon Sep 17 00:00:00 2001 From: jschoi Date: Thu, 25 Sep 2025 09:42:36 +0900 Subject: [PATCH] =?UTF-8?q?fix=20:=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8?= =?UTF-8?q?=20=EC=84=A0=ED=83=9D=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EB=B0=8F=20Execution=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../run/executions/ListComponent.vue | 227 +++++++----------- src/router/index.js | 51 ++-- src/views/Select.vue | 16 +- 3 files changed, 119 insertions(+), 175 deletions(-) diff --git a/src/components/templates/run/executions/ListComponent.vue b/src/components/templates/run/executions/ListComponent.vue index e997b37..5869825 100644 --- a/src/components/templates/run/executions/ListComponent.vue +++ b/src/components/templates/run/executions/ListComponent.vue @@ -55,52 +55,47 @@ function fmtDuration(start?: string, end?: string) { const pad = (n: number) => String(n).padStart(2, "0"); return `${h}:${pad(m)}:${pad(sec)}`; } - -// ✅ 모든 Runs 페이징 수집 후 테이블에 주입 +const displayNo = (i: number) => { + const start = (data.value.params.pageNum - 1) * data.value.params.pageSize; + return data.value.totalDataLength - (start + i); +}; async function loadRunsAll() { runsLoading.value = true; try { const all: any[] = []; - const seen = new Set(); - // 첫 페이지부터 page_size 명시 (SDK 케이스 둘 다 지원) - let first = await ExecutionsService.search({ pageSize: 79 }); + // 1페이지 + let resp = await ExecutionsService.search({ + page_size: 500, + } as any); + all.push(...(resp?.data?.runs ?? [])); - all.push(...(first?.data?.runs ?? [])); + // 다음 토큰 추출(스네이크/카멜 모두 대비) let token: string | undefined = - first?.data?.next_page_token ?? first?.data?.nextPageToken; + resp?.data?.next_page_token ?? resp?.data?.nextPageToken; // 다음 페이지들 - let guard = 0; - while (token && !seen.has(token) && guard < 1000) { + const seen = new Set(); + while (token && !seen.has(token)) { seen.add(token); - // snake_case 우선 호출 - let page = await ExecutionsService.search({ pageSize: 79 }); - - // camelCase만 받는 SDK 대비 폴백 - if ( - (!Array.isArray(page?.data?.runs) || page?.data?.runs.length === 0) && - (page?.data?.next_page_token === token || - page?.data?.next_page_token === undefined) - ) { - page = await ExecutionsService.search({ - pageToken: token, - pageSize: 500, - } as any); - } - - all.push(...(page?.data?.runs ?? [])); - token = page?.data?.next_page_token ?? page?.data?.nextPageToken; - guard += 1; + // ✅ 무조건 token을 넣어서 호출 (snake/camel 둘 다 넣기) + resp = await ExecutionsService.search({ + page_token: token, + pageToken: token, + page_size: 500, + } as any); + + all.push(...(resp?.data?.runs ?? [])); + token = resp?.data?.next_page_token ?? resp?.data?.nextPageToken; } - // 중복 방지(안전) + // 중복 방지 const dedup = Array.from( new Map(all.map((r: any) => [r?.run_id ?? r?.id ?? r?.name, r])).values(), ); - // 👉 테이블 데이터로 매핑해서 그대로 꽂기 + // 테이블 행으로 매핑 data.value.results = dedup.map((r: any, idx: number) => ({ no: idx + 1, name: r?.display_name ?? r?.name ?? r?.run_id ?? "(no name)", @@ -112,38 +107,27 @@ async function loadRunsAll() { r?.pipeline_version_reference?.pipeline_version_id ?? "-", startTime: fmtStart(r?.created_at), - registryStatus: r?.storage_state ?? "-", // AVAILABLE 등 - // 필요 시 원본 활용 + registryStatus: r?.storage_state ?? "-", run_id: r?.run_id, raw: r, })); - // 카운트/페이지 길이 갱신 + experimentOptions.value = Array.from( + new Set(data.value.results.map((r: any) => String(r.experiment || "-"))), + ).filter((v) => v && v !== "-"); + + workflowOptions.value = Array.from( + new Set(data.value.results.map((r: any) => String(r.workflow || "-"))), + ).filter((v) => v && v !== "-"); data.value.totalDataLength = data.value.results.length; setPaginationLength(); - - console.log( - "[Runs] total_size(from API):", - first?.data?.total_size ?? first?.data?.totalSize, - "fetched:", - data.value.results.length, - ); - console.table( - data.value.results.slice(0, 50).map((r: any) => ({ - no: r.no, - run_id: r.run_id, - name: r.name, - status: r.status, - startTime: r.startTime, - duration: r.duration, - })), - ); } catch (e: any) { console.error("[Runs] 호출 실패:", e?.response?.data ?? e); } finally { runsLoading.value = false; } } + const pagedResults = computed(() => { const { pageNum, pageSize } = data.value.params; const start = (pageNum - 1) * pageSize; @@ -171,6 +155,8 @@ const searchOptions = [ { searchType: "Registry Status", searchText: "registryStatus" }, ]; +const experimentOptions = ref([]); +const workflowOptions = ref([]); const execDialogOpen = ref(false); const execMode = ref<"create" | "edit" | "clone">("create"); const execSelected = ref(null); @@ -192,6 +178,8 @@ const data = ref({ pageSize: 10, searchType: "", searchText: "", + experimentFilter: "", + workflowFilter: "", }, results: [], totalDataLength: 0, @@ -205,96 +193,52 @@ const data = ref({ userOption: [], }); -const getCodeList = () => { - // UserService.search(data.value.params).then((d) => { - // if (d.status === 200) { - // data.value.userOption = d.data.userList; - // } - // }); -}; +const filteredResults = computed(() => { + const { searchType, searchText, experimentFilter, workflowFilter } = + data.value.params; + + let list = data.value.results; -const getData = () => { - const params = { ...data.value.params }; - if (params.searchType === "" || params.searchText === "") { - delete params.searchType; - delete params.searchText; + // 실드롭다운 필터 + if (experimentFilter) { + list = list.filter((r) => String(r.experiment).includes(experimentFilter)); + } + if (workflowFilter) { + list = list.filter((r) => String(r.workflow).includes(workflowFilter)); } - data.value.results = [ - { - no: 11, - name: "run-batch32-lr0.001", - status: "Succeeded", - duration: "0:00:21", - experiment: "Baseline Model Training", - workflow: "baseline_train_pipeline", - startTime: "2025-05-20 10:12", - registryStatus: "Registered", - }, - { - no: 10, - name: "run-batch64-lr0.001", - status: "Failed", - duration: "0:00:20", - experiment: "Baseline Model Training", - workflow: "baseline_train_pipeline", - startTime: "2025-05-20 09:10", - registryStatus: "Not Registered", - }, - { - no: 9, - name: "run-batch32-lr0.0005", - status: "Succeeded", - duration: "0:00:21", - experiment: "Baseline Model Training", - workflow: "baseline_train_pipeline", - startTime: "2025-05-19 10:12", - registryStatus: "Registered", - }, - { - no: 8, - name: "run-batch64-lr0.0005", - status: "Running", - duration: "0:00:20", - experiment: "Baseline Model Training", - workflow: "baseline_train_pipeline", - startTime: "2025-05-18 11:50", - registryStatus: "Not Registered", - }, - { - no: 7, - name: "run-augmented-data", - status: "Succeeded", - duration: "0:00:20", - experiment: "Baseline Model Training", - workflow: "baseline_train_pipeline", - startTime: "2025-05-17 09:12", - registryStatus: "Registered", - }, - { - no: 6, - name: "run-augmented-data", - status: "Succeeded", - duration: "0:00:20", - experiment: "Baseline Model Training", - workflow: "baseline_train_pipeline", - startTime: "2025-05-17 09:12", - registryStatus: "Registered", - }, - { - no: 5, - name: "run-augmented-data", - status: "Succeeded", - duration: "0:00:20", - experiment: "Baseline Model Training", - workflow: "baseline_train_pipeline", - startTime: "2025-05-17 09:12", - registryStatus: "Registered", - }, - ]; - data.value.totalDataLength = data.value.results.length; - setPaginationLength(); -}; + // 텍스트 검색 + const q = (searchText || "").trim().toLowerCase(); + if (q) { + if (!searchType) { + // All: 여러 필드 OR 매칭 + list = list.filter((r) => { + const pool = [ + r.name, + r.status, + r.duration, + r.experiment, + r.workflow, + r.registryStatus, + r.startTime, + ]; + return pool.some((v) => + String(v ?? "") + .toLowerCase() + .includes(q), + ); + }); + } else { + list = list.filter((r) => + String(r[searchType] ?? "") + .toLowerCase() + .includes(q), + ); + } + } + + return list; +}); const setPaginationLength = () => { if (data.value.totalDataLength % data.value.params.pageSize === 0) { data.value.pageLength = @@ -318,7 +262,6 @@ const handleClone = () => { const changePageNum = (page) => { data.value.params.pageNum = page; - getData(); }; const openCreateExecution = () => { execMode.value = "create"; @@ -364,7 +307,6 @@ const getSelectedAllData = () => { onMounted(() => { loadRunsAll(); - getCodeList(); }); @@ -542,8 +484,8 @@ onMounted(() => { @@ -554,7 +496,8 @@ onMounted(() => { style="min-width: 36px" /> - {{ item.no }} + + {{ displayNo(i) }} {{ item.name }} { :total-visible="10" color="primary" rounded="circle" - @update:model-value="getData" + @update:model-value="changePageNum" > diff --git a/src/router/index.js b/src/router/index.js index 82f1c49..5da2208 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -122,16 +122,13 @@ const router = createRouter({ routes, }); -router.beforeEach((to) => { +router.beforeEach((to, from) => { const authed = !!( typeof storage.getToken === "function" && storage.getToken() ); - - const isLogin = to.name === "login" || to.path === "/login"; - const isSignup = to.name === "signup" || to.path === "/signup"; - const isSelect = to.name === "select" || to.path === "/select"; - - const bootDone = sessionStorage.getItem("initialRedirectDone") === "1"; + const isLogin = to.name === "login"; + const isSignup = to.name === "signup"; + const isSelect = to.name === "select"; if (!authed) { if (!isLogin && !isSignup) { @@ -140,32 +137,26 @@ router.beforeEach((to) => { return true; } - if (!bootDone && !isSelect && !isLogin && !isSignup) { - sessionStorage.setItem("initialRedirectDone", "1"); - return { name: "select", replace: true, query: { redirect: to.fullPath } }; - } - - if (to.matched.some((r) => r.meta?.requiresAdmin)) { - try { - const raw = - typeof storage?.getAuth === "function" - ? storage.getAuth() - : JSON.parse(localStorage.getItem("autoflow-auth") || "null"); - - const roles = raw?.userInfo?.roles ?? raw?.roles ?? []; - const authCd = raw?.userInfo?.authCd ?? raw?.authCd ?? raw?.auth; + const hasProject = !!localStorage.getItem("projectId"); // ✅ 프로젝트 선택 여부 + const bootDone = sessionStorage.getItem("initialRedirectDone") === "1"; - const isAdmin = - (Array.isArray(roles) - ? roles.includes("ROLE_ADMIN") - : roles === "ROLE_ADMIN") || authCd === "ADMIN"; + // 이미 프로젝트 선택됨 → 어떤 화면이든 통과 + if (hasProject) return true; - if (!isAdmin) { - return { name: "home", replace: true }; - } - } catch { - return { name: "home", replace: true }; + // 아직 프로젝트 미선택 + if (!bootDone) { + // ✅ 선택 화면에 "들어온 순간"을 부트 완료로 간주 (여기서 한 번만 세팅) + if (isSelect) { + sessionStorage.setItem("initialRedirectDone", "1"); + return true; } + // ✅ select 로 1회만 보냄 + return { name: "select", replace: true, query: { redirect: to.fullPath } }; + } + + // 부트 완료인데 여전히 프로젝트 미선택이면, select만 허용 + if (!isSelect) { + return { name: "select", replace: true, query: { redirect: to.fullPath } }; } return true; diff --git a/src/views/Select.vue b/src/views/Select.vue index a2503de..4b3f684 100644 --- a/src/views/Select.vue +++ b/src/views/Select.vue @@ -1,6 +1,6 @@