Merge pull request #2013 from AstrBotDevs/feat/danger-plugin

[Copilot] feat: 添加风险插件安装确认对话框以及风险插件标签特殊处理
This commit is contained in:
Soulter
2025-07-05 19:18:14 +08:00
committed by GitHub
4 changed files with 90 additions and 7 deletions
@@ -49,6 +49,11 @@ const reloadExtension = () => {
};
const $confirm = inject("$confirm");
const installExtension = async () => {
emit('install', props.extension);
};
const uninstallExtension = async () => {
if (typeof $confirm !== "function") {
console.error(tm("card.errors.confirmNotRegistered"));
@@ -117,6 +122,10 @@ const viewReadme = () => {
<v-icon icon="mdi-cogs" start></v-icon>
{{ extension.handlers?.length }}{{ tm("card.status.handlersCount") }}
</v-chip>
<v-chip v-for="tag in extension.tags" :key="tag" :color="tag === 'danger' ? 'error' : 'primary'" label
size="small" class="ml-2">
{{ tag === 'danger' ? tm('tags.danger') : tag }}
</v-chip>
</div>
<div class="mt-2" :class="{ 'text-caption': $vuetify.display.xs }" style="max-height: 65px; overflow-y: auto;">
@@ -139,7 +148,7 @@ const viewReadme = () => {
<v-btn color="teal-accent-4" :text="tm('buttons.viewDocs')" variant="text" @click="viewReadme"></v-btn>
<v-btn v-if="!marketMode" color="teal-accent-4" :text="tm('buttons.actions')" variant="text" @click="reveal = true"></v-btn>
<v-btn v-if="marketMode && !extension?.installed" color="teal-accent-4" :text="tm('buttons.install')" variant="text"
@click="emit('install', extension)"></v-btn>
@click="installExtension"></v-btn>
<v-btn v-if="marketMode && extension?.installed" color="teal-accent-4" :text="tm('status.installed')" variant="text" disabled></v-btn>
</v-card-actions>
@@ -200,6 +209,7 @@ const viewReadme = () => {
</v-card>
</v-expand-transition>
</v-card>
</template>
<style scoped>
@@ -79,6 +79,9 @@
"devDocs": "Extension Development Docs",
"submitRepo": "Submit Extension Repository"
},
"tags": {
"danger": "Danger"
},
"dialogs": {
"error": {
"title": "Error Information",
@@ -112,6 +115,12 @@
"title": "Install Extension",
"fromFile": "Install from File",
"fromUrl": "Install from URL"
},
"danger_warning": {
"title": "Dangerous Plugin Warning",
"message": "This plugin has been flagged as containing security risks, including unsafe code or functionalities that may cause system malfunctions or data loss. Do you wish to proceed with the installation?",
"confirm": "Continue",
"cancel": "Cancel"
}
},
"messages": {
@@ -164,4 +173,4 @@
"confirmNotRegistered": "$confirm not properly registered"
}
}
}
}
@@ -79,6 +79,9 @@
"devDocs": "插件开发文档",
"submitRepo": "提交插件仓库"
},
"tags": {
"danger": "危险"
},
"dialogs": {
"error": {
"title": "错误信息",
@@ -112,6 +115,12 @@
"title": "安装插件",
"fromFile": "从文件安装",
"fromUrl": "从链接安装"
},
"danger_warning": {
"title": "警告",
"message": "该插件可能包含不安全的代码或功能,可能导致系统异常或数据损失等。请确认是否继续安装?",
"confirm": "继续",
"cancel": "取消"
}
},
"messages": {
@@ -164,4 +173,4 @@
"confirmNotRegistered": "$confirm 未正确注册"
}
}
}
}
+59 -4
View File
@@ -58,6 +58,10 @@ const isListView = ref(false);
const pluginSearch = ref("");
const loading_ = ref(false);
// 危险插件确认对话框
const dangerConfirmDialog = ref(false);
const selectedDangerPlugin = ref(null);
// 插件市场相关
const extension_url = ref("");
const dialog = ref(false);
@@ -421,6 +425,35 @@ const open = (link) => {
}
};
// 为表格视图创建一个处理安装插件的函数
const handleInstallPlugin = async (plugin) => {
if (plugin.tags && plugin.tags.includes('danger')) {
selectedDangerPlugin.value = plugin;
dangerConfirmDialog.value = true;
} else {
extension_url.value = plugin.repo;
dialog.value = true;
uploadTab.value = 'url';
}
};
// 确认安装危险插件
const confirmDangerInstall = () => {
if (selectedDangerPlugin.value) {
extension_url.value = selectedDangerPlugin.value.repo;
dialog.value = true;
uploadTab.value = 'url';
}
dangerConfirmDialog.value = false;
selectedDangerPlugin.value = null;
};
// 取消安装危险插件
const cancelDangerInstall = () => {
dangerConfirmDialog.value = false;
selectedDangerPlugin.value = null;
};
// 插件市场显示完整插件名称
const trimExtensionName = () => {
pluginMarketData.value.forEach(plugin => {
@@ -823,7 +856,7 @@ onMounted(async () => {
<v-row style="margin-top: 8px;">
<v-col cols="12" md="6" lg="6" v-for="plugin in pinnedPlugins" :key="plugin.name">
<ExtensionCard :extension="plugin" class="h-120 rounded-lg" market-mode="true" :highlight="true"
@install="extension_url = plugin.repo; dialog = true; uploadTab = 'url'" @view-readme="open(plugin.repo)">
@install="handleInstallPlugin(plugin)" @view-readme="open(plugin.repo)">
</ExtensionCard>
</v-col>
</v-row>
@@ -871,12 +904,12 @@ onMounted(async () => {
</template>
<template v-slot:item.tags="{ item }">
<span v-if="item.tags.length === 0">-</span>
<v-chip v-for="tag in item.tags" :key="tag" color="primary" size="x-small">
<v-chip v-for="tag in item.tags" :key="tag" :color="tag === 'danger' ? 'error' : 'primary'" size="x-small" v-show="tag !== 'danger'">
{{ tag }}</v-chip>
</template>
<template v-slot:item.actions="{ item }">
<v-btn v-if="!item.installed" class="text-none mr-2" size="x-small" variant="flat"
@click="extension_url = item.repo; dialog = true; uploadTab = 'url'">
@click="handleInstallPlugin(item)">
<v-icon>mdi-download</v-icon></v-btn>
<v-btn v-else class="text-none mr-2" size="x-small" variant="flat" border
disabled><v-icon>mdi-check</v-icon></v-btn>
@@ -960,7 +993,7 @@ onMounted(async () => {
</v-list-item>
</v-list>
</v-menu>
</div>
</div>
</th>
</tr>
</thead>
@@ -1078,6 +1111,28 @@ onMounted(async () => {
<ReadmeDialog v-model:show="readmeDialog.show" :plugin-name="readmeDialog.pluginName"
:repo-url="readmeDialog.repoUrl" />
<!-- 危险插件确认对话框 -->
<v-dialog v-model="dangerConfirmDialog" width="500" persistent>
<v-card>
<v-card-title class="text-h5 d-flex align-center">
<v-icon color="warning" class="mr-2">mdi-alert-circle</v-icon>
{{ tm('dialogs.danger_warning.title') }}
</v-card-title>
<v-card-text>
<div>{{ tm('dialogs.danger_warning.message') }}</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" @click="cancelDangerInstall">
{{ tm('dialogs.danger_warning.cancel') }}
</v-btn>
<v-btn color="warning" @click="confirmDangerInstall">
{{ tm('dialogs.danger_warning.confirm') }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 上传插件对话框 -->
<v-dialog v-model="dialog" width="500">
<v-card>