mirror of https://github.com/halo-dev/halo
feat: show the startup status of the plugin (#5520)
#### What type of PR is this? /area ui /kind feature /milestone 2.14.x #### What this PR does / why we need it: Show the startup status of the plugin <img width="444" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/0f5d47c2-9d81-4cb7-b114-6b2eebe753c5"> #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/5254 #### Does this PR introduce a user-facing change? ```release-note 插件支持显示启动中的状态。 ```pull/5787/head
parent
a635881d34
commit
5a0f735efb
|
@ -20,7 +20,7 @@ import { computed, onMounted, provide, ref, watch } from "vue";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
import { useQuery } from "@tanstack/vue-query";
|
import { useQuery } from "@tanstack/vue-query";
|
||||||
import type { Plugin } from "@halo-dev/api-client";
|
import { PluginStatusPhaseEnum, type Plugin } from "@halo-dev/api-client";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import { usePluginBatchOperations } from "./composables/use-plugin";
|
import { usePluginBatchOperations } from "./composables/use-plugin";
|
||||||
|
@ -63,11 +63,26 @@ const { data, isLoading, isFetching, refetch } = useQuery<Plugin[]>({
|
||||||
},
|
},
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
refetchInterval: (data) => {
|
refetchInterval: (data) => {
|
||||||
const deletingPlugins = data?.filter(
|
const hasDeletingData = data?.some(
|
||||||
(plugin) => !!plugin.metadata.deletionTimestamp
|
(plugin) => !!plugin.metadata.deletionTimestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
return deletingPlugins?.length ? 2000 : false;
|
if (hasDeletingData) {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasStartingData = data?.some(
|
||||||
|
(plugin) =>
|
||||||
|
plugin.spec.enabled &&
|
||||||
|
plugin.status?.phase !==
|
||||||
|
(PluginStatusPhaseEnum.Started || PluginStatusPhaseEnum.Failed)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasStartingData) {
|
||||||
|
return 3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { computed, inject, markRaw, ref, toRefs } from "vue";
|
import { computed, inject, markRaw, ref, toRefs } from "vue";
|
||||||
import { usePluginLifeCycle } from "../composables/use-plugin";
|
import { usePluginLifeCycle } from "../composables/use-plugin";
|
||||||
import type { Plugin } from "@halo-dev/api-client";
|
import { type Plugin, PluginStatusPhaseEnum } from "@halo-dev/api-client";
|
||||||
import { formatDatetime } from "@/utils/date";
|
import { formatDatetime } from "@/utils/date";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
|
@ -43,7 +43,7 @@ const { plugin } = toRefs(props);
|
||||||
|
|
||||||
const selectedNames = inject<Ref<string[]>>("selectedNames", ref([]));
|
const selectedNames = inject<Ref<string[]>>("selectedNames", ref([]));
|
||||||
|
|
||||||
const { getFailedMessage, uninstall } = usePluginLifeCycle(plugin);
|
const { getStatusMessage, uninstall } = usePluginLifeCycle(plugin);
|
||||||
|
|
||||||
const pluginUpgradeModalVisible = ref(false);
|
const pluginUpgradeModalVisible = ref(false);
|
||||||
|
|
||||||
|
@ -146,86 +146,105 @@ const { operationItems } = useOperationItemExtensionPoint<Plugin>(
|
||||||
const { startFields, endFields } = useEntityFieldItemExtensionPoint<Plugin>(
|
const { startFields, endFields } = useEntityFieldItemExtensionPoint<Plugin>(
|
||||||
"plugin:list-item:field:create",
|
"plugin:list-item:field:create",
|
||||||
plugin,
|
plugin,
|
||||||
computed((): EntityFieldItem[] => [
|
computed((): EntityFieldItem[] => {
|
||||||
{
|
const { enabled } = props.plugin.spec || {};
|
||||||
position: "start",
|
const { phase } = props.plugin.status || {};
|
||||||
priority: 10,
|
|
||||||
component: markRaw(LogoField),
|
const shouldHideStatusDot =
|
||||||
props: {
|
!enabled || (enabled && phase === PluginStatusPhaseEnum.Started);
|
||||||
plugin: props.plugin,
|
|
||||||
},
|
const getStatusDotState = () => {
|
||||||
},
|
if (
|
||||||
{
|
enabled &&
|
||||||
position: "start",
|
phase !==
|
||||||
priority: 20,
|
(PluginStatusPhaseEnum.Started || PluginStatusPhaseEnum.Failed)
|
||||||
component: markRaw(VEntityField),
|
) {
|
||||||
props: {
|
return "default";
|
||||||
title: props.plugin.spec.displayName,
|
}
|
||||||
description: props.plugin.spec.description,
|
return "error";
|
||||||
route: {
|
};
|
||||||
name: "PluginDetail",
|
|
||||||
params: { name: props.plugin.metadata.name },
|
return [
|
||||||
|
{
|
||||||
|
position: "start",
|
||||||
|
priority: 10,
|
||||||
|
component: markRaw(LogoField),
|
||||||
|
props: {
|
||||||
|
plugin: props.plugin,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
position: "start",
|
||||||
position: "end",
|
priority: 20,
|
||||||
priority: 10,
|
component: markRaw(VEntityField),
|
||||||
component: markRaw(StatusDotField),
|
props: {
|
||||||
props: {
|
title: props.plugin.spec.displayName,
|
||||||
tooltip: getFailedMessage(),
|
description: props.plugin.spec.description,
|
||||||
state: "error",
|
route: {
|
||||||
animate: true,
|
name: "PluginDetail",
|
||||||
|
params: { name: props.plugin.metadata.name },
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
hidden: props.plugin.status?.phase !== "FAILED",
|
{
|
||||||
},
|
position: "end",
|
||||||
{
|
priority: 10,
|
||||||
position: "end",
|
component: markRaw(StatusDotField),
|
||||||
priority: 20,
|
props: {
|
||||||
component: markRaw(StatusDotField),
|
tooltip: getStatusMessage(),
|
||||||
props: {
|
state: getStatusDotState(),
|
||||||
tooltip: t("core.common.status.deleting"),
|
animate: true,
|
||||||
state: "warning",
|
},
|
||||||
animate: true,
|
hidden: shouldHideStatusDot,
|
||||||
},
|
},
|
||||||
hidden: !props.plugin.metadata.deletionTimestamp,
|
{
|
||||||
},
|
position: "end",
|
||||||
{
|
priority: 20,
|
||||||
position: "end",
|
component: markRaw(StatusDotField),
|
||||||
priority: 30,
|
props: {
|
||||||
component: markRaw(AuthorField),
|
tooltip: t("core.common.status.deleting"),
|
||||||
props: {
|
state: "warning",
|
||||||
plugin: props.plugin,
|
animate: true,
|
||||||
|
},
|
||||||
|
hidden: !props.plugin.metadata.deletionTimestamp,
|
||||||
},
|
},
|
||||||
hidden: !props.plugin.spec.author,
|
{
|
||||||
},
|
position: "end",
|
||||||
{
|
priority: 30,
|
||||||
position: "end",
|
component: markRaw(AuthorField),
|
||||||
priority: 40,
|
props: {
|
||||||
component: markRaw(VEntityField),
|
plugin: props.plugin,
|
||||||
props: {
|
},
|
||||||
description: props.plugin.spec.version,
|
hidden: !props.plugin.spec.author,
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
position: "end",
|
||||||
position: "end",
|
priority: 40,
|
||||||
priority: 50,
|
component: markRaw(VEntityField),
|
||||||
component: markRaw(VEntityField),
|
props: {
|
||||||
props: {
|
description: props.plugin.spec.version,
|
||||||
description: formatDatetime(props.plugin.metadata.creationTimestamp),
|
},
|
||||||
},
|
},
|
||||||
hidden: !props.plugin.metadata.creationTimestamp,
|
{
|
||||||
},
|
position: "end",
|
||||||
{
|
priority: 50,
|
||||||
position: "end",
|
component: markRaw(VEntityField),
|
||||||
priority: 60,
|
props: {
|
||||||
component: markRaw(SwitchField),
|
description: formatDatetime(props.plugin.metadata.creationTimestamp),
|
||||||
props: {
|
},
|
||||||
plugin: props.plugin,
|
hidden: !props.plugin.metadata.creationTimestamp,
|
||||||
},
|
},
|
||||||
permissions: ["system:plugins:manage"],
|
{
|
||||||
},
|
position: "end",
|
||||||
])
|
priority: 60,
|
||||||
|
component: markRaw(SwitchField),
|
||||||
|
props: {
|
||||||
|
plugin: props.plugin,
|
||||||
|
},
|
||||||
|
permissions: ["system:plugins:manage"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
})
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { ComputedRef, Ref } from "vue";
|
import type { ComputedRef, Ref } from "vue";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { type Plugin } from "@halo-dev/api-client";
|
import { type Plugin, PluginStatusPhaseEnum } from "@halo-dev/api-client";
|
||||||
import { cloneDeep } from "lodash-es";
|
import { cloneDeep } from "lodash-es";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { Dialog, Toast } from "@halo-dev/components";
|
import { Dialog, Toast } from "@halo-dev/components";
|
||||||
|
@ -9,7 +9,7 @@ import { useMutation } from "@tanstack/vue-query";
|
||||||
|
|
||||||
interface usePluginLifeCycleReturn {
|
interface usePluginLifeCycleReturn {
|
||||||
isStarted: ComputedRef<boolean | undefined>;
|
isStarted: ComputedRef<boolean | undefined>;
|
||||||
getFailedMessage: () => string | undefined;
|
getStatusMessage: () => string | undefined;
|
||||||
changeStatus: () => void;
|
changeStatus: () => void;
|
||||||
changingStatus: Ref<boolean>;
|
changingStatus: Ref<boolean>;
|
||||||
uninstall: (deleteExtensions?: boolean) => void;
|
uninstall: (deleteExtensions?: boolean) => void;
|
||||||
|
@ -22,18 +22,33 @@ export function usePluginLifeCycle(
|
||||||
|
|
||||||
const isStarted = computed(() => {
|
const isStarted = computed(() => {
|
||||||
return (
|
return (
|
||||||
plugin?.value?.status?.phase === "STARTED" && plugin.value?.spec.enabled
|
plugin?.value?.status?.phase === PluginStatusPhaseEnum.Started &&
|
||||||
|
plugin.value?.spec.enabled
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getFailedMessage = () => {
|
const getStatusMessage = () => {
|
||||||
if (!plugin?.value) return;
|
if (!plugin?.value) return;
|
||||||
|
|
||||||
|
const { enabled } = plugin.value.spec || {};
|
||||||
|
const { phase } = plugin.value.status || {};
|
||||||
|
|
||||||
|
// Starting up
|
||||||
|
if (
|
||||||
|
enabled &&
|
||||||
|
phase !== (PluginStatusPhaseEnum.Started || PluginStatusPhaseEnum.Failed)
|
||||||
|
) {
|
||||||
|
return t("core.common.status.starting_up");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starting failed
|
||||||
if (!isStarted.value) {
|
if (!isStarted.value) {
|
||||||
const lastCondition = plugin.value.status?.conditions?.[0];
|
const lastCondition = plugin.value.status?.conditions?.[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
[lastCondition?.reason, lastCondition?.message].join(":") || "Unknown"
|
[lastCondition?.reason, lastCondition?.message]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(":") || "Unknown"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -138,7 +153,7 @@ export function usePluginLifeCycle(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isStarted,
|
isStarted,
|
||||||
getFailedMessage,
|
getStatusMessage,
|
||||||
changeStatus,
|
changeStatus,
|
||||||
changingStatus,
|
changingStatus,
|
||||||
uninstall,
|
uninstall,
|
||||||
|
|
|
@ -1607,6 +1607,7 @@ core:
|
||||||
not_activated: Not activated
|
not_activated: Not activated
|
||||||
installed: Installed
|
installed: Installed
|
||||||
not_installed: Not installed
|
not_installed: Not installed
|
||||||
|
starting_up: Starting
|
||||||
text:
|
text:
|
||||||
none: None
|
none: None
|
||||||
tip: Tip
|
tip: Tip
|
||||||
|
|
|
@ -1551,6 +1551,7 @@ core:
|
||||||
not_activated: 未启用
|
not_activated: 未启用
|
||||||
installed: 已安装
|
installed: 已安装
|
||||||
not_installed: 未安装
|
not_installed: 未安装
|
||||||
|
starting_up: 启动中
|
||||||
text:
|
text:
|
||||||
none: 无
|
none: 无
|
||||||
tip: 提示
|
tip: 提示
|
||||||
|
|
|
@ -1509,6 +1509,7 @@ core:
|
||||||
not_activated: 未啟用
|
not_activated: 未啟用
|
||||||
installed: 已安裝
|
installed: 已安裝
|
||||||
not_installed: 未安裝
|
not_installed: 未安裝
|
||||||
|
starting_up: 启动中
|
||||||
text:
|
text:
|
||||||
none: 無
|
none: 無
|
||||||
tip: 提示
|
tip: 提示
|
||||||
|
|
Loading…
Reference in New Issue