diff --git a/apps/client-web/src/core.ts b/apps/client-web/src/core.ts index 5a1b70e..3e2f24c 100644 --- a/apps/client-web/src/core.ts +++ b/apps/client-web/src/core.ts @@ -7,6 +7,9 @@ import { configStore, + initializeTelemetry, + shutdownTelemetry, + flushTelemetry, ExtensionRegistryOriginResolver, DEFAULT_EXTENSION_REGISTRY_ORIGIN, localStorageAdapter, @@ -205,9 +208,10 @@ export function getExtensionSetupContribution(name: string): ExtensionSetupContr } /** Replace the browser-owned lease runtime after a validated Settings cutover. */ -export function adoptWebPeerRuntime(runtime: WebPeerRuntime): void { +export async function adoptWebPeerRuntime(runtime: WebPeerRuntime): Promise { webPeerRuntime?.stop() webPeerRuntime = runtime + await initializeTelemetry(configStore.metaConfig, WEB_PEER_IDENTITY.applicationVersion) JobManager.startWorker() } @@ -216,6 +220,7 @@ export async function stopWebPeerRuntime(): Promise { webPeerRuntime = null currentWebPeer.value = null await JobManager.stopWorker() + await shutdownTelemetry() } /** Start the lease after Settings has mounted and loaded recovery configuration. */ @@ -226,7 +231,7 @@ export async function startConfiguredWebPeerRuntime(): Promise { const peer = await candidate.register() await configStore.loadPeerConfig() await candidate.start() - adoptWebPeerRuntime(candidate) + await adoptWebPeerRuntime(candidate) currentWebPeer.value = { id: peer.id, name: peer.name } } catch (error) { candidate.stop() @@ -335,7 +340,7 @@ export async function initializeCore(options: { loadPeerConfig?: boolean } = {}) const peer = await candidate.register() await configStore.loadPeerConfig() await candidate.start() - adoptWebPeerRuntime(candidate) + await adoptWebPeerRuntime(candidate) currentWebPeer.value = { id: peer.id, name: peer.name } } catch (error) { candidate.stop() @@ -353,3 +358,8 @@ export async function initializeCore(options: { loadPeerConfig?: boolean } = {}) export async function shutdownCore(): Promise { await stopWebPeerRuntime() } + +// Page termination is best effort; normal connection shutdown awaits bounded SDK drain. +window.addEventListener('pagehide', () => { + void flushTelemetry() +}) diff --git a/apps/client-web/src/locales/messages/en.json b/apps/client-web/src/locales/messages/en.json index be3759e..3d5cfc7 100644 --- a/apps/client-web/src/locales/messages/en.json +++ b/apps/client-web/src/locales/messages/en.json @@ -69,7 +69,11 @@ "resetConfirmMessage": "Are you sure you want to reset all settings to default? This action cannot be undone.", "exportConfig": "Export", "importConfig": "Import", - "importError": "Unable to restore configuration. Check the file and deployment connection." + "importError": "Unable to restore configuration. Check the file and deployment connection.", + "telemetryEnabled": "Enable telemetry for this browser Peer", + "telemetryHelp": "Takes effect after saving the connection. Requires deployment observability endpoints; existing Job logs remain available.", + "telemetryRelay": "Peer telemetry relay URL (optional)", + "telemetryRelayHelp": "Use only a trusted deployment Peer relay. This connection’s short-lived Peer identity authenticates requests; leave empty to use deployment public observability endpoints." }, "source": { "deleteConfirmTitle": "Delete Source", @@ -198,7 +202,8 @@ "retry": "Retry", "notAvailable": "N/A", "notFound": "Job not found", - "sourceNotFound": "Source not found" + "sourceNotFound": "Source not found", + "openDiagnostics": "Open diagnostics (search by Job ID)" }, "logs": { "loading": "Loading logs…", diff --git a/apps/client-web/src/locales/messages/zh-CN.json b/apps/client-web/src/locales/messages/zh-CN.json index e2d1946..d945afb 100644 --- a/apps/client-web/src/locales/messages/zh-CN.json +++ b/apps/client-web/src/locales/messages/zh-CN.json @@ -69,7 +69,11 @@ "resetConfirmMessage": "确定要重置所有设置为默认值吗?此操作无法撤销。", "exportConfig": "导出", "importConfig": "导入", - "importError": "无法恢复配置,请检查文件及部署连接。" + "importError": "无法恢复配置,请检查文件及部署连接。", + "telemetryEnabled": "启用此浏览器 Peer 的遥测", + "telemetryHelp": "保存连接后生效。需要部署配置观测出口;原有 Job 日志继续保留。", + "telemetryRelay": "Peer 遥测中转地址(可选)", + "telemetryRelayHelp": "仅填写可信部署 Peer 的中转地址。此连接的短期 Peer 身份将用于认证;留空使用部署的公开观测出口。" }, "source": { "deleteConfirmTitle": "删除数据源", @@ -199,7 +203,8 @@ "retry": "重试", "notAvailable": "不可用", "notFound": "未找到任务", - "sourceNotFound": "未找到数据源" + "sourceNotFound": "未找到数据源", + "openDiagnostics": "打开诊断入口(使用 Job ID 查询)" }, "logs": { "loading": "正在读取日志…", diff --git a/apps/client-web/src/views/jobs/job/job.vue b/apps/client-web/src/views/jobs/job/job.vue index 20ca40e..4bed875 100644 --- a/apps/client-web/src/views/jobs/job/job.vue +++ b/apps/client-web/src/views/jobs/job/job.vue @@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n' import { useAsyncState, useIntervalFn } from '@vueuse/core' import { InkLoading, InkButton, InkField, InkSkeleton } from '@inkcre/ui-web' import LogsViewer from '@/components/obsrv/LogsViewer/LogsViewer.vue' -import { APIError, Job, JobStatus, Source } from '@inkcre/core' +import { APIError, Job, JobStatus, Source, telemetryDiagnosticsUrl } from '@inkcre/core' import dayjs from 'dayjs' const route = useRoute() @@ -147,6 +147,14 @@ watch( + {{ t('job.openDiagnostics') }} +