mirror of https://github.com/halo-dev/halo
feat: add batch operation feature for plugins (#4482)
#### What type of PR is this? /area console /kind feature /milestone 2.9.x #### What this PR does / why we need it: 支持对插件进行批量操作。 <img width="577" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/ec3ed727-e151-44d0-8c18-b6ec8a309ea9"> #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/4475 #### Special notes for your reviewer: 测试批量对插件进行启用、停用、卸载。 #### Does this PR introduce a user-facing change? ```release-note Console 端的插件管理支持批量操作。 ```pull/4495/head
parent
e40b5d2388
commit
f01e04f5a0
|
@ -732,11 +732,20 @@ core:
|
||||||
toast_success: Reset configuration successfully
|
toast_success: Reset configuration successfully
|
||||||
uninstall:
|
uninstall:
|
||||||
title: Are you sure you want to uninstall this plugin?
|
title: Are you sure you want to uninstall this plugin?
|
||||||
|
uninstall_in_batch:
|
||||||
|
title: Are you sure you want to uninstall these plugin?
|
||||||
uninstall_and_delete_config:
|
uninstall_and_delete_config:
|
||||||
|
button: Uninstall and delete config
|
||||||
title: Are you sure you want to uninstall this plugin and its corresponding configuration?
|
title: Are you sure you want to uninstall this plugin and its corresponding configuration?
|
||||||
|
uninstall_and_delete_config_in_batch:
|
||||||
|
button: Uninstall and delete config
|
||||||
|
title: Are you sure you want to uninstall these plugin and its corresponding configuration?
|
||||||
uninstall_when_enabled:
|
uninstall_when_enabled:
|
||||||
confirm_text: Stop running and uninstall
|
confirm_text: Stop running and uninstall
|
||||||
description: The current plugin is still in the enabled state and will be uninstalled after it stops running. This operation cannot be undone.
|
description: The current plugin is still in the enabled state and will be uninstalled after it stops running. This operation cannot be undone.
|
||||||
|
change_status_in_batch:
|
||||||
|
activate_title: Are you sure you want to activate these plugins?
|
||||||
|
inactivate_title: Are you sure you want to inactivate these plugins?
|
||||||
remote_download:
|
remote_download:
|
||||||
title: Remote download address detected, do you want to download?
|
title: Remote download address detected, do you want to download?
|
||||||
description: "Please carefully verify whether this address can be trusted: {url}"
|
description: "Please carefully verify whether this address can be trusted: {url}"
|
||||||
|
@ -749,9 +758,6 @@ core:
|
||||||
items:
|
items:
|
||||||
create_time_desc: Latest Installed
|
create_time_desc: Latest Installed
|
||||||
create_time_asc: Earliest Installed
|
create_time_asc: Earliest Installed
|
||||||
list:
|
|
||||||
actions:
|
|
||||||
uninstall_and_delete_config: Uninstall and delete config
|
|
||||||
upload_modal:
|
upload_modal:
|
||||||
titles:
|
titles:
|
||||||
install: Install plugin
|
install: Install plugin
|
||||||
|
@ -1197,7 +1203,8 @@ core:
|
||||||
preview: Preview
|
preview: Preview
|
||||||
recovery: Recovery
|
recovery: Recovery
|
||||||
delete_permanently: Delete Permanently
|
delete_permanently: Delete Permanently
|
||||||
active: Active
|
activate: Activate
|
||||||
|
inactivate: Inactivate
|
||||||
download: Download
|
download: Download
|
||||||
copy: Copy
|
copy: Copy
|
||||||
upload: Upload
|
upload: Upload
|
||||||
|
|
|
@ -732,11 +732,20 @@ core:
|
||||||
toast_success: 重置配置成功
|
toast_success: 重置配置成功
|
||||||
uninstall:
|
uninstall:
|
||||||
title: 确定要卸载该插件吗?
|
title: 确定要卸载该插件吗?
|
||||||
|
uninstall_in_batch:
|
||||||
|
title: 确定要卸载所选插件吗?
|
||||||
uninstall_and_delete_config:
|
uninstall_and_delete_config:
|
||||||
|
button: 卸载并删除配置
|
||||||
title: 确定要卸载该插件以及对应的配置吗?
|
title: 确定要卸载该插件以及对应的配置吗?
|
||||||
|
uninstall_and_delete_config_in_batch:
|
||||||
|
button: 卸载并删除配置
|
||||||
|
title: 确定要卸载所选插件以及对应的配置吗?
|
||||||
uninstall_when_enabled:
|
uninstall_when_enabled:
|
||||||
confirm_text: 停止运行并卸载
|
confirm_text: 停止运行并卸载
|
||||||
description: 当前插件还在启用状态,将在停止运行后卸载,该操作不可恢复。
|
description: 当前插件还在启用状态,将在停止运行后卸载,该操作不可恢复。
|
||||||
|
change_status_in_batch:
|
||||||
|
activate_title: 确定要启用所选插件吗?
|
||||||
|
inactivate_title: 确定要停用所选插件吗?
|
||||||
remote_download:
|
remote_download:
|
||||||
title: 检测到了远程下载地址,是否需要下载?
|
title: 检测到了远程下载地址,是否需要下载?
|
||||||
description: 请仔细鉴别此地址是否可信:{url}
|
description: 请仔细鉴别此地址是否可信:{url}
|
||||||
|
@ -749,9 +758,6 @@ core:
|
||||||
items:
|
items:
|
||||||
create_time_desc: 较近安装
|
create_time_desc: 较近安装
|
||||||
create_time_asc: 较早安装
|
create_time_asc: 较早安装
|
||||||
list:
|
|
||||||
actions:
|
|
||||||
uninstall_and_delete_config: 卸载并删除配置
|
|
||||||
upload_modal:
|
upload_modal:
|
||||||
titles:
|
titles:
|
||||||
install: 安装插件
|
install: 安装插件
|
||||||
|
@ -1197,7 +1203,8 @@ core:
|
||||||
preview: 预览
|
preview: 预览
|
||||||
recovery: 恢复
|
recovery: 恢复
|
||||||
delete_permanently: 永久删除
|
delete_permanently: 永久删除
|
||||||
active: 启用
|
activate: 启用
|
||||||
|
inactivate: 停用
|
||||||
download: 下载
|
download: 下载
|
||||||
copy: 复制
|
copy: 复制
|
||||||
upload: 上传
|
upload: 上传
|
||||||
|
|
|
@ -732,11 +732,20 @@ core:
|
||||||
toast_success: 重置配置成功
|
toast_success: 重置配置成功
|
||||||
uninstall:
|
uninstall:
|
||||||
title: 確定要卸載該插件嗎?
|
title: 確定要卸載該插件嗎?
|
||||||
|
uninstall_in_batch:
|
||||||
|
title: 確定要卸載所選插件嗎?
|
||||||
uninstall_and_delete_config:
|
uninstall_and_delete_config:
|
||||||
|
button: 卸載並刪除配置
|
||||||
title: 確定要卸載該插件以及對應的配置嗎?
|
title: 確定要卸載該插件以及對應的配置嗎?
|
||||||
|
uninstall_and_delete_config_in_batch:
|
||||||
|
button: 卸載並刪除配置
|
||||||
|
title: 確定要卸載所選插件以及對應的配置嗎?
|
||||||
uninstall_when_enabled:
|
uninstall_when_enabled:
|
||||||
confirm_text: 停止運行並卸載
|
confirm_text: 停止運行並卸載
|
||||||
description: 當前插件還在啟用狀態,將在停止運行後卸載,該操作不可恢復。
|
description: 當前插件還在啟用狀態,將在停止運行後卸載,該操作不可恢復。
|
||||||
|
change_status_in_batch:
|
||||||
|
activate_title: 確定要啟用所選插件嗎?
|
||||||
|
inactivate_title: 確定要停用所選插件嗎?
|
||||||
remote_download:
|
remote_download:
|
||||||
title: 偵測到遠端下載地址,是否需要下載?
|
title: 偵測到遠端下載地址,是否需要下載?
|
||||||
description: 請仔細鑑別此地址是否可信:{url}
|
description: 請仔細鑑別此地址是否可信:{url}
|
||||||
|
@ -749,9 +758,6 @@ core:
|
||||||
items:
|
items:
|
||||||
create_time_desc: 較近安裝
|
create_time_desc: 較近安裝
|
||||||
create_time_asc: 較早安裝
|
create_time_asc: 較早安裝
|
||||||
list:
|
|
||||||
actions:
|
|
||||||
uninstall_and_delete_config: 卸載並刪除配置
|
|
||||||
upload_modal:
|
upload_modal:
|
||||||
titles:
|
titles:
|
||||||
install: 安裝插件
|
install: 安裝插件
|
||||||
|
@ -1197,7 +1203,8 @@ core:
|
||||||
preview: 預覽
|
preview: 預覽
|
||||||
recovery: 恢復
|
recovery: 恢復
|
||||||
delete_permanently: 永久刪除
|
delete_permanently: 永久刪除
|
||||||
active: 啟用
|
activate: 啟用
|
||||||
|
inactivate: 停用
|
||||||
download: 下載
|
download: 下載
|
||||||
copy: 複製
|
copy: 複製
|
||||||
upload: 上傳
|
upload: 上傳
|
||||||
|
|
|
@ -194,7 +194,7 @@ const handleUninstall = async (theme: Theme, deleteExtensions?: boolean) => {
|
||||||
#dropdownItems
|
#dropdownItems
|
||||||
>
|
>
|
||||||
<VDropdownItem v-if="!isActivated" @click="handleActiveTheme(true)">
|
<VDropdownItem v-if="!isActivated" @click="handleActiveTheme(true)">
|
||||||
{{ $t("core.common.buttons.active") }}
|
{{ $t("core.common.buttons.activate") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
<VDropdownItem @click="emit('upgrade')">
|
<VDropdownItem @click="emit('upgrade')">
|
||||||
{{ $t("core.common.buttons.upgrade") }}
|
{{ $t("core.common.buttons.upgrade") }}
|
||||||
|
|
|
@ -81,7 +81,7 @@ const { isActivated, handleActiveTheme } = useThemeLifeCycle(theme);
|
||||||
|
|
||||||
<template #dropdownItems>
|
<template #dropdownItems>
|
||||||
<VDropdownItem v-if="!isActivated" @click="handleActiveTheme()">
|
<VDropdownItem v-if="!isActivated" @click="handleActiveTheme()">
|
||||||
{{ $t("core.common.buttons.active") }}
|
{{ $t("core.common.buttons.activate") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
<VDropdownItem @click="emit('open-settings')">
|
<VDropdownItem @click="emit('open-settings')">
|
||||||
{{ $t("core.common.buttons.setting") }}
|
{{ $t("core.common.buttons.setting") }}
|
||||||
|
|
|
@ -202,7 +202,7 @@ onMounted(() => {
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleActiveTheme()"
|
@click="handleActiveTheme()"
|
||||||
>
|
>
|
||||||
{{ $t("core.common.buttons.active") }}
|
{{ $t("core.common.buttons.activate") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton type="secondary" size="sm" @click="previewModal = true">
|
<VButton type="secondary" size="sm" @click="previewModal = true">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
|
|
@ -10,6 +10,8 @@ import {
|
||||||
VSpace,
|
VSpace,
|
||||||
VLoading,
|
VLoading,
|
||||||
Dialog,
|
Dialog,
|
||||||
|
VDropdown,
|
||||||
|
VDropdownItem,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import PluginListItem from "./components/PluginListItem.vue";
|
import PluginListItem from "./components/PluginListItem.vue";
|
||||||
import PluginInstallationModal from "./components/PluginInstallationModal.vue";
|
import PluginInstallationModal from "./components/PluginInstallationModal.vue";
|
||||||
|
@ -20,6 +22,10 @@ import { useQuery } from "@tanstack/vue-query";
|
||||||
import type { Plugin } from "@halo-dev/api-client";
|
import 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 { watch } from "vue";
|
||||||
|
import { provide } from "vue";
|
||||||
|
import type { Ref } from "vue";
|
||||||
|
import { usePluginBatchOperations } from "./composables/use-plugin";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { currentUserHasPermission } = usePermission();
|
const { currentUserHasPermission } = usePermission();
|
||||||
|
@ -68,6 +74,33 @@ const { data, isLoading, isFetching, refetch } = useQuery<Plugin[]>({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// selection
|
||||||
|
const selectedNames = ref<string[]>([]);
|
||||||
|
provide<Ref<string[]>>("selectedNames", selectedNames);
|
||||||
|
const checkedAll = ref(false);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => selectedNames.value,
|
||||||
|
(value) => {
|
||||||
|
checkedAll.value = value.length === data.value?.length;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCheckAllChange = (e: Event) => {
|
||||||
|
const { checked } = e.target as HTMLInputElement;
|
||||||
|
if (checked) {
|
||||||
|
selectedNames.value =
|
||||||
|
data.value?.map((plugin) => {
|
||||||
|
return plugin.metadata.name;
|
||||||
|
}) || [];
|
||||||
|
} else {
|
||||||
|
selectedNames.value.length = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { handleChangeStatusInBatch, handleUninstallInBatch } =
|
||||||
|
usePluginBatchOperations(selectedNames);
|
||||||
|
|
||||||
// handle remote download url from route
|
// handle remote download url from route
|
||||||
const routeRemoteDownloadUrl = useRouteQuery<string | null>(
|
const routeRemoteDownloadUrl = useRouteQuery<string | null>(
|
||||||
"remote-download-url"
|
"remote-download-url"
|
||||||
|
@ -123,8 +156,50 @@ onMounted(() => {
|
||||||
<div
|
<div
|
||||||
class="relative flex flex-col items-start sm:flex-row sm:items-center"
|
class="relative flex flex-col items-start sm:flex-row sm:items-center"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-permission="['system:posts:manage']"
|
||||||
|
class="mr-4 hidden items-center sm:flex"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="checkedAll"
|
||||||
|
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||||
|
type="checkbox"
|
||||||
|
@change="handleCheckAllChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="flex w-full flex-1 items-center gap-2 sm:w-auto">
|
<div class="flex w-full flex-1 items-center gap-2 sm:w-auto">
|
||||||
<SearchInput v-model="keyword" />
|
<SearchInput v-if="!selectedNames.length" v-model="keyword" />
|
||||||
|
<VSpace v-else>
|
||||||
|
<VButton @click="handleChangeStatusInBatch(true)">
|
||||||
|
{{ $t("core.common.buttons.activate") }}
|
||||||
|
</VButton>
|
||||||
|
<VButton @click="handleChangeStatusInBatch(false)">
|
||||||
|
{{ $t("core.common.buttons.inactivate") }}
|
||||||
|
</VButton>
|
||||||
|
<VDropdown>
|
||||||
|
<VButton type="danger">
|
||||||
|
{{ $t("core.common.buttons.uninstall") }}
|
||||||
|
</VButton>
|
||||||
|
<template #popper>
|
||||||
|
<VDropdownItem
|
||||||
|
type="danger"
|
||||||
|
@click="handleUninstallInBatch(false)"
|
||||||
|
>
|
||||||
|
{{ $t("core.common.buttons.uninstall") }}
|
||||||
|
</VDropdownItem>
|
||||||
|
<VDropdownItem
|
||||||
|
type="danger"
|
||||||
|
@click="handleUninstallInBatch(true)"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
"core.plugin.operations.uninstall_and_delete_config.button"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</VDropdownItem>
|
||||||
|
</template>
|
||||||
|
</VDropdown>
|
||||||
|
</VSpace>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex sm:mt-0">
|
<div class="mt-4 flex sm:mt-0">
|
||||||
<VSpace spacing="lg">
|
<VSpace spacing="lg">
|
||||||
|
@ -223,6 +298,7 @@ onMounted(() => {
|
||||||
<li v-for="plugin in data" :key="plugin.metadata.name">
|
<li v-for="plugin in data" :key="plugin.metadata.name">
|
||||||
<PluginListItem
|
<PluginListItem
|
||||||
:plugin="plugin"
|
:plugin="plugin"
|
||||||
|
:is-selected="selectedNames.includes(plugin.metadata.name)"
|
||||||
@open-upgrade-modal="handleOpenUploadModal"
|
@open-upgrade-modal="handleOpenUploadModal"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -10,13 +10,15 @@ import {
|
||||||
VDropdownItem,
|
VDropdownItem,
|
||||||
VDropdownDivider,
|
VDropdownDivider,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import { markRaw, toRefs } from "vue";
|
import { inject, toRefs, markRaw } 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 } 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";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import type { Ref } from "vue";
|
||||||
|
import { ref } from "vue";
|
||||||
import { useEntityDropdownItemExtensionPoint } from "@/composables/use-entity-extension-points";
|
import { useEntityDropdownItemExtensionPoint } from "@/composables/use-entity-extension-points";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import EntityDropdownItems from "@/components/entity/EntityDropdownItems.vue";
|
import EntityDropdownItems from "@/components/entity/EntityDropdownItems.vue";
|
||||||
|
@ -27,11 +29,10 @@ const router = useRouter();
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
plugin?: Plugin;
|
plugin: Plugin;
|
||||||
|
isSelected?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{ isSelected: false }
|
||||||
plugin: undefined,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -40,6 +41,8 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const { plugin } = toRefs(props);
|
const { plugin } = toRefs(props);
|
||||||
|
|
||||||
|
const selectedNames = inject<Ref<string[]>>("selectedNames", ref([]));
|
||||||
|
|
||||||
const { getFailedMessage, changeStatus, changingStatus, uninstall } =
|
const { getFailedMessage, changeStatus, changingStatus, uninstall } =
|
||||||
usePluginLifeCycle(plugin);
|
usePluginLifeCycle(plugin);
|
||||||
|
|
||||||
|
@ -124,7 +127,7 @@ const { dropdownItems } = useEntityDropdownItemExtensionPoint<Plugin>(
|
||||||
props: {
|
props: {
|
||||||
type: "danger",
|
type: "danger",
|
||||||
},
|
},
|
||||||
label: t("core.plugin.list.actions.uninstall_and_delete_config"),
|
label: t("core.plugin.operations.uninstall_and_delete_config.button"),
|
||||||
visible: true,
|
visible: true,
|
||||||
action: () => uninstall(true),
|
action: () => uninstall(true),
|
||||||
},
|
},
|
||||||
|
@ -146,33 +149,45 @@ const { dropdownItems } = useEntityDropdownItemExtensionPoint<Plugin>(
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VEntity>
|
<VEntity :is-selected="isSelected">
|
||||||
|
<template
|
||||||
|
v-if="currentUserHasPermission(['system:plugins:manage'])"
|
||||||
|
#checkbox
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="selectedNames"
|
||||||
|
:value="plugin.metadata.name"
|
||||||
|
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||||
|
name="post-checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<template #start>
|
<template #start>
|
||||||
<VEntityField>
|
<VEntityField>
|
||||||
<template #description>
|
<template #description>
|
||||||
<VAvatar
|
<VAvatar
|
||||||
:alt="plugin?.spec.displayName"
|
:alt="plugin.spec.displayName"
|
||||||
:src="plugin?.status?.logo"
|
:src="plugin.status?.logo"
|
||||||
size="md"
|
size="md"
|
||||||
></VAvatar>
|
></VAvatar>
|
||||||
</template>
|
</template>
|
||||||
</VEntityField>
|
</VEntityField>
|
||||||
<VEntityField
|
<VEntityField
|
||||||
:title="plugin?.spec.displayName"
|
:title="plugin.spec.displayName"
|
||||||
:description="plugin?.spec.description"
|
:description="plugin.spec.description"
|
||||||
:route="{
|
:route="{
|
||||||
name: 'PluginDetail',
|
name: 'PluginDetail',
|
||||||
params: { name: plugin?.metadata.name },
|
params: { name: plugin.metadata.name },
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #end>
|
<template #end>
|
||||||
<VEntityField v-if="plugin?.status?.phase === 'FAILED'">
|
<VEntityField v-if="plugin.status?.phase === 'FAILED'">
|
||||||
<template #description>
|
<template #description>
|
||||||
<VStatusDot v-tooltip="getFailedMessage()" state="error" animate />
|
<VStatusDot v-tooltip="getFailedMessage()" state="error" animate />
|
||||||
</template>
|
</template>
|
||||||
</VEntityField>
|
</VEntityField>
|
||||||
<VEntityField v-if="plugin?.metadata.deletionTimestamp">
|
<VEntityField v-if="plugin.metadata.deletionTimestamp">
|
||||||
<template #description>
|
<template #description>
|
||||||
<VStatusDot
|
<VStatusDot
|
||||||
v-tooltip="$t('core.common.status.deleting')"
|
v-tooltip="$t('core.common.status.deleting')"
|
||||||
|
@ -181,22 +196,22 @@ const { dropdownItems } = useEntityDropdownItemExtensionPoint<Plugin>(
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</VEntityField>
|
</VEntityField>
|
||||||
<VEntityField v-if="plugin?.spec.author">
|
<VEntityField v-if="plugin.spec.author">
|
||||||
<template #description>
|
<template #description>
|
||||||
<a
|
<a
|
||||||
:href="plugin?.spec.author.website"
|
:href="plugin.spec.author.website"
|
||||||
class="hidden text-sm text-gray-500 hover:text-gray-900 sm:block"
|
class="hidden text-sm text-gray-500 hover:text-gray-900 sm:block"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
@{{ plugin?.spec.author.name }}
|
@{{ plugin.spec.author.name }}
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</VEntityField>
|
</VEntityField>
|
||||||
<VEntityField :description="plugin?.spec.version" />
|
<VEntityField :description="plugin.spec.version" />
|
||||||
<VEntityField v-if="plugin?.metadata.creationTimestamp">
|
<VEntityField v-if="plugin.metadata.creationTimestamp">
|
||||||
<template #description>
|
<template #description>
|
||||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||||
{{ formatDatetime(plugin?.metadata.creationTimestamp) }}
|
{{ formatDatetime(plugin.metadata.creationTimestamp) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</VEntityField>
|
</VEntityField>
|
||||||
|
@ -204,7 +219,7 @@ const { dropdownItems } = useEntityDropdownItemExtensionPoint<Plugin>(
|
||||||
<template #description>
|
<template #description>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<VSwitch
|
<VSwitch
|
||||||
:model-value="plugin?.spec.enabled"
|
:model-value="plugin.spec.enabled"
|
||||||
:disabled="changingStatus"
|
:disabled="changingStatus"
|
||||||
@click="changeStatus"
|
@click="changeStatus"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -143,3 +143,101 @@ export function usePluginLifeCycle(
|
||||||
uninstall,
|
uninstall,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function usePluginBatchOperations(names: Ref<string[]>) {
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
function handleUninstallInBatch(deleteExtensions: boolean) {
|
||||||
|
Dialog.warning({
|
||||||
|
title: `${
|
||||||
|
deleteExtensions
|
||||||
|
? t(
|
||||||
|
"core.plugin.operations.uninstall_and_delete_config_in_batch.title"
|
||||||
|
)
|
||||||
|
: t("core.plugin.operations.uninstall_in_batch.title")
|
||||||
|
}`,
|
||||||
|
description: t("core.common.dialog.descriptions.cannot_be_recovered"),
|
||||||
|
confirmType: "danger",
|
||||||
|
confirmText: t("core.common.buttons.uninstall"),
|
||||||
|
cancelText: t("core.common.buttons.cancel"),
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < names.value.length; i++) {
|
||||||
|
await apiClient.extension.plugin.deletepluginHaloRunV1alpha1Plugin({
|
||||||
|
name: names.value[i],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (deleteExtensions) {
|
||||||
|
const { data: plugin } =
|
||||||
|
await apiClient.extension.plugin.getpluginHaloRunV1alpha1Plugin(
|
||||||
|
{
|
||||||
|
name: names.value[i],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { settingName, configMapName } = plugin.spec;
|
||||||
|
|
||||||
|
if (settingName) {
|
||||||
|
await apiClient.extension.setting.deletev1alpha1Setting(
|
||||||
|
{
|
||||||
|
name: settingName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mute: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configMapName) {
|
||||||
|
await apiClient.extension.configMap.deletev1alpha1ConfigMap(
|
||||||
|
{
|
||||||
|
name: configMapName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mute: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to uninstall plugin in batch", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChangeStatusInBatch(enabled: boolean) {
|
||||||
|
Dialog.info({
|
||||||
|
title: enabled
|
||||||
|
? t("core.plugin.operations.change_status_in_batch.activate_title")
|
||||||
|
: t("core.plugin.operations.change_status_in_batch.inactivate_title"),
|
||||||
|
confirmText: t("core.common.buttons.confirm"),
|
||||||
|
cancelText: t("core.common.buttons.cancel"),
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < names.value.length; i++) {
|
||||||
|
const { data: pluginToUpdate } =
|
||||||
|
await apiClient.extension.plugin.getpluginHaloRunV1alpha1Plugin({
|
||||||
|
name: names.value[i],
|
||||||
|
});
|
||||||
|
|
||||||
|
pluginToUpdate.spec.enabled = enabled;
|
||||||
|
await apiClient.extension.plugin.updatepluginHaloRunV1alpha1Plugin({
|
||||||
|
name: pluginToUpdate.metadata.name,
|
||||||
|
plugin: pluginToUpdate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to change plugin status in batch", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { handleUninstallInBatch, handleChangeStatusInBatch };
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue