feat(support-info): add app support information

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-07-09 09:52:45 +08:00
1 parent 328147b559
commit 28db8bc1b8
16 files changed
+564 -3

No files matched your search

@@ -57,6 +57,7 @@ import {
installMcpServer,
forgetWebdavSyncSecretsPassphrase,
forgetWebdavSavedPassword,
getAppSupportInfo,
listSystemFonts,
saveWebdavSyncSecretsPreference,
saveWebdavSavedPassword,
@@ -65,6 +66,7 @@ import {
webdavSyncSecretsStatus,
webdavSyncTest,
webdavSyncUpload,
type AppSupportInfo,
type AiModelInfo,
type McpServerStatus,
type WebDavConfig,
@@ -96,6 +98,7 @@ import { LOCALE_OPTIONS } from "@/lib/app/localeOptions";
import { DEFAULT_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES, DEFAULT_WEB_DAV_REMOTE_PATH, normalizedWebDavAutoUploadInterval, writeWebDavAutoUploadFields } from "@/lib/webdav/webdavAutoUploadConfig";
import { apiUrl } from "@/lib/common/webPath";
import { DEFAULT_UI_FONT_FAMILY, SYSTEM_UI_FONT_FAMILY } from "@/lib/app/appFonts";
import { buildAppSupportInfoRows, formatAppSupportInfoForClipboard, type AppSupportInfoLabels } from "@/lib/app/supportInfo";
const { t } = useI18n();
const { toast } = useToast();
@@ -1195,6 +1198,20 @@ function setSidebarActivation(value: "single" | "double") {
const activeSettingsTab = ref("appearance");
const isWeb = !isTauriRuntime();
const displayedAppVersion = computed(() => (props.appVersion ? `v${props.appVersion}` : ""));
const appSupportInfo = ref<AppSupportInfo | null>(null);
const appSupportInfoLoading = ref(false);
const appSupportInfoError = ref("");
const appSupportInfoCopied = ref(false);
const appSupportInfoLabels = computed<AppSupportInfoLabels>(() => ({
appVersion: t("settings.supportInfoAppVersion"),
runtime: t("settings.supportInfoRuntime"),
runtimeDesktop: t("settings.supportInfoRuntimeDesktop"),
runtimeWeb: t("settings.supportInfoRuntimeWeb"),
operatingSystem: t("settings.supportInfoOperatingSystem"),
architecture: t("settings.supportInfoArchitecture"),
unknown: t("settings.supportInfoUnknown"),
}));
const appSupportInfoRows = computed(() => (appSupportInfo.value ? buildAppSupportInfoRows(appSupportInfo.value, appSupportInfoLabels.value) : []));
type SettingsCategory = "editor" | "formatter" | "appearance" | "navigation" | "data" | "shortcuts" | "snippets" | "sync" | "ai" | "mcp" | "security" | "about";
const settingsCategoryNav = computed<{ value: SettingsCategory; label: string }[]>(() => [
{ value: "appearance", label: t("settings.appearanceTab") },
@@ -1236,6 +1253,44 @@ async function copyDebugLogs() {
}, 1500);
}
function fallbackAppSupportInfo(): AppSupportInfo {
return {
appVersion: props.appVersion || "",
runtime: isWeb ? "web" : "desktop",
osName: "",
osVersion: null,
arch: "",
};
}
async function refreshAppSupportInfo() {
if (appSupportInfoLoading.value) return;
appSupportInfoLoading.value = true;
appSupportInfoError.value = "";
try {
appSupportInfo.value = await getAppSupportInfo();
} catch (e: any) {
appSupportInfo.value = appSupportInfo.value || fallbackAppSupportInfo();
appSupportInfoError.value = e?.message || String(e);
} finally {
appSupportInfoLoading.value = false;
}
}
async function copyAppSupportInfo() {
if (!appSupportInfo.value) await refreshAppSupportInfo();
if (!appSupportInfo.value) return;
try {
await copyToClipboard(formatAppSupportInfoForClipboard(appSupportInfo.value, appSupportInfoLabels.value));
appSupportInfoCopied.value = true;
window.setTimeout(() => {
appSupportInfoCopied.value = false;
}, 1500);
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
function clearDebugLogs() {
clearStoredDebugLogs();
debugLogCopied.value = false;
@@ -1572,6 +1627,7 @@ watch(
syncAiEditState();
if (!isWeb && activeSettingsTab.value === "mcp") void refreshMcpStatus();
if (!isWeb && activeSettingsTab.value === "ai" && aiIsCodexCli.value) void ensureCodexMcpStatus();
if (activeSettingsTab.value === "about") void refreshAppSupportInfo();
await scrollToInitialSettingsSection();
}
},
@@ -1608,6 +1664,7 @@ watch([webdavAutoUploadEnabled, webdavAutoUploadIntervalMinutes], () => {
watch(activeSettingsTab, (tab) => {
if (tab === "mcp" && !mcpStatus.value && !mcpStatusLoading.value) void refreshMcpStatus();
if (tab === "ai" && aiIsCodexCli.value) void ensureCodexMcpStatus();
if (tab === "about" && !appSupportInfo.value) void refreshAppSupportInfo();
if (tab === "appearance") {
checkLayoutDescTruncation();
checkIconThemeDescTruncation();
@@ -4144,6 +4201,29 @@ onUnmounted(cleanupPreviewEditor);
</div>
</div>
<div class="rounded-lg border p-4">
<div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div class="min-w-0 space-y-1">
<Label>{{ t("settings.supportInfoTitle") }}</Label>
<p class="text-sm text-muted-foreground">{{ t("settings.supportInfoDescription") }}</p>
</div>
<Button type="button" variant="outline" size="sm" class="shrink-0" :disabled="appSupportInfoLoading && !appSupportInfo" @click="copyAppSupportInfo">
<Loader2 v-if="appSupportInfoLoading && !appSupportInfo" class="mr-1 h-3.5 w-3.5 animate-spin" />
<CheckCircle2 v-else-if="appSupportInfoCopied" class="mr-1 h-3.5 w-3.5" />
<Copy v-else class="mr-1 h-3.5 w-3.5" />
{{ appSupportInfoCopied ? t("settings.supportInfoCopied") : t("settings.supportInfoCopy") }}
</Button>
</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>
</div>
</div>
<p v-else class="mt-4 text-sm text-muted-foreground">{{ t("settings.supportInfoLoading") }}</p>
<p v-if="appSupportInfoError" class="mt-3 text-xs text-destructive">{{ t("settings.supportInfoLoadFailed", { message: appSupportInfoError }) }}</p>
</div>
<div class="rounded-lg border p-4">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div class="min-w-0 space-y-1">
+13
View File
@@ -3254,6 +3254,19 @@ export default {
mcpRefresh: "Check again",
mcpGuide: "MCP guide",
aboutDescription: "An open-source, lightweight database management tool.",
supportInfoTitle: "Support information",
supportInfoDescription: "Copy these environment details when filing issues or asking for help.",
supportInfoCopy: "Copy support info",
supportInfoCopied: "Copied",
supportInfoLoading: "Loading support information...",
supportInfoLoadFailed: "Failed to load full support information: {message}",
supportInfoAppVersion: "DBX Version",
supportInfoRuntime: "Runtime",
supportInfoRuntimeDesktop: "Desktop",
supportInfoRuntimeWeb: "Web",
supportInfoOperatingSystem: "Operating System",
supportInfoArchitecture: "Architecture",
supportInfoUnknown: "Unknown",
community: "Community",
qqGroup: "QQ Group",
wechatGroup: "WeChat Group",
+13
View File
@@ -3155,6 +3155,19 @@ export default withEnglishFallback({
mcpRefresh: "Comprobar de nuevo",
mcpGuide: "Guía MCP",
aboutDescription: "Una herramienta de administración de bases de datos liviana y de código abierto.",
supportInfoTitle: "Información de soporte",
supportInfoDescription: "Copia estos datos del entorno al crear issues o pedir ayuda.",
supportInfoCopy: "Copiar información",
supportInfoCopied: "Copiado",
supportInfoLoading: "Cargando información de soporte...",
supportInfoLoadFailed: "No se pudo cargar toda la información de soporte: {message}",
supportInfoAppVersion: "Versión de DBX",
supportInfoRuntime: "Entorno",
supportInfoRuntimeDesktop: "Escritorio",
supportInfoRuntimeWeb: "Web",
supportInfoOperatingSystem: "Sistema operativo",
supportInfoArchitecture: "Arquitectura",
supportInfoUnknown: "Desconocido",
community: "Comunidad",
qqGroup: "Grupo QQ",
wechatGroup: "Grupo WeChat",
+13
View File
@@ -3153,6 +3153,19 @@ export default withEnglishFallback({
mcpRefresh: "Verifica di nuovo",
mcpGuide: "Guida MCP",
aboutDescription: "Uno strumento di gestione di database leggero e open-source.",
supportInfoTitle: "Informazioni di supporto",
supportInfoDescription: "Copia questi dettagli dell'ambiente quando apri issue o chiedi aiuto.",
supportInfoCopy: "Copia informazioni",
supportInfoCopied: "Copiato",
supportInfoLoading: "Caricamento informazioni di supporto...",
supportInfoLoadFailed: "Impossibile caricare tutte le informazioni di supporto: {message}",
supportInfoAppVersion: "Versione DBX",
supportInfoRuntime: "Ambiente",
supportInfoRuntimeDesktop: "Desktop",
supportInfoRuntimeWeb: "Web",
supportInfoOperatingSystem: "Sistema operativo",
supportInfoArchitecture: "Architettura",
supportInfoUnknown: "Sconosciuto",
community: "Community",
qqGroup: "Gruppo QQ",
wechatGroup: "Gruppo WeChat",
+13
View File
@@ -3153,6 +3153,19 @@ export default withEnglishFallback({
queryExportKeysetOptimizationEnabled: "キーセット最適化を試行",
queryExportKeysetOptimizationEnabledDescription: "安全に認識された単一テーブルクエリにのみ適用。複雑なクエリは自動的にフォールバックします。",
aboutDescription: "オープンソースの軽量データベース管理ツール。",
supportInfoTitle: "サポート情報",
supportInfoDescription: "Issueの作成や問い合わせ時に、この環境情報をコピーできます。",
supportInfoCopy: "サポート情報をコピー",
supportInfoCopied: "コピーしました",
supportInfoLoading: "サポート情報を読み込み中...",
supportInfoLoadFailed: "完全なサポート情報の読み込みに失敗しました: {message}",
supportInfoAppVersion: "DBXバージョン",
supportInfoRuntime: "実行環境",
supportInfoRuntimeDesktop: "デスクトップ",
supportInfoRuntimeWeb: "Web",
supportInfoOperatingSystem: "オペレーティングシステム",
supportInfoArchitecture: "アーキテクチャ",
supportInfoUnknown: "不明",
community: "コミュニティ",
qqGroup: "QQグループ",
wechatGroup: "WeChatグループ",
+13
View File
@@ -3154,6 +3154,19 @@ export default withEnglishFallback({
mcpRefresh: "Verificar novamente",
mcpGuide: "Guia do MCP",
aboutDescription: "Uma ferramenta de gerenciamento de banco de dados leve e de código aberto.",
supportInfoTitle: "Informações de suporte",
supportInfoDescription: "Copie estes detalhes do ambiente ao abrir issues ou pedir ajuda.",
supportInfoCopy: "Copiar informações",
supportInfoCopied: "Copiado",
supportInfoLoading: "Carregando informações de suporte...",
supportInfoLoadFailed: "Falha ao carregar todas as informações de suporte: {message}",
supportInfoAppVersion: "Versão do DBX",
supportInfoRuntime: "Ambiente",
supportInfoRuntimeDesktop: "Desktop",
supportInfoRuntimeWeb: "Web",
supportInfoOperatingSystem: "Sistema operacional",
supportInfoArchitecture: "Arquitetura",
supportInfoUnknown: "Desconhecido",
community: "Comunidade",
qqGroup: "Grupo do QQ",
wechatGroup: "Grupo do WeChat",
+13
View File
@@ -3257,6 +3257,19 @@ export default withEnglishFallback({
mcpRefresh: "重新检查",
mcpGuide: "MCP 指南",
aboutDescription: "开源、轻量的数据库管理工具。",
supportInfoTitle: "支持信息",
supportInfoDescription: "提交 issue 或寻求帮助时,可复制这些环境信息。",
supportInfoCopy: "复制支持信息",
supportInfoCopied: "已复制",
supportInfoLoading: "正在加载支持信息...",
supportInfoLoadFailed: "完整支持信息加载失败:{message}",
supportInfoAppVersion: "DBX 版本",
supportInfoRuntime: "运行环境",
supportInfoRuntimeDesktop: "桌面端",
supportInfoRuntimeWeb: "Web 端",
supportInfoOperatingSystem: "操作系统",
supportInfoArchitecture: "系统架构",
supportInfoUnknown: "未知",
community: "社区",
qqGroup: "QQ 群",
wechatGroup: "微信交流群",
+13
View File
@@ -2955,6 +2955,19 @@ export default withEnglishFallback({
jdbcDeleteSuccess: "驅動程式已刪除",
jdbcNoDrivers: "還沒有匯入 JDBC 驅動程式。",
aboutDescription: "開源、輕量的資料庫管理工具。",
supportInfoTitle: "支援資訊",
supportInfoDescription: "提交 issue 或尋求協助時,可複製這些環境資訊。",
supportInfoCopy: "複製支援資訊",
supportInfoCopied: "已複製",
supportInfoLoading: "正在載入支援資訊...",
supportInfoLoadFailed: "完整支援資訊載入失敗:{message}",
supportInfoAppVersion: "DBX 版本",
supportInfoRuntime: "執行環境",
supportInfoRuntimeDesktop: "桌面端",
supportInfoRuntimeWeb: "Web 端",
supportInfoOperatingSystem: "作業系統",
supportInfoArchitecture: "系統架構",
supportInfoUnknown: "未知",
community: "社群",
qqGroup: "QQ 群",
wechatGroup: "微信交流群",
+67
View File
@@ -0,0 +1,67 @@
import type { AppSupportInfo } from "@/lib/backend/tauri";
export interface AppSupportInfoLabels {
appVersion: string;
runtime: string;
runtimeDesktop: string;
runtimeWeb: string;
operatingSystem: string;
architecture: string;
unknown: string;
}
export interface AppSupportInfoRow {
key: keyof Pick<AppSupportInfoLabels, "appVersion" | "runtime" | "operatingSystem" | "architecture">;
label: string;
value: string;
}
export function normalizeSupportInfoVersion(version: string | null | undefined, unknownLabel: string): string {
const trimmed = version?.trim();
if (!trimmed) return unknownLabel;
return trimmed.startsWith("v") || trimmed.startsWith("V") ? `v${trimmed.slice(1)}` : `v${trimmed}`;
}
export function formatSupportInfoRuntime(runtime: AppSupportInfo["runtime"], labels: AppSupportInfoLabels): string {
if (runtime === "desktop") return labels.runtimeDesktop;
if (runtime === "web") return labels.runtimeWeb;
return labels.unknown;
}
export function formatSupportInfoOperatingSystem(info: AppSupportInfo, unknownLabel: string): string {
const name = info.osName?.trim();
const version = info.osVersion?.trim();
if (!name && !version) return unknownLabel;
return [name, version].filter(Boolean).join(" ");
}
export function buildAppSupportInfoRows(info: AppSupportInfo, labels: AppSupportInfoLabels): AppSupportInfoRow[] {
return [
{
key: "appVersion",
label: labels.appVersion,
value: normalizeSupportInfoVersion(info.appVersion, labels.unknown),
},
{
key: "runtime",
label: labels.runtime,
value: formatSupportInfoRuntime(info.runtime, labels),
},
{
key: "operatingSystem",
label: labels.operatingSystem,
value: formatSupportInfoOperatingSystem(info, labels.unknown),
},
{
key: "architecture",
label: labels.architecture,
value: info.arch?.trim() || labels.unknown,
},
];
}
export function formatAppSupportInfoForClipboard(info: AppSupportInfo, labels: AppSupportInfoLabels): string {
return buildAppSupportInfoRows(info, labels)
.map((row) => `${row.label}: ${row.value}`)
.join("\n");
}
+2
View File
@@ -440,6 +440,7 @@ export const checkForUpdates = forward("checkForUpdates");
export const getSystemProxyUrl = forward("getSystemProxyUrl");
export const downloadAndInstallUpdate = forward("downloadAndInstallUpdate");
export const getAppVersion = forward("getAppVersion");
export const getAppSupportInfo = forward("getAppSupportInfo");
// Layout
export const saveSidebarLayout = forward("saveSidebarLayout");
@@ -450,6 +451,7 @@ export const loadSidebarLayout = forward("loadSidebarLayout");
// ---------------------------------------------------------------------------
export type {
AppSupportInfo,
AiMessage,
AiCompletionRequest,
AiTaskContract,
+12
View File
@@ -97,6 +97,7 @@ import type {
ExplainSqlBuildResult,
DroppedFilePreviewSqlOptions,
MongoGridFsFileInfo,
AppSupportInfo,
} from "@/lib/backend/tauri";
import type { QueryEditability } from "@/lib/sql/sqlAnalysis";
import type {
@@ -2102,6 +2103,17 @@ export async function getAppVersion(): Promise<string> {
return res.version;
}
export async function getAppSupportInfo(): Promise<AppSupportInfo> {
const appVersion = await getAppVersion();
return {
appVersion,
runtime: "web",
osName: navigator.platform || "web",
osVersion: null,
arch: "",
};
}
// ---------------------------------------------------------------------------
// Layout
// ---------------------------------------------------------------------------
+12
View File
@@ -200,6 +200,14 @@ export interface WebDavSyncSecretsStatus {
hasSavedPassphrase: boolean;
}
export interface AppSupportInfo {
appVersion: string;
runtime: "desktop" | "web";
osName: string;
osVersion?: string | null;
arch: string;
}
export interface QueryPagination {
limit: number;
offset: number;
@@ -1301,6 +1309,10 @@ export async function getAppVersion(): Promise<string> {
return getVersion();
}
export async function getAppSupportInfo(): Promise<AppSupportInfo> {
return invoke<AppSupportInfo>("get_app_support_info");
}
// --- Redis ---
export interface RedisKeyInfo {
key_display: string;