mirror of
https://github.com/halo-dev/halo.git
synced 2025-12-20 16:44:38 +08:00
Refactor plugin reconciliation for dependency mechanism (#5900)
#### What type of PR is this? /kind improvement /area core /area plugin #### What this PR does / why we need it: This PR wholly refactors plugin reconciliation to implement dependency mechanism. Currently, - If we disable plugin which has dependents, the plugin must wait for dependents to be disabled. - If we enable plugin which has dependencies , the plugin must wait for dependencies to be enabled. - If we upgrade plugin which has dependents, the plugin must request dependents to be unloaded. After the plugin is unloaded, the plugin must cancel unload request for dependents. #### Which issue(s) this PR fixes: Fixes #5872 #### Special notes for your reviewer: #### Does this PR introduce a user-facing change? ```release-note 优化被依赖的插件的升级,启用和禁用 ```
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
<script lang="ts" setup>
|
||||
import { VButton, VModal } from "@halo-dev/components";
|
||||
import type { Plugin } from "@halo-dev/api-client";
|
||||
import { formatDatetime, relativeTimeTo } from "@/utils/date";
|
||||
import { ref } from "vue";
|
||||
|
||||
withDefaults(defineProps<{ plugin: Plugin }>(), {});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "close"): void;
|
||||
}>();
|
||||
|
||||
const modal = ref();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VModal
|
||||
ref="modal"
|
||||
:body-class="['!p-0']"
|
||||
:title="$t('core.plugin.conditions_modal.title')"
|
||||
:width="900"
|
||||
layer-closable
|
||||
@close="emit('close')"
|
||||
>
|
||||
<table class="min-w-full divide-y divide-gray-100">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th
|
||||
class="px-4 py-3 text-left text-sm font-semibold text-gray-900 sm:w-96"
|
||||
scope="col"
|
||||
>
|
||||
{{ $t("core.plugin.conditions_modal.fields.type") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-4 py-3 text-left text-sm font-semibold text-gray-900"
|
||||
>
|
||||
{{ $t("core.plugin.conditions_modal.fields.status") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-4 py-3 text-left text-sm font-semibold text-gray-900"
|
||||
>
|
||||
{{ $t("core.plugin.conditions_modal.fields.reason") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-4 py-3 text-left text-sm font-semibold text-gray-900"
|
||||
>
|
||||
{{ $t("core.plugin.conditions_modal.fields.message") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-4 py-3 text-left text-sm font-semibold text-gray-900"
|
||||
>
|
||||
{{ $t("core.plugin.conditions_modal.fields.last_transition_time") }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 bg-white">
|
||||
<tr
|
||||
v-for="(condition, index) in plugin?.status?.conditions"
|
||||
:key="index"
|
||||
>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
|
||||
{{ condition.type }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500">
|
||||
{{ condition.status }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500">
|
||||
{{ condition.reason || "-" }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500">
|
||||
{{ condition.message || "-" }}
|
||||
</td>
|
||||
<td
|
||||
v-tooltip="formatDatetime(condition.lastTransitionTime)"
|
||||
class="whitespace-nowrap px-4 py-3 text-sm text-gray-500"
|
||||
>
|
||||
{{ relativeTimeTo(condition.lastTransitionTime) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<template #footer>
|
||||
<VButton @click="modal.close()">
|
||||
{{ $t("core.common.buttons.close") }}
|
||||
</VButton>
|
||||
</template>
|
||||
</VModal>
|
||||
</template>
|
||||
@@ -43,7 +43,8 @@ const { plugin } = toRefs(props);
|
||||
|
||||
const selectedNames = inject<Ref<string[]>>("selectedNames", ref([]));
|
||||
|
||||
const { getStatusMessage, uninstall } = usePluginLifeCycle(plugin);
|
||||
const { getStatusDotState, getStatusMessage, uninstall } =
|
||||
usePluginLifeCycle(plugin);
|
||||
|
||||
const pluginUpgradeModalVisible = ref(false);
|
||||
|
||||
@@ -147,19 +148,11 @@ const { startFields, endFields } = useEntityFieldItemExtensionPoint<Plugin>(
|
||||
"plugin:list-item:field:create",
|
||||
plugin,
|
||||
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.Failed) {
|
||||
return "error";
|
||||
}
|
||||
|
||||
return "default";
|
||||
};
|
||||
phase === PluginStatusPhaseEnum.Started ||
|
||||
phase === PluginStatusPhaseEnum.Disabled;
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useMutation } from "@tanstack/vue-query";
|
||||
|
||||
interface usePluginLifeCycleReturn {
|
||||
isStarted: ComputedRef<boolean | undefined>;
|
||||
getStatusDotState: () => string;
|
||||
getStatusMessage: () => string | undefined;
|
||||
changeStatus: () => void;
|
||||
changingStatus: Ref<boolean>;
|
||||
@@ -27,26 +28,41 @@ export function usePluginLifeCycle(
|
||||
);
|
||||
});
|
||||
|
||||
const getStatusDotState = () => {
|
||||
const { phase } = plugin?.value?.status || {};
|
||||
const { enabled } = plugin?.value?.spec || {};
|
||||
|
||||
if (enabled && phase === PluginStatusPhaseEnum.Failed) {
|
||||
return "error";
|
||||
}
|
||||
|
||||
if (phase === PluginStatusPhaseEnum.Disabling) {
|
||||
return "warning";
|
||||
}
|
||||
|
||||
return "default";
|
||||
};
|
||||
|
||||
const getStatusMessage = () => {
|
||||
if (!plugin?.value) return;
|
||||
|
||||
const { enabled } = plugin.value.spec || {};
|
||||
const { phase } = plugin.value.status || {};
|
||||
|
||||
// Starting failed
|
||||
if (enabled && phase === PluginStatusPhaseEnum.Failed) {
|
||||
if (
|
||||
phase === PluginStatusPhaseEnum.Failed ||
|
||||
phase === PluginStatusPhaseEnum.Disabling
|
||||
) {
|
||||
const lastCondition = plugin.value.status?.conditions?.[0];
|
||||
|
||||
return (
|
||||
[lastCondition?.reason, lastCondition?.message]
|
||||
.filter(Boolean)
|
||||
.join(":") || "Unknown"
|
||||
.join(": ") || "Unknown"
|
||||
);
|
||||
}
|
||||
|
||||
// Starting up
|
||||
if (
|
||||
enabled &&
|
||||
phase !== (PluginStatusPhaseEnum.Started || PluginStatusPhaseEnum.Failed)
|
||||
) {
|
||||
return t("core.common.status.starting_up");
|
||||
@@ -153,6 +169,7 @@ export function usePluginLifeCycle(
|
||||
|
||||
return {
|
||||
isStarted,
|
||||
getStatusDotState,
|
||||
getStatusMessage,
|
||||
changeStatus,
|
||||
changingStatus,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
VAlert,
|
||||
VButton,
|
||||
VDescription,
|
||||
VDescriptionItem,
|
||||
VSwitch,
|
||||
@@ -8,12 +9,18 @@ import {
|
||||
import type { Ref } from "vue";
|
||||
import { computed, inject } from "vue";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import type { Plugin, Role } from "@halo-dev/api-client";
|
||||
import {
|
||||
PluginStatusPhaseEnum,
|
||||
type Plugin,
|
||||
type Role,
|
||||
} from "@halo-dev/api-client";
|
||||
import { pluginLabels, roleLabels } from "@/constants/labels";
|
||||
import { rbacAnnotations } from "@/constants/annotations";
|
||||
import { usePluginLifeCycle } from "../composables/use-plugin";
|
||||
import { formatDatetime } from "@/utils/date";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { ref } from "vue";
|
||||
import PluginConditionsModal from "../components/PluginConditionsModal.vue";
|
||||
|
||||
const plugin = inject<Ref<Plugin | undefined>>("plugin");
|
||||
const { changeStatus, changingStatus } = usePluginLifeCycle(plugin);
|
||||
@@ -60,9 +67,30 @@ const pluginRoleTemplateGroups = computed<RoleTemplateGroup[]>(() => {
|
||||
});
|
||||
return groups;
|
||||
});
|
||||
|
||||
// Error alert
|
||||
const conditionsModalVisible = ref(false);
|
||||
|
||||
const errorAlertVisible = computed(() => {
|
||||
const { phase } = plugin?.value?.status || {};
|
||||
|
||||
return (
|
||||
phase !== PluginStatusPhaseEnum.Started &&
|
||||
phase !== PluginStatusPhaseEnum.Disabled
|
||||
);
|
||||
});
|
||||
|
||||
const lastCondition = computed(() => {
|
||||
return plugin?.value?.status?.conditions?.[0];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PluginConditionsModal
|
||||
v-if="conditionsModalVisible && plugin"
|
||||
:plugin="plugin"
|
||||
@close="conditionsModalVisible = false"
|
||||
/>
|
||||
<Transition mode="out-in" name="fade">
|
||||
<div class="overflow-hidden rounded-b-base">
|
||||
<div class="flex items-center justify-between bg-white px-4 py-4 sm:px-6">
|
||||
@@ -80,18 +108,21 @@ const pluginRoleTemplateGroups = computed<RoleTemplateGroup[]>(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
plugin?.status?.phase === 'FAILED' &&
|
||||
plugin?.status?.conditions?.length
|
||||
"
|
||||
v-if="errorAlertVisible && lastCondition"
|
||||
class="w-full px-4 pb-2 sm:px-6"
|
||||
>
|
||||
<VAlert
|
||||
type="error"
|
||||
:title="plugin?.status?.conditions?.[0].reason"
|
||||
:description="plugin?.status?.conditions?.[0].message"
|
||||
:title="lastCondition.reason"
|
||||
:description="lastCondition.message"
|
||||
:closable="false"
|
||||
/>
|
||||
>
|
||||
<template #actions>
|
||||
<VButton size="sm" @click="conditionsModalVisible = true">
|
||||
{{ $t("core.plugin.detail.operations.view_conditions.button") }}
|
||||
</VButton>
|
||||
</template>
|
||||
</VAlert>
|
||||
</div>
|
||||
<div class="border-t border-gray-200">
|
||||
<VDescription>
|
||||
|
||||
Reference in New Issue
Block a user