mirror of https://github.com/halo-dev/halo-admin
feat: support bulk modification of plugin status
Signed-off-by: Ryan Wang <i@ryanc.cc>pull/588/head
parent
086343ce74
commit
8f95fce0e4
|
@ -25,11 +25,11 @@ const buttonClassification = {
|
||||||
|
|
||||||
const theme: Record<string, Record<string, string>> = {
|
const theme: Record<string, Record<string, string>> = {
|
||||||
global: {
|
global: {
|
||||||
outer: "formkit-disabled:opacity-50 py-3",
|
outer: "formkit-disabled:opacity-50",
|
||||||
help: "text-xs text-gray-500",
|
help: "text-xs text-gray-500",
|
||||||
messages: "list-none p-0 mt-1 mb-0",
|
messages: "list-none p-0 mt-1 mb-0",
|
||||||
message: "text-red-500 mb-1 text-xs",
|
message: "text-red-500 mb-1 text-xs",
|
||||||
form: "flex flex-col divide-y divide-gray-100",
|
form: "flex flex-col divide-y divide-gray-100 space-y-3",
|
||||||
},
|
},
|
||||||
button: buttonClassification,
|
button: buttonClassification,
|
||||||
color: {
|
color: {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
IconArrowDown,
|
IconArrowDown,
|
||||||
IconPlug,
|
IconPlug,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
|
useDialog,
|
||||||
VButton,
|
VButton,
|
||||||
VCard,
|
VCard,
|
||||||
VPageHeader,
|
VPageHeader,
|
||||||
|
@ -11,15 +12,25 @@ import {
|
||||||
VSwitch,
|
VSwitch,
|
||||||
VTag,
|
VTag,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref, watch } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import type { Plugin } from "@/types/extension";
|
import type { Plugin } from "@/types/extension";
|
||||||
import { axiosInstance } from "@halo-dev/admin-shared";
|
import { axiosInstance } from "@halo-dev/admin-shared";
|
||||||
|
import cloneDeep from "lodash.clonedeep";
|
||||||
|
|
||||||
const checkAll = ref(false);
|
const checkedAll = ref(false);
|
||||||
const plugins = ref<Plugin[]>([]);
|
const plugins = ref<Plugin[]>([] as Plugin[]);
|
||||||
|
const selectedPlugins = ref<string[]>([]);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const dialog = useDialog();
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => selectedPlugins.value,
|
||||||
|
(newValue) => {
|
||||||
|
checkedAll.value = newValue.length === plugins.value?.length;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const handleRouteToDetail = (plugin: Plugin) => {
|
const handleRouteToDetail = (plugin: Plugin) => {
|
||||||
router.push({
|
router.push({
|
||||||
|
@ -28,9 +39,9 @@ const handleRouteToDetail = (plugin: Plugin) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function isStarted(plugin: Plugin) {
|
const isStarted = (plugin: Plugin) => {
|
||||||
return plugin.status?.phase === "STARTED" && plugin.spec.enabled;
|
return plugin.status?.phase === "STARTED" && plugin.spec.enabled;
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleFetchPlugins = async () => {
|
const handleFetchPlugins = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -43,18 +54,65 @@ const handleFetchPlugins = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangePluginStatus = async (plugin: Plugin) => {
|
const handleChangeStatus = (plugin: Plugin) => {
|
||||||
|
const pluginToUpdate = cloneDeep(plugin);
|
||||||
|
|
||||||
|
dialog.info({
|
||||||
|
title: `确定要${plugin.spec.enabled ? "停止" : "启动"}该插件吗?`,
|
||||||
|
onConfirm: async () => {
|
||||||
try {
|
try {
|
||||||
plugin.spec.enabled = !plugin.spec.enabled;
|
pluginToUpdate.spec.enabled = !pluginToUpdate.spec.enabled;
|
||||||
await axiosInstance.put(
|
await axiosInstance.put(
|
||||||
`/apis/plugin.halo.run/v1alpha1/plugins/${plugin.metadata.name}`,
|
`/apis/plugin.halo.run/v1alpha1/plugins/${plugin.metadata.name}`,
|
||||||
plugin
|
pluginToUpdate
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckedAllChange = () => {
|
||||||
|
if (checkedAll.value) {
|
||||||
|
selectedPlugins.value = plugins.value.map((plugin) => plugin.metadata.name);
|
||||||
|
} else {
|
||||||
|
selectedPlugins.value.length = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeStatusInBatch = (enable: boolean) => {
|
||||||
|
const pluginsToUpdate = plugins.value.filter(
|
||||||
|
(plugin) =>
|
||||||
|
selectedPlugins.value.includes(plugin.metadata.name) &&
|
||||||
|
plugin.spec.enabled !== enable
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pluginsToUpdate.length === 0) {
|
||||||
|
alert("没有需要更新的插件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.warning({
|
||||||
|
title: `确定要${enable ? "启动" : "停止"}所选插件吗?`,
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
for (const plugin of pluginsToUpdate) {
|
||||||
|
plugin.spec.enabled = enable;
|
||||||
|
await axiosInstance.put(
|
||||||
|
`/apis/plugin.halo.run/v1alpha1/plugins/${plugin.metadata.name}`,
|
||||||
|
plugin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(handleFetchPlugins);
|
onMounted(handleFetchPlugins);
|
||||||
|
@ -83,19 +141,31 @@ onMounted(handleFetchPlugins);
|
||||||
>
|
>
|
||||||
<div class="mr-4 hidden items-center sm:flex">
|
<div class="mr-4 hidden items-center sm:flex">
|
||||||
<input
|
<input
|
||||||
v-model="checkAll"
|
v-model="checkedAll"
|
||||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
@change="handleCheckedAllChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex w-full flex-1 sm:w-auto">
|
<div class="flex w-full flex-1 sm:w-auto">
|
||||||
<FormKit
|
<FormKit
|
||||||
v-if="!checkAll"
|
v-if="!selectedPlugins.length"
|
||||||
placeholder="输入关键词搜索"
|
placeholder="输入关键词搜索"
|
||||||
type="text"
|
type="text"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<VSpace v-else>
|
<VSpace v-else>
|
||||||
<VButton type="default">禁用</VButton>
|
<VButton
|
||||||
|
type="default"
|
||||||
|
@click="handleChangeStatusInBatch(true)"
|
||||||
|
>
|
||||||
|
启用
|
||||||
|
</VButton>
|
||||||
|
<VButton
|
||||||
|
type="default"
|
||||||
|
@click="handleChangeStatusInBatch(false)"
|
||||||
|
>
|
||||||
|
禁用
|
||||||
|
</VButton>
|
||||||
<VButton type="danger">卸载</VButton>
|
<VButton type="danger">卸载</VButton>
|
||||||
</VSpace>
|
</VSpace>
|
||||||
</div>
|
</div>
|
||||||
|
@ -225,18 +295,19 @@ onMounted(handleFetchPlugins);
|
||||||
<li v-for="(plugin, index) in plugins" :key="index">
|
<li v-for="(plugin, index) in plugins" :key="index">
|
||||||
<div
|
<div
|
||||||
:class="{
|
:class="{
|
||||||
'bg-gray-100': checkAll,
|
'bg-gray-100': selectedPlugins.includes(plugin.metadata.name),
|
||||||
}"
|
}"
|
||||||
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
|
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-show="checkAll"
|
v-show="selectedPlugins.includes(plugin.metadata.name)"
|
||||||
class="absolute inset-y-0 left-0 w-0.5 bg-themeable-primary"
|
class="absolute inset-y-0 left-0 w-0.5 bg-themeable-primary"
|
||||||
></div>
|
></div>
|
||||||
<div class="relative flex flex-row items-center">
|
<div class="relative flex flex-row items-center">
|
||||||
<div class="mr-4 hidden items-center sm:flex">
|
<div class="mr-4 hidden items-center sm:flex">
|
||||||
<input
|
<input
|
||||||
v-model="checkAll"
|
v-model="selectedPlugins"
|
||||||
|
:value="plugin.metadata.name"
|
||||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
|
@ -283,12 +354,12 @@ onMounted(handleFetchPlugins);
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
:href="plugin.spec.homepage"
|
:href="plugin.spec.homepage"
|
||||||
class="hidden text-sm text-gray-500 hover:text-gray-900 sm:block"
|
class="text-sm text-gray-500 hover:text-gray-900"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
@{{ plugin.spec.author }}
|
@{{ plugin.spec.author }}
|
||||||
</a>
|
</a>
|
||||||
<span class="hidden text-sm text-gray-500 sm:block">
|
<span class="text-sm text-gray-500">
|
||||||
{{ plugin.spec.version }}
|
{{ plugin.spec.version }}
|
||||||
</span>
|
</span>
|
||||||
<time class="text-sm text-gray-500" datetime="2020-01-07">
|
<time class="text-sm text-gray-500" datetime="2020-01-07">
|
||||||
|
@ -297,7 +368,7 @@ onMounted(handleFetchPlugins);
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<VSwitch
|
<VSwitch
|
||||||
:model-value="isStarted(plugin)"
|
:model-value="isStarted(plugin)"
|
||||||
@click="handleChangePluginStatus(plugin)"
|
@click="handleChangeStatus(plugin)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="cursor-pointer">
|
<span class="cursor-pointer">
|
||||||
|
|
Loading…
Reference in New Issue