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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ { selected = item }"
+ />
+
-
+ fetchApps,
+ selected
+ };
+ }
+};
+
\ No newline at end of file
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 5f6098e..92e38c0 100644
--- a/frontend/src/components/ShowDialog.vue
+++ b/frontend/src/components/ShowDialog.vue
@@ -4,7 +4,7 @@
{{ name }}
-
+
@@ -12,7 +12,7 @@
-
+
@@ -30,6 +30,7 @@ export default {
height:String,
minWidth:String,
minHeight:String,
+ okBtnLabel:String,
okBtnLoading:Boolean,
okBtnAutoClose:{
type: Boolean,
@@ -41,7 +42,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..8ed619c
--- /dev/null
+++ b/frontend/src/components/dialog/DetailFieldTable.vue
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ props.row[col.name] }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..93b97fb
--- /dev/null
+++ b/frontend/src/components/dialog/VersionHistory.vue
@@ -0,0 +1,100 @@
+
+ { selected = item }"
+ >
+
+
+ {{ p.row.id }}
+ 現在
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/dialog/VersionInput.vue b/frontend/src/components/dialog/VersionInput.vue
new file mode 100644
index 0000000..0658fa9
--- /dev/null
+++ b/frontend/src/components/dialog/VersionInput.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
diff --git a/frontend/src/control/flowctrl.ts b/frontend/src/control/flowctrl.ts
index ff8bb04..a88ee73 100644
--- a/frontend/src/control/flowctrl.ts
+++ b/frontend/src/control/flowctrl.ts
@@ -32,7 +32,7 @@ export class FlowCtrl {
* @returns
*/
async UpdateFlow(jsonData: any): Promise {
- const result = await api.put('api/flow/' + jsonData.flowid, jsonData);
+ const result = await api.put('api/flow', jsonData);
console.info(result.data);
return true;
}
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..3a07975 100644
--- a/frontend/src/pages/AppManagement.vue
+++ b/frontend/src/pages/AppManagement.vue
@@ -37,14 +37,34 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+ 削除してもよろしいですか?
+
+
+
+
+
+
+
+
@@ -56,20 +76,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 +89,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,14 +98,20 @@ 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 versionDialog = ref();
const showSelectApp=ref(false);
+const showVersionHistory=ref(false);
const isAdding = ref(false);
+const deleteDialog = ref(false);
+const deleteUserLoading = ref(false);
const actionList = [
{ label: '設定', icon: 'account_tree', action: toEditFlowPage },
@@ -137,6 +154,7 @@ const filterInitRows = (row: {id: string}) => {
const showAddAppDialog = () => {
showSelectApp.value = true;
+ dgFilter.value = ''
}
const closeSelectAppDialog = async (val: 'OK'|'Cancel') => {
@@ -150,31 +168,72 @@ const closeSelectAppDialog = async (val: 'OK'|'Cancel') => {
}
function removeRow(app:IAppDisplay) {
- return
+ targetRow.value = app;
+ deleteDialog.value = true;
+}
+
+const deleteApp = async () => {
+ deleteUserLoading.value = true;
+ try {
+ await api.delete(`api/apps/${targetRow.value?.id}`);
+ await getApps();
+ } catch (error) {
+ $q.notify({
+ icon: 'error',
+ color: 'negative',
+ message: 'アプリの削除に失敗しました'
+ });
+ } finally {
+ deleteUserLoading.value = false;
+ deleteDialog.value = false;
+ }
}
function showHistory(app:IAppDisplay) {
- return
+ targetRow.value = app;
+ showVersionHistory.value = true;
+ dgFilter.value = ''
+}
+
+const closeHistoryDg = async (val: 'OK'|'Cancel') => {
+ showVersionHistory.value = true;
+ if (val == 'OK' && versionDialog.value.selected[0]) {
+ isAdding.value = true;
+ await api.put(`api/appversions/${targetRow.value?.id}/${versionDialog.value.selected[0].id}`)
+ await getApps();
+ }
+ showVersionHistory.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 d0ecce1..45585df 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,22 @@ const onSaveActionProps=(props:IActionProperty[])=>{
}
};
+const onSaveVersion = async () => {
+ versionSubmit.value = { appId: store.appInfo?.appId }
+ saveVersionAction.value = true;
+}
+
+const closeSaveVersionDg = async (val: 'OK'|'CANCEL') => {
+ if (val == 'OK') {
+ await onSaveAllFlow();
+ await api.post('api/apps', {
+ 'appid': versionSubmit.value?.appId,
+ 'versionname': versionSubmit.value?.name,
+ 'comment': versionSubmit.value?.comment
+ })
+ }
+}
+
const onSaveFlow = async () => {
const targetFlow = store.selectedFlow;
if (targetFlow === undefined) {
@@ -316,11 +348,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
@@ -328,24 +358,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..8d9cff9 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();
@@ -163,10 +163,10 @@ const columns = [
// classes: inactiveRowClass
// },
{ name: 'name', label: '環境名', field: 'name', align: 'left', sortable: true, classes: inactiveRowClass },
- { name: 'active', label: 'x', align: 'left', field: 'domainActive', classes: inactiveRowClass },
+ { name: 'active', label: '', 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 496165c..150d06e 100644
--- a/frontend/src/types/AppTypes.ts
+++ b/frontend/src/types/AppTypes.ts
@@ -1,10 +1,69 @@
-import { IUser } from './UserTypes';
-
-export interface IManagedApp {
- appid: string;
- appname: string;
- domainurl: string;
- version: string;
- updateuser: IUser;
- update_time: string;
-}
+import { IUser, IUserDisplay } from './UserTypes';
+
+export interface IManagedApp {
+ appid: string;
+ appname: string;
+ domainurl: string;
+ 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 IVersionSubmit {
+ appId: string;
+ name?: string;
+ comment?: string;
+}
+
+export interface IAppVersion {
+ id: number;
+ version: number;
+ appid: string;
+ versionname: string
+ comment: string;
+ updater: IUserDisplay;
+ updateTime: string;
+ creator: IUserDisplay;
+ createTime: string;
+}
+
+export interface IAppVersionDisplay {
+ id: number;
+ version: number;
+ appid: string;
+ name: string
+ comment: string;
+ updater: IUserDisplay;
+ updateTime: string;
+ creator: IUserDisplay;
+ createTime: string;
+}
\ No newline at end of file