feat(settings): add update check and MCP uninstall
This commit is contained in:
12 files changed
+189
-9
No files matched your search
@@ -67,6 +67,7 @@ import {
|
||||
aiTestConnection,
|
||||
checkMcpServerStatus,
|
||||
installMcpServer,
|
||||
uninstallMcpServer,
|
||||
forgetSnippetSavedToken,
|
||||
forgetWebdavSyncSecretsPassphrase,
|
||||
forgetWebdavSavedPassword,
|
||||
@@ -196,10 +197,12 @@ const props = defineProps<{
|
||||
initialSection?: string;
|
||||
navigationRequestId?: number;
|
||||
appVersion?: string;
|
||||
checkingUpdates?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:open": [value: boolean];
|
||||
"check-updates": [];
|
||||
}>();
|
||||
|
||||
const isSettingsPage = computed(() => props.variant === "page");
|
||||
@@ -1723,7 +1726,7 @@ async function exportDebugLogs() {
|
||||
|
||||
// ---------- MCP Server ----------
|
||||
type McpConfigTab = "claude" | "cursor" | "trae" | "vscode" | "windsurf" | "codex" | "opencode" | "cherry-studio";
|
||||
type McpCopyKind = "install" | `${McpConfigTab}-config`;
|
||||
type McpCopyKind = "install" | "uninstall" | `${McpConfigTab}-config`;
|
||||
|
||||
const mcpStatus = ref<McpServerStatus | null>(null);
|
||||
const mcpStatusLoading = ref(false);
|
||||
@@ -1736,6 +1739,7 @@ const mcpPolicyLoading = ref(false);
|
||||
const mcpPolicySaving = ref(false);
|
||||
const mcpPolicyLoadError = ref("");
|
||||
const mcpInstalling = ref(false);
|
||||
const mcpUninstalling = ref(false);
|
||||
const mcpInstallMessage = ref("");
|
||||
const mcpInstallError = ref(false);
|
||||
const mcpExecutionMode = computed(() => mcpExecutionModeFromPolicy(settingsStore.mcpGlobalPolicy));
|
||||
@@ -1854,6 +1858,8 @@ const mcpCommand = computed(() => {
|
||||
return mcpStatus.value.installed ? mcpStatus.value.update_command : mcpStatus.value.install_command;
|
||||
});
|
||||
|
||||
const mcpUninstallCommand = computed(() => mcpStatus.value?.uninstall_command || "npm uninstall -g @dbx-app/mcp-server");
|
||||
|
||||
async function refreshMcpStatus() {
|
||||
if (mcpStatusLoading.value) return;
|
||||
mcpStatusLoading.value = true;
|
||||
@@ -1888,7 +1894,7 @@ async function copyMcpText(kind: McpCopyKind, value: string) {
|
||||
}
|
||||
|
||||
async function installMcp() {
|
||||
if (mcpInstalling.value) return;
|
||||
if (mcpInstalling.value || mcpUninstalling.value) return;
|
||||
mcpInstalling.value = true;
|
||||
mcpInstallMessage.value = "";
|
||||
mcpInstallError.value = false;
|
||||
@@ -1911,6 +1917,27 @@ async function installMcp() {
|
||||
}
|
||||
}
|
||||
|
||||
async function uninstallMcp() {
|
||||
if (mcpInstalling.value || mcpUninstalling.value || !mcpStatus.value?.installed) return;
|
||||
if (!window.confirm(t("settings.mcpUninstallConfirm"))) return;
|
||||
mcpUninstalling.value = true;
|
||||
mcpInstallMessage.value = "";
|
||||
mcpInstallError.value = false;
|
||||
try {
|
||||
mcpInstallMessage.value = await uninstallMcpServer();
|
||||
await refreshMcpStatus();
|
||||
} catch (e: any) {
|
||||
mcpInstallMessage.value = t("settings.mcpUninstallFailed", { error: e?.message || String(e) });
|
||||
mcpInstallError.value = true;
|
||||
} finally {
|
||||
mcpUninstalling.value = false;
|
||||
window.setTimeout(() => {
|
||||
mcpInstallMessage.value = "";
|
||||
mcpInstallError.value = false;
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- WebDAV Sync ----------
|
||||
const webdavEndpoint = ref(localStorage.getItem("dbx-webdav-endpoint") || "");
|
||||
const webdavUsername = ref(localStorage.getItem("dbx-webdav-username") || "");
|
||||
@@ -2873,7 +2900,7 @@ const aiSupportsAnthropicApiStyle = computed(() => aiEditProvider.value === "cus
|
||||
const aiCliMcpNeedsInstall = computed(() => aiIsCliProvider.value && (!mcpStatus.value || !mcpStatus.value.installed));
|
||||
const aiCliMcpCanInstall = computed(() => {
|
||||
const status = mcpStatus.value;
|
||||
return !mcpInstalling.value && !!status?.npm_available && (!status.installed || status.update_available);
|
||||
return !mcpInstalling.value && !mcpUninstalling.value && !!status?.npm_available && (!status.installed || status.update_available);
|
||||
});
|
||||
const aiCliMcpActionLabel = computed(() => {
|
||||
if (!mcpStatus.value?.installed) return t("settings.mcpInstallButton");
|
||||
@@ -6273,7 +6300,7 @@ onUnmounted(() => {
|
||||
<CheckCircle2 v-if="mcpCopied === 'install'" class="h-4 w-4 text-green-500" />
|
||||
<Copy v-else class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button type="button" variant="default" :disabled="mcpInstalling || !mcpStatus?.npm_available || (mcpStatus?.installed && !mcpStatus?.update_available)" @click="installMcp">
|
||||
<Button type="button" variant="default" :disabled="mcpInstalling || mcpUninstalling || !mcpStatus?.npm_available || (mcpStatus?.installed && !mcpStatus?.update_available)" @click="installMcp">
|
||||
<Loader2 v-if="mcpInstalling" class="mr-2 h-4 w-4 animate-spin" />
|
||||
<CheckCircle2 v-if="!mcpInstalling && mcpStatus?.installed && !mcpStatus?.update_available" class="mr-2 h-4 w-4" />
|
||||
{{ mcpInstalling ? t("settings.mcpInstalling") : !mcpStatus?.installed ? t("settings.mcpInstallButton") : mcpStatus?.update_available ? t("settings.mcpUpdateButton") : t("settings.mcpUpToDate") }}
|
||||
@@ -6287,6 +6314,24 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isWeb && mcpStatus?.installed" class="space-y-2">
|
||||
<Label>{{ t("settings.mcpUninstallCommand") }}</Label>
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<div class="min-w-0 flex-1 overflow-x-auto rounded-md border bg-background px-3 py-2 font-mono text-xs whitespace-nowrap">
|
||||
{{ mcpUninstallCommand }}
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="icon" :title="t('common.copy')" @click="copyMcpText('uninstall', mcpUninstallCommand)">
|
||||
<CheckCircle2 v-if="mcpCopied === 'uninstall'" class="h-4 w-4 text-green-500" />
|
||||
<Copy v-else class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button type="button" variant="outline" class="text-destructive hover:text-destructive" :disabled="mcpInstalling || mcpUninstalling || !mcpStatus.npm_available" @click="uninstallMcp">
|
||||
<Loader2 v-if="mcpUninstalling" class="mr-2 h-4 w-4 animate-spin" />
|
||||
<Trash2 v-else class="mr-2 h-4 w-4" />
|
||||
{{ mcpUninstalling ? t("settings.mcpUninstalling") : t("settings.mcpUninstallButton") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.mcpConfigOptionsHint") }}
|
||||
@@ -6611,7 +6656,7 @@ onUnmounted(() => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ChangelogPanel />
|
||||
<ChangelogPanel :checking-updates="props.checkingUpdates" @check-updates="emit('check-updates')" />
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ChevronDown, ExternalLink, Loader2, RefreshCw, Tag } from "@lucide/vue";
|
||||
import { ChevronDown, CloudDownload, ExternalLink, Loader2, RefreshCw, Tag } from "@lucide/vue";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@@ -13,6 +13,19 @@ const PAGE_SIZE = 5;
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
checkingUpdates?: boolean;
|
||||
}>(),
|
||||
{
|
||||
checkingUpdates: false,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
"check-updates": [];
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const error = ref("");
|
||||
const releases = ref<ChangelogRelease[]>([]);
|
||||
@@ -123,6 +136,11 @@ watch(changelogLang, (lang) => {
|
||||
<p class="text-sm text-muted-foreground">{{ t("settings.changelogDescription") }}</p>
|
||||
</div>
|
||||
<div class="settings-about-section-actions flex shrink-0 flex-wrap items-center gap-2">
|
||||
<Button type="button" variant="outline" size="sm" :disabled="checkingUpdates" @click="emit('check-updates')">
|
||||
<Loader2 v-if="checkingUpdates" class="mr-1 h-3.5 w-3.5 animate-spin" />
|
||||
<CloudDownload v-else class="mr-1 h-3.5 w-3.5" />
|
||||
{{ t("updates.check") }}
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="sm" :disabled="loading" @click="openExternalUrl(changelogWebsiteUrl(changelogLang))">
|
||||
<ExternalLink class="mr-1 h-3.5 w-3.5" />
|
||||
{{ t("settings.changelogOpenWebsite") }}
|
||||
|
||||
Reference in new issue
Block a user