diff --git a/api/conf/api.yaml b/api/conf/api.yaml index 4af8c7be..80114b12 100644 --- a/api/conf/api.yaml +++ b/api/conf/api.yaml @@ -1781,27 +1781,27 @@ serviceActions: cm-damselfly: - Model{Istargetmodel}: + GetUserModel: method: get resourcePath: /model/{isTargetModel} description: "Get a list of all user models." - Onpremmodel: + CreateOnpremmodel: method: post resourcePath: /onpremmodel description: "Create a new on-premise model with the given information." - Onpremmodel: + ListOnpremmodel: method: get resourcePath: /onpremmodel description: "Get a list of on-premise models." - Onpremmodel{Id}: + UpdateOnpremmodel: method: put resourcePath: /onpremmodel/{id} description: "Update a on-premise model with the given information." - Onpremmodel{Id}: + DeleteOnpremmodel: method: delete resourcePath: /onpremmodel/{id} description: "Delete a on-premise model with the given information." - Onpremmodel{Id}: + GetOnpremmodel: method: get resourcePath: /onpremmodel/{id} description: "Get a specific on-premise model." @@ -1809,23 +1809,23 @@ serviceActions: method: get resourcePath: /readyz description: "Check Damselfly is ready" - Cloudmodel: + CreateCloudmodel: method: post resourcePath: /cloudmodel description: "Create a new cloud model with the given information." - Cloudmodel: + ListCloudmodel: method: get resourcePath: /cloudmodel description: "Get a list of cloud models." - Cloudmodel{Id}: + DeleteCloudmodel: method: delete resourcePath: /cloudmodel/{id} description: "Delete a cloud model with the given information." - Cloudmodel{Id}: + GetCloudmodel: method: get resourcePath: /cloudmodel/{id} description: "Get a specific cloud model." - Cloudmodel{Id}: + UpdateCloudmodel: method: put resourcePath: /cloudmodel/{id} description: "Update a cloud model with the given information." diff --git a/front/.env.sample b/front/.env.sample index 163dcfbe..021d0aff 100644 --- a/front/.env.sample +++ b/front/.env.sample @@ -1,4 +1,4 @@ VITE_BACKEND_ENDPOINT = '/api' -VITE_BACKEND_URL = 'http://localhost:4000' +VITE_BACKEND_URL = 'https://devmigapi.onecloudcon.com' VITE_PROJECT_NAME = 'MIGRATOR' diff --git a/front/src/app/i18n/en.json b/front/src/app/i18n/en.json index 55f2f96a..ced1fc51 100644 --- a/front/src/app/i18n/en.json +++ b/front/src/app/i18n/en.json @@ -92,6 +92,9 @@ }, "COPY_BUTTON": { "COPIED": "Copy!" + }, + "SELECT_DROPDOWN": { + "SELECT" : "select item" } } } diff --git a/front/src/app/providers/router/index.ts b/front/src/app/providers/router/index.ts index 402be39a..3e869397 100644 --- a/front/src/app/providers/router/index.ts +++ b/front/src/app/providers/router/index.ts @@ -16,7 +16,6 @@ import { RoleType } from '../../../shared/libs/accessControl/pageAccessHelper/ty import { getMinimalPageAccessPermissionList } from '../../../shared/libs'; import { toLower } from 'lodash'; import { tempRoutes } from './routes/temp.ts'; -import WorkflowTemplate from '@/features/workflow/workflowDesigner/ui/WorkflowDesigner.vue'; import NotFound from '@/pages/error/404/NotFound.vue'; //TODO admin부분 고려 @@ -45,7 +44,7 @@ export class McmpRouter { ...authRoutes, { path: '/test', - component: WorkflowTemplate, + component: MainLayout, }, { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }, ]; diff --git a/front/src/entities/workflow/api/index.ts b/front/src/entities/workflow/api/index.ts index bfeac827..ad21d69f 100644 --- a/front/src/entities/workflow/api/index.ts +++ b/front/src/entities/workflow/api/index.ts @@ -9,6 +9,7 @@ const UPDATE_WORKFLOW = 'update-workflow'; const RUN_WORKFLOW = 'run-workflow'; const DELETE_WORKFLOW = 'delete-workflow'; +// const GET_DISK_TYPE = 'GET_DISK_TYPE'; const GET_WORKFLOW_TEMPLATE_LIST = 'list-workflow-template'; const GET_TASK_COMPONENT_LIST = 'list-task-component'; diff --git a/front/src/entities/workflow/model/types.ts b/front/src/entities/workflow/model/types.ts index 9b6698b3..294f8751 100644 --- a/front/src/entities/workflow/model/types.ts +++ b/front/src/entities/workflow/model/types.ts @@ -8,36 +8,19 @@ export interface IWorkflow { } export interface ITaskGroupResponse { description: string; - id: string; name: string; + id?: string; tasks: Array; task_groups?: Array; } export interface ITaskResponse { - dependencies: any[]; + dependencies: any; name: string; path_params: any; - request_body: { - name: string; - installMonAgent: string; - label: string; - systemLabel: string; - description: string; - vm: Array; - }; + request_body: string; task_component: string; -} -export interface ITaskVmResponse { - name: string; - subGroupSize: string; - label: string; - description: string; - commonSpec: string; - commonImage: string; - rootDiskType: string; - rootDiskSize: string; - vmUserPassword: string; - connectionName: string; + query_params?: any; + id?: string; } export interface IWorkflowResponse { diff --git a/front/src/features/workflow/model/types.ts b/front/src/features/workflow/model/types.ts deleted file mode 100644 index ec0239a0..00000000 --- a/front/src/features/workflow/model/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Step as _Step } from 'sequential-workflow-model'; - -export interface IMci { - name: string; - description: string; - vms: IVm[]; -} - -interface IVm { - id: string; - name: string; - serverQuantity: string; - commonSpec: string; - osImage: string; - diskType: string; - diskSize: string; - password: string; - connectionName: string; -} - -export interface Step extends _Step { - sequence?: Step[]; - branches?: { true: Step[]; false: Step[] }; - componentType: 'switch' | 'container' | 'task'; - type: 'if' | 'MCI' | 'bettle_task'; - properties: { - isDeletable: boolean; - mci?: IMci; - }; -} - -export interface IWorkFlowDesignerFormData { - sequence: Step[]; -} - -let t = { - title: 'Root Disk Size', - model: { - value: '', - errorMessage: null, - isValid: true, - validating: false, - touched: false, - }, - type: 'text', -}; diff --git a/front/src/features/workflow/model/workflowToolModel.ts b/front/src/features/workflow/model/workflowToolModel.ts deleted file mode 100644 index 09be9d6a..00000000 --- a/front/src/features/workflow/model/workflowToolModel.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { useWorkflowStore } from '@/entities/workflow/model/stores.ts'; -import { - IWorkFlowDesignerFormData, - Step, -} from '@/features/workflow/model/types.ts'; -import { - ITaskGroupResponse, - ITaskResponse, - IWorkflow, -} from '@/entities/workflow/model/types.ts'; -import getRandomId from '@/shared/utils/uuid'; - -export function useWorkflowToolModel() { - const workflowStore = useWorkflowStore(); - function getWorkflowToolData( - type: 'template' | 'data' = 'data', - workflowId: string, - ) { - let workflow; - if (type === 'template') { - workflow = workflowStore.getTemplateById(workflowId); - } else { - workflow = workflowStore.getWorkFlowById(workflowId); - } - - if (workflow) { - convertWorkFlowToDesignerFormData(workflow); - } - } - - function convertWorkFlowToDesignerFormData( - workflow: IWorkflow, - ): IWorkFlowDesignerFormData { - const sequence: Step[] = []; - - // 스택에 부모 taskGroup과 현재 taskGroup을 함께 저장 - const stack: { - parentTaskGroup: Step | null; - currentTaskGroup: ITaskGroupResponse; - }[] = workflow.data.task_groups.map(taskGroup => ({ - parentTaskGroup: null, - currentTaskGroup: taskGroup, - })); - - while (stack.length) { - const { parentTaskGroup, currentTaskGroup } = stack.pop()!; - - const currentDesignerTaskGroup = - convertToDesignerTaskGroup(currentTaskGroup); - - if (currentTaskGroup.tasks) { - for (const task of currentTaskGroup.tasks) { - const currentDesignerTask = convertToDesignerTask(task); - currentDesignerTaskGroup.sequence!.push(currentDesignerTask); - } - } - - if (parentTaskGroup) { - parentTaskGroup.sequence!.push(currentDesignerTaskGroup); - } else { - sequence.push(currentDesignerTaskGroup); - } - - if (currentTaskGroup.task_groups) { - for (const subTaskGroups of currentTaskGroup.task_groups) { - stack.push({ - parentTaskGroup: currentDesignerTaskGroup, - currentTaskGroup: subTaskGroups, - }); - } - } - } - - return { sequence }; - } - - function convertToDesignerTask(task: ITaskResponse): Step { - return { - id: getRandomId(), - name: task.name, - componentType: 'task', - type: 'bettle_task', - properties: { - isDeletable: true, - mci: { - name: task.request_body.name, - description: task.request_body.description, - vms: task.request_body.vm.map(vm => ({ - id: vm.label, - name: vm.name, - serverQuantity: vm.subGroupSize, - commonSpec: vm.commonSpec, - osImage: vm.commonImage, - diskType: vm.rootDiskType, - diskSize: vm.rootDiskSize, - password: vm.vmUserPassword, - connectionName: vm.connectionName, - })), - }, - }, - }; - } - - function convertToDesignerTaskGroup(taskGroup: ITaskGroupResponse): Step { - return { - id: getRandomId(), - name: taskGroup.name, - componentType: 'container', - type: 'MCI', - properties: { - isDeletable: true, - }, - sequence: [], - }; - } - - return { - getWorkflowToolData, - setWorkflowSequenceModel: convertWorkFlowToDesignerFormData, - }; -} diff --git a/front/src/features/workflow/ui/WorkflowTool.vue b/front/src/features/workflow/ui/WorkflowTool.vue deleted file mode 100644 index 666cc06b..00000000 --- a/front/src/features/workflow/ui/WorkflowTool.vue +++ /dev/null @@ -1,73 +0,0 @@ - - - - - diff --git a/front/src/features/workflow/workflowDesigner/ui/WorkflowDesigner.vue b/front/src/features/workflow/workflowDesigner/ui/WorkflowDesigner.vue deleted file mode 100644 index fa65e68a..00000000 --- a/front/src/features/workflow/workflowDesigner/ui/WorkflowDesigner.vue +++ /dev/null @@ -1,172 +0,0 @@ - - - - - diff --git a/front/src/features/workflow/workflowEditor/model/stepEditorProviderModel.ts b/front/src/features/workflow/workflowEditor/model/stepEditorProviderModel.ts deleted file mode 100644 index 3e3df0d1..00000000 --- a/front/src/features/workflow/workflowEditor/model/stepEditorProviderModel.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { useInputModel } from '@/shared/hooks/input/useInputModel.ts'; -import { reactive } from 'vue'; - -export function useStepEditorProviderModel() { - const formValues = reactive({ - entity: { - targetModel: { - title: 'Target Model ID', - model: useInputModel(''), - type: 'text', - }, - mciName: { - title: 'MCI Name', - model: useInputModel(''), - type: 'text', - }, - mciDescription: { - title: 'MCI Description', - model: useInputModel(''), - type: 'text', - }, - }, - accordions: [loadAccordionSlotModel(false)], - }); - - function addAccordionSlot() { - // @ts-ignore - formValues.accordions.push(loadAccordionSlotModel(false)); - } - function loadAccordionSlotModel(visible: boolean) { - return { - icon: 'ic_chevron-down', - visible, - value: { - vms: loadVmsModel(), - }, - }; - } - - function loadVmsModel() { - return { - header: { - vmName: { - title: 'Vm Name', - model: useInputModel(''), - type: 'text', - }, - }, - body: { - serverQuantity: { - title: 'Server Quantity', - model: useInputModel(''), - type: 'text', - }, - commonSpec: { - title: 'Common Spec', - model: useInputModel(''), - type: 'text', - }, - commonOsImage: { - title: 'Common OS Image', - model: useInputModel(''), - type: 'text', - }, - rootDiskType: { - title: 'Root Disk Type', - options: [ - { value: null, text: 'Select Root Disk Type' }, - { value: 'a', text: 'This is First option' }, - { value: 'b', text: 'Selected Option' }, - { - value: { C: '3PO' }, - text: 'This is an option with object value', - }, - ], - type: 'select', - selected: null, - }, - rootDiskSize: { - title: 'Root Disk Size', - model: useInputModel(''), - type: 'text', - }, - userPassword: { - title: 'User Password', - model: useInputModel(''), - type: 'text', - }, - connectionName: { - title: 'Connection Name', - model: useInputModel(''), - type: 'text', - }, - }, - }; - } - return { formValues, addAccordionSlot }; -} diff --git a/front/src/features/workflow/workflowEditor/model/types.ts b/front/src/features/workflow/workflowEditor/model/types.ts new file mode 100644 index 00000000..4bb8a88d --- /dev/null +++ b/front/src/features/workflow/workflowEditor/model/types.ts @@ -0,0 +1,16 @@ +import { Step as _Step } from 'sequential-workflow-model'; + +export interface Step extends _Step { + sequence?: Step[]; + branches?: { true: Step[]; false: Step[] }; + componentType: 'switch' | 'container' | 'task'; + type: string; + properties: { + isDeletable: boolean; + model?: object; + }; +} + +export interface IWorkFlowDesignerFormData { + sequence: Step[]; +} diff --git a/front/src/features/workflow/workflowEditor/model/workflowEditorModel.ts b/front/src/features/workflow/workflowEditor/model/workflowEditorModel.ts new file mode 100644 index 00000000..3793b6d1 --- /dev/null +++ b/front/src/features/workflow/workflowEditor/model/workflowEditorModel.ts @@ -0,0 +1,168 @@ +import { useWorkflowStore } from '@/entities/workflow/model/stores.ts'; +import { + IWorkFlowDesignerFormData, + Step, +} from '@/features/workflow/workflowEditor/model/types.ts'; +import { + ITaskGroupResponse, + ITaskResponse, + IWorkflow, +} from '@/entities/workflow/model/types.ts'; +import getRandomId from '@/shared/utils/uuid'; +import { toolboxSteps } from '@/features/workflow/workflowEditor/sequential/designer/toolbox/model/toolboxSteps.ts'; +import { parseRequestBody } from '@/shared/utils/stringToObject'; +import { Sequence } from 'sequential-workflow-designer'; + +export function useWorkflowToolModel() { + const workflowStore = useWorkflowStore(); + const { defineTaskGroupStep, defineBettleTaskStep } = toolboxSteps(); + + function getWorkflowToolData( + workflowId: string, + type: 'template' | 'data' = 'data', + ) { + let workflow; + if (type === 'template') { + workflow = workflowStore.getTemplateById(workflowId); + } else { + workflow = workflowStore.getWorkFlowById(workflowId); + } + + if (workflow) { + convertCicadaToDesignerFormData(workflow); + } + } + + function convertCicadaToDesignerFormData( + workflow: IWorkflow, + ): IWorkFlowDesignerFormData { + const sequence: Step[] = []; + + // 스택에 부모 taskGroup과 현재 taskGroup을 함께 저장 + const stack: { + parentTaskGroup: Step | null; + currentTaskGroup: ITaskGroupResponse; + }[] = workflow.data.task_groups.map(taskGroup => ({ + parentTaskGroup: null, + currentTaskGroup: taskGroup, + })); + + while (stack.length) { + const { parentTaskGroup, currentTaskGroup } = stack.pop()!; + + const currentDesignerTaskGroup = + convertToDesignerTaskGroup(currentTaskGroup); + + if (currentTaskGroup.tasks) { + for (const task of currentTaskGroup.tasks) { + const currentDesignerTask = convertToDesignerTask(task); + currentDesignerTaskGroup.sequence!.push(currentDesignerTask); + } + } + + if (parentTaskGroup) { + parentTaskGroup.sequence!.push(currentDesignerTaskGroup); + } else { + sequence.push(currentDesignerTaskGroup); + } + + if (currentTaskGroup.task_groups) { + for (const subTaskGroups of currentTaskGroup.task_groups) { + stack.push({ + parentTaskGroup: currentDesignerTaskGroup, + currentTaskGroup: subTaskGroups, + }); + } + } + } + + return { sequence }; + } + + function convertToDesignerTask(task: ITaskResponse): Step { + const parsedString: object = parseRequestBody(task.request_body); + + return defineBettleTaskStep(getRandomId(), task.name, 'task', { + model: parsedString, + }); + } + + function convertToDesignerTaskGroup(taskGroup: ITaskGroupResponse): Step { + return defineTaskGroupStep(getRandomId(), taskGroup.name, 'MCI', { + model: { description: taskGroup.description }, + }); + } + + function convertDesignerSequenceToCicada(sequence: Step[]) { + if (!validationSequence(sequence)) { + throw new Error(); + } + + const cicadaObject: ITaskGroupResponse[] = []; + + const stack: { + parentNode: ITaskGroupResponse | null; + currentNode: Step; + }[] = sequence.map((step: Step) => ({ + parentNode: null, + currentNode: step, + })); + + while (stack.length) { + const { parentNode, currentNode } = stack.pop()!; + + const taskGroup: ITaskGroupResponse = { + description: '', + name: '', + tasks: [], + }; + + if (currentNode.componentType === 'container') { + const tasks: any = []; + + currentNode.sequence?.forEach(step => { + if (step.componentType === 'container') { + stack.push({ parentNode: taskGroup, currentNode: step }); + } else if (step.componentType === 'task') { + tasks.push(convertToCicadaTask(step)); + } + }); + + taskGroup.description = + currentNode.properties.model?.['description'] ?? ''; + taskGroup.name = currentNode.name; + taskGroup.tasks = tasks; + } + + if (parentNode === null) { + cicadaObject.push(taskGroup); + } else { + parentNode.task_groups = parentNode.task_groups || []; + parentNode.task_groups.push(taskGroup); + } + } + + return cicadaObject; + } + + function convertToCicadaTask(step: Step) { + if (step.componentType === 'task') { + return { + name: step.name, + request_body: JSON.stringify(step.properties.model, null, 2), + }; + } + } + + function validationSequence(sequence: Step[]): boolean { + return !sequence.some(step => { + return step.componentType === 'task'; + }); + } + + return { + getWorkflowToolData, + convertCicadaToDesignerFormData, + convertDesignerSequenceToCicada, + }; +} diff --git a/front/src/features/workflow/workflowEditor/sequential/designer/editor/model/beetleTaskEditorModel.ts b/front/src/features/workflow/workflowEditor/sequential/designer/editor/model/beetleTaskEditorModel.ts new file mode 100644 index 00000000..cf642e80 --- /dev/null +++ b/front/src/features/workflow/workflowEditor/sequential/designer/editor/model/beetleTaskEditorModel.ts @@ -0,0 +1,245 @@ +import { useInputModel } from '@/shared/hooks/input/useInputModel.ts'; +import { computed, reactive, ref, Ref, UnwrapRef } from 'vue'; +import object from 'async-validator/dist-types/validator/object'; +import { isArray, values } from 'lodash'; + +type EntityContext = { + type: 'entity'; + context: { + subject: 'Entity'; + values: Array; + }; +}; + +type InputContext = { + type: 'input'; + context: { + title: string; + model: ReturnType>; + }; +}; + +type KeyValueInputContext = { + type: 'keyValueInput'; + context: { + title: ReturnType>; + model: ReturnType>; + }; +}; + +type AccordionSlotContext = { + header: { + icon: string; + title: string; // index + }; + content: Array; +}; + +type AccordionContext = { + type: 'accordion'; + context: { + subject: string; + values: Array; + }; + index: number; + originalData: Array; +}; + +type ConvertedData = EntityContext | AccordionContext; + +export function useTaskEditorModel() { + const formContext = ref([]); + + function loadInputContext( + key: string, + value: string | '' | null, + ): InputContext { + return { + type: 'input', + context: { + title: key, + model: useInputModel(value ?? ''), + }, + }; + } + + function loadKeyValueInputContext(): KeyValueInputContext { + return { + type: 'keyValueInput', + context: { + title: useInputModel(''), + model: useInputModel(''), + }, + }; + } + + function loadAccordionContext( + object: object, + index: number, + ): AccordionSlotContext { + return { + header: { + icon: 'ic_chevron-down', + title: index.toString(), + }, + content: Object.entries(object).map( + ([key, value]: [key: string, value: string]) => { + return loadInputContext(key, value); + }, + ), + }; + } + + function setFormContext(object: object | '') { + const context: ConvertedData[] = [ + { + type: 'entity', + context: { + subject: 'Entity', + values: [], + }, + }, + ]; + if (typeof object === 'object') { + Object.entries(object).forEach( + ([key, value]: [key: string, value: string | Array], index) => { + if (typeof value === 'string') { + if (context[0].type === 'entity') { + context[0].context.values.push(loadInputContext(key, value)); + } + } else if (isArray(value)) { + context.push({ + type: 'accordion', + originalData: value, + context: { + subject: key, + values: value.map((el, index) => + loadAccordionContext(el, index), + ), + }, + index, + }); + } + }, + ); + } + // @ts-ignore + formContext.value = context; + } + + function convertFormModelToStepProperties(): object { + const properties = {}; + + formContext.value.forEach(data => { + if (data.type === 'entity') { + let convertedObject = []; + data.context.values.forEach(value => { + if (value.type === 'keyValueInput') { + if ( + value.context.title.value !== '' && + !entityKeyValidation(value.context.title) + ) { + //@ts-ignore + convertedObject.push(getKeyValueInputData(value.context)); + } + } else if (value.type === 'input') { + //@ts-ignore + convertedObject.push(getInputData(value.context)); + } + }); + + Object.assign(properties, ...convertedObject); + } else if (data.type === 'accordion') { + const accordionData = { + [data.context.subject]: data.context.values.map(value => + // @ts-ignore + getAccordionSlotData(value), + ), + }; + Object.assign(properties, accordionData); + } + }); + return properties; + } + + function getAccordionSlotData(accordionSlotContext: AccordionSlotContext) { + const object = {}; + accordionSlotContext.content.forEach(data => { + Object.assign(object, getInputData(data.context)); + }); + + return object; + } + + function getKeyValueInputData(object: KeyValueInputContext['context']) { + return { + // @ts-ignore + [object.title.value]: object.model.value, + }; + } + + function getInputData(inputContext: InputContext['context']) { + return { + [inputContext.title]: inputContext.model.value, + }; + } + + function addEntity( + target: UnwrapRef>, + ) { + // @ts-ignore + target.push(loadKeyValueInputContext()); + } + + function addArray(parentIndex: number) { + if (formContext.value[parentIndex].type === 'accordion') { + formContext.value[parentIndex].context.values.push( + // @ts-ignore + loadAccordionContext(formContext.value[parentIndex].originalData[0], 0), + ); + } + } + //return 같은게 있으면 true 없으면 false + function entityKeyValidation( + model: UnwrapRef>>, + ): boolean { + if (formContext.value[0].type === 'entity') { + let valid = formContext.value[0].context.values.some(value => { + // @ts-ignore + if (value.type === 'input') { + // @ts-ignore + return value.context.title === model.value; + } + return false; + }); + model.isValid = !valid; + return valid; + } + return false; + } + + function deleteEntity(index: number) { + if (formContext.value[0].type === 'entity') { + formContext.value[0].context.values.splice(index, 1); + } + } + function deleteArrayElement( + targetArr: + | UnwrapRef> + | UnwrapRef>, + targetIndex: number, + ) { + targetArr.splice(targetIndex, 1); + } + + return { + formContext, + setFormContext, + convertFormModelToStepProperties, + addEntity, + addArray, + entityKeyValidation, + deleteEntity, + deleteArrayElement, + }; +} diff --git a/front/src/features/workflow/workflowDesigner/model/editorProviders.ts b/front/src/features/workflow/workflowEditor/sequential/designer/editor/model/editorProviders.ts similarity index 68% rename from front/src/features/workflow/workflowDesigner/model/editorProviders.ts rename to front/src/features/workflow/workflowEditor/sequential/designer/editor/model/editorProviders.ts index dec1eb8a..5d9c9f3b 100644 --- a/front/src/features/workflow/workflowDesigner/model/editorProviders.ts +++ b/front/src/features/workflow/workflowEditor/sequential/designer/editor/model/editorProviders.ts @@ -1,12 +1,14 @@ import { insertDynamicComponent } from '@/shared/utils'; -import { getSequencePath } from '@/features/workflow/workflowEditor/model/utils.ts'; -import StepEditorProvider from '@/features/workflow/workflowEditor/ui/StepEditorProvider.vue'; +import { getSequencePath } from '@/features/workflow/workflowEditor/sequential/designer/editor/model/utils.ts'; +import BeetleTaskEditor from '@/features/workflow/workflowEditor/sequential/designer/editor/ui/BeetleTaskEditor.vue'; +import Vue from 'vue'; +import { SourceServicePage } from '@/pages/sourceServices'; export function editorProviders() { const editor = document.createElement('div'); editor.style.width = '100%'; editor.style.height = '100%'; - editor.style.overflow = 'scroll'; + return { defaultRootEditorProvider: function (definition, rootContext, isReadonly) { const textArea = document.createElement('textarea'); @@ -24,20 +26,22 @@ export function editorProviders() { definition, isReadonly, ) { - console.log(step); - console.log(definition); + // console.log('step !'); + // console.log(step.properties); //각각에 만들어야할 Vue component 정의 if (step.componentType === 'switch' && step.type == 'if') { } - if (step.componentType === 'container' && step.type == 'MCI') { + if (step.componentType === 'container') { } - if (step.componentType === 'task' && step.type == 'bettle_task') { + if (step.componentType === 'task') { + //toolboxModel에서 가공하는곳 참고 insertDynamicComponent( - StepEditorProvider, - { id: 'tst' }, + BeetleTaskEditor, + { step }, { - 'button-click': () => { - console.log('event 발생'); + saveContext: e => { + console.log(e); + step.properties.model = e; }, }, editor, diff --git a/front/src/features/workflow/workflowEditor/model/utils.ts b/front/src/features/workflow/workflowEditor/sequential/designer/editor/model/utils.ts similarity index 100% rename from front/src/features/workflow/workflowEditor/model/utils.ts rename to front/src/features/workflow/workflowEditor/sequential/designer/editor/model/utils.ts diff --git a/front/src/features/workflow/workflowEditor/sequential/designer/editor/ui/BeetleTaskEditor.vue b/front/src/features/workflow/workflowEditor/sequential/designer/editor/ui/BeetleTaskEditor.vue new file mode 100644 index 00000000..e4718087 --- /dev/null +++ b/front/src/features/workflow/workflowEditor/sequential/designer/editor/ui/BeetleTaskEditor.vue @@ -0,0 +1,315 @@ + + + + + diff --git a/front/src/features/workflow/workflowEditor/ui/RootEditorProvider.vue b/front/src/features/workflow/workflowEditor/sequential/designer/editor/ui/TaskGroupEditor.vue similarity index 100% rename from front/src/features/workflow/workflowEditor/ui/RootEditorProvider.vue rename to front/src/features/workflow/workflowEditor/sequential/designer/editor/ui/TaskGroupEditor.vue diff --git a/front/src/features/workflow/workflowDesigner/model/flowChartModel.ts b/front/src/features/workflow/workflowEditor/sequential/designer/model/sequentialDesignerModel.ts similarity index 64% rename from front/src/features/workflow/workflowDesigner/model/flowChartModel.ts rename to front/src/features/workflow/workflowEditor/sequential/designer/model/sequentialDesignerModel.ts index d5c7f485..a4afdef0 100644 --- a/front/src/features/workflow/workflowDesigner/model/flowChartModel.ts +++ b/front/src/features/workflow/workflowEditor/sequential/designer/model/sequentialDesignerModel.ts @@ -5,10 +5,10 @@ import { } from 'sequential-workflow-designer'; import { Definition, Step } from 'sequential-workflow-model'; import getRandomId from '@/shared/utils/uuid'; -import { toolboxSteps } from '@/features/workflow/workflowDesigner/model/toolboxSteps.ts'; -import { editorProviders } from '@/features/workflow/workflowDesigner/model/editorProviders.ts'; +import { toolboxSteps } from '@/features/workflow/workflowEditor/sequential/designer/toolbox/model/toolboxSteps.ts'; +import { editorProviders } from '@/features/workflow/workflowEditor/sequential/designer/editor/model/editorProviders.ts'; -export function useFlowChartModel(refs: any) { +export function useSequentialDesignerModel(refs: any) { let designer: Designer | null = null; const placeholder = refs.placeholder; @@ -32,6 +32,20 @@ export function useFlowChartModel(refs: any) { }; let definition: Definition; let configuration: DesignerConfiguration; + let toolBoxGroup: Array<{ name: string; steps: Step[] }> = [ + { + name: 'Tool', + steps: [], + }, + { + name: 'taskGroup', + steps: [], + }, + { + name: 'Components', + steps: [], + }, + ]; function defineDefaultDefinition(workflowName: string, sequence: Step[]) { return { @@ -45,36 +59,37 @@ export function useFlowChartModel(refs: any) { function defineStepEvent() { return { // all properties in this section are optional - iconUrlProvider: (componentType, type) => { + iconUrlProvider: (componentType: any, type: any) => { return `/src/shared/asset/image/testSvg.svg`; }, - - isDraggable: (step, parentSequence) => { - return step.name !== 'y'; - }, + // + // isDraggable: (step, parentSequence) => { + // return step.name !== 'y'; + // }, isDeletable: (step, parentSequence) => { return step.properties['isDeletable']; }, isDuplicable: (step, parentSequence) => { return true; }, - canInsertStep: (step, targetSequence, targetIndex) => { - return true; - }, - canMoveStep: (sourceSequence, step, targetSequence, targetIndex) => { - return !step.properties['isLocked']; - }, - canDeleteStep: (step, parentSequence) => { - return confirm('Are you sure?'); - }, + // canInsertStep: (step, targetSequence, targetIndex) => { + // return true; + // }, + // canMoveStep: (sourceSequence, step, targetSequence, targetIndex) => { + // return !step.properties['isLocked']; + // }, + // canDeleteStep: (step, parentSequence) => { + // return confirm('Are you sure?'); + // }, }; } function defineStepValidate() { return { - // all validators are optional - step: (step, parentSequence, definition) => { + // console.log('parentSequence'); + // console.log(parentSequence); + // console.log(definition); return true; }, root: definition => { @@ -83,20 +98,32 @@ export function useFlowChartModel(refs: any) { }; } - function defineToolboxGroups() { - return [ + function setToolboxGroupsSteps( + toolSteps: Step[] | null, + taskGroupSteps: Step[] | null, + componentSteps: Step[], + ) { + toolBoxGroup = [ { name: 'Tool', - steps: [toolboxSteps().defineIfStep(getRandomId(), [], [])], + steps: toolSteps ?? [], }, { - name: 'Components', - steps: [ - toolboxSteps().defineTaskGroupStep(getRandomId()), - toolboxSteps().defineBettleTaskStep(getRandomId()), + name: 'TaskGroup', + steps: taskGroupSteps ?? [ + toolboxSteps().defineTaskGroupStep( + getRandomId(), + 'TaskGroup', + 'taskGroup', + ), ], }, + { + name: 'Components', + steps: componentSteps, + }, ]; + // console.log(toolBoxGroup); } function loadConfiguration() { @@ -105,7 +132,7 @@ export function useFlowChartModel(refs: any) { validator: defineStepValidate(), toolbox: { isCollapsed: designerOptionsState.toolbox.isCollapsed, - groups: defineToolboxGroups(), + groups: toolBoxGroup, }, editors: { @@ -153,11 +180,16 @@ export function useFlowChartModel(refs: any) { }); } + function getDesigner() { + return designer; + } return { designer, designerOptionsState, setDefaultSequence, + setToolboxGroupsSteps, initDesigner, draw, + getDesigner, }; } diff --git a/front/src/features/workflow/workflowEditor/sequential/designer/shortcut/ui/SequentialShortCut.vue b/front/src/features/workflow/workflowEditor/sequential/designer/shortcut/ui/SequentialShortCut.vue new file mode 100644 index 00000000..7b7dc65d --- /dev/null +++ b/front/src/features/workflow/workflowEditor/sequential/designer/shortcut/ui/SequentialShortCut.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/front/src/features/workflow/workflowEditor/sequential/designer/toolbox/model/api/index.ts b/front/src/features/workflow/workflowEditor/sequential/designer/toolbox/model/api/index.ts new file mode 100644 index 00000000..e3ec274c --- /dev/null +++ b/front/src/features/workflow/workflowEditor/sequential/designer/toolbox/model/api/index.ts @@ -0,0 +1,21 @@ +import { IAxiosResponse, useAxiosPost } from '@/shared/libs'; + +const GET_TASK_COMPONENT_LIST = 'list-task-component'; +export interface ITaskInfoResponse { + created_at: string; + updated_at: string; + id: string; + name: string; + data: { + options: { + request_body: string; + }; + }; +} + +export function getTaskComponentList() { + return useAxiosPost>, null>( + GET_TASK_COMPONENT_LIST, + null, + ); +} diff --git a/front/src/features/workflow/workflowEditor/sequential/designer/toolbox/model/toolboxModel.ts b/front/src/features/workflow/workflowEditor/sequential/designer/toolbox/model/toolboxModel.ts new file mode 100644 index 00000000..4506957b --- /dev/null +++ b/front/src/features/workflow/workflowEditor/sequential/designer/toolbox/model/toolboxModel.ts @@ -0,0 +1,42 @@ +import { + getTaskComponentList, + ITaskInfoResponse, +} from '@/features/workflow/workflowEditor/sequential/designer/toolbox/model/api'; +import { parseRequestBody } from '@/shared/utils/stringToObject'; +import getRandomId from '@/shared/utils/uuid'; +import { Step } from '@/features/workflow/workflowEditor/model/types.ts'; +import { toolboxSteps } from '@/features/workflow/workflowEditor/sequential/designer/toolbox/model/toolboxSteps.ts'; + +export function useSequentialToolboxModel() { + const resGetTaskComponentList = getTaskComponentList(); + const loadStepsFunc = toolboxSteps(); + + async function getTaskComponents() { + const res = await resGetTaskComponentList.execute(); + return processToolBoxTaskListResponse(res.data.responseData!); + } + + function processToolBoxTaskListResponse(res: ITaskInfoResponse[]) { + const taskStepsModels: Step[] = []; + res.forEach((res: ITaskInfoResponse) => { + const parsedString: object = parseRequestBody( + res.data.options.request_body, + ); + + taskStepsModels.push( + loadStepsFunc.defineBettleTaskStep( + getRandomId(), + res.name ?? 'undefined', + 'task', + { + model: parsedString, + }, + ), + ); + }); + + return taskStepsModels; + } + + return getTaskComponents(); +} diff --git a/front/src/features/workflow/workflowDesigner/model/toolboxSteps.ts b/front/src/features/workflow/workflowEditor/sequential/designer/toolbox/model/toolboxSteps.ts similarity index 63% rename from front/src/features/workflow/workflowDesigner/model/toolboxSteps.ts rename to front/src/features/workflow/workflowEditor/sequential/designer/toolbox/model/toolboxSteps.ts index 4a216734..7a7ca558 100644 --- a/front/src/features/workflow/workflowDesigner/model/toolboxSteps.ts +++ b/front/src/features/workflow/workflowEditor/sequential/designer/toolbox/model/toolboxSteps.ts @@ -1,4 +1,4 @@ -import { Step } from 'sequential-workflow-model'; +import { Step } from '@/features/workflow/workflowEditor/model/types.ts'; export function toolboxSteps() { return { @@ -17,28 +17,41 @@ export function toolboxSteps() { }, }; }, - defineTaskGroupStep(id: string) { + + defineTaskGroupStep( + id: string, + name: string, + type: string, + properties: { model: object }, + ): Step { return { componentType: 'container', id, - type: 'MCI', - name: 'Task Group', + type, + name, properties: { isDeletable: true, + ...properties, }, sequence: [ //task ], }; }, - defineBettleTaskStep(id: string) { + defineBettleTaskStep( + id: string, + name: string, + type: string, + properties: { model: object }, + ): Step { return { componentType: 'task', id, - type: 'bettle_task', - name: 'bettle_task', + type, + name, properties: { isDeletable: true, + ...properties, }, sequence: [ // 없어야함. diff --git a/front/src/features/workflow/workflowEditor/sequential/designer/ui/SequentialDesigner.vue b/front/src/features/workflow/workflowEditor/sequential/designer/ui/SequentialDesigner.vue new file mode 100644 index 00000000..dfcbbe22 --- /dev/null +++ b/front/src/features/workflow/workflowEditor/sequential/designer/ui/SequentialDesigner.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/front/src/features/workflow/workflowEditor/ui/StepEditorProvider.vue b/front/src/features/workflow/workflowEditor/ui/StepEditorProvider.vue deleted file mode 100644 index ff4be799..00000000 --- a/front/src/features/workflow/workflowEditor/ui/StepEditorProvider.vue +++ /dev/null @@ -1,210 +0,0 @@ - - - - - diff --git a/front/src/features/workflow/workflowEditor/ui/WorkflowEditor.vue b/front/src/features/workflow/workflowEditor/ui/WorkflowEditor.vue new file mode 100644 index 00000000..9a6de2da --- /dev/null +++ b/front/src/features/workflow/workflowEditor/ui/WorkflowEditor.vue @@ -0,0 +1,248 @@ + + + + + diff --git a/front/src/main.ts b/front/src/main.ts index 39f654dc..1aef22a4 100644 --- a/front/src/main.ts +++ b/front/src/main.ts @@ -8,17 +8,11 @@ import { createPinia, PiniaVuePlugin } from 'pinia'; import VueRouter from 'vue-router'; import { McmpRouter } from './app/providers/router'; import { i18n } from './app/i18n'; -// import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'; - -// import 'bootstrap/dist/css/bootstrap.css'; -// import 'bootstrap-vue/dist/bootstrap-vue.css'; const pinia = createPinia(); Vue.use(PiniaVuePlugin); Vue.use(MirinaeDesignSystem); Vue.use(VueRouter); -// Vue.use(BootstrapVue); -// Vue.use(IconsPlugin); new Vue({ i18n, diff --git a/front/src/pages/sourceTemplate/ui/SourceTemplate.vue b/front/src/pages/sourceTemplate/ui/SourceTemplate.vue index 3f11eca2..7389db3e 100644 --- a/front/src/pages/sourceTemplate/ui/SourceTemplate.vue +++ b/front/src/pages/sourceTemplate/ui/SourceTemplate.vue @@ -1,6 +1,5 @@ @@ -14,7 +13,6 @@ const pageName = 'sourceTemplate';
-
diff --git a/front/src/shared/ui/Button/dynamicIconButton/DynamicTableIconButton.vue b/front/src/shared/ui/Button/dynamicIconButton/DynamicTableIconButton.vue index 1991f5c9..532d7bdf 100644 --- a/front/src/shared/ui/Button/dynamicIconButton/DynamicTableIconButton.vue +++ b/front/src/shared/ui/Button/dynamicIconButton/DynamicTableIconButton.vue @@ -1,5 +1,6 @@ + + + + diff --git a/front/src/shared/utils/stringToObject/index.ts b/front/src/shared/utils/stringToObject/index.ts new file mode 100644 index 00000000..0588ce61 --- /dev/null +++ b/front/src/shared/utils/stringToObject/index.ts @@ -0,0 +1,9 @@ +export function parseRequestBody(requestBodyString: string): object { + try { + // JSON.parse를 사용하여 문자열을 객체로 변환 + const parsedObject = JSON.parse(requestBodyString); + return parsedObject; + } catch (error) { + return {}; + } +} diff --git a/front/src/widgets/workflow/workflowTool/ui/WorkflowTool.vue b/front/src/widgets/workflow/workflowTool/ui/WorkflowTool.vue index ada21063..e6480cf5 100644 --- a/front/src/widgets/workflow/workflowTool/ui/WorkflowTool.vue +++ b/front/src/widgets/workflow/workflowTool/ui/WorkflowTool.vue @@ -1,6 +1,7 @@