From adac240170a04083a0091856ca16ae9d567b0f41 Mon Sep 17 00:00:00 2001 From: jschoi Date: Tue, 21 Oct 2025 20:48:25 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=ED=99=88=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?Deployment=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B0=94=EC=9D=B8?= =?UTF-8?q?=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../atoms/organisms/DeploymentDialog.vue | 189 ++++++++++++++++-- .../atoms/organisms/ExternalDatasetDialog.vue | 104 +++++++--- .../management/ExternalAuthController.ts | 13 ++ src/components/service/index.ts | 10 + .../service/management/DatasetService.ts | 13 +- .../ExternalAuthControllerService.ts | 11 +- .../templates/Datasets/ListComponent.vue | 4 +- .../templates/Datasets/ViewComponent.vue | 99 +++++---- .../templates/home/ListComponent.vue | 32 +-- .../run/executions/ViewComponent.vue | 14 +- 10 files changed, 383 insertions(+), 106 deletions(-) create mode 100644 src/components/models/management/ExternalAuthController.ts diff --git a/src/components/atoms/organisms/DeploymentDialog.vue b/src/components/atoms/organisms/DeploymentDialog.vue index f5ce076..f64ab52 100644 --- a/src/components/atoms/organisms/DeploymentDialog.vue +++ b/src/components/atoms/organisms/DeploymentDialog.vue @@ -1,5 +1,7 @@ @@ -107,11 +129,10 @@ onMounted(fetchList); -
Dataset Name Images Label Type - Base Path + + Description Action @@ -157,11 +179,28 @@ onMounted(fetchList); {{ r.ds_dataset_name }} {{ r.ds_dataset_image_count }} {{ r.labelling_tool_kr }} - {{ r.ds_dataset_base_path }} + + + + + + - Select + Select + @@ -177,4 +216,19 @@ onMounted(fetchList); Close + + + + + 알림 + {{ successMsg }} + + 확인 + + + diff --git a/src/components/models/management/ExternalAuthController.ts b/src/components/models/management/ExternalAuthController.ts new file mode 100644 index 0000000..8541eb2 --- /dev/null +++ b/src/components/models/management/ExternalAuthController.ts @@ -0,0 +1,13 @@ +export type EdgePkgInfoVOModel = { + sw_id: string; + sw_version: number; + sw_name: string; + auth_id: string; + edPkgSerial: number; + archiveType: 0 | 1; + execYn: 0 | 1; + secretAt: boolean; + downloadLocation: string; + user_id: string; + creation_datetime: string; +}; diff --git a/src/components/service/index.ts b/src/components/service/index.ts index c9f196d..0e42b69 100644 --- a/src/components/service/index.ts +++ b/src/components/service/index.ts @@ -53,6 +53,16 @@ export const request = { onUploadProgress: progress, }); }, + postWithConfig: (uri: string, data: any, config?: any): any => { + return axios.post(`${API_URL}${uri}`, data, config); + }, + postMultipartWithParams: (uri: string, form: FormData, params?: any): any => { + return axios.post(`${API_URL}${uri}`, form, { + params, + headers: { "Content-Type": "multipart/form-data" }, + }); + }, + postResponseFile: ( uri: string, param: any, diff --git a/src/components/service/management/DatasetService.ts b/src/components/service/management/DatasetService.ts index 34d2949..96acfce 100644 --- a/src/components/service/management/DatasetService.ts +++ b/src/components/service/management/DatasetService.ts @@ -28,8 +28,17 @@ export const DatasetService = { listExternal(query: ExternalListQuery) { return request.get("/api/datasets/list", query as any); }, - saveExternal(payload: SaveExternalDatasetPayload) { - return request.post("/api/datasets/dataset/save", payload); + const params = new URLSearchParams(); + ( + Object.entries(payload) as [keyof SaveExternalDatasetPayload, any][] + ).forEach(([k, v]) => { + if (v !== undefined && v !== null) params.append(k, String(v)); + }); + + return request.post( + `/api/datasets/dataset/save?${params.toString()}`, + null, + ); }, }; diff --git a/src/components/service/management/ExternalAuthControllerService.ts b/src/components/service/management/ExternalAuthControllerService.ts index 6952f78..3dce57f 100644 --- a/src/components/service/management/ExternalAuthControllerService.ts +++ b/src/components/service/management/ExternalAuthControllerService.ts @@ -1,3 +1,4 @@ +import { EdgePkgInfoVOModel } from "@/components/models/management/ExternalAuthController"; import { request } from "@/components/service/index"; export const ExternalAuthControllerService = { @@ -5,11 +6,11 @@ export const ExternalAuthControllerService = { return request.post("/api/external-auth/signin", { id, password }); }, - add: (token: string, edgePkgInfoVO: string, file: any) => { - return request.post("/api/external-auth/add", { - token, - edgePkgInfoVO, - file, + add: (params: EdgePkgInfoVOModel, file: File | Blob) => { + const fd = new FormData(); + fd.append("file", file); + return request.postWithConfig("/api/external-auth/register-with-file", fd, { + params, }); }, diff --git a/src/components/templates/Datasets/ListComponent.vue b/src/components/templates/Datasets/ListComponent.vue index 824caa3..265d180 100644 --- a/src/components/templates/Datasets/ListComponent.vue +++ b/src/components/templates/Datasets/ListComponent.vue @@ -69,7 +69,6 @@ const data = ref({ allSelected: false, selected: [] as Array<{ deviceKey: number }>, isCreateVisible: false, - isExternalVisible: false, isUploadVisible: false, isModalVisible: false, isConfirmDialogVisible: false, @@ -574,9 +573,10 @@ watch( diff --git a/src/components/templates/Datasets/ViewComponent.vue b/src/components/templates/Datasets/ViewComponent.vue index cebca34..5d8a7ac 100644 --- a/src/components/templates/Datasets/ViewComponent.vue +++ b/src/components/templates/Datasets/ViewComponent.vue @@ -23,58 +23,75 @@ const experimentInfo = ref({ fileSize: "-", }); -const downloadObjectName = computed(() => detailRaw.value?.storagePath || ""); -const canDownload = computed(() => !!downloadObjectName.value); +// storagePath가 경로(슬래시 포함)이면 우선 사용, 아니면 storedName 사용 +const downloadObjectName = computed(() => { + const sp = detailRaw.value?.storagePath as string | undefined; + const sn = detailRaw.value?.storedName as string | undefined; + const raw = sp && /[\\/]/.test(sp) ? sp : sn || sp || ""; + return (raw || "").replace(/^\/+/, "").replace(/\/{2,}/g, "/"); +}); async function handleDownload() { - const key = downloadObjectName.value.trim(); + const key = downloadObjectName.value; if (!key) return; - const res = await AttachmentsService.downloadFile(key); + try { + // 1차: 정규화된 키로 시도 (경로형/파일명형 모두 커버) + const res = await AttachmentsService.downloadFile(key); - const ct = String(res.headers["content-type"] || "").toLowerCase(); - if (ct.includes("application/json")) { - const text = await (res.data as Blob).text(); - try { - const json = JSON.parse(text); - throw new Error(json.message || text); - } catch { - throw new Error(text); + // 서버가 에러(JSON) 보낸 경우 감지 + const ct = String(res.headers["content-type"] || "").toLowerCase(); + if (ct.includes("application/json")) { + const text = await (res.data as Blob).text(); + try { + const json = JSON.parse(text); + throw new Error(json.message || text); + } catch { + throw new Error(text); + } } - } - const cd = res.headers["content-disposition"] || ""; - let filename: string | undefined; - const mUtf8 = cd.match(/filename\*\s*=\s*UTF-8''([^;]+)/i); - const mStd = cd.match(/filename\s*=\s*(?:"([^"]+)"|([^;]+))/i); - if (mUtf8?.[1]) { - try { - filename = decodeURIComponent(mUtf8[1].trim()); - } catch { - filename = mUtf8[1].trim(); - } - } else if (mStd) { - filename = (mStd[1] || mStd[2])?.trim(); - } + // 파일명 추출(헤더 없으면 basename으로) + const cd = res.headers["content-disposition"] || ""; + const mUtf8 = cd.match(/filename\*\s*=\s*UTF-8''([^;]+)/i); + const mStd = cd.match(/filename\s*=\s*(?:"([^"]+)"|([^;]+))/i); + let filename = + (mUtf8 && decodeURIComponent(mUtf8[1].trim())) || + (mStd && (mStd[1] || mStd[2])?.trim()) || + key.split(/[\\/]/).pop() || + "download.bin"; - // 2) 헤더가 없거나 못 읽으면: objectName의 basename으로 강제(fallback) - if (!filename) { - // key 예: "11fc4121-...-mlflow_pipeline.yaml" 또는 "dir/subdir/11fc...yaml" - const parts = key.split(/[\\/]/); - filename = parts[parts.length - 1] || "download.bin"; + const blob = new Blob([res.data]); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.setAttribute("download", filename); + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); + } catch (err) { + // 2차 폴백: 혹시나 경로형이 안 맞으면 basename으로 재시도 + const base = key.split(/[\\/]/).pop() || key; + if (base && base !== key) { + const res2 = await AttachmentsService.downloadFile(base); + const blob = new Blob([res2.data]); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.setAttribute("download", base); + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); + return; + } + throw err; } - - const blob = new Blob([res.data]); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.setAttribute("download", filename); - document.body.appendChild(a); - a.click(); - a.remove(); - URL.revokeObjectURL(url); } +const canDownload = computed(() => !!downloadObjectName.value); + // -------- utils -------- const formatIso = (s?: string) => s ? String(s).replace("T", " ").slice(0, 19) : "-"; diff --git a/src/components/templates/home/ListComponent.vue b/src/components/templates/home/ListComponent.vue index a233ba4..48ebc72 100644 --- a/src/components/templates/home/ListComponent.vue +++ b/src/components/templates/home/ListComponent.vue @@ -369,25 +369,23 @@ async function loadDatasetActivity() { })) .filter((g) => Number.isFinite(g.id)); - // 초기화 datasetsByGroup.value = {}; groupLoaded.value = {}; groupLoading.value = {}; datasetCountByGroup.value = {}; - // ✅ 여기서 그룹별 첨부(데이터셋) 개수만 미리 가져오기 + // (선택) 개수만 먼저 계산 const tasks = groupSummaries.value.map((g) => AttachmentsService.search({ projectId: currentProjectId.value, page: 0, - size: 1, // 내용은 1개만, 총 개수만 쓰기 위함 + size: 1, refType: "DATASET", refId: g.id, sortField: "id", sortDirection: "DESC", } as any) .then((res: any) => { - // 백엔드 페이징 키 여러 가능성 고려 const total = Number( res?.data?.totalElements ?? res?.data?.total ?? @@ -406,8 +404,12 @@ async function loadDatasetActivity() { }; }), ); - await Promise.allSettled(tasks); + + // ✅ 아코디언 제거 → 모두 펼쳐서 보여주므로 미리 로드 + await Promise.all( + groupSummaries.value.map((g) => loadDatasetsForGroup(g.id, g.name)), + ); } catch (err) { console.error("[Dashboard] loadDatasetActivity error:", err); groupSummaries.value = []; @@ -677,7 +679,7 @@ watch(showKubeflowDetails, async () => { - +
{
-
+
{ - +
{
-
+
- +

@@ -859,7 +861,7 @@ watch(showKubeflowDetails, async () => {

-
+
{ - +

- Dataset Update Activity + DataGroup Update Activity

{ - + diff --git a/src/components/templates/run/executions/ViewComponent.vue b/src/components/templates/run/executions/ViewComponent.vue index 0e6c9d6..9ff3235 100644 --- a/src/components/templates/run/executions/ViewComponent.vue +++ b/src/components/templates/run/executions/ViewComponent.vue @@ -12,9 +12,10 @@ import { } from "vue"; import Plotly from "plotly.js-dist-min"; import CompareRunsDialog from "@/components/atoms/organisms/CompareRunDialog.vue"; +import DeploymentDialog from "@/components/atoms/organisms/DeploymentDialog.vue"; /* ========= Constants & Types ========= */ const AUTH_KEY = "external-auth"; - +const externalToken = computed(() => externalAuth.value?.token ?? ""); type ExternalAuth = { id: string; name: string; token: string }; type PackageOption = { label: string; value: string; raw: any }; type MetricKV = { key: string; value: number }; @@ -679,7 +680,12 @@ const openDeploymentModal = async (fullPath?: string) => { const closeCreateModal = () => { isEditVisible.value = false; }; - +const saveData = (payload: any) => { + // 필요 시 서버에 저장/리프레시 등 처리 + console.log("[DeploymentDialog payload]", payload); + // 완료 후 모달 닫기 + isEditVisible.value = false; +}; /* ========= Timeline (fallback) ========= */ const rawHistory = computed(() => { const h = @@ -1433,12 +1439,12 @@ const artifactsLoading = ref(false); :packages-loading="packagesLoading" :packages-error="packagesError" :artifact-path="lastArtifactUri" + :token="externalToken" @close-modal="closeCreateModal" - @handle-data="() => {}" + @handle-data="saveData" :user-option="[]" /> -