Merge branch 'feature/main-js' of http://192.168.10.110/Autoflow/autoflow-web-console into feature/main-js

main
bjkim 9 months ago
commit 446b6c2f00

1
components.d.ts vendored

@ -32,6 +32,7 @@ declare module 'vue' {
TrainingScriptBaseDoalog: typeof import('./src/components/atoms/organisms/TrainingScriptBaseDoalog.vue')['default']
ViewComponent: typeof import('./src/components/templates/Datasets/ViewComponent.vue')['default']
WorkflowDialog: typeof import('./src/components/atoms/organisms/WorkflowDialog.vue')['default']
WorkflowsBaseDialog: typeof import('./src/components/atoms/organisms/WorkflowsBaseDialog.vue')['default']
WorkflowsCreateDialog: typeof import('./src/components/atoms/organisms/WorkflowsCreateDialog.vue')['default']
WorkflowsUploadDialog: typeof import('./src/components/atoms/organisms/WorkflowsUploadDialog.vue')['default']
}

7
package-lock.json generated

@ -11,6 +11,7 @@
"@fontsource/roboto": "5.2.5",
"@mdi/font": "7.4.47",
"axios": "^1.11.0",
"dayjs": "^1.11.18",
"monaco-editor": "^0.52.2",
"plotly.js-dist-min": "^3.0.1",
"prettier": "^3.5.3",
@ -2640,6 +2641,12 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/dayjs": {
"version": "1.11.18",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
"integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",

@ -13,6 +13,7 @@
"@fontsource/roboto": "5.2.5",
"@mdi/font": "7.4.47",
"axios": "^1.11.0",
"dayjs": "^1.11.18",
"monaco-editor": "^0.52.2",
"plotly.js-dist-min": "^3.0.1",
"prettier": "^3.5.3",

@ -42,6 +42,15 @@ const onSave = () => {
const onClose = () => {
emit("update:modelValue", false);
};
function onEsc(e: KeyboardEvent) {
if (e.key === "Escape") {
emit("update:modelValue");
}
}
onMounted(() => window.addEventListener("keydown", onEsc));
onBeforeUnmount(() => window.removeEventListener("keydown", onEsc));
</script>
<template>

@ -3,15 +3,31 @@ import IconArrowDown from "@/components/atoms/button/IconArrowDown.vue";
import IconArrowUp from "@/components/atoms/button/IconArrowUp.vue";
import IconDeleteBtn from "@/components/atoms/button/IconDeleteBtn.vue";
import IconModifyBtn from "@/components/atoms/button/IconModifyBtn.vue";
import { ref, watch } from "vue";
import { computed, onBeforeUnmount, onMounted, watch, ref } from "vue";
import { AutoflowService } from "@/components/service/management/AutoflowService";
import { storage } from "@/utils/storage";
import { Workflow } from "@/components/models/management/Autoflow";
import type { Workflow } from "@/components/models/management/Autoflow";
import { storeToRefs } from "pinia";
import { useAutoflowStore } from "@/stores/autoflowStore";
const { projectId } = storeToRefs(useAutoflowStore());
const props = defineProps<{
editData?: any;
mode?: "create" | "edit";
userOption?: any[];
}>();
const emit = defineEmits<{
(e: "close-modal"): void;
(e: "saved", value: any): void;
}>();
const isEdit = computed(() => props.mode === "edit");
const saving = ref(false);
const errorMsg = ref("");
const { projectId } = storeToRefs(useAutoflowStore());
const steps = ref([
{ order: 1, stepName: "Data Load", type: "DataPrep", status: "Configured" },
{
@ -28,38 +44,49 @@ const steps = ref([
},
]);
const props = defineProps({
editData: Object,
mode: String,
userOption: Array,
});
const emit = defineEmits(["handle-data", "close-modal", "saved"]);
const visible = ref(true);
/** form state */
const form = ref({
name: "",
description: "",
});
/** props.editData -> form 바인딩 */
function hydrateFormFromEdit(data: any) {
if (!data) return;
form.value.name = data.workflowName ?? data.name ?? "";
form.value.description = data.workflowDescription ?? data.description ?? "";
}
onMounted(() => {
if (isEdit.value) hydrateFormFromEdit(props.editData);
});
watch(
() => props.editData,
(v) => {
if (isEdit.value) hydrateFormFromEdit(v);
},
);
/** 시간 포맷 */
const nowLocalIso = (): string => {
const t = new Date(Date.now() - new Date().getTimezoneOffset() * 60000);
return t.toISOString().slice(0, 23); // , 'Z'
return t.toISOString().slice(0, 23);
};
const now = nowLocalIso();
const submit = async () => {
async function submit() {
errorMsg.value = "";
if (!form.value.name.trim()) {
const name = form.value.name.trim();
if (!name) {
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 ??
@ -72,8 +99,9 @@ const submit = async () => {
return;
}
const now = nowLocalIso();
const payload: Workflow = {
workflowName: form.value.name.trim(),
workflowName: name,
workflowDescription: form.value.description?.trim() || "",
uploadYn: "Y",
regUserId,
@ -84,32 +112,57 @@ const submit = async () => {
try {
saving.value = true;
if (isEdit.value) {
// : id ( deviceKey id )
const rawId = props.editData?.id ?? props.editData?.deviceKey;
const id = Number(rawId);
if (!id) {
errorMsg.value = "수정할 ID가 없습니다.";
return;
}
const { data } = await AutoflowService.update(id, payload);
emit("saved", data);
emit("close-modal");
} else {
//
const { data } = await AutoflowService.add(payload);
emit("saved", data);
emit("close-modal");
}
} catch (e) {
console.error("워크플로우 저장 실패:", e);
errorMsg.value = "저장에 실패했습니다. 잠시 후 다시 시도하세요.";
} finally {
saving.value = false;
}
};
}
/** ESC로 닫기 */
function onEsc(e: KeyboardEvent) {
if (e.key === "Escape") emit("close-modal");
}
onMounted(() => window.addEventListener("keydown", onEsc));
onBeforeUnmount(() => window.removeEventListener("keydown", onEsc));
</script>
<template>
<v-card class="rounded-lg overflow-hidden">
<!-- 타이틀 영역 -->
<v-card>
<!-- 타이틀 -->
<v-card-title
class="text-white font-weight-bold text-h6"
style="background-color: #1976d2"
>
Create Workflow
{{ isEdit ? "Edit Workflow" : "Create Workflow" }}
</v-card-title>
<v-card-text class="pa-6">
<div class="text-subtitle-1 font-weight-medium mb-4">
Workflow Information
</div>
<v-form @submit.prevent="submit">
<div class="mb-5">
<label class="text-subtitle-2 font-weight-medium mb-1 d-block"
@ -118,6 +171,7 @@ const submit = async () => {
<v-text-field
v-model="form.name"
variant="outlined"
:disabled="saving"
dense
hide-details
required
@ -131,29 +185,40 @@ const submit = async () => {
<v-textarea
v-model="form.description"
variant="outlined"
:disabled="saving"
rows="3"
dense
hide-details
/>
</div>
<div v-if="errorMsg" class="mt-3 text-error">{{ errorMsg }}</div>
</v-form>
</v-card-text>
<v-card-text class="pt-6 pb-4 px-6">
<div class="text-subtitle-1 font-weight-medium mb-4">Workflow Steps</div>
<v-row class="align-center mb-4">
<v-col cols="auto">
<v-btn color="primary" small>
<v-icon left>mdi-plus</v-icon>
Add Step
</v-btn>
</v-col>
<v-col cols="auto">
<v-btn color="success" small>Save</v-btn>
</v-col>
<v-col cols="auto">
<v-btn color="grey" small>Cancel</v-btn>
</v-col>
<v-col cols="auto"
><v-btn color="primary" small :disabled="saving"
><v-icon left>mdi-plus</v-icon> Add Step</v-btn
></v-col
>
<v-col cols="auto"
><v-btn color="success" small :loading="saving" @click="submit">{{
isEdit ? "Update" : "Save"
}}</v-btn></v-col
>
<v-col cols="auto"
><v-btn
color="grey"
small
:disabled="saving"
@click="$emit('close-modal')"
>Cancel</v-btn
></v-col
>
<v-spacer />
</v-row>
@ -169,7 +234,7 @@ const submit = async () => {
</thead>
<tbody>
<tr
v-for="(step, i) in steps"
v-for="step in steps"
:key="step.order"
style="border: 1px solid #ccc"
>
@ -188,7 +253,6 @@ const submit = async () => {
<td class="text-center">
<IconArrowUp />
<IconArrowDown />
<IconModifyBtn />
<IconDeleteBtn />
</td>
@ -198,8 +262,14 @@ const submit = async () => {
</v-card-text>
<v-card-actions class="justify-end" style="padding: 16px 24px">
<v-btn color="success" @click="submit">Save</v-btn>
<v-btn text class="white--text" @click="$emit('close-modal')"
<v-btn color="success" :loading="saving" @click="submit">{{
isEdit ? "Update" : "Save"
}}</v-btn>
<v-btn
text
class="white--text"
:disabled="saving"
@click="$emit('close-modal')"
>Close</v-btn
>
</v-card-actions>

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
const props = defineProps({
editData: Object,
@ -22,6 +22,14 @@ const onChooseFile = () => {
const submit = () => {
emit("handle-data", form.value);
};
function onEsc(e: KeyboardEvent) {
if (e.key === "Escape") {
emit("close-modal");
}
}
onMounted(() => window.addEventListener("keydown", onEsc));
onBeforeUnmount(() => window.removeEventListener("keydown", onEsc));
</script>
<template>

@ -1,7 +1,6 @@
<script setup lang="ts">
import { ref, onMounted, computed } from "vue";
import { useRoute } from "vue-router";
import { useRouter } from "vue-router";
import { useRoute, useRouter } from "vue-router";
import { menuUtils } from "@/utils/menuUtils";
import { storage } from "@/utils/storage";
import logo from "@/assets/iteration (1).png";

@ -88,10 +88,12 @@ const logOut = () => {
UserManagerService.signOut()
.catch(console.error)
.finally(() => {
localStorage.removeItem("autoflow-auth");
localStorage.removeItem("projectName");
localStorage.removeItem("projectId");
username.value = "";
projectName.value = "";
sessionStorage.removeItem("initialRedirectDone");
router.push("/login");
});
};
@ -149,7 +151,7 @@ watchEffect(() => {
<v-spacer></v-spacer>
<v-menu location="bottom end">
<template v-slot:activator="{ props }">
<v-tooltip location="bottom" text="settings">
<v-tooltip location="bottom" text="Settings">
<template #activator="{ props }">
<v-btn icon color="primary" class="mr-3" v-bind="props">
<v-icon>mdi-cog</v-icon>

@ -4,7 +4,9 @@ export const AutoflowService = {
add: (payload: Workflow) => {
return request.post("/api/workflows", payload);
},
getAll: () => request.get("/api/workflows", {}),
getAll: () => {
return request.get("/api/workflows", {});
},
delete: (id: Number) => {
return request.delete(`/api/workflows/${id}`, {});
@ -12,4 +14,7 @@ export const AutoflowService = {
view: (id: Number) => {
return request.get(`/api/workflows/${id}`, {});
},
update: (id: number, payload: Workflow) => {
return request.put(`/api/workflows/${id}`, payload);
},
};

@ -0,0 +1,20 @@
import { Workflow } from "@/components/models/management/Autoflow";
import { request } from "@/components/service/index";
export const AutoflowStepService = {
add: (payload: Workflow) => {
return request.post("/api/workflow-steps", payload);
},
getAll: () => {
request.get("/api/workflow-steps", {});
},
delete: (id: Number) => {
return request.delete(`/api/workflow-steps${id}`, {});
},
view: (id: Number) => {
return request.get(`/api/workflow-steps${id}`, {});
},
update: (id: number, payload: Workflow) => {
return request.put(`/api/workflow-steps${id}`, payload);
},
};

@ -6,6 +6,7 @@ import IconInfoBtn from "@/components/atoms/button/IconInfoBtn.vue";
import { onMounted, ref, watch } from "vue";
import ViewComponent from "@/components/templates/stepconfig/ViewComponent.vue";
import StapComfigDialog from "@/components/atoms/organisms/StapComfigDialog.vue";
import { AutoflowStepService } from "@/components/service/management/AutoflowStepService";
// const store = commonStore();
const openView = ref(false);

@ -5,15 +5,17 @@ import IconSettingBtn from "@/components/atoms/button/IconSettingBtn.vue";
// import FormComponent from "@/components/device/FormComponent.vue";
import { onMounted, ref, watch } from "vue";
import ViewComponent from "@/components/templates/workflow/ViewComponent.vue";
import WorkflowsCreateDialog from "@/components/atoms/organisms/WorkflowsCreateDialog.vue";
import WorkflowsBaseDialog from "@/components/atoms/organisms/WorkflowsBaseDialog.vue";
import WorkflowsUploadDialog from "@/components/atoms/organisms/WorkflowsUploadDialog.vue";
import { AutoflowService } from "@/components/service/management/AutoflowService";
import { commonStore } from "@/stores/commonStore";
import IconInfoBtn from "@/components/atoms/button/IconInfoBtn.vue";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import tz from "dayjs/plugin/timezone";
const store = commonStore();
const openView = ref(false);
const openModify = ref(false);
const tableHeader = [
{
label: "No",
@ -111,6 +113,15 @@ const data = ref({
userOption: [],
});
dayjs.extend(utc);
dayjs.extend(tz);
const KST = "Asia/Seoul";
const formatDateTime = (
v?: string | number | Date,
fmt = "YYYY-MM-DD HH:mm:ss",
) => (v ? dayjs(v).tz(KST).format(fmt) : "");
const getCodeList = () => {
// UserService.search(data.value.params).then((d) => {
// if (d.status === 200) {
@ -122,6 +133,7 @@ const getCodeList = () => {
const toRow = (workflow: any, index: number, offset: number) => ({
no: offset + index + 1,
name: workflow.workflowName,
description: workflow.workflowDescription,
version: workflow.version,
stepCount: workflow.stepCount,
configProgress: workflow.configProgress,
@ -171,39 +183,83 @@ const setPaginationLength = () => {
total % pageSize === 0 ? total / pageSize : Math.ceil(total / pageSize);
};
const saveData = (formData) => {
const saveData = (formData: any) => {
if (data.value.modalMode === "create") {
// DeviceService.add(formData).then((d) => {
// if (d.status === 200) {
// data.value.isModalVisible = false;
// store.setSnackbarMsg({
// text: " .",
// result: 200,
// });
// changePageNum(1);
// } else {
// store.setSnackbarMsg({
// text: d,
// result: 500,
// });
// }
// });
AutoflowService.add(formData)
.then((res) => {
if (res.status === 200 || res.status === 201) {
data.value.isCreateVisible = false;
store.setSnackbarMsg({
text: "등록 되었습니다.",
result: 200,
color: "success",
});
changePageNum(1); //
} else {
// DeviceService.update(formData.deviceKey, formData).then((d) => {
// if (d.status === 200) {
// data.value.isModalVisible = false;
// store.setSnackbarMsg({
// text: " .",
// result: 200,
// });
// changePageNum();
// } else {
// store.setSnackbarMsg({
// text: d,
// result: 500,
// });
// }
// });
store.setSnackbarMsg({
text: "등록 실패",
result: 500,
color: "warning",
});
}
})
.catch((err) => {
console.error("등록 에러:", err);
store.setSnackbarMsg({
text: "등록 중 오류가 발생했습니다.",
result: 500,
color: "error",
});
})
.finally(() => {
getData();
});
} else {
//
const id =
Number(formData?.id) ??
Number(formData?.deviceKey) ??
Number(data.value.selectedData?.deviceKey) ??
Number(data.value.selectedData?.id);
if (!id) {
store.setSnackbarMsg({
text: "수정할 ID가 없습니다.",
result: 500,
color: "error",
});
return;
}
AutoflowService.update(id, formData)
.then((res) => {
if (res.status === 200) {
data.value.isCreateVisible = false;
store.setSnackbarMsg({
text: "수정 되었습니다.",
result: 200,
color: "success",
});
changePageNum(data.value.params.pageNum); //
} else {
store.setSnackbarMsg({
text: "수정 실패",
result: 500,
color: "warning",
});
}
})
.catch((err) => {
console.error("수정 에러:", err);
store.setSnackbarMsg({
text: "수정 중 오류가 발생했습니다.",
result: 500,
color: "error",
});
})
.finally(() => {
getData(); //
});
}
};
@ -299,12 +355,16 @@ const openDetailModal = (selectedItem) => {
data.value.selectedData = selectedItem;
openView.value = true;
};
const openModifyModal = (item: { workflow: string; stepName: string }) => {
const openModifyModal = (item: any) => {
console.log("[openModifyModal] row =", item);
data.value.selectedData = {
workflow: item.workflow,
stepName: item.stepName,
id: item.deviceKey,
workflowName: item.name,
workflowDescription: item.description,
};
openModify.value = true;
data.value.modalMode = "edit";
data.value.isCreateVisible = true;
};
const openCreateModal = () => {
@ -441,9 +501,9 @@ onMounted(() => {
</v-sheet>
</v-sheet>
<v-sheet class="justify-end mb-2">
<v-btn color="info" class="mr-4" @click="openUploadModal"
<!-- <v-btn color="info" class="mr-4" @click="openUploadModal"
>Upload Workflow
</v-btn>
</v-btn> -->
<v-btn color="info" @click="openCreateModal"
>Create Workflow
</v-btn>
@ -510,10 +570,10 @@ onMounted(() => {
<td>{{ item.stepCount }}</td>
<td>{{ item.configProgress }}</td>
<td>{{ item.kubeflowStatus }}</td>
<td>{{ item.registDt }}</td>
<td>{{ formatDateTime(item.registDt) }}</td>
<td style="white-space: nowrap">
<IconInfoBtn @on-click="openDetailModal(item)" />
<IconSettingBtn />
<!-- <IconSettingBtn /> -->
<IconModifyBtn @on-click="openModifyModal(item)" />
<IconDeleteBtn
@on-click="
@ -541,7 +601,8 @@ onMounted(() => {
</v-card>
</v-container>
<v-dialog v-model="data.isCreateVisible" max-width="800" persistent>
<WorkflowsCreateDialog
<WorkflowsBaseDialog
:key="data.modalMode + String(data.selectedData?.deviceKey ?? '')"
:edit-data="data.selectedData"
:mode="data.modalMode"
@close-modal="closeCreateModal"
@ -558,12 +619,6 @@ onMounted(() => {
:user-option="data.userOption"
/>
</v-dialog>
<v-dialog v-model="openModify" max-width="600px">
<StapComfigDialog
v-model="openModify"
:selectedData="data.selectedData"
/>
</v-dialog>
</div>
<div class="w-100" v-else>

@ -5,19 +5,14 @@
*/
// Plugins
import { registerPlugins } from '@/plugins'
import { registerPlugins } from "@/plugins";
// Components
import App from './App.vue'
import App from "./App.vue";
// Composables
import { createApp } from 'vue'
import { createApp } from "vue";
import "unfonts.css";
// Styles
import 'unfonts.css'
const app = createApp(App);
const app = createApp(App)
registerPlugins(app)
app.mount('#app')
registerPlugins(app);
app.mount("#app");

@ -137,7 +137,7 @@ const routes = [
name: "signup",
path: `/signup`,
meta: {
title: "로그인",
title: "회원가입",
requiresAuth: false,
},
component: () => import("@/pages/SignupView.vue"),
@ -145,31 +145,34 @@ const routes = [
];
const router = createRouter({
history: createWebHistory(import.meta.env.VITE_ROOT_PATH),
history: createWebHistory(import.meta.env.BASE_URL),
routes,
});
// router.beforeEach((to) => {
// if (!storage.getAuth() && to.name !== "signin") {
// return { name: "signin" };
// } else if (storage.getAuth() && to.name === "signin") {
// return { name: "main" };
// }
//
// // 권한 검사
// if (to.meta.requiresAuth) {
// if (storage.getAuth().auth !== "ADMIN") {
// // const store = commonStore();
// // store.setSnackbarMsg({
// // text: "접근 권한이 없습니다.",
// // result: 500,
// // color: "error",
// // });
// return { name: "main" };
// }
// }
//
// return true; // 허용된 경우 라우트 진행
// });
router.beforeEach((to) => {
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";
if (!authed) {
if (!isLogin && !isSignup) {
return { name: "login", replace: true, query: { redirect: to.fullPath } };
}
return true;
}
if (!bootDone && !isSelect && !isLogin && !isSignup) {
sessionStorage.setItem("initialRedirectDone", "1");
return { name: "select", replace: true, query: { redirect: to.fullPath } };
}
return true;
});
export default router;

Loading…
Cancel
Save