From 66f661e2a15597d7807bc50278b9f3bb4c06d8a1 Mon Sep 17 00:00:00 2001 From: jubnl Date: Thu, 18 Jun 2026 09:59:14 +0200 Subject: [PATCH] fix(airtrail): gate airtrail update behind a user setting, on airtrail update: rebuild payload from fresh data to prevent any data loss --- client/src/api/client.ts | 2 +- .../Settings/AirTrailConnectionSection.tsx | 12 +- server/src/db/migrations.ts | 9 + .../nest/integrations/airtrail.controller.ts | 1 + .../src/services/airtrail/airtrailMapper.ts | 2 +- .../src/services/airtrail/airtrailService.ts | 23 ++- server/src/services/airtrail/airtrailSync.ts | 49 ++++-- .../tests/unit/services/airtrailSync.test.ts | 160 ++++++++++++++++++ shared/src/airtrail/airtrail.schema.ts | 6 + shared/src/i18n/ar/settings.ts | 2 + shared/src/i18n/br/settings.ts | 2 + shared/src/i18n/cs/settings.ts | 2 + shared/src/i18n/de/settings.ts | 2 + shared/src/i18n/en/settings.ts | 2 + shared/src/i18n/es/settings.ts | 2 + shared/src/i18n/fr/settings.ts | 2 + shared/src/i18n/gr/settings.ts | 2 + shared/src/i18n/hu/settings.ts | 2 + shared/src/i18n/id/settings.ts | 2 + shared/src/i18n/it/settings.ts | 2 + shared/src/i18n/ja/settings.ts | 2 + shared/src/i18n/ko/settings.ts | 2 + shared/src/i18n/nl/settings.ts | 2 + shared/src/i18n/pl/settings.ts | 2 + shared/src/i18n/ru/settings.ts | 2 + shared/src/i18n/tr/settings.ts | 2 + shared/src/i18n/uk/settings.ts | 2 + shared/src/i18n/zh-TW/settings.ts | 2 + shared/src/i18n/zh/settings.ts | 2 + 29 files changed, 283 insertions(+), 21 deletions(-) create mode 100644 server/tests/unit/services/airtrailSync.test.ts diff --git a/client/src/api/client.ts b/client/src/api/client.ts index 899453c8..01a15e1b 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -489,7 +489,7 @@ export const addonsApi = { export const airtrailApi = { getSettings: () => apiClient.get('/integrations/airtrail/settings').then(r => r.data), - saveSettings: (data: { url: string; apiKey?: string; allowInsecureTls?: boolean }) => + saveSettings: (data: { url: string; apiKey?: string; allowInsecureTls?: boolean; writeEnabled?: boolean }) => apiClient.put('/integrations/airtrail/settings', data).then(r => r.data), status: () => apiClient.get('/integrations/airtrail/status').then(r => r.data), test: (data: { url?: string; apiKey?: string; allowInsecureTls?: boolean }) => diff --git a/client/src/components/Settings/AirTrailConnectionSection.tsx b/client/src/components/Settings/AirTrailConnectionSection.tsx index 874e8af5..d41d4694 100644 --- a/client/src/components/Settings/AirTrailConnectionSection.tsx +++ b/client/src/components/Settings/AirTrailConnectionSection.tsx @@ -19,6 +19,7 @@ export default function AirTrailConnectionSection(): React.ReactElement { const [url, setUrl] = useState('') const [apiKey, setApiKey] = useState('') const [allowInsecureTls, setAllowInsecureTls] = useState(false) + const [writeEnabled, setWriteEnabled] = useState(false) const [connected, setConnected] = useState(false) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) @@ -30,6 +31,7 @@ export default function AirTrailConnectionSection(): React.ReactElement { .then(d => { setUrl(d.url || '') setAllowInsecureTls(!!d.allowInsecureTls) + setWriteEnabled(!!d.writeEnabled) setConnected(!!d.connected) }) .catch(() => {}) @@ -46,7 +48,7 @@ export default function AirTrailConnectionSection(): React.ReactElement { const handleSave = async () => { setSaving(true) try { - const d = await airtrailApi.saveSettings({ url: url.trim(), allowInsecureTls, ...keyPayload() }) + const d = await airtrailApi.saveSettings({ url: url.trim(), allowInsecureTls, writeEnabled, ...keyPayload() }) const status = await airtrailApi.status().catch(() => ({ connected: false })) setConnected(!!status.connected) setApiKey('') @@ -107,6 +109,14 @@ export default function AirTrailConnectionSection(): React.ReactElement { {t('settings.airtrail.allowInsecureTls')} +
+
+ setWriteEnabled(v => !v)} /> + {t('settings.airtrail.writeBack')} +
+

{t('settings.airtrail.writeBackHint')}

+
+