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="[]"
/>
-