diff --git a/.env.dev b/.env.dev index 759edf0..b0c7266 100644 --- a/.env.dev +++ b/.env.dev @@ -1,3 +1,3 @@ NODE_ENV = "dev" -VITE_APP_API_SERVER_URL = "http://localhost:80" +VITE_APP_API_SERVER_URL = "http://localhost:8080" VITE_ROOT_PATH = "" \ No newline at end of file diff --git a/components.d.ts b/components.d.ts index 17340ba..7daf980 100644 --- a/components.d.ts +++ b/components.d.ts @@ -10,7 +10,7 @@ declare module 'vue' { export interface GlobalComponents { AppFooter: typeof import('./src/components/AppFooter.vue')['default'] CompareComponent: typeof import('./src/components/templates/run/executions/CompareComponent.vue')['default'] - copy: typeof import('./src/components/atoms/organisms/TrainingScriptBaseDoalog copy.vue')['default'] + copy: typeof import('./src/components/templates/run/executions/ListComponent copy.vue')['default'] DatasetBaseDoalog: typeof import('./src/components/atoms/organisms/DatasetBaseDoalog.vue')['default'] DatasetsBaseDoalog: typeof import('./src/components/atoms/organisms/DatasetsBaseDoalog.vue')['default'] DatesetBaseDoalog: typeof import('./src/components/atoms/organisms/DatesetBaseDoalog.vue')['default'] @@ -30,6 +30,7 @@ declare module 'vue' { IconSettingBtn: typeof import('./src/components/atoms/button/IconSettingBtn.vue')['default'] LayoutComponent: typeof import('./src/components/common/LayoutComponent.vue')['default'] ListComponent: typeof import('./src/components/templates/Datasets/ListComponent.vue')['default'] + ListComponentback: typeof import('./src/components/templates/run/executions/ListComponentback.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] SidebarHeader: typeof import('./src/components/common/SidebarHeader.vue')['default'] diff --git a/src/components/atoms/organisms/WorkflowsBaseDialog.vue b/src/components/atoms/organisms/WorkflowsBaseDialog.vue index 52b0a29..068830b 100644 --- a/src/components/atoms/organisms/WorkflowsBaseDialog.vue +++ b/src/components/atoms/organisms/WorkflowsBaseDialog.vue @@ -9,7 +9,7 @@ import { storage } from "@/utils/storage"; import type { Workflow } from "@/components/models/management/Workflow"; import { storeToRefs } from "pinia"; import { useAutoflowStore } from "@/stores/autoflowStore"; -import { kubeflowService } from "@/components/service/management/KubeflowService"; +import { KubeflowService } from "@/components/service/management/KubeflowService"; import { toKubeflowForm, type KubeflowUploadDto, @@ -241,7 +241,7 @@ async function submit() { uploadfile: form.value.file, }; const fd = toKubeflowForm(dto); - const { data } = await kubeflowService.upload(fd); + const { data } = await KubeflowService.upload(fd); emit("saved", data); emit("close-modal"); } diff --git a/src/components/atoms/organisms/WorkflowsRunDialog.vue b/src/components/atoms/organisms/WorkflowsRunDialog.vue index 4fe3ad7..283a81f 100644 --- a/src/components/atoms/organisms/WorkflowsRunDialog.vue +++ b/src/components/atoms/organisms/WorkflowsRunDialog.vue @@ -1,19 +1,17 @@ - diff --git a/src/components/templates/workflow/ViewComponent.vue b/src/components/templates/workflow/ViewComponent.vue index 44daca1..7465f1e 100644 --- a/src/components/templates/workflow/ViewComponent.vue +++ b/src/components/templates/workflow/ViewComponent.vue @@ -3,6 +3,7 @@ import { onMounted, ref, watch, onBeforeUnmount } from "vue"; import * as monaco from "monaco-editor"; import "monaco-editor/min/vs/editor/editor.main.css"; import { WorkflowService } from "@/components/service/management/WorkflowService"; +import { AttachmentsService } from "@/components/service/management/AttachmentsService"; // ⬅️ 파일 읽기용 type TabKey = "details" | "yaml"; @@ -15,7 +16,20 @@ const activeTab = ref("details"); const editorRef = ref(null); let editorInstance: monaco.editor.IStandaloneCodeEditor | null = null; -// 화면에 뿌릴 상세 데이터(요청 항목만) +function ensureEditor() { + if (editorInstance || !editorRef.value) return; + editorInstance = monaco.editor.create(editorRef.value, { + value: defaultYaml, + language: "yaml", + theme: "vs-dark", + readOnly: true, + automaticLayout: true, + minimap: { enabled: false }, + lineNumbers: "on", + }); +} + +// 화면에 뿌릴 상세 데이터 const detail = ref({ name: "", version: "", @@ -41,7 +55,7 @@ spec: args: ["echo hello"] `; -// 간단한 날짜 포맷터 (ISO/T 포함 모두 대응) +// 날짜 포맷 function formatDateTime(raw?: string): string { if (!raw) return "-"; const s = String(raw).replace("T", " "); @@ -49,13 +63,30 @@ function formatDateTime(raw?: string): string { return m ? m[1] : s.slice(0, 19); } -// ===== 상세 조회 ===== +/** ⬅️ storagePath(object key)로부터 YAML 읽기 */ +async function loadYamlFromStoragePath(objectName?: string) { + const key = (objectName || "").trim(); + if (!key) return false; + try { + const res = await AttachmentsService.readTextByPath(key); + const text = + typeof res?.data === "string" ? res.data : String(res?.data ?? ""); + ensureEditor(); + editorInstance?.setValue(text || defaultYaml); + return true; + } catch (e) { + console.warn("[Workflow Detail] readTextByPath failed:", e); + return false; + } +} + +/** 상세 조회 → YAML 문자열 우선 → storagePath 후보들 시도 → 실패 시 기본 YAML */ async function fetchDetail(id: number | string) { try { const res = await WorkflowService.view(Number(id)); const d = res?.data ?? {}; - // 백엔드 필드명(스네이크/카멜) 혼재 대비 매핑 + // 정보 매핑(스네이크/카멜 혼용 방어) detail.value = { name: d.name ?? d.workflowName ?? "", version: String(d.version ?? ""), @@ -66,7 +97,9 @@ async function fetchDetail(id: number | string) { regDt: formatDateTime(d.reg_dt ?? d.regDt ?? d.regDate), }; - // YAML (있으면 보여주고, 없으면 기본 예시) + ensureEditor(); + + // 1) 서버가 YAML 문자열을 직접 줄 때 const yamlFromServer = d.workflowYaml || d.yaml || @@ -74,27 +107,45 @@ async function fetchDetail(id: number | string) { d.specYaml || d.yamlStr || ""; - if (editorInstance) { - editorInstance.setValue(yamlFromServer || defaultYaml); + if (yamlFromServer) { + editorInstance!.setValue(yamlFromServer); + return; + } + + // 2) 업로드 파일의 object key(=storagePath 등) 후보 + const objectKeyCandidates = [ + d.yamlStoragePath, + d.yaml_object_name, + d.yamlObjectName, + d.storagePath, + d.storedName, + d.objectName, + d.object_key, + d.yamlPath, + d.filePath, + d.yamlFile, + d.yamlFilePath, + ].filter(Boolean) as string[]; + + // 3) 후보 중 하나라도 성공하면 반환 + for (const key of objectKeyCandidates) { + const ok = await loadYamlFromStoragePath(key); + if (ok) return; } + + // 4) 전부 실패하면 기본 YAML 출력 + editorInstance!.setValue(defaultYaml); } catch (e) { console.error("[Workflow Detail] view API failed:", e); + ensureEditor(); + editorInstance!.setValue(defaultYaml); } } // ===== 마운트 & 변경 감지 ===== onMounted(() => { - if (editorRef.value) { - editorInstance = monaco.editor.create(editorRef.value, { - value: defaultYaml, - language: "yaml", - theme: "vs-dark", - readOnly: true, - automaticLayout: true, - minimap: { enabled: false }, - lineNumbers: "on", - }); - } + // 탭 전환 관계없이 미리 생성해도 ok (automaticLayout) + ensureEditor(); }); watch( @@ -107,14 +158,20 @@ watch( { immediate: true }, ); +// YAML 탭으로 전환되었을 때도 에디터 보장 +watch( + () => activeTab.value, + (tab) => { + if (tab === "yaml") ensureEditor(); + }, +); + onBeforeUnmount(() => { - if (editorInstance) { - editorInstance.dispose(); - editorInstance = null; - } + editorInstance?.dispose(); + editorInstance = null; }); -// ===== (나중 사용) Step 테이블 정의 ===== +// (나중 사용) Step 테이블 const stepHeaders = [ { title: "Order", key: "order", width: "10%", align: "center" }, { title: "Step Name", key: "name", width: "40%", align: "center" }, 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 @@