diff --git a/ui/console-src/modules/system/plugins/PluginDetail.vue b/ui/console-src/modules/system/plugins/PluginDetail.vue index eeca579ed..c12735fed 100644 --- a/ui/console-src/modules/system/plugins/PluginDetail.vue +++ b/ui/console-src/modules/system/plugins/PluginDetail.vue @@ -1,123 +1,23 @@ <script lang="ts" setup> -// core libs -import { consoleApiClient, coreApiClient } from "@halo-dev/api-client"; -import { computed, provide, ref } from "vue"; -import { useRoute } from "vue-router"; - -// libs -import { cloneDeep } from "lodash-es"; - -// components +import type { Plugin, Setting } from "@halo-dev/api-client"; import { VAvatar, VCard, VPageHeader, VTabbar } from "@halo-dev/components"; - -// types -import { usePluginModuleStore } from "@/stores/plugin"; -import { usePermission } from "@/utils/permission"; -import type { Plugin, Setting, SettingForm } from "@halo-dev/api-client"; -import type { PluginTab } from "@halo-dev/console-shared"; -import { useQuery } from "@tanstack/vue-query"; -import { useRouteQuery } from "@vueuse/router"; import type { Ref } from "vue"; -import { markRaw } from "vue"; -import { useI18n } from "vue-i18n"; -import DetailTab from "./tabs/Detail.vue"; -import SettingTab from "./tabs/Setting.vue"; - -const { currentUserHasPermission } = usePermission(); -const { t } = useI18n(); - -const initialTabs = ref<PluginTab[]>([ - { - id: "detail", - label: t("core.plugin.tabs.detail"), - component: markRaw(DetailTab), - }, -]); +import { provide, toRefs } from "vue"; +import { useRoute } from "vue-router"; +import { usePluginDetailTabs } from "./composables/use-plugin"; const route = useRoute(); -const tabs = ref<PluginTab[]>(cloneDeep(initialTabs.value)); -const activeTab = useRouteQuery<string>("tab", tabs.value[0].id); +const { name } = toRefs(route.params); + +const { plugin, setting, activeTab, tabs } = usePluginDetailTabs( + name as Ref<string | undefined>, + true +); provide<Ref<string>>("activeTab", activeTab); - -const { data: plugin } = useQuery({ - queryKey: ["plugin", route.params.name], - queryFn: async () => { - const { data } = await coreApiClient.plugin.plugin.getPlugin({ - name: route.params.name as string, - }); - return data; - }, - async onSuccess(data) { - if ( - !data.spec.settingName || - !currentUserHasPermission(["system:plugins:manage"]) - ) { - tabs.value = [...initialTabs.value, ...(await getTabsFromExtensions())]; - } - }, -}); - provide<Ref<Plugin | undefined>>("plugin", plugin); - -const { data: setting } = useQuery({ - queryKey: ["plugin-setting", plugin], - queryFn: async () => { - const { data } = await consoleApiClient.plugin.plugin.fetchPluginSetting({ - name: plugin.value?.metadata.name as string, - }); - return data; - }, - enabled: computed(() => { - return ( - !!plugin.value && - !!plugin.value.spec.settingName && - currentUserHasPermission(["system:plugins:manage"]) - ); - }), - async onSuccess(data) { - if (data) { - const { forms } = data.spec; - tabs.value = [ - ...initialTabs.value, - ...(await getTabsFromExtensions()), - ...forms.map((item: SettingForm) => { - return { - id: item.group, - label: item.label || "", - component: markRaw(SettingTab), - }; - }), - ] as PluginTab[]; - } - }, -}); - provide<Ref<Setting | undefined>>("setting", setting); - -async function getTabsFromExtensions() { - const { pluginModuleMap } = usePluginModuleStore(); - - const currentPluginModule = pluginModuleMap[route.params.name as string]; - - if (!currentPluginModule) { - return []; - } - - const callbackFunction = - currentPluginModule?.extensionPoints?.["plugin:self:tabs:create"]; - - if (typeof callbackFunction !== "function") { - return []; - } - - const pluginTabs = await callbackFunction(); - - return pluginTabs.filter((tab) => { - return currentUserHasPermission(tab.permissions); - }); -} </script> <template> <VPageHeader :title="plugin?.spec?.displayName"> diff --git a/ui/console-src/modules/system/plugins/components/PluginDetailModal.vue b/ui/console-src/modules/system/plugins/components/PluginDetailModal.vue new file mode 100644 index 000000000..7d2a2c210 --- /dev/null +++ b/ui/console-src/modules/system/plugins/components/PluginDetailModal.vue @@ -0,0 +1,66 @@ +<script lang="ts" setup> +import type { Plugin, Setting } from "@halo-dev/api-client"; +import { IconLink, VButton, VModal, VTabbar } from "@halo-dev/components"; +import { provide, ref, toRefs, type Ref } from "vue"; +import { usePluginDetailTabs } from "../composables/use-plugin"; + +const props = withDefaults(defineProps<{ name: string }>(), {}); + +const emit = defineEmits<{ + (event: "close"): void; +}>(); + +const modal = ref<InstanceType<typeof VModal> | null>(null); + +const { name } = toRefs(props); + +const { plugin, setting, tabs, activeTab } = usePluginDetailTabs(name, false); + +provide<Ref<string>>("activeTab", activeTab); +provide<Ref<Plugin | undefined>>("plugin", plugin); +provide<Ref<Setting | undefined>>("setting", setting); +</script> + +<template> + <VModal + ref="modal" + :title="plugin?.spec.displayName" + :centered="true" + :width="920" + height="calc(100vh - 20px)" + mount-to-body + @close="emit('close')" + > + <template #actions> + <span> + <RouterLink + :to="{ + name: 'PluginDetail', + params: { name }, + }" + > + <IconLink /> + </RouterLink> + </span> + </template> + <VTabbar + v-model:active-id="activeTab" + :items=" + tabs.map((tab) => { + return { label: tab.label, id: tab.id }; + }) + " + type="outline" + /> + <div class="-m-4 mt-2"> + <template v-for="tab in tabs" :key="tab.id"> + <component :is="tab.component" v-if="activeTab === tab.id" /> + </template> + </div> + <template #footer> + <VButton @click="modal?.close()"> + {{ $t("core.common.buttons.close") }} + </VButton> + </template> + </VModal> +</template> diff --git a/ui/console-src/modules/system/plugins/components/extension-points/ExtensionDefinitionListItem.vue b/ui/console-src/modules/system/plugins/components/extension-points/ExtensionDefinitionListItem.vue index 3c37f2a20..a73a09369 100644 --- a/ui/console-src/modules/system/plugins/components/extension-points/ExtensionDefinitionListItem.vue +++ b/ui/console-src/modules/system/plugins/components/extension-points/ExtensionDefinitionListItem.vue @@ -11,7 +11,8 @@ import { VEntityField, } from "@halo-dev/components"; import { useQuery } from "@tanstack/vue-query"; -import { computed } from "vue"; +import { computed, ref } from "vue"; +import PluginDetailModal from "../PluginDetailModal.vue"; const props = withDefaults( defineProps<{ extensionDefinition: ExtensionDefinition }>(), @@ -36,9 +37,16 @@ const matchedPlugin = computed(() => { props.extensionDefinition.metadata.labels?.["plugin.halo.run/plugin-name"] ); }); + +const pluginDetailModalVisible = ref(false); </script> <template> + <PluginDetailModal + v-if="pluginDetailModalVisible && matchedPlugin" + :name="matchedPlugin.metadata.name" + @close="pluginDetailModalVisible = false" + /> <VEntity> <template v-if="$slots['selection-indicator']" #checkbox> <slot name="selection-indicator" /> @@ -61,15 +69,12 @@ const matchedPlugin = computed(() => { <template v-if="matchedPlugin" #end> <VEntityField> <template #description> - <RouterLink + <div class="cursor-pointer rounded p-1 text-gray-600 transition-all hover:text-blue-600 group-hover:bg-gray-200/60" - :to="{ - name: 'PluginDetail', - params: { name: matchedPlugin?.metadata.name }, - }" + @click.prevent="pluginDetailModalVisible = true" > <IconSettings /> - </RouterLink> + </div> </template> </VEntityField> </template> diff --git a/ui/console-src/modules/system/plugins/components/extension-points/ExtensionDefinitionSingletonView.vue b/ui/console-src/modules/system/plugins/components/extension-points/ExtensionDefinitionSingletonView.vue index e1457d028..02586b788 100644 --- a/ui/console-src/modules/system/plugins/components/extension-points/ExtensionDefinitionSingletonView.vue +++ b/ui/console-src/modules/system/plugins/components/extension-points/ExtensionDefinitionSingletonView.vue @@ -131,6 +131,7 @@ async function onExtensionChange(e: Event) { <label class="cursor-pointer transition-all" :class="{ 'pointer-events-none opacity-50': isSubmitting }" + @click.stop > <ExtensionDefinitionListItem :extension-definition="item"> <template #selection-indicator> diff --git a/ui/console-src/modules/system/plugins/tabs/Detail.vue b/ui/console-src/modules/system/plugins/components/tabs/Detail.vue similarity index 96% rename from ui/console-src/modules/system/plugins/tabs/Detail.vue rename to ui/console-src/modules/system/plugins/components/tabs/Detail.vue index 0078abd70..0a3a3939d 100644 --- a/ui/console-src/modules/system/plugins/tabs/Detail.vue +++ b/ui/console-src/modules/system/plugins/components/tabs/Detail.vue @@ -2,6 +2,7 @@ import { rbacAnnotations } from "@/constants/annotations"; import { pluginLabels, roleLabels } from "@/constants/labels"; import { formatDatetime } from "@/utils/date"; +import { usePermission } from "@/utils/permission"; import { PluginStatusPhaseEnum, coreApiClient, @@ -18,8 +19,10 @@ import { import { useQuery } from "@tanstack/vue-query"; import type { Ref } from "vue"; import { computed, inject, ref } from "vue"; -import PluginConditionsModal from "../components/PluginConditionsModal.vue"; -import { usePluginLifeCycle } from "../composables/use-plugin"; +import { usePluginLifeCycle } from "../../composables/use-plugin"; +import PluginConditionsModal from "../PluginConditionsModal.vue"; + +const { currentUserHasPermission } = usePermission(); const plugin = inject<Ref<Plugin | undefined>>("plugin"); const { changeStatus, changingStatus } = usePluginLifeCycle(plugin); @@ -45,7 +48,11 @@ const { data: pluginRoleTemplates } = useQuery({ return data.items; }, cacheTime: 0, - enabled: computed(() => !!plugin?.value?.metadata.name), + enabled: computed( + () => + !!plugin?.value?.metadata.name && + currentUserHasPermission(["system:roles:view"]) + ), }); const pluginRoleTemplateGroups = computed<RoleTemplateGroup[]>(() => { diff --git a/ui/console-src/modules/system/plugins/tabs/Setting.vue b/ui/console-src/modules/system/plugins/components/tabs/Setting.vue similarity index 95% rename from ui/console-src/modules/system/plugins/tabs/Setting.vue rename to ui/console-src/modules/system/plugins/components/tabs/Setting.vue index bd5bdf575..460a82f5b 100644 --- a/ui/console-src/modules/system/plugins/tabs/Setting.vue +++ b/ui/console-src/modules/system/plugins/components/tabs/Setting.vue @@ -1,19 +1,11 @@ <script lang="ts" setup> -// core libs -import { computed, inject, ref, type Ref } from "vue"; - -// hooks -import { useSettingFormConvert } from "@console/composables/use-setting-form"; -import { consoleApiClient } from "@halo-dev/api-client"; - -// components -import { Toast, VButton } from "@halo-dev/components"; - -// types import StickyBlock from "@/components/sticky-block/StickyBlock.vue"; +import { useSettingFormConvert } from "@console/composables/use-setting-form"; import type { ConfigMap, Plugin, Setting } from "@halo-dev/api-client"; +import { consoleApiClient } from "@halo-dev/api-client"; +import { Toast, VButton } from "@halo-dev/components"; import { useQuery, useQueryClient } from "@tanstack/vue-query"; -import { toRaw } from "vue"; +import { computed, inject, ref, toRaw, type Ref } from "vue"; import { useI18n } from "vue-i18n"; const { t } = useI18n(); diff --git a/ui/console-src/modules/system/plugins/composables/use-plugin.ts b/ui/console-src/modules/system/plugins/composables/use-plugin.ts index 0a562f74d..0c01c9cea 100644 --- a/ui/console-src/modules/system/plugins/composables/use-plugin.ts +++ b/ui/console-src/modules/system/plugins/composables/use-plugin.ts @@ -1,15 +1,21 @@ +import { usePluginModuleStore } from "@/stores/plugin"; +import { usePermission } from "@/utils/permission"; import { PluginStatusPhaseEnum, consoleApiClient, coreApiClient, type Plugin, + type SettingForm, } from "@halo-dev/api-client"; -import type { ComputedRef, Ref } from "vue"; -import { computed } from "vue"; - import { Dialog, Toast } from "@halo-dev/components"; -import { useMutation } from "@tanstack/vue-query"; +import type { PluginTab } from "@halo-dev/console-shared"; +import { useMutation, useQuery } from "@tanstack/vue-query"; +import { useRouteQuery } from "@vueuse/router"; +import type { ComputedRef, Ref } from "vue"; +import { computed, markRaw, ref } from "vue"; import { useI18n } from "vue-i18n"; +import DetailTab from "../components/tabs/Detail.vue"; +import SettingTab from "../components/tabs/Setting.vue"; interface usePluginLifeCycleReturn { isStarted: ComputedRef<boolean | undefined>; @@ -270,3 +276,105 @@ export function usePluginBatchOperations(names: Ref<string[]>) { return { handleUninstallInBatch, handleChangeStatusInBatch }; } + +export function usePluginDetailTabs( + pluginName: Ref<string | undefined>, + recordsActiveTab: boolean +) { + const { currentUserHasPermission } = usePermission(); + const { t } = useI18n(); + + const initialTabs = [ + { + id: "detail", + label: t("core.plugin.tabs.detail"), + component: markRaw(DetailTab), + }, + ]; + + const tabs = ref<PluginTab[]>(initialTabs); + const activeTab = recordsActiveTab + ? useRouteQuery<string>("tab", tabs.value[0].id) + : ref(tabs.value[0].id); + + const { data: plugin } = useQuery({ + queryKey: ["plugin", pluginName], + queryFn: async () => { + const { data } = await coreApiClient.plugin.plugin.getPlugin({ + name: pluginName.value as string, + }); + return data; + }, + async onSuccess(data) { + if ( + !data.spec.settingName || + !currentUserHasPermission(["system:plugins:manage"]) + ) { + tabs.value = [...initialTabs, ...(await getTabsFromExtensions())]; + } + }, + }); + + const { data: setting } = useQuery({ + queryKey: ["plugin-setting", plugin], + queryFn: async () => { + const { data } = await consoleApiClient.plugin.plugin.fetchPluginSetting({ + name: plugin.value?.metadata.name as string, + }); + return data; + }, + enabled: computed(() => { + return ( + !!plugin.value && + !!plugin.value.spec.settingName && + currentUserHasPermission(["system:plugins:manage"]) + ); + }), + async onSuccess(data) { + if (data) { + const { forms } = data.spec; + tabs.value = [ + ...initialTabs, + ...(await getTabsFromExtensions()), + ...forms.map((item: SettingForm) => { + return { + id: item.group, + label: item.label || "", + component: markRaw(SettingTab), + }; + }), + ] as PluginTab[]; + } + }, + }); + + async function getTabsFromExtensions() { + const { pluginModuleMap } = usePluginModuleStore(); + + const currentPluginModule = pluginModuleMap[pluginName.value as string]; + + if (!currentPluginModule) { + return []; + } + + const callbackFunction = + currentPluginModule?.extensionPoints?.["plugin:self:tabs:create"]; + + if (typeof callbackFunction !== "function") { + return []; + } + + const pluginTabs = await callbackFunction(); + + return pluginTabs.filter((tab) => { + return currentUserHasPermission(tab.permissions); + }); + } + + return { + plugin, + setting, + tabs, + activeTab, + }; +} diff --git a/ui/console-src/modules/system/plugins/module.ts b/ui/console-src/modules/system/plugins/module.ts index 105f11d6f..ef511aac1 100644 --- a/ui/console-src/modules/system/plugins/module.ts +++ b/ui/console-src/modules/system/plugins/module.ts @@ -6,9 +6,12 @@ import type { RouteRecordRaw } from "vue-router"; import PluginDetail from "./PluginDetail.vue"; import PluginExtensionPointSettings from "./PluginExtensionPointSettings.vue"; import PluginList from "./PluginList.vue"; +import PluginDetailModal from "./components/PluginDetailModal.vue"; export default definePlugin({ - components: {}, + components: { + PluginDetailModal, + }, routes: [ { path: "/plugins",