diff --git a/src/components/atoms/organisms/WorkflowsCreateDialog.vue b/src/components/atoms/organisms/WorkflowsCreateDialog.vue
index 21395c8..45e5c49 100644
--- a/src/components/atoms/organisms/WorkflowsCreateDialog.vue
+++ b/src/components/atoms/organisms/WorkflowsCreateDialog.vue
@@ -4,7 +4,11 @@ import IconArrowUp from "@/components/button/IconArrowUp.vue";
import IconDeleteBtn from "@/components/button/IconDeleteBtn.vue";
import IconModifyBtn from "@/components/button/IconModifyBtn.vue";
import { ref, watch } from "vue";
-
+import { AutoflowService } from "@/components/service/management/AutoflowService";
+import { storage } from "@/utils/storage";
+import { Workflow } from "@/components/models/management/Autoflow";
+const saving = ref(false);
+const errorMsg = ref("");
const steps = ref([
{ order: 1, stepName: "Data Load", type: "DataPrep", status: "Configured" },
{
@@ -27,7 +31,7 @@ const props = defineProps({
userOption: Array,
});
-const emit = defineEmits(["handle-data", "close-modal"]);
+const emit = defineEmits(["handle-data", "close-modal", "saved"]);
const visible = ref(true);
@@ -36,8 +40,55 @@ const form = ref({
description: "",
});
-const submit = () => {
- emit("handle-data", form.value);
+const nowLocalIso = (): string => {
+ const t = new Date(Date.now() - new Date().getTimezoneOffset() * 60000);
+ return t.toISOString().slice(0, 23); // 밀리초까지 포함, 끝의 'Z' 제거
+};
+const now = nowLocalIso();
+const submit = async () => {
+ errorMsg.value = "";
+ if (!form.value.name.trim()) {
+ errorMsg.value = "Workflow Name은 필수입니다.";
+ return;
+ }
+
+ const authObj =
+ (typeof storage?.getAuth === "function" ? storage.getAuth() : null) ??
+ JSON.parse(localStorage.getItem("autoflow-auth") || "{}");
+
+ // 2) username 추출 (userinfo / userInfo 모두 대응)
+ const regUserId =
+ authObj?.userInfo?.username ??
+ authObj?.userinfo?.username ??
+ authObj?.username ??
+ authObj?.userId ??
+ "";
+
+ if (!regUserId) {
+ errorMsg.value = "로그인 사용자 정보를 찾을 수 없습니다.";
+ return;
+ }
+
+ const payload: Workflow = {
+ workflowName: form.value.name.trim(),
+ workflowDescription: form.value.description?.trim() || "",
+ uploadYn: "Y",
+ regUserId,
+ regDt: now,
+ modDt: now,
+ };
+
+ try {
+ saving.value = true;
+ const { data } = await AutoflowService.add(payload);
+ emit("saved", data);
+ emit("close-modal");
+ } catch (e) {
+ console.error("워크플로우 저장 실패:", e);
+ errorMsg.value = "저장에 실패했습니다. 잠시 후 다시 시도하세요.";
+ } finally {
+ saving.value = false;
+ }
};
diff --git a/src/components/common/LayoutComponent.vue b/src/components/common/LayoutComponent.vue
index 8529562..6c95356 100644
--- a/src/components/common/LayoutComponent.vue
+++ b/src/components/common/LayoutComponent.vue
@@ -5,6 +5,15 @@ import DrawerComponent from "@/components/common/DrawerComponent.vue";
import { watchEffect } from "vue";
import Select from "@/views/Select.vue";
import { UserManagerService } from "@/components/service/management/userManagerService";
+
+import { storeToRefs } from "pinia";
+import { useAutoflowStore } from "@/stores/autoflowStore";
+
+const autoflow = useAutoflowStore();
+const { ProjectName } = storeToRefs(autoflow);
+const showPasswordModal = ref(false);
+const selectedUserData = ref({});
+
const route = useRoute();
const router = useRouter();
@@ -62,15 +71,12 @@ const goSelect = () => {
};
const logOut = () => {
- // 1) 서버에 로그아웃 API 호출
UserManagerService.signOut()
.catch((err) => {
- // 403 Forbidden 이든 네트워크 에러든,
- // 일단 콘솔에 찍어 보고
console.error("logout API failed:", err);
})
.finally(() => {
- // 2) 로컬 토큰 삭제 & 로그인 페이지 이동
+ autoflow.setProjectName("");
storage.clearAuth();
router.push("/login");
});
@@ -79,9 +85,6 @@ const goHome = () => {
router.push("/main");
};
-const showPasswordModal = ref(false);
-const selectedUserData = ref({});
-
watchEffect(() => {
// const auth = storage.getAuth().auth;
// if (auth === "ADMIN") {
@@ -130,7 +133,9 @@ watchEffect(() => {
ADMIN_001
-
No Project Selected
+
+ {{ ProjectName || "No Project Selected" }}
+
mdi-arrow-down-drop-circle-outline
diff --git a/src/components/models/management/Autoflow.ts b/src/components/models/management/Autoflow.ts
new file mode 100644
index 0000000..8fc7695
--- /dev/null
+++ b/src/components/models/management/Autoflow.ts
@@ -0,0 +1,8 @@
+export interface Workflow {
+ workflowName: string;
+ workflowDescription?: string;
+ uploadYn: "Y" | "N";
+ regUserId: string;
+ regDt: string;
+ modDt: string;
+}
diff --git a/src/components/service/index.ts b/src/components/service/index.ts
index 7d677ea..d923c50 100644
--- a/src/components/service/index.ts
+++ b/src/components/service/index.ts
@@ -74,17 +74,8 @@ export const request = {
});
},
postNoParam: (uri: string): any => {
- // axios.defaults.withCredentials=true 이 켜져 있으니
- // Set-Cookie 헤더로 내려오는 쿠키를 자동 저장합니다.
return axios.post(`${API_URL}${uri}`, null);
},
- // postOptimization: (uri: string, param: any): any => {
- // return axios.post(`${PYTHON_API_URL}${uri}`, param);
- // },
-
- // postPython: (uri: string, param: any): Promise => {
- // return axios.post(`${PYTHON_API_URL}${uri}`, param);
- // },
};
// axios.defaults.withCredentials = true;
diff --git a/src/components/service/management/AutoflowService.ts b/src/components/service/management/AutoflowService.ts
new file mode 100644
index 0000000..e677e18
--- /dev/null
+++ b/src/components/service/management/AutoflowService.ts
@@ -0,0 +1,12 @@
+import { Workflow } from "@/components/models/management/Autoflow";
+import { request } from "@/components/service/index";
+export const AutoflowService = {
+ add: (payload: Workflow) => {
+ return request.post("/api/workflows", payload);
+ },
+ getAll: () => request.get("/api/workflows", {}),
+
+ delete: (id: Number) => {
+ return request.delete(`/api/workflows/${id}`, {});
+ },
+};
diff --git a/src/components/service/management/userManagerService.ts b/src/components/service/management/userManagerService.ts
index 4b1bae9..51407e4 100644
--- a/src/components/service/management/userManagerService.ts
+++ b/src/components/service/management/userManagerService.ts
@@ -22,6 +22,6 @@ export const UserManagerService = {
return request.post("/management/user/update", param);
},
delete: (param: User) => {
- return request.post("/management/user/delete", param);
+ return request.delete("/management/user/delete", param);
},
};
diff --git a/src/components/workflow/ListComponent.vue b/src/components/workflow/ListComponent.vue
index a0beba8..22e46ef 100644
--- a/src/components/workflow/ListComponent.vue
+++ b/src/components/workflow/ListComponent.vue
@@ -7,7 +7,9 @@ import { onMounted, ref, watch } from "vue";
import ViewComponent from "@/components/workflow/ViewComponent.vue";
import WorkflowsCreateDialog from "@/components/atoms/organisms/WorkflowsCreateDialog.vue";
import WorkflowsUploadDialog from "@/components/atoms/organisms/WorkflowsUploadDialog.vue";
-// const store = commonStore();
+import { AutoflowService } from "../service/management/AutoflowService";
+import { commonStore } from "@/stores/commonStore";
+const store = commonStore();
const openView = ref(false);
const openModify = ref(false);
@@ -120,91 +122,56 @@ const getCodeList = () => {
// });
};
+const toRow = (w: any, i: number, offset: number) => ({
+ no: offset + i + 1,
+ name: w.workflowName,
+ version: w.version ?? "v1.0",
+ stepCount: w.stepCount ?? w.steps?.length ?? 0,
+ configProgress: `${w.configDone ?? 0}/${w.configTotal ?? w.steps?.length ?? 0}`,
+ kubeflowStatus: w.uploadYn === "Y" ? "Uploaded" : "Not Uploaded",
+ registDt: w.regDt ?? w.regDate ?? "-",
+ deviceKey: w.id ?? w.workflowId ?? offset + i,
+});
+
const getData = () => {
- const params = { ...data.value.params };
- if (params.searchType === "" || params.searchText === "") {
- delete params.searchType;
- delete params.searchText;
- }
- data.value.results = [
- {
- no: 5,
- name: "sentiment-analysis",
- version: "v2.0",
- stepCount: 2,
- configProgress: "0/2",
- kubeflowStatus: "Not Uploaded",
- registDt: "2025-06-10T00:00:00Z",
- },
- {
- no: 4,
- name: "image-classfier",
- version: "v2.0",
- stepCount: 3,
- configProgress: "1/3",
- kubeflowStatus: "Not Uploaded",
- registDt: "2025-06-09T00:00:00Z",
- },
- {
- no: 3,
- name: "user-clustering",
- version: "v1.0",
- stepCount: 3,
- configProgress: "0/3",
- kubeflowStatus: "Not Uploaded",
- registDt: "2025-06-01T00:00:00Z",
- },
- {
- no: 2,
- name: "time-series-train",
- version: "v1.0",
- stepCount: 2,
- configProgress: "1/3",
- kubeflowStatus: "Not Uploaded",
- registDt: "2025-05-29T00:00:00Z",
- },
- {
- no: 1,
- name: "customer-churn-pred",
- version: "v1.0",
- stepCount: 3,
- configProgress: "0/3",
- kubeflowStatus: "Not Uploaded",
- registDt: "2025-05-31T00:00:00Z",
- },
- ];
- data.value.totalDataLength = 5;
- // DeviceService.search(params).then((d) => {
- // if (d.status === 200) {
- // data.value.results = d.data.deviceList;
- // data.value.totalDataLength = d.data.totalCount;
- // setTimeout(() => {
- // setPaginationLength();
- // }, 200);
- // } else {
- // store.setSnackbarMsg({
- // text: "디바이스 조회 실패",
- // color: "error",
- // });
- // }
- // });
- // DeviceService.search().then((d) => {
- // data.value.totalDataLength = d.data.totalCount;
- // setTimeout(() => {
- // setPaginationLength();
- // }, 200);
- // });
-};
+ const params = data.value.params;
+ const pageNum = params.pageNum;
+ const pageSize = params.pageSize;
+ const startIndex = (pageNum - 1) * pageSize;
+ AutoflowService.getAll()
+ .then((res) => {
+ if (res.status !== 200) {
+ console.error("워크플로우 조회 실패:", res);
+
+ return;
+ }
+ const result = res.data;
+ console.log(result);
+ const rawList = Array.isArray(result) ? result : (result.content ?? []);
+ const totalCount = Array.isArray(result)
+ ? rawList.length
+ : (result.totalElements ?? rawList.length);
+ const currentPageList = Array.isArray(result)
+ ? rawList.slice(startIndex, startIndex + pageSize)
+ : rawList;
+
+ data.value.results = currentPageList.map((w: any, i: number) =>
+ toRow(w, i, startIndex),
+ );
+ data.value.totalDataLength = totalCount;
+
+ setPaginationLength();
+ })
+ .catch((err) => {
+ console.error("워크플로우 조회 에러:", err);
+ });
+};
const setPaginationLength = () => {
- if (data.value.totalDataLength % data.value.params.pageSize === 0) {
- data.value.pageLength =
- data.value.totalDataLength / data.value.params.pageSize;
- } else {
- data.value.pageLength = Math.ceil(
- data.value.totalDataLength / data.value.params.pageSize,
- );
- }
+ const total = data.value.totalDataLength || 0;
+ const pageSize = data.value.params.pageSize || 10;
+ data.value.pageLength =
+ total % pageSize === 0 ? total / pageSize : Math.ceil(total / pageSize);
};
const saveData = (formData) => {
@@ -244,42 +211,68 @@ const saveData = (formData) => {
};
const removeData = (value) => {
- let removeList = value ? value : data.value.selected;
- const remove = (code) => {
- // return DeviceService.delete(code).then((d) => {
- // if (d.status !== 200) {
- // store.setSnackbarMsg({
- // text: d,
- // result: 500,
- // });
- // }
- // });
- };
+ const removeList = value ?? data.value.selected;
+ if (!removeList || removeList.length === 0) return;
- if (removeList.length === 1) {
- remove(removeList[0].deviceKey).then(() => {
- // store.setSnackbarMsg({
- // text: "삭제되었습니다.",
- // result: 200,
- // });
- changePageNum();
- data.value.isConfirmDialogVisible = false;
- data.value.selected = [];
- data.value.allSelected = false;
+ const ids = removeList.map((x) => x.deviceKey);
+
+ const remove = (id) =>
+ AutoflowService.delete(id).then((res) => {
+ if (res.status < 200 || res.status >= 300) {
+ return Promise.reject(res);
+ }
});
+
+ const after = () => {
+ if (
+ ids.length >= data.value.results.length &&
+ data.value.params.pageNum > 1
+ ) {
+ data.value.params.pageNum -= 1;
+ }
+ getData();
+
+ data.value.isConfirmDialogVisible = false;
+ data.value.selected = [];
+ data.value.allSelected = false;
+ };
+
+ if (ids.length === 1) {
+ remove(ids[0])
+ .then(() => {
+ store.setSnackbarMsg({
+ color: "success",
+ text: "삭제되었습니다.",
+ result: 200,
+ });
+ after();
+ })
+ .catch((err) => {
+ store.setSnackbarMsg({
+ color: "warning",
+ text: "삭제 실패",
+ result: 500,
+ });
+ console.error(err);
+ });
} else {
- Promise.all(removeList.map((item) => remove(item.deviceKey))).finally(
- () => {
- // store.setSnackbarMsg({
- // text: "모두 삭제되었습니다.",
- // result: 200,
- // });
- changePageNum();
- data.value.isConfirmDialogVisible = false;
- data.value.selected = [];
- data.value.allSelected = false;
- },
- );
+ Promise.all(ids.map(remove))
+ .then(() => {
+ store.setSnackbarMsg({
+ color: "success",
+ text: "모두 삭제되었습니다.",
+ result: 200,
+ });
+ })
+ .catch((err) => {
+ store.setSnackbarMsg({
+ color: "warning",
+ text: "일부 삭제 실패",
+ result: 500,
+ });
+ console.error(err);
+ })
+ .finally(after);
}
};
@@ -331,11 +324,11 @@ const openUploadModal = () => {
};
const closeCreateModal = () => {
data.value.isModalVisible = false;
- data.value.isCreateVisible = null;
+ data.value.isCreateVisible = false;
};
const closeUploadModal = () => {
data.value.isModalVisible = false;
- data.value.isUploadVisible = null;
+ data.value.isUploadVisible = false;
};
const getSelectedAllData = () => {
@@ -347,7 +340,19 @@ const getSelectedAllData = () => {
})
: [];
};
+watch(
+ () => data.value.isCreateVisible,
+ (now, prev) => {
+ if (prev && !now) getData();
+ },
+);
+watch(
+ () => data.value.isUploadVisible,
+ (now, prev) => {
+ if (prev && !now) getData();
+ },
+);
onMounted(() => {
getData();
getCodeList();
@@ -356,22 +361,6 @@ onMounted(() => {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{
:total-visible="10"
color="primary"
rounded="circle"
- @update:model-value="getData"
+ @update:model-value="changePageNum"
>
diff --git a/src/pages/LoginView.vue b/src/pages/LoginView.vue
index e067008..5231b87 100644
--- a/src/pages/LoginView.vue
+++ b/src/pages/LoginView.vue
@@ -1,4 +1,5 @@