diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 083049bd..093106c9 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -1,3 +1,21 @@
-
+
+
diff --git a/frontend/src/components/plugin/CreateEditPluginDialog.vue b/frontend/src/components/plugin/CreateEditPluginDialog.vue
new file mode 100644
index 00000000..14446ec7
--- /dev/null
+++ b/frontend/src/components/plugin/CreateEditPluginDialog.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/plugin/PluginForm.vue b/frontend/src/components/plugin/PluginForm.vue
new file mode 100644
index 00000000..4665f6b6
--- /dev/null
+++ b/frontend/src/components/plugin/PluginForm.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/plugin/plugin.ts b/frontend/src/components/plugin/plugin.ts
new file mode 100644
index 00000000..652e38de
--- /dev/null
+++ b/frontend/src/components/plugin/plugin.ts
@@ -0,0 +1,30 @@
+import {readonly} from 'vue';
+import {Store} from 'vuex';
+import useForm from '@/components/form/form';
+import usePluginService from '@/services/plugin/pluginService';
+import {getDefaultFormComponentData} from '@/utils/form';
+
+type Plugin = CPlugin;
+
+// get new plugin
+export const getNewPlugin = (): Plugin => {
+ return {};
+};
+
+// form component data
+const formComponentData = getDefaultFormComponentData(getNewPlugin);
+
+const usePlugin = (store: Store) => {
+ // store
+ const ns = 'plugin';
+
+ // form rules
+ const formRules = readonly({});
+
+ return {
+ ...useForm(ns, store, usePluginService(store), formComponentData),
+ formRules,
+ };
+};
+
+export default usePlugin;
diff --git a/frontend/src/constants/plugin.ts b/frontend/src/constants/plugin.ts
new file mode 100644
index 00000000..f0b65473
--- /dev/null
+++ b/frontend/src/constants/plugin.ts
@@ -0,0 +1,2 @@
+export const PLUGIN_UI_COMPONENT_TYPE_VIEW = 'view';
+export const PLUGIN_UI_COMPONENT_TYPE_TAB = 'tab';
diff --git a/frontend/src/interfaces/models/plugin.d.ts b/frontend/src/interfaces/models/plugin.d.ts
new file mode 100644
index 00000000..24bd0acd
--- /dev/null
+++ b/frontend/src/interfaces/models/plugin.d.ts
@@ -0,0 +1,20 @@
+interface CPlugin extends BaseModel {
+ name?: string;
+ description?: string;
+ type?: string;
+ proto?: string;
+ active?: boolean;
+ endpoint?: string;
+ cmd?: string;
+ ui_components?: PluginUIComponent[];
+ ui_sidebar_navs?: MenuItem[];
+}
+
+interface PluginUIComponent {
+ name?: string;
+ title?: string;
+ src?: string;
+ type?: string;
+ path?: string;
+ parent_paths?: string[];
+}
diff --git a/frontend/src/interfaces/services/request.d.ts b/frontend/src/interfaces/services/request.d.ts
index 920f5614..43b54d16 100644
--- a/frontend/src/interfaces/services/request.d.ts
+++ b/frontend/src/interfaces/services/request.d.ts
@@ -2,6 +2,7 @@ interface ListRequestParams {
page?: number;
size?: number;
conditions?: FilterConditionData[] | string;
+ all?: boolean | string | number;
}
interface BatchRequestPayload {
diff --git a/frontend/src/interfaces/store/index.d.ts b/frontend/src/interfaces/store/index.d.ts
index b641e26f..afa8ed18 100644
--- a/frontend/src/interfaces/store/index.d.ts
+++ b/frontend/src/interfaces/store/index.d.ts
@@ -14,6 +14,7 @@ declare global {
schedule: ScheduleStoreState;
user: UserStoreState;
token: TokenStoreState;
+ plugin: PluginStoreState;
}
type StoreGetter = (state: S, getters: StoreGetter, rootState: RootStoreState, rootGetters: any) => T;
@@ -103,6 +104,7 @@ declare global {
collapseSidebar: StoreMutation>;
expandActions: StoreMutation>;
collapseActions: StoreMutation>;
+ setTabs: StoreMutation;
setAfterSave: StoreMutation, (() => Promise)[]>;
}
@@ -133,7 +135,8 @@ declare global {
| 'tag'
| 'dataCollection'
| 'user'
- | 'token';
+ | 'token'
+ | 'plugin';
type ListStoreNamespace =
'node'
| 'project'
@@ -143,7 +146,8 @@ declare global {
| 'dataCollection'
| 'schedule'
| 'user'
- | 'token';
+ | 'token'
+ | 'plugin';
interface StoreContext {
namespace: StoreNamespace;
diff --git a/frontend/src/interfaces/store/modules/layout.d.ts b/frontend/src/interfaces/store/modules/layout.d.ts
index dc57f98e..392787db 100644
--- a/frontend/src/interfaces/store/modules/layout.d.ts
+++ b/frontend/src/interfaces/store/modules/layout.d.ts
@@ -26,6 +26,7 @@ declare global {
}
interface LayoutStoreMutations extends MutationTree {
+ setMenuItems: StoreMutation;
setSideBarCollapsed: StoreMutation;
setTabs: StoreMutation;
setActiveTabId: StoreMutation;
diff --git a/frontend/src/interfaces/store/modules/node.d.ts b/frontend/src/interfaces/store/modules/node.d.ts
index 4a91d440..b8d093fc 100644
--- a/frontend/src/interfaces/store/modules/node.d.ts
+++ b/frontend/src/interfaces/store/modules/node.d.ts
@@ -1,5 +1,3 @@
-type Node = CNode;
-
type NodeStoreModule = BaseModule;
type NodeStoreState = BaseStoreState;
diff --git a/frontend/src/interfaces/store/modules/plugin.d.ts b/frontend/src/interfaces/store/modules/plugin.d.ts
new file mode 100644
index 00000000..c11cc4aa
--- /dev/null
+++ b/frontend/src/interfaces/store/modules/plugin.d.ts
@@ -0,0 +1,9 @@
+type PluginStoreModule = BaseModule;
+
+type PluginStoreState = BaseStoreState;
+
+type PluginStoreGetters = BaseStoreGetters;
+
+type PluginStoreMutations = BaseStoreMutations;
+
+type PluginStoreActions = BaseStoreActions;
diff --git a/frontend/src/layouts/BasicLayout.vue b/frontend/src/layouts/BasicLayout.vue
index eb5def30..4f1fdd9a 100644
--- a/frontend/src/layouts/BasicLayout.vue
+++ b/frontend/src/layouts/BasicLayout.vue
@@ -1,22 +1,23 @@
-
+
-
-
+
+
-
+
+
+
diff --git a/frontend/src/views/plugin/detail/tabs/PluginDetailTabOverview.vue b/frontend/src/views/plugin/detail/tabs/PluginDetailTabOverview.vue
new file mode 100644
index 00000000..56271f24
--- /dev/null
+++ b/frontend/src/views/plugin/detail/tabs/PluginDetailTabOverview.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/frontend/src/views/plugin/list/PluginList.vue b/frontend/src/views/plugin/list/PluginList.vue
new file mode 100644
index 00000000..1d556468
--- /dev/null
+++ b/frontend/src/views/plugin/list/PluginList.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/plugin/list/pluginList.ts b/frontend/src/views/plugin/list/pluginList.ts
new file mode 100644
index 00000000..d9174b52
--- /dev/null
+++ b/frontend/src/views/plugin/list/pluginList.ts
@@ -0,0 +1,123 @@
+import useList from '@/layouts/list';
+import {useStore} from 'vuex';
+import {getDefaultUseListOptions, setupListComponent} from '@/utils/list';
+import {computed, h} from 'vue';
+import {TABLE_COLUMN_NAME_ACTIONS} from '@/constants/table';
+import {ElMessageBox} from 'element-plus';
+import usePluginService from '@/services/plugin/pluginService';
+import NavLink from '@/components/nav/NavLink.vue';
+import {useRouter} from 'vue-router';
+
+type Plugin = CPlugin;
+
+const usePluginList = () => {
+ // router
+ const router = useRouter();
+
+ // store
+ const ns = 'plugin';
+ const store = useStore();
+ const {commit} = store;
+
+ // services
+ const {
+ getList,
+ deleteById,
+ } = usePluginService(store);
+
+ // nav actions
+ const navActions = computed(() => [
+ {
+ name: 'common',
+ children: [
+ {
+ buttonType: 'label',
+ label: 'New Plugin',
+ tooltip: 'New Plugin',
+ icon: ['fa', 'plus'],
+ type: 'success',
+ onClick: () => {
+ commit(`${ns}/showDialog`, 'create');
+ }
+ }
+ ]
+ }
+ ]);
+
+ // table columns
+ const tableColumns = computed>(() => [
+ {
+ key: 'name', // name
+ label: 'Name',
+ icon: ['fa', 'font'],
+ width: '150',
+ value: (row: Plugin) => h(NavLink, {
+ path: `/plugins/${row._id}`,
+ label: row.name,
+ }),
+ hasSort: true,
+ hasFilter: true,
+ allowFilterSearch: true,
+ },
+ {
+ key: 'description',
+ label: 'Description',
+ icon: ['fa', 'comment-alt'],
+ width: 'auto',
+ hasFilter: true,
+ allowFilterSearch: true,
+ },
+ {
+ key: TABLE_COLUMN_NAME_ACTIONS,
+ label: 'Actions',
+ fixed: 'right',
+ width: '200',
+ buttons: [
+ {
+ type: 'primary',
+ icon: ['fa', 'search'],
+ tooltip: 'View',
+ onClick: (row) => {
+ router.push(`/plugins/${row._id}`);
+ },
+ },
+ // {
+ // type: 'info',
+ // size: 'mini',
+ // icon: ['fa', 'clone'],
+ // tooltip: 'Clone',
+ // onClick: (row) => {
+ // console.log('clone', row);
+ // }
+ // },
+ {
+ type: 'danger',
+ size: 'mini',
+ icon: ['fa', 'trash-alt'],
+ tooltip: 'Delete',
+ disabled: (row: Plugin) => !!row.active,
+ onClick: async (row: Plugin) => {
+ const res = await ElMessageBox.confirm('Are you sure to delete?', 'Delete');
+ if (res) {
+ await deleteById(row._id as string);
+ }
+ await getList();
+ },
+ },
+ ],
+ disableTransfer: true,
+ }
+ ]);
+
+ // options
+ const opts = getDefaultUseListOptions(navActions, tableColumns);
+
+ // init
+ setupListComponent(ns, store, []);
+
+ return {
+ ...useList(ns, store, opts)
+ };
+};
+
+export default usePluginList;