Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion astrbot/core/star/star_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,11 +680,18 @@ async def install_plugin(self, repo_url: str, proxy=""):

return plugin_info

async def uninstall_plugin(self, plugin_name: str):
async def uninstall_plugin(
self,
plugin_name: str,
delete_config: bool = False,
delete_data: bool = False,
):
"""卸载指定的插件。

Args:
plugin_name (str): 要卸载的插件名称
delete_config (bool): 是否删除插件配置文件,默认为 False
delete_data (bool): 是否删除插件数据,默认为 False

Raises:
Exception: 当插件不存在、是保留插件时,或删除插件文件夹失败时抛出异常
Expand Down Expand Up @@ -714,13 +721,59 @@ async def uninstall_plugin(self, plugin_name: str):

await self._unbind_plugin(plugin_name, plugin.module_path)

# 删除插件文件夹
try:
remove_dir(os.path.join(ppath, root_dir_name))
except Exception as e:
raise Exception(
f"移除插件成功,但是删除插件文件夹失败: {e!s}。您可以手动删除该文件夹,位于 addons/plugins/ 下。",
)

# 删除插件配置文件
if delete_config and root_dir_name:
config_file = os.path.join(
self.plugin_config_path,
f"{root_dir_name}_config.json",
)
if os.path.exists(config_file):
try:
os.remove(config_file)
logger.info(f"已删除插件 {plugin_name} 的配置文件")
except Exception as e:
logger.warning(f"删除插件配置文件失败: {e!s}")

# 删除插件持久化数据
# 注意:需要检查两个可能的目录名(plugin_data 和 plugins_data)
# data/temp 目录可能被多个插件共享,不自动删除以防误删
if delete_data and root_dir_name:
data_base_dir = os.path.dirname(ppath) # data/

# 删除 data/plugin_data 下的插件持久化数据(单数形式,新版本)
plugin_data_dir = os.path.join(
data_base_dir, "plugin_data", root_dir_name
)
if os.path.exists(plugin_data_dir):
try:
remove_dir(plugin_data_dir)
logger.info(
f"已删除插件 {plugin_name} 的持久化数据 (plugin_data)"
)
except Exception as e:
logger.warning(f"删除插件持久化数据失败 (plugin_data): {e!s}")

# 删除 data/plugins_data 下的插件持久化数据(复数形式,旧版本兼容)
plugins_data_dir = os.path.join(
data_base_dir, "plugins_data", root_dir_name
)
if os.path.exists(plugins_data_dir):
try:
remove_dir(plugins_data_dir)
logger.info(
f"已删除插件 {plugin_name} 的持久化数据 (plugins_data)"
)
except Exception as e:
logger.warning(f"删除插件持久化数据失败 (plugins_data): {e!s}")

async def _unbind_plugin(self, plugin_name: str, plugin_module_path: str):
"""解绑并移除一个插件。

Expand Down
8 changes: 7 additions & 1 deletion astrbot/dashboard/routes/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,15 @@ async def uninstall_plugin(self):

post_data = await request.json
plugin_name = post_data["name"]
delete_config = post_data.get("delete_config", False)
delete_data = post_data.get("delete_data", False)
try:
logger.info(f"正在卸载插件 {plugin_name}")
await self.plugin_manager.uninstall_plugin(plugin_name)
await self.plugin_manager.uninstall_plugin(
plugin_name,
delete_config=delete_config,
delete_data=delete_data,
)
logger.info(f"卸载插件 {plugin_name} 成功")
return Response().ok(None, "卸载成功").__dict__
except Exception as e:
Expand Down
24 changes: 12 additions & 12 deletions dashboard/src/components/shared/ExtensionCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { ref, computed, inject } from 'vue';
import { useCustomizerStore } from "@/stores/customizer";
import { useModuleI18n } from '@/i18n/composables';
import UninstallConfirmDialog from './UninstallConfirmDialog.vue';

const props = defineProps({
extension: {
Expand Down Expand Up @@ -31,6 +32,7 @@ const emit = defineEmits([
]);

const reveal = ref(false);
const showUninstallDialog = ref(false);

// 国际化
const { tm } = useModuleI18n('features/extension');
Expand All @@ -55,19 +57,11 @@ const installExtension = async () => {
};

const uninstallExtension = async () => {
if (typeof $confirm !== "function") {
console.error(tm("card.errors.confirmNotRegistered"));
return;
}

const confirmed = await $confirm({
title: tm("dialogs.uninstall.title"),
message: tm("dialogs.uninstall.message"),
});
showUninstallDialog.value = true;
};

if (confirmed) {
emit("uninstall", props.extension);
}
const handleUninstallConfirm = (options: { deleteConfig: boolean; deleteData: boolean }) => {
emit("uninstall", props.extension, options);
};

const toggleActivation = () => {
Expand Down Expand Up @@ -220,6 +214,12 @@ const viewReadme = () => {
</v-card-actions>
</v-card>

<!-- 卸载确认对话框 -->
<UninstallConfirmDialog
v-model="showUninstallDialog"
@confirm="handleUninstallConfirm"
/>

</template>

<style scoped>
Expand Down
135 changes: 135 additions & 0 deletions dashboard/src/components/shared/UninstallConfirmDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<template>
<v-dialog
v-model="show"
max-width="500"
@click:outside="handleCancel"
@keydown.esc="handleCancel"
>
<v-card>
<v-card-title class="text-h5">
{{ tm('dialogs.uninstall.title') }}
</v-card-title>

<v-card-text>
<div class="mb-4">
{{ tm('dialogs.uninstall.message') }}
</div>

<v-divider class="my-4"></v-divider>

<div class="text-subtitle-2 mb-3">{{ t('core.common.actions') }}:</div>

<v-checkbox
v-model="deleteConfig"
:label="tm('dialogs.uninstall.deleteConfig')"
color="warning"
hide-details
class="mb-2"
>
<template v-slot:append>
<v-tooltip location="top">
<template v-slot:activator="{ props }">
<v-icon v-bind="props" size="small" color="grey">mdi-information-outline</v-icon>
</template>
<span>{{ tm('dialogs.uninstall.configHint') }}</span>
</v-tooltip>
</template>
</v-checkbox>

<v-checkbox
v-model="deleteData"
:label="tm('dialogs.uninstall.deleteData')"
color="error"
hide-details
>
<template v-slot:append>
<v-tooltip location="top">
<template v-slot:activator="{ props }">
<v-icon v-bind="props" size="small" color="grey">mdi-information-outline</v-icon>
</template>
<span>{{ tm('dialogs.uninstall.dataHint') }}</span>
</v-tooltip>
</template>
</v-checkbox>

<v-alert
v-if="deleteConfig || deleteData"
type="warning"
variant="tonal"
density="compact"
class="mt-4"
>
<template v-slot:prepend>
<v-icon>mdi-alert</v-icon>
</template>
{{ t('messages.validation.operation_cannot_be_undone') }}
</v-alert>
</v-card-text>

<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="grey"
variant="text"
@click="handleCancel"
>
{{ t('core.common.cancel') }}
</v-btn>
<v-btn
color="error"
variant="elevated"
@click="handleConfirm"
>
{{ t('core.common.confirm') }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue';
import { useI18n, useModuleI18n } from '@/i18n/composables';

const props = defineProps({
modelValue: {
type: Boolean,
default: false,
},
});

const emit = defineEmits(['update:modelValue', 'confirm', 'cancel']);

const { t } = useI18n();
const { tm } = useModuleI18n('features/extension');

const show = ref(props.modelValue);
const deleteConfig = ref(false);
const deleteData = ref(false);

watch(() => props.modelValue, (val) => {
show.value = val;
if (val) {
// 重置选项
deleteConfig.value = false;
deleteData.value = false;
}
});

watch(show, (val) => {
emit('update:modelValue', val);
});

const handleConfirm = () => {
emit('confirm', {
deleteConfig: deleteConfig.value,
deleteData: deleteData.value,
});
show.value = false;
};

const handleCancel = () => {
emit('cancel');
show.value = false;
};
</script>
6 changes: 5 additions & 1 deletion dashboard/src/i18n/locales/en-US/features/extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@
},
"uninstall": {
"title": "Confirm Deletion",
"message": "Are you sure you want to delete this extension?"
"message": "Are you sure you want to delete this extension?",
"deleteConfig": "Also delete plugin configuration file",
"deleteData": "Also delete plugin persistent data",
"configHint": "Configuration file located in data/config directory",
"dataHint": "Deletes data in data/plugin_data and data/plugins_data"
},
"install": {
"title": "Install Extension",
Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/i18n/locales/en-US/messages/validation.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
"invalid_date": "Please enter a valid date",
"date_range": "Invalid date range",
"upload_failed": "File upload failed",
"network_error": "Network connection error, please try again"
"network_error": "Network connection error, please try again",
"operation_cannot_be_undone": "⚠️ This operation cannot be undone, please choose carefully!"
}
6 changes: 5 additions & 1 deletion dashboard/src/i18n/locales/zh-CN/features/extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@
},
"uninstall": {
"title": "删除确认",
"message": "你确定要删除当前插件吗?"
"message": "你确定要删除当前插件吗?",
"deleteConfig": "同时删除插件配置文件",
"deleteData": "同时删除插件持久化数据",
"configHint": "配置文件位于 data/config 目录",
"dataHint": "删除 data/plugin_data 和 data/plugins_data 目录下的数据"
},
"install": {
"title": "安装插件",
Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/i18n/locales/zh-CN/messages/validation.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
"invalid_date": "请输入有效的日期",
"date_range": "日期范围无效",
"upload_failed": "文件上传失败",
"network_error": "网络连接错误,请重试"
"network_error": "网络连接错误,请重试",
"operation_cannot_be_undone": "⚠️ 此操作无法撤销,请谨慎选择!"
}
Loading