Workflow API 추가(수정 x)

main
jschoi 10 months ago
parent fe1d252c5c
commit f4ea2f30b7

@ -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;
}
};
</script>

@ -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(() => {
</v-tooltip>
<div style="min-width: 180px" class="d-flex flex-column align-end">
<div class="font-weight-black">ADMIN_001</div>
<div class="text-subtitle-2">No Project Selected</div>
<div class="text-subtitle-2">
{{ ProjectName || "No Project Selected" }}
</div>
</div>
<v-btn icon color="primary" v-bind="props" class="mr-3">
<v-icon>mdi-arrow-down-drop-circle-outline</v-icon>

@ -0,0 +1,8 @@
export interface Workflow {
workflowName: string;
workflowDescription?: string;
uploadYn: "Y" | "N";
regUserId: string;
regDt: string;
modDt: string;
}

@ -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<any> => {
// return axios.post(`${PYTHON_API_URL}${uri}`, param);
// },
};
// axios.defaults.withCredentials = true;

@ -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}`, {});
},
};

@ -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);
},
};

@ -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;
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;
}
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 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) {
const total = data.value.totalDataLength || 0;
const pageSize = data.value.params.pageSize || 10;
data.value.pageLength =
data.value.totalDataLength / data.value.params.pageSize;
} else {
data.value.pageLength = Math.ceil(
data.value.totalDataLength / data.value.params.pageSize,
);
}
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;
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();
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;
};
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(() => {
<template>
<div class="w-100" v-if="!openView">
<!-- <v-dialog v-model="data.isModalVisible" max-width="600" persistent>-->
<!-- <FormComponent-->
<!-- :edit-data="data.selectedData"-->
<!-- :mode="data.modalMode"-->
<!-- @close-modal="closeModal"-->
<!-- @handle-data="saveData"-->
<!-- :user-option="data.userOption"-->
<!-- />-->
<!-- </v-dialog>-->
<!-- <v-dialog v-model="data.isConfirmDialogVisible" persistent max-width="300">-->
<!-- <ConfirmDialogComponent-->
<!-- @cancel="data.isConfirmDialogVisible = false"-->
<!-- @delete="removeData(undefined)"-->
<!-- @init="(data.selected = []), (data.allSelected = false)"-->
<!-- />-->
<!-- </v-dialog>-->
<v-container fluid class="h-100 pa-5 d-flex flex-column align-center">
<v-card
flat
@ -547,7 +536,7 @@ onMounted(() => {
:total-visible="10"
color="primary"
rounded="circle"
@update:model-value="getData"
@update:model-value="changePageNum"
></v-pagination>
</v-card-actions>
</v-col>

@ -1,4 +1,5 @@
<script setup>
import axios from "axios";
import { ref } from "vue";
import { useRouter } from "vue-router";
import { storage } from "@/utils/storage";
@ -6,7 +7,8 @@ import logo from "@/assets/wordmark.png";
import logo2 from "@/assets/workflow.png";
import { UserManagerService } from "@/components/service/management/userManagerService";
import { TokenService } from "@/components/service/token/tokenService";
const API_URL = import.meta.env.VITE_APP_API_SERVER_URL;
const router = useRouter();
const data = ref({
@ -44,14 +46,12 @@ const signIn = () => {
};
UserManagerService.signIn(payload)
// .then((res) => TokenService.refreshToken().then(() => res))
.then((res) => {
// 200 OK
// ( )
storage.setAuth(res.data);
router.push("/select");
})
.catch((err) => {
// 401 Unauthorized
if (err.response?.status === 401) {
data.value.snackbarText = "아이디 또는 비밀번호가 올바르지 않습니다.";
} else {

@ -0,0 +1,15 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useAutoflowStore = defineStore("autoflowStore", () => {
const ProjectName = ref<String>("");
const setProjectName = (v) => {
ProjectName.value = v;
};
return {
ProjectName,
setProjectName,
};
});

@ -0,0 +1,96 @@
export const CommonFunctions = {
stringTruncate: (str: string, len: number) => {
return str.length > len ? str.slice(0, len) + "..." : str;
},
getFileExtension: (filename: string) => {
const index = filename.lastIndexOf(".");
return index !== -1 ? filename.substring(index + 1).toLowerCase() : null;
},
getDateFormatYmd: (date: string) => {
if (date != null && date != "") {
const inputDate = new Date(date);
const year = inputDate.getFullYear();
const month = (inputDate.getMonth() + 1).toString().padStart(2, "0");
const day = inputDate.getDate().toString().padStart(2, "0");
const formattedDate = `${year}.${month}.${day}`;
return formattedDate;
} else {
return "";
}
},
getDateFormatHis: (date: string) => {
if (date != null && date != "") {
const timeMatch = date.match(/\b\d{2}:\d{2}:\d{2}\b/);
if (timeMatch) {
return timeMatch[0];
} else {
return "";
}
} else {
return "";
}
},
getQuantumDateFormat: (dateString: string) => {
if (dateString != null && dateString != "" && dateString != "-") {
const date = new Date(dateString);
// 날짜와 시간을 각각 포맷팅
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}.${month}.${day} ${hours}:${minutes}:${seconds}`;
} else {
return "-";
}
dateString;
},
getQuantumDateFormatToCustom: (dateString: string) => {
if (dateString != null && dateString != "" && dateString != "-") {
const date = new Date(dateString);
// toLocaleString을 사용해 형식 지정
return date.toLocaleString("en-US", {
month: "short", // Oct
day: "2-digit", // 07
year: "numeric", // 2024
hour: "2-digit", // 10
minute: "2-digit", // 37
hour12: true, // AM/PM 형식
});
} else {
return "-";
}
},
getTimeDifferenceInSeconds: (dateString1: string, dateString2: string) => {
const date1 = new Date(dateString1);
const date2 = new Date(dateString2);
// 두 날짜의 밀리초 차이 계산
const differenceInMs = Math.abs(date1.getTime() - date2.getTime());
// 밀리초 차이를 초 단위로 변환
const differenceInSeconds = Math.floor(differenceInMs / 1000);
return differenceInSeconds;
},
getJobChartData: (data: string[]) => {
const binaryMapping: Record<string, string> = {
"0x0": "00",
"0x1": "01",
"0x2": "10",
"0x3": "11",
};
const frequency: Record<string, number> = {};
data.forEach((d) => {
const binary = binaryMapping[d] || "00";
frequency[binary] = (frequency[binary] || 0) + 1;
});
return frequency;
},
};

@ -2,6 +2,8 @@
import { ApiProject, UiProject } from "@/components/models/project/Project";
import { UserManagerService } from "@/components/service/management/userManagerService";
import { ProjectService } from "@/components/service/project/projectService";
import { useAutoflowStore } from "@/stores/autoflowStore";
import { storeToRefs } from "pinia";
import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
@ -14,6 +16,7 @@ const projects = ref<UiProject[]>([]);
const userOptions = ref<string[]>([]);
const modalMode = ref<"create" | "edit">("create");
const editingProjectId = ref<number | null>(null);
const autoflowStore = useAutoflowStore();
const router = useRouter();
const form = ref({
prjCd: "",
@ -40,7 +43,6 @@ const loadProjects = async (): Promise<void> => {
const loadUsers = async () => {
try {
const res = await UserManagerService.getAll();
// res.data [{id, username, }, ]
userOptions.value = res.data.map((u: any) => u.username);
} catch (err) {
console.error("사용자 조회 실패:", err);
@ -49,6 +51,7 @@ const loadUsers = async () => {
const selectProject = (idx: number): void => {
const p = projects.value[idx];
autoflowStore.setProjectName(p.title);
router.push("/home");
};
@ -121,11 +124,10 @@ const modifyProject = () => {
const p = projects.value[idx];
modalMode.value = "edit";
editingProjectId.value = p.id;
//
form.value.prjCd = p.title; // title prjCd
form.value.prjCd = p.title;
form.value.prjNm = p.title;
form.value.prjDesc = p.description;
form.value.selectedUsers = p.creator.split(","); //
form.value.selectedUsers = p.creator.split(",");
dialog.value = true;
};
const cancel = () => {

@ -15,7 +15,7 @@ import { fileURLToPath, URL } from "node:url";
// https://vitejs.dev/config/
export default defineConfig({
// 배포할때는 주석 풀기
base: "/autoflow/",
// base: "/autoflow/",
plugins: [
VueRouter(),
Layouts(),

Loading…
Cancel
Save