improve(settings): rework about support info and debug log layout
This commit is contained in:
13 files changed
+152
-41
No files matched your search
@@ -156,6 +156,7 @@ import {
|
||||
webdavSyncSecretsStatus,
|
||||
webdavSyncTest,
|
||||
webdavSyncUpload,
|
||||
listInstalledAgentsLocal,
|
||||
type AppSupportInfo,
|
||||
type McpHttpServerSettings,
|
||||
type McpHttpServerStatus,
|
||||
@@ -2229,9 +2230,30 @@ const appSupportInfoLabels = computed<AppSupportInfoLabels>(() => ({
|
||||
runtimeWeb: t("settings.supportInfoRuntimeWeb"),
|
||||
operatingSystem: t("settings.supportInfoOperatingSystem"),
|
||||
architecture: t("settings.supportInfoArchitecture"),
|
||||
userAgent: t("settings.supportInfoUserAgent"),
|
||||
databaseTypes: t("settings.supportInfoDatabaseTypes"),
|
||||
localDriverVersions: t("settings.supportInfoLocalDriverVersions"),
|
||||
aiProviders: t("settings.supportInfoAiProviders"),
|
||||
unknown: t("settings.supportInfoUnknown"),
|
||||
}));
|
||||
const appSupportInfoRows = computed(() => (appSupportInfo.value ? buildAppSupportInfoRows(appSupportInfo.value, appSupportInfoLabels.value) : []));
|
||||
const appSupportInfoRows = computed(() => {
|
||||
if (!appSupportInfo.value) return [];
|
||||
const info = appSupportInfo.value;
|
||||
return buildAppSupportInfoRows(info, appSupportInfoLabels.value).map((row) => {
|
||||
const details: string[] = [];
|
||||
if (row.key === "appVersion") {
|
||||
if (info.databaseTypes?.length) details.push(`${appSupportInfoLabels.value.databaseTypes}: ${info.databaseTypes.join(", ")}`);
|
||||
if (info.localDriverVersions?.length) {
|
||||
details.push(`${appSupportInfoLabels.value.localDriverVersions}: ${info.localDriverVersions.map((driver) => `${driver.dbType} ${driver.version}`).join(", ")}`);
|
||||
}
|
||||
}
|
||||
if (row.key === "operatingSystem") {
|
||||
if (info.userAgent?.trim()) details.push(`${appSupportInfoLabels.value.userAgent}: ${info.userAgent.trim()}`);
|
||||
if (info.aiProviders?.length) details.push(`${appSupportInfoLabels.value.aiProviders}: ${info.aiProviders.join(", ")}`);
|
||||
}
|
||||
return { ...row, tooltip: details.join("\n") };
|
||||
});
|
||||
});
|
||||
const settingsCategoryNav = computed<{ value: SettingsCategory; label: string }[]>(() => [
|
||||
{ value: "appearance", label: t("settings.appearanceTab") },
|
||||
{ value: "editor", label: t("settings.editorTab") },
|
||||
@@ -2457,7 +2479,33 @@ async function refreshAppSupportInfo() {
|
||||
appSupportInfoLoading.value = true;
|
||||
appSupportInfoError.value = "";
|
||||
try {
|
||||
appSupportInfo.value = await getAppSupportInfo();
|
||||
const [info, localDrivers] = await Promise.all([getAppSupportInfo(), listInstalledAgentsLocal().catch(() => [])]);
|
||||
const databaseTypes = [
|
||||
...new Set(
|
||||
connectionStore.connections
|
||||
.map((connection) => {
|
||||
const dbType = connection.db_type.trim();
|
||||
const profile = connection.driver_profile?.trim();
|
||||
return profile && profile !== dbType ? `${dbType}/${profile}` : dbType;
|
||||
})
|
||||
.filter(Boolean),
|
||||
),
|
||||
].sort();
|
||||
const localDriverVersions = localDrivers
|
||||
.filter((driver) => driver.installed && driver.installed_version?.trim())
|
||||
.map((driver) => ({ dbType: driver.db_type, version: driver.installed_version!.trim() }))
|
||||
.sort((a, b) => a.dbType.localeCompare(b.dbType));
|
||||
const aiProviders = [
|
||||
...new Set(
|
||||
settingsStore.aiConfigs
|
||||
.filter((config) => {
|
||||
const preset = getAiProviderPreset(config.provider, config.endpoint);
|
||||
return config.endpoint.trim() && config.model.trim() && (!preset.requiresApiKey || config.apiKey.trim());
|
||||
})
|
||||
.map((config) => getAiProviderPreset(config.provider, config.endpoint).label),
|
||||
),
|
||||
].sort();
|
||||
appSupportInfo.value = { ...info, databaseTypes, localDriverVersions, aiProviders };
|
||||
} catch (e: any) {
|
||||
appSupportInfo.value = appSupportInfo.value || fallbackAppSupportInfo();
|
||||
appSupportInfoError.value = e?.message || String(e);
|
||||
@@ -6164,29 +6212,6 @@ onUnmounted(() => {
|
||||
<Switch id="update-notifications-enabled" v-model="editUpdateNotificationsEnabled" />
|
||||
</div>
|
||||
|
||||
<div v-if="!isWeb" class="settings-item flex flex-col gap-3 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="space-y-1">
|
||||
<Label for="debug-logging-enabled">{{ t("settings.debugLoggingEnabled") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.debugLoggingEnabledDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Switch id="debug-logging-enabled" v-model="editDebugLoggingEnabled" />
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" size="sm" @click="clearDebugLogs">
|
||||
{{ t("settings.debugLogsClear") }}
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="sm" @click="copyDebugLogs">
|
||||
{{ debugLogCopied ? t("settings.debugLogsCopied") : t("settings.debugLogsCopy") }}
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="sm" @click="exportDebugLogs">
|
||||
{{ debugLogDownloaded ? t("settings.debugLogsDownloaded") : t("settings.debugLogsDownload") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-appearance-group" data-icon-theme-settings>
|
||||
<Label>{{ t("settings.iconTheme") }}</Label>
|
||||
<div class="settings-appearance-choice-grid settings-icon-theme-grid">
|
||||
@@ -9296,16 +9321,25 @@ LIMIT 100;</pre
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="appSupportInfoRows.length" class="mt-4 grid gap-3 sm:grid-cols-2">
|
||||
<div v-for="row in appSupportInfoRows" :key="row.key" class="min-w-0 rounded-md bg-muted/30 px-3 py-2">
|
||||
<div class="text-xs font-medium text-muted-foreground">
|
||||
{{ row.label }}
|
||||
</div>
|
||||
<div class="mt-1 min-w-0 select-text break-words font-mono text-xs text-foreground">
|
||||
{{ row.value }}
|
||||
</div>
|
||||
<TooltipProvider v-if="appSupportInfoRows.length">
|
||||
<div class="mt-4 grid gap-3 sm:grid-cols-4">
|
||||
<Tooltip v-for="row in appSupportInfoRows" :key="row.key">
|
||||
<TooltipTrigger as-child>
|
||||
<div class="min-w-0 rounded-md bg-muted/30 px-3 py-2" :class="row.tooltip ? 'cursor-help' : ''">
|
||||
<div class="text-xs font-medium text-muted-foreground">
|
||||
{{ row.label }}
|
||||
</div>
|
||||
<div class="mt-1 min-w-0 select-text break-words font-mono text-xs text-foreground">
|
||||
{{ row.value }}
|
||||
</div>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent v-if="row.tooltip" class="max-w-[520px] whitespace-pre-line break-words text-xs leading-relaxed" side="top" align="start" :side-offset="8">
|
||||
{{ row.tooltip }}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
<p v-else class="mt-4 text-sm text-muted-foreground">
|
||||
{{ t("settings.supportInfoLoading") }}
|
||||
</p>
|
||||
@@ -9326,7 +9360,28 @@ LIMIT 100;</pre
|
||||
:get-draft-conflict-categories="getDraftConflictCategories"
|
||||
/>
|
||||
|
||||
<ChangelogPanel :checking-updates="props.checkingUpdates" @check-updates="emit('check-updates')" />
|
||||
<div v-if="!isWeb" class="settings-item flex flex-col gap-3 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="space-y-1">
|
||||
<Label for="debug-logging-enabled">{{ t("settings.debugLoggingEnabled") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.debugLoggingEnabledDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Switch id="debug-logging-enabled" v-model="editDebugLoggingEnabled" />
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" size="sm" @click="clearDebugLogs">
|
||||
{{ t("settings.debugLogsClear") }}
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="sm" @click="copyDebugLogs">
|
||||
{{ debugLogCopied ? t("settings.debugLogsCopied") : t("settings.debugLogsCopy") }}
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="sm" @click="exportDebugLogs">
|
||||
{{ debugLogDownloaded ? t("settings.debugLogsDownloaded") : t("settings.debugLogsDownload") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-item rounded-lg border p-4">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
@@ -9348,6 +9403,8 @@ LIMIT 100;</pre
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChangelogPanel :checking-updates="props.checkingUpdates" @check-updates="emit('check-updates')" />
|
||||
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<button type="button" class="rounded-lg border p-4 text-left transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" @click="openExternalUrl('https://qm.qq.com/cgi-bin/qm/qr?k=&group_code=1087880322')">
|
||||
<div class="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||
|
||||
+2
-2
@@ -128,8 +128,8 @@ describe("EditorSettingsDialog choice card containment", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("restores large icon theme choices below the debug log controls", () => {
|
||||
expect(sourceIndexForKey("iconTheme")).toBeGreaterThan(sourceIndexForKey("debugLoggingEnabled"));
|
||||
it("keeps large icon theme choices in appearance and debug logs in About", () => {
|
||||
expect(sourceIndexForKey("iconTheme")).toBeLessThan(sourceIndexForKey("debugLoggingEnabled"));
|
||||
expect(templateSource).toContain("data-icon-theme-settings");
|
||||
for (const key of ["iconThemeDefault", "iconThemeBlack"] as const) {
|
||||
const block = buttonBlockForKey(key);
|
||||
|
||||
@@ -7716,6 +7716,10 @@ export default {
|
||||
supportInfoRuntimeWeb: "Web",
|
||||
supportInfoOperatingSystem: "Operating System",
|
||||
supportInfoArchitecture: "Architecture",
|
||||
supportInfoUserAgent: "User-Agent",
|
||||
supportInfoDatabaseTypes: "Database Types",
|
||||
supportInfoLocalDriverVersions: "Local Driver Versions",
|
||||
supportInfoAiProviders: "AI Providers",
|
||||
supportInfoUnknown: "Unknown",
|
||||
settingsTransferTitle: "Import & export settings",
|
||||
settingsTransferDescription: "Export app settings to a file to back up your configuration, or restore settings from a backup file.",
|
||||
|
||||
@@ -7332,6 +7332,10 @@ export default withEnglishFallback({
|
||||
mcpQueryTimeoutSaveStatus_saving: "Guardando...",
|
||||
mcpQueryTimeoutSaveStatus_saved: "Guardado",
|
||||
mcpQueryTimeoutSaveStatus_failed: "Error al guardar",
|
||||
supportInfoUserAgent: "User-Agent",
|
||||
supportInfoDatabaseTypes: "Tipos de bases de datos",
|
||||
supportInfoLocalDriverVersions: "Versiones de controladores locales",
|
||||
supportInfoAiProviders: "Proveedores de IA",
|
||||
},
|
||||
driverStore: {
|
||||
jreDirRemoveFailed: "No se pudo eliminar el directorio JRE antiguo: {path} (error original: {error})",
|
||||
|
||||
@@ -7332,6 +7332,10 @@ export default withEnglishFallback({
|
||||
mcpQueryTimeoutSaveStatus_saving: "Salvataggio in corso...",
|
||||
mcpQueryTimeoutSaveStatus_saved: "Salvato",
|
||||
mcpQueryTimeoutSaveStatus_failed: "Salvataggio non riuscito",
|
||||
supportInfoUserAgent: "User-Agent",
|
||||
supportInfoDatabaseTypes: "Tipi di database",
|
||||
supportInfoLocalDriverVersions: "Versioni dei driver locali",
|
||||
supportInfoAiProviders: "Provider AI",
|
||||
},
|
||||
driverStore: {
|
||||
jreDirRemoveFailed: "Impossibile rimuovere la vecchia directory JRE: {path} (errore originale: {error})",
|
||||
|
||||
@@ -7384,6 +7384,10 @@ export default withEnglishFallback({
|
||||
mcpQueryTimeoutSaveStatus_saving: "保存中...",
|
||||
mcpQueryTimeoutSaveStatus_saved: "保存済み",
|
||||
mcpQueryTimeoutSaveStatus_failed: "保存失敗",
|
||||
supportInfoUserAgent: "User-Agent",
|
||||
supportInfoDatabaseTypes: "データベースの種類",
|
||||
supportInfoLocalDriverVersions: "ローカルドライバーのバージョン",
|
||||
supportInfoAiProviders: "AIプロバイダー",
|
||||
},
|
||||
driverStore: {
|
||||
jreDirRemoveFailed: "古い JRE ディレクトリを削除できませんでした: {path}(元のエラー: {error})",
|
||||
|
||||
@@ -7056,6 +7056,10 @@ export default withEnglishFallback({
|
||||
mcpQueryTimeoutSaveStatus_saving: "저장 중...",
|
||||
mcpQueryTimeoutSaveStatus_saved: "저장됨",
|
||||
mcpQueryTimeoutSaveStatus_failed: "저장 실패",
|
||||
supportInfoUserAgent: "User-Agent",
|
||||
supportInfoDatabaseTypes: "데이터베이스 유형",
|
||||
supportInfoLocalDriverVersions: "로컬 드라이버 버전",
|
||||
supportInfoAiProviders: "AI 제공자",
|
||||
},
|
||||
driverStore: {
|
||||
jreDirRemoveFailed: "이전 JRE 디렉터리 제거 실패: {path} (원래 오류: {error})",
|
||||
|
||||
@@ -7334,6 +7334,10 @@ export default withEnglishFallback({
|
||||
mcpQueryTimeoutSaveStatus_saving: "Salvando...",
|
||||
mcpQueryTimeoutSaveStatus_saved: "Salvo",
|
||||
mcpQueryTimeoutSaveStatus_failed: "Falha ao salvar",
|
||||
supportInfoUserAgent: "User-Agent",
|
||||
supportInfoDatabaseTypes: "Tipos de banco de dados",
|
||||
supportInfoLocalDriverVersions: "Versões de drivers locais",
|
||||
supportInfoAiProviders: "Provedores de IA",
|
||||
},
|
||||
driverStore: {
|
||||
jreDirRemoveFailed: "Não foi possível remover o diretório JRE antigo: {path} (erro original: {error})",
|
||||
|
||||
@@ -7701,6 +7701,10 @@ export default withEnglishFallback({
|
||||
supportInfoRuntimeWeb: "Web 端",
|
||||
supportInfoOperatingSystem: "操作系统",
|
||||
supportInfoArchitecture: "系统架构",
|
||||
supportInfoUserAgent: "User-Agent",
|
||||
supportInfoDatabaseTypes: "数据库类型",
|
||||
supportInfoLocalDriverVersions: "本地驱动版本",
|
||||
supportInfoAiProviders: "AI 供应商",
|
||||
supportInfoUnknown: "未知",
|
||||
settingsTransferTitle: "配置导入与导出",
|
||||
settingsTransferDescription: "导出应用设置到文件进行配置备份,或从备份文件恢复配置。",
|
||||
|
||||
@@ -6649,6 +6649,10 @@ export default withEnglishFallback({
|
||||
mcpQueryTimeoutSaveStatus_saving: "儲存中...",
|
||||
mcpQueryTimeoutSaveStatus_saved: "已儲存",
|
||||
mcpQueryTimeoutSaveStatus_failed: "儲存失敗",
|
||||
supportInfoUserAgent: "User-Agent",
|
||||
supportInfoDatabaseTypes: "資料庫類型",
|
||||
supportInfoLocalDriverVersions: "本機驅動程式版本",
|
||||
supportInfoAiProviders: "AI 供應商",
|
||||
},
|
||||
driverStore: {
|
||||
jreDirRemoveFailed: "無法刪除舊的 JRE 目錄:{path}(原始錯誤:{error})",
|
||||
|
||||
@@ -7,6 +7,10 @@ export interface AppSupportInfoLabels {
|
||||
runtimeWeb: string;
|
||||
operatingSystem: string;
|
||||
architecture: string;
|
||||
userAgent: string;
|
||||
databaseTypes: string;
|
||||
localDriverVersions: string;
|
||||
aiProviders: string;
|
||||
unknown: string;
|
||||
}
|
||||
|
||||
@@ -35,6 +39,10 @@ export function formatSupportInfoOperatingSystem(info: AppSupportInfo, unknownLa
|
||||
return [name, version].filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
export function collectBrowserSupportInfo(): string {
|
||||
return typeof navigator === "undefined" ? "" : navigator.userAgent?.trim() || "";
|
||||
}
|
||||
|
||||
export function buildAppSupportInfoRows(info: AppSupportInfo, labels: AppSupportInfoLabels): AppSupportInfoRow[] {
|
||||
return [
|
||||
{
|
||||
@@ -61,7 +69,13 @@ export function buildAppSupportInfoRows(info: AppSupportInfo, labels: AppSupport
|
||||
}
|
||||
|
||||
export function formatAppSupportInfoForClipboard(info: AppSupportInfo, labels: AppSupportInfoLabels): string {
|
||||
return buildAppSupportInfoRows(info, labels)
|
||||
.map((row) => `${row.label}: ${row.value}`)
|
||||
.join("\n");
|
||||
const rows = buildAppSupportInfoRows(info, labels).map((row) => `${row.label}: ${row.value}`);
|
||||
const extraRows: Array<[string, string | null | undefined]> = [];
|
||||
if (info.userAgent?.trim()) extraRows.push([labels.userAgent, info.userAgent]);
|
||||
if (info.databaseTypes?.length) extraRows.push([labels.databaseTypes, info.databaseTypes.join(", ")]);
|
||||
if (info.localDriverVersions?.length) {
|
||||
extraRows.push([labels.localDriverVersions, info.localDriverVersions.map((driver) => `${driver.dbType} ${driver.version}`).join(", ")]);
|
||||
}
|
||||
if (info.aiProviders?.length) extraRows.push([labels.aiProviders, info.aiProviders.join(", ")]);
|
||||
return rows.concat(extraRows.map(([label, value]) => `${label}: ${value?.trim() || labels.unknown}`)).join("\n");
|
||||
}
|
||||
@@ -247,6 +247,7 @@ import type {
|
||||
} from "@/types/nacos";
|
||||
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
|
||||
import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
|
||||
import { collectBrowserSupportInfo } from "@/lib/app/supportInfo";
|
||||
import { normalizeConnectionTestResult } from "@/lib/connection/connectionDatabaseInfo";
|
||||
import type { AnnotationFile, SchemaSnapshot } from "@/docs/types";
|
||||
|
||||
@@ -4526,6 +4527,7 @@ export async function getAppSupportInfo(): Promise<AppSupportInfo> {
|
||||
osName: navigator.platform || "web",
|
||||
osVersion: null,
|
||||
arch: "",
|
||||
userAgent: collectBrowserSupportInfo(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { invoke as tauriInvoke } from "@tauri-apps/api/core";
|
||||
import { assertUpdateAllowsCommand } from "@/lib/app/updatePreparation";
|
||||
import { collectBrowserSupportInfo } from "@/lib/app/supportInfo";
|
||||
|
||||
function invoke<T>(command: string, args?: Record<string, unknown>): Promise<T> {
|
||||
assertUpdateAllowsCommand(command);
|
||||
@@ -371,6 +372,10 @@ export interface AppSupportInfo {
|
||||
osName: string;
|
||||
osVersion?: string | null;
|
||||
arch: string;
|
||||
userAgent?: string;
|
||||
databaseTypes?: string[];
|
||||
localDriverVersions?: Array<{ dbType: string; version: string }>;
|
||||
aiProviders?: string[];
|
||||
}
|
||||
|
||||
export interface QueryPagination {
|
||||
@@ -2559,7 +2564,8 @@ export async function getAppVersion(): Promise<string> {
|
||||
}
|
||||
|
||||
export async function getAppSupportInfo(): Promise<AppSupportInfo> {
|
||||
return invoke<AppSupportInfo>("get_app_support_info");
|
||||
const info = await invoke<AppSupportInfo>("get_app_support_info");
|
||||
return { ...info, userAgent: collectBrowserSupportInfo() };
|
||||
}
|
||||
|
||||
// --- Redis ---
|
||||
|
||||
Reference in new issue
Block a user