From 7221f97139e1d798ad1651d7338fb469e399903a Mon Sep 17 00:00:00 2001 From: xue jiahao Date: Fri, 6 Dec 2024 13:04:42 +0800 Subject: [PATCH 1/4] Add save version dialog # Conflicts: # frontend/src/types/AppTypes.ts --- .../src/components/dialog/VersionInput.vue | 39 +++++++++++++++++++ frontend/src/pages/FlowChart.vue | 34 +++++++++++++++- frontend/src/types/AppTypes.ts | 27 ++++++++----- 3 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 frontend/src/components/dialog/VersionInput.vue diff --git a/frontend/src/components/dialog/VersionInput.vue b/frontend/src/components/dialog/VersionInput.vue new file mode 100644 index 0000000..7e5fec2 --- /dev/null +++ b/frontend/src/components/dialog/VersionInput.vue @@ -0,0 +1,39 @@ + + diff --git a/frontend/src/pages/FlowChart.vue b/frontend/src/pages/FlowChart.vue index d0ecce1..38aeb48 100644 --- a/frontend/src/pages/FlowChart.vue +++ b/frontend/src/pages/FlowChart.vue @@ -14,6 +14,15 @@ + + + + + + 新バージョン保存 + + + @@ -75,6 +84,10 @@ + + + + (); const $q = useQuasar(); const store = useFlowEditorStore(); const authStore = useAuthStore(); @@ -117,6 +132,7 @@ const prevNodeIfo = ref({ }); // const refFlow = ref(null); const showAddAction = ref(false); +const saveVersionAction = ref(false); const drawerRight = ref(false); const filter=ref(""); const model = ref(""); @@ -177,7 +193,7 @@ const onDeleteAllNextNodes = (node: IActionNode) => { } const closeDg = (val: any) => { console.log("Dialog closed->", val); - if (val == 'OK') { + if (val == 'OK' && appDg?.value?.selected?.length > 0) { const data = appDg.value.selected[0]; const actionProps = JSON.parse(data.property); const outputPoint = JSON.parse(data.outputPoints); @@ -245,6 +261,20 @@ const onSaveActionProps=(props:IActionProperty[])=>{ } }; +const onSaveVersion = async () => { + versionInfo.value = { + id: '1' // TODO + } + saveVersionAction.value = true; + // await onSaveAllFlow(); +} + +const closeSaveVersionDg = (val: 'OK'|'CANCEL') => { + if (val == 'OK') { + console.log(versionInfo.value); + } +} + const onSaveFlow = async () => { const targetFlow = store.selectedFlow; if (targetFlow === undefined) { diff --git a/frontend/src/types/AppTypes.ts b/frontend/src/types/AppTypes.ts index 496165c..9a5eea8 100644 --- a/frontend/src/types/AppTypes.ts +++ b/frontend/src/types/AppTypes.ts @@ -1,10 +1,17 @@ -import { IUser } from './UserTypes'; - -export interface IManagedApp { - appid: string; - appname: string; - domainurl: string; - version: string; - updateuser: IUser; - update_time: string; -} +import { IUser } from './UserTypes'; + +export interface IManagedApp { + appid: string; + appname: string; + domainurl: string; + version: string; + user: IUser; + updateuser: IUser; + update_time: string; +} + +export interface IVersionInfo { + id: string; + name?: string; + desc?: string; +} \ No newline at end of file From 305868f091a33b6aab7a4a90bc761832b152646a Mon Sep 17 00:00:00 2001 From: xue jiahao Date: Fri, 6 Dec 2024 23:29:16 +0800 Subject: [PATCH 2/4] [UI] version page --- frontend/src/components/AppSelectBox.vue | 106 ++++++------------ frontend/src/components/ShowDialog.vue | 5 +- .../components/dialog/DetailFieldTable.vue | 88 +++++++++++++++ .../src/components/dialog/VersionHistory.vue | 82 ++++++++++++++ .../src/components/dialog/VersionInput.vue | 9 +- frontend/src/layouts/MainLayout.vue | 4 +- frontend/src/pages/AppManagement.vue | 67 +++++++---- frontend/src/pages/FlowChart.vue | 57 ++++++---- frontend/src/pages/TenantDomain.vue | 4 +- frontend/src/types/AppTypes.ts | 36 +++++- 10 files changed, 333 insertions(+), 125 deletions(-) create mode 100644 frontend/src/components/dialog/DetailFieldTable.vue create mode 100644 frontend/src/components/dialog/VersionHistory.vue diff --git a/frontend/src/components/AppSelectBox.vue b/frontend/src/components/AppSelectBox.vue index a887449..6c55105 100644 --- a/frontend/src/components/AppSelectBox.vue +++ b/frontend/src/components/AppSelectBox.vue @@ -1,24 +1,19 @@ + - + fetchApps, + selected + }; + } +}; + \ No newline at end of file diff --git a/frontend/src/components/ShowDialog.vue b/frontend/src/components/ShowDialog.vue index 5f6098e..729c442 100644 --- a/frontend/src/components/ShowDialog.vue +++ b/frontend/src/components/ShowDialog.vue @@ -4,7 +4,7 @@ {{ name }} - + @@ -41,7 +41,8 @@ export default { } }, emits: [ - 'close' + 'close', + 'update:visible' ], setup(props, context) { const CloseDialogue = (val) => { diff --git a/frontend/src/components/dialog/DetailFieldTable.vue b/frontend/src/components/dialog/DetailFieldTable.vue new file mode 100644 index 0000000..84068c9 --- /dev/null +++ b/frontend/src/components/dialog/DetailFieldTable.vue @@ -0,0 +1,88 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/dialog/VersionHistory.vue b/frontend/src/components/dialog/VersionHistory.vue new file mode 100644 index 0000000..0205e04 --- /dev/null +++ b/frontend/src/components/dialog/VersionHistory.vue @@ -0,0 +1,82 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/dialog/VersionInput.vue b/frontend/src/components/dialog/VersionInput.vue index 7e5fec2..8748144 100644 --- a/frontend/src/components/dialog/VersionInput.vue +++ b/frontend/src/components/dialog/VersionInput.vue @@ -2,14 +2,15 @@ @@ -22,9 +23,11 @@ const props = defineProps<{ modelValue: IVersionInfo; }>(); +const defaultTitle = `${new Date().toLocaleString()}`; + const versionInfo = ref({ ...props.modelValue, - name: props.modelValue.name || `新バージョン ${new Date().toLocaleString()}`, + name: props.modelValue.name || defaultTitle, }); const emit = defineEmits(['update:modelValue']); diff --git a/frontend/src/layouts/MainLayout.vue b/frontend/src/layouts/MainLayout.vue index 4d613ea..9ad772c 100644 --- a/frontend/src/layouts/MainLayout.vue +++ b/frontend/src/layouts/MainLayout.vue @@ -38,8 +38,10 @@ import { computed, onMounted, reactive } from 'vue'; import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue'; import DomainSelector from 'components/DomainSelector.vue'; import { useAuthStore } from 'stores/useAuthStore'; +import { useRoute } from 'vue-router'; const authStore = useAuthStore(); +const route = useRoute() const noDomain = computed(() => !authStore.hasDomain); const essentialLinks: EssentialLinkProps[] = reactive([ @@ -128,7 +130,7 @@ const version = process.env.version; const productName = process.env.productName; onMounted(() => { - authStore.setLeftMenu(true); + authStore.setLeftMenu(!route.path.startsWith('/FlowChart/')); }); function toggleLeftDrawer() { diff --git a/frontend/src/pages/AppManagement.vue b/frontend/src/pages/AppManagement.vue index 387f249..cfa9fca 100644 --- a/frontend/src/pages/AppManagement.vue +++ b/frontend/src/pages/AppManagement.vue @@ -37,13 +37,18 @@ - + + + + + + @@ -56,20 +61,11 @@ import { useAuthStore } from 'stores/useAuthStore'; import { useFlowEditorStore } from 'stores/flowEditor'; import { router } from 'src/router'; import { date } from 'quasar' -import { IManagedApp } from 'src/types/AppTypes'; +import { IManagedApp, IAppDisplay, IAppVersion } from 'src/types/AppTypes'; import ShowDialog from 'src/components/ShowDialog.vue'; import AppSelectBox from 'src/components/AppSelectBox.vue'; import TableActionMenu from 'components/TableActionMenu.vue'; - -interface IAppDisplay{ - id:string; - sortId: number; - name:string; - url:string; - user:string; - version:string; - updatetime:string; -} +import VersionHistory from 'components/dialog/VersionHistory.vue'; const authStore = useAuthStore(); const numberStringSorting = (a: string, b: string) => parseInt(a, 10) - parseInt(b, 10); @@ -78,8 +74,8 @@ const columns = [ { name: 'id', label: 'アプリID', field: 'id', align: 'left', sortable: true, sort: numberStringSorting }, { name: 'name', label: 'アプリ名', field: 'name', align: 'left', sortable: true }, { name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true }, - { name: 'user', label: '最後更新者', field: 'user', align: 'left', sortable: true}, - { name: 'updatetime', label: '最後更新日', field: 'updatetime', align: 'left', sortable: true}, + { name: 'updateUser', label: '最後更新者', field: (row: IAppDisplay) => row.updateUser.fullName, align: 'left', sortable: true}, + { name: 'updateTime', label: '最後更新日', field: 'updateTime', align: 'left', sortable: true}, { name: 'version', label: 'バージョン', field: 'version', align: 'left', sortable: true, sort: numberStringSorting }, { name: 'actions', label: '', field: 'actions' } ]; @@ -87,13 +83,16 @@ const columns = [ const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 }); const loading = ref(false); const filter = ref(''); +const dgFilter = ref(''); const rows = ref([]); +const targetRow = ref(); const rowIds = new Set(); const $q = useQuasar() const store = useFlowEditorStore(); const appDialog = ref(); const showSelectApp=ref(false); +const showVersionHistory=ref(false); const isAdding = ref(false); const actionList = [ @@ -137,6 +136,7 @@ const filterInitRows = (row: {id: string}) => { const showAddAppDialog = () => { showSelectApp.value = true; + dgFilter.value = '' } const closeSelectAppDialog = async (val: 'OK'|'Cancel') => { @@ -150,31 +150,54 @@ const closeSelectAppDialog = async (val: 'OK'|'Cancel') => { } function removeRow(app:IAppDisplay) { + targetRow.value = app; return } function showHistory(app:IAppDisplay) { - return + targetRow.value = app; + showVersionHistory.value = true; + dgFilter.value = '' +} + +const closeHistoryDg = async (val: 'OK'|'Cancel') => { + showSelectApp.value = true; + if (val == 'OK' && appDialog.value.selected[0]) { + isAdding.value = true; + await getApps(); + } + showSelectApp.value = false; + isAdding.value = false; } const appToAppDisplay = (app: IManagedApp) => { + const user = app.updateuser; return { id: app.appid, sortId: parseInt(app.appid, 10), name: app.appname, url: `${app.domainurl}/k/${app.appid}`, - user: `${app.updateuser.first_name} ${app.updateuser.last_name}` , - updatetime:date.formatDate(app.update_time, 'YYYY/MM/DD HH:mm'), - version: app.version - } + version: app.version, + updateTime:date.formatDate(app.update_time, 'YYYY/MM/DD HH:mm'), + updateUser: { + id: user.id, + firstName: user.first_name, + lastName: user.last_name, + fullNameSearch: (user.last_name + user.first_name).toLowerCase(), + fullName: user.last_name + ' ' + user.first_name, + email: user.email, + isSuperuser: user.is_superuser, + isActive: user.is_active, + } + } as IAppDisplay } -function toEditFlowPage(app:IAppDisplay) { +async function toEditFlowPage(app:IAppDisplay) { store.setApp({ appId: app.id, name: app.name }); store.selectFlow(undefined); - router.push('/FlowChart/' + app.id); + await router.push('/FlowChart/' + app.id); }; diff --git a/frontend/src/pages/FlowChart.vue b/frontend/src/pages/FlowChart.vue index 38aeb48..cd66e12 100644 --- a/frontend/src/pages/FlowChart.vue +++ b/frontend/src/pages/FlowChart.vue @@ -16,7 +16,7 @@ - + 新バージョン保存 @@ -100,7 +100,7 @@ import { ref, reactive, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes'; -import { IManagedApp, IVersionInfo } from 'src/types/AppTypes'; +import { IAppDisplay, IManagedApp, IVersionInfo } from 'src/types/AppTypes'; import { storeToRefs } from 'pinia'; import { useFlowEditorStore } from 'stores/flowEditor'; import { useAuthStore } from 'stores/useAuthStore'; @@ -346,11 +346,9 @@ const onSaveAllFlow= async ()=>{ const fetchData = async () => { initLoading.value = true; if (store.appInfo === undefined && route?.params?.id !== undefined) { - const { appid, appname } = await fetchAppById(route.params.id as string); - store.setApp({ - appId: appid, - name: appname - }); + // only for page refreshed + const app = await fetchAppById(route.params.id as string); + store.setApp(app); }; await store.loadFlow(); initLoading.value = false @@ -358,24 +356,39 @@ const fetchData = async () => { } const fetchAppById = async(id: string) => { - try { - const result = await api.get('api/apps'); - return result.data.find((item: IManagedApp) => item.appid === id ) as IManagedApp; - } catch (e) { - console.error(e); - const result = await api.get(`api/v1/app?app=${id}`); - const data = result?.data; - if (data?.message) { - $q.notify({ - type: 'negative', - caption: "エラー", - message: data.message - }); - } - return { appid: data.appId, appname: data.name }; + let result = await api.get('api/apps'); + const app = result.data?.data?.find((item: IManagedApp) => item.appid === id ) as IManagedApp; + if (app) { + return convertManagedAppToAppInfo(app); } + + result = await api.get(`api/v1/app?app=${id}`); + const kApp = result?.data as IAppDisplay | KErrorMsg; + if (isErrorMsg(kApp)) { + $q.notify({ + type: 'negative', + caption: 'エラー', + message: kApp.message, + }); + } + return kApp; } +type KErrorMsg = { + message: string; +} + +const isErrorMsg = (e: IAppDisplay | KErrorMsg): e is KErrorMsg => { + return 'message' in e; +}; + +const convertManagedAppToAppInfo = (app: IManagedApp): AppInfo => { + return { + appId: app.appid, + name: app.appname + } +}; + const onClearFilter=()=>{ filter.value=''; } diff --git a/frontend/src/pages/TenantDomain.vue b/frontend/src/pages/TenantDomain.vue index 4518044..35e5719 100644 --- a/frontend/src/pages/TenantDomain.vue +++ b/frontend/src/pages/TenantDomain.vue @@ -145,7 +145,7 @@ import { useAuthStore } from 'stores/useAuthStore'; import { useDomainStore } from 'stores/useDomainStore'; import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue'; import TableActionMenu from 'components/TableActionMenu.vue'; -import { IDomain, IDomainOwnerDisplay, IDomainSubmit } from '../types/DomainTypes'; +import { IDomain, IDomainDisplay, IDomainOwnerDisplay, IDomainSubmit } from '../types/DomainTypes'; const authStore = useAuthStore(); const domainStore = useDomainStore(); @@ -166,7 +166,7 @@ const columns = [ { name: 'active', label: 'x', align: 'left', field: 'domainActive', classes: inactiveRowClass }, { name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true, classes: inactiveRowClass }, { name: 'user', label: 'ログイン名', field: 'user', align: 'left', classes: inactiveRowClass }, - { name: 'owner', label: '所有者', field: row => row.owner.fullName, align: 'left', classes: inactiveRowClass }, + { name: 'owner', label: '所有者', field: (row: IDomainOwnerDisplay) => row.owner.fullName, align: 'left', classes: inactiveRowClass }, { name: 'actions', label: '', field: 'actions', classes: inactiveRowClass } ]; diff --git a/frontend/src/types/AppTypes.ts b/frontend/src/types/AppTypes.ts index 9a5eea8..3e04b93 100644 --- a/frontend/src/types/AppTypes.ts +++ b/frontend/src/types/AppTypes.ts @@ -1,4 +1,4 @@ -import { IUser } from './UserTypes'; +import { IUser, IUserDisplay } from './UserTypes'; export interface IManagedApp { appid: string; @@ -7,11 +7,45 @@ export interface IManagedApp { version: string; user: IUser; updateuser: IUser; + create_time: string; update_time: string; } +export interface IManagedApp { + appid: string; + appname: string; + domainurl: string; + version: string; + user: IUser; + updateuser: IUser; + create_time: string; + update_time: string; +} + +export interface IAppDisplay{ + id:string; + sortId: number; + name:string; + url:string; + updateUser: IUserDisplay; + updateTime:string; + version:string; +} + export interface IVersionInfo { id: string; name?: string; desc?: string; +} + +export interface IAppVersion { + id: number; + version: number; + appid: string; + name: string + comment: string; + updater: IUserDisplay; + updateTime: string; + creator: IUserDisplay; + createTime: string; } \ No newline at end of file From b95548e7f792e7c3bf69941db11e08ff3fc04749 Mon Sep 17 00:00:00 2001 From: xue jiahao Date: Mon, 9 Dec 2024 14:31:24 +0800 Subject: [PATCH 3/4] version with backend --- .../ShareDomain/ShareDomainDialog.vue | 25 ++++++- frontend/src/components/ShowDialog.vue | 3 +- .../components/dialog/DetailFieldTable.vue | 22 ++++++- .../src/components/dialog/VersionHistory.vue | 66 ++++++++++++------- .../src/components/dialog/VersionInput.vue | 7 +- frontend/src/control/flowctrl.ts | 2 +- frontend/src/pages/AppManagement.vue | 13 ++-- frontend/src/pages/FlowChart.vue | 24 +++---- frontend/src/pages/TenantDomain.vue | 2 +- frontend/src/types/AppTypes.ts | 18 +++++ 10 files changed, 133 insertions(+), 49 deletions(-) diff --git a/frontend/src/components/ShareDomain/ShareDomainDialog.vue b/frontend/src/components/ShareDomain/ShareDomainDialog.vue index a6d6e14..3fe4a34 100644 --- a/frontend/src/components/ShareDomain/ShareDomainDialog.vue +++ b/frontend/src/components/ShareDomain/ShareDomainDialog.vue @@ -41,7 +41,7 @@ - + @@ -54,6 +54,7 @@ import { IDomainOwnerDisplay } from '../../types/DomainTypes'; import { IUser, IUserDisplay } from '../../types/UserTypes'; import { api } from 'boot/axios'; import SharingUserList from 'components/ShareDomain/SharingUserList.vue'; +import { Dialog } from 'quasar' interface Props { modelValue: boolean; @@ -111,6 +112,28 @@ watch( } ); +const checkClose = () => { + if (!canSharedUserFilter.value) { + close(); + return; + } + Dialog.create({ + title: '注意', + message: '選択済だがまだ付与未完了のユーザーがあります。
必要な操作を選んでください。', + html: true, + persistent: true, + ok: { + color: 'primary', + label: '付与' + }, + cancel: '直接閉じる', + }).onCancel(() => { + close(); + }).onOk(() => { + shareTo(canSharedUserFilter.value as IUserDisplay); + }); +}; + const close = () => { emit('close'); }; diff --git a/frontend/src/components/ShowDialog.vue b/frontend/src/components/ShowDialog.vue index 729c442..92e38c0 100644 --- a/frontend/src/components/ShowDialog.vue +++ b/frontend/src/components/ShowDialog.vue @@ -12,7 +12,7 @@ - + @@ -30,6 +30,7 @@ export default { height:String, minWidth:String, minHeight:String, + okBtnLabel:String, okBtnLoading:Boolean, okBtnAutoClose:{ type: Boolean, diff --git a/frontend/src/components/dialog/DetailFieldTable.vue b/frontend/src/components/dialog/DetailFieldTable.vue index 84068c9..8ed619c 100644 --- a/frontend/src/components/dialog/DetailFieldTable.vue +++ b/frontend/src/components/dialog/DetailFieldTable.vue @@ -6,13 +6,21 @@ - @@ -41,6 +49,14 @@ export default { fetchData: { type: Function as PropType<() => Promise>, required: true + }, + sortBy: { + type: String, + required: false + }, + sortDesc: { + type: Boolean, + required: false } }, emits: ['update:selected'], @@ -64,6 +80,8 @@ export default { selected, isLoaded, pagination: ref({ + sortBy: props.sortBy || undefined, + descending: props.sortDesc || undefined, rowsPerPage: 10 }), emitSelected diff --git a/frontend/src/components/dialog/VersionHistory.vue b/frontend/src/components/dialog/VersionHistory.vue index 0205e04..93b97fb 100644 --- a/frontend/src/components/dialog/VersionHistory.vue +++ b/frontend/src/components/dialog/VersionHistory.vue @@ -3,19 +3,32 @@ detailField="comment" type="single" :columns="columns" + sortBy="id" + :sortDesc="true" :fetchData="fetchVersionHistory" @update:selected="(item) => { selected = item }" - /> + > + +