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 { usePermission } from "@/utils/permission";
|
||||
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 { useRouteQuery } from "@vueuse/router";
|
||||
import { usePluginBatchOperations } from "./composables/use-plugin";
|
||||
|
@ -63,11 +63,26 @@ const { data, isLoading, isFetching, refetch } = useQuery<Plugin[]>({
|
|||
},
|
||||
keepPreviousData: true,
|
||||
refetchInterval: (data) => {
|
||||
const deletingPlugins = data?.filter(
|
||||
const hasDeletingData = data?.some(
|
||||
(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 { computed, inject, markRaw, ref, toRefs } from "vue";
|
||||
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 { usePermission } from "@/utils/permission";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
|
@ -43,7 +43,7 @@ const { plugin } = toRefs(props);
|
|||
|
||||
const selectedNames = inject<Ref<string[]>>("selectedNames", ref([]));
|
||||
|
||||
const { getFailedMessage, uninstall } = usePluginLifeCycle(plugin);
|
||||
const { getStatusMessage, uninstall } = usePluginLifeCycle(plugin);
|
||||
|
||||
const pluginUpgradeModalVisible = ref(false);
|
||||
|
||||
|
@ -146,86 +146,105 @@ const { operationItems } = useOperationItemExtensionPoint<Plugin>(
|
|||
const { startFields, endFields } = useEntityFieldItemExtensionPoint<Plugin>(
|
||||
"plugin:list-item:field:create",
|
||||
plugin,
|
||||
computed((): EntityFieldItem[] => [
|
||||
{
|
||||
position: "start",
|
||||
priority: 10,
|
||||
component: markRaw(LogoField),
|
||||
props: {
|
||||
plugin: props.plugin,
|
||||
},
|
||||
},
|
||||
{
|
||||
position: "start",
|
||||
priority: 20,
|
||||
component: markRaw(VEntityField),
|
||||
props: {
|
||||
title: props.plugin.spec.displayName,
|
||||
description: props.plugin.spec.description,
|
||||
route: {
|
||||
name: "PluginDetail",
|
||||
params: { name: props.plugin.metadata.name },
|
||||
computed((): EntityFieldItem[] => {
|
||||
const { enabled } = props.plugin.spec || {};
|
||||
const { phase } = props.plugin.status || {};
|
||||
|
||||
const shouldHideStatusDot =
|
||||
!enabled || (enabled && phase === PluginStatusPhaseEnum.Started);
|
||||
|
||||
const getStatusDotState = () => {
|
||||
if (
|
||||
enabled &&
|
||||
phase !==
|
||||
(PluginStatusPhaseEnum.Started || PluginStatusPhaseEnum.Failed)
|
||||
) {
|
||||
return "default";
|
||||
}
|
||||
return "error";
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
position: "start",
|
||||
priority: 10,
|
||||
component: markRaw(LogoField),
|
||||
props: {
|
||||
plugin: props.plugin,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
position: "end",
|
||||
priority: 10,
|
||||
component: markRaw(StatusDotField),
|
||||
props: {
|
||||
tooltip: getFailedMessage(),
|
||||
state: "error",
|
||||
animate: true,
|
||||
{
|
||||
position: "start",
|
||||
priority: 20,
|
||||
component: markRaw(VEntityField),
|
||||
props: {
|
||||
title: props.plugin.spec.displayName,
|
||||
description: props.plugin.spec.description,
|
||||
route: {
|
||||
name: "PluginDetail",
|
||||
params: { name: props.plugin.metadata.name },
|
||||
},
|
||||
},
|
||||
},
|
||||
hidden: props.plugin.status?.phase !== "FAILED",
|
||||
},
|
||||
{
|
||||
position: "end",
|
||||
priority: 20,
|
||||
component: markRaw(StatusDotField),
|
||||
props: {
|
||||
tooltip: t("core.common.status.deleting"),
|
||||
state: "warning",
|
||||
animate: true,
|
||||
{
|
||||
position: "end",
|
||||
priority: 10,
|
||||
component: markRaw(StatusDotField),
|
||||
props: {
|
||||
tooltip: getStatusMessage(),
|
||||
state: getStatusDotState(),
|
||||
animate: true,
|
||||
},
|
||||
hidden: shouldHideStatusDot,
|
||||
},
|
||||
hidden: !props.plugin.metadata.deletionTimestamp,
|
||||
},
|
||||
{
|
||||
position: "end",
|
||||
priority: 30,
|
||||
component: markRaw(AuthorField),
|
||||
props: {
|
||||
plugin: props.plugin,
|
||||
{
|
||||
position: "end",
|
||||
priority: 20,
|
||||
component: markRaw(StatusDotField),
|
||||
props: {
|
||||
tooltip: t("core.common.status.deleting"),
|
||||
state: "warning",
|
||||
animate: true,
|
||||
},
|
||||
hidden: !props.plugin.metadata.deletionTimestamp,
|
||||
},
|
||||
hidden: !props.plugin.spec.author,
|
||||
},
|
||||
{
|
||||
position: "end",
|
||||
priority: 40,
|
||||
component: markRaw(VEntityField),
|
||||
props: {
|
||||
description: props.plugin.spec.version,
|
||||
{
|
||||
position: "end",
|
||||
priority: 30,
|
||||
component: markRaw(AuthorField),
|
||||
props: {
|
||||
plugin: props.plugin,
|
||||
},
|
||||
hidden: !props.plugin.spec.author,
|
||||
},
|
||||
},
|
||||
{
|
||||
position: "end",
|
||||
priority: 50,
|
||||
component: markRaw(VEntityField),
|
||||
props: {
|
||||
description: formatDatetime(props.plugin.metadata.creationTimestamp),
|
||||
{
|
||||
position: "end",
|
||||
priority: 40,
|
||||
component: markRaw(VEntityField),
|
||||
props: {
|
||||
description: props.plugin.spec.version,
|
||||
},
|
||||
},
|
||||
hidden: !props.plugin.metadata.creationTimestamp,
|
||||
},
|
||||
{
|
||||
position: "end",
|
||||
priority: 60,
|
||||
component: markRaw(SwitchField),
|
||||
props: {
|
||||
plugin: props.plugin,
|
||||
{
|
||||
position: "end",
|
||||
priority: 50,
|
||||
component: markRaw(VEntityField),
|
||||
props: {
|
||||
description: formatDatetime(props.plugin.metadata.creationTimestamp),
|
||||
},
|
||||
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>
|
||||
<template>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { ComputedRef, Ref } 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 { apiClient } from "@/utils/api-client";
|
||||
import { Dialog, Toast } from "@halo-dev/components";
|
||||
|
@ -9,7 +9,7 @@ import { useMutation } from "@tanstack/vue-query";
|
|||
|
||||
interface usePluginLifeCycleReturn {
|
||||
isStarted: ComputedRef<boolean | undefined>;
|
||||
getFailedMessage: () => string | undefined;
|
||||
getStatusMessage: () => string | undefined;
|
||||
changeStatus: () => void;
|
||||
changingStatus: Ref<boolean>;
|
||||
uninstall: (deleteExtensions?: boolean) => void;
|
||||
|
@ -22,18 +22,33 @@ export function usePluginLifeCycle(
|
|||
|
||||
const isStarted = computed(() => {
|
||||
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;
|
||||
|
||||
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) {
|
||||
const lastCondition = plugin.value.status?.conditions?.[0];
|
||||
|
||||
return (
|
||||
[lastCondition?.reason, lastCondition?.message].join(":") || "Unknown"
|
||||
[lastCondition?.reason, lastCondition?.message]
|
||||
.filter(Boolean)
|
||||
.join(":") || "Unknown"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -138,7 +153,7 @@ export function usePluginLifeCycle(
|
|||
|
||||
return {
|
||||
isStarted,
|
||||
getFailedMessage,
|
||||
getStatusMessage,
|
||||
changeStatus,
|
||||
changingStatus,
|
||||
uninstall,
|
||||
|
|
|
@ -1607,6 +1607,7 @@ core:
|
|||
not_activated: Not activated
|
||||
installed: Installed
|
||||
not_installed: Not installed
|
||||
starting_up: Starting
|
||||
text:
|
||||
none: None
|
||||
tip: Tip
|
||||
|
|
|
@ -1551,6 +1551,7 @@ core:
|
|||
not_activated: 未启用
|
||||
installed: 已安装
|
||||
not_installed: 未安装
|
||||
starting_up: 启动中
|
||||
text:
|
||||
none: 无
|
||||
tip: 提示
|
||||
|
|
|
@ -1509,6 +1509,7 @@ core:
|
|||
not_activated: 未啟用
|
||||
installed: 已安裝
|
||||
not_installed: 未安裝
|
||||
starting_up: 启动中
|
||||
text:
|
||||
none: 無
|
||||
tip: 提示
|
||||
|
|
Loading…
Reference in New Issue