From 271c98eddbf1f992b57c94248b5f7a65b52b10c0 Mon Sep 17 00:00:00 2001
From: Nicolas Van Labeke <nicolas.vanlabeke@hivemq.com>
Date: Tue, 28 Jan 2025 18:51:21 +0000
Subject: [PATCH 1/8] fix(29319): add missing capabilities enum

---
 .../src/api/hooks/useFrontendServices/useGetCapability.ts  | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/hivemq-edge/src/frontend/src/api/hooks/useFrontendServices/useGetCapability.ts b/hivemq-edge/src/frontend/src/api/hooks/useFrontendServices/useGetCapability.ts
index cf82d78fe4..83447c1818 100644
--- a/hivemq-edge/src/frontend/src/api/hooks/useFrontendServices/useGetCapability.ts
+++ b/hivemq-edge/src/frontend/src/api/hooks/useFrontendServices/useGetCapability.ts
@@ -3,9 +3,16 @@ import type { Capability } from '@/api/__generated__'
 
 import { useGetCapabilities } from './useGetCapabilities.ts'
 
+/**
+ * Another nonsensical backend magic code that needs to be duplicated (and therefore disconnected) in the frontend
+ * We have a single source of truth with OpenAPI; can we finally just use it?
+ */
 export enum CAPABILITY {
   PERSISTENCE = 'mqtt-persistence',
   DATAHUB = 'data-hub',
+  BIDIRECTIONAL_ADAPTER = 'bi-directional protocol adapters',
+  CONTROL_PLANE = 'control-plane-connectivity',
+  WRITEABLE_CONFIG = 'config-writeable',
 }
 
 export const useGetCapability = (id: string) => {

From fb6a5621cd55433affbaeca450b61f86fead5ad0 Mon Sep 17 00:00:00 2001
From: Nicolas Van Labeke <nicolas.vanlabeke@hivemq.com>
Date: Tue, 28 Jan 2025 18:51:43 +0000
Subject: [PATCH 2/8] feat(29319): add notification

---
 .../hooks/useGetManagedNotifications.tsx        | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.tsx b/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.tsx
index 431fa47c62..58ad0fb872 100644
--- a/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.tsx
+++ b/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.tsx
@@ -8,12 +8,14 @@ import { ExternalLinkIcon } from '@chakra-ui/icons'
 import { useGetReleases } from '@/api/hooks/useGitHub/useGetReleases.ts'
 import { useGetNotifications } from '@/api/hooks/useFrontendServices/useGetNotifications.ts'
 import { useGetConfiguration } from '@/api/hooks/useFrontendServices/useGetConfiguration.ts'
+import { CAPABILITY, useGetCapability } from '@/api/hooks/useFrontendServices/useGetCapability.ts'
 
 export const useGetManagedNotifications = () => {
   const { t } = useTranslation()
   const { data: configuration } = useGetConfiguration()
   const { data: releases, isSuccess: isReleasesSuccess } = useGetReleases()
   const { data: notification, isSuccess: isNotificationsSuccess } = useGetNotifications()
+  const isWritableConfig = useGetCapability(CAPABILITY.WRITEABLE_CONFIG)
   const [readNotifications, setReadNotifications] = useState<string[]>([])
   const [skip] = useLocalStorage<string[]>('edge.notifications', [])
 
@@ -51,6 +53,21 @@ export const useGetManagedNotifications = () => {
       list.push(...toasts)
     }
 
+    if (!isWritableConfig && !skip.includes(CAPABILITY.WRITEABLE_CONFIG)) {
+      // TODO[EDGE] The important feature is when the config is NOT writable (API request will fail)
+      //  The question is whether undefined (because it's not found) and undefined (because it is not supported)
+      //  have the same effect
+
+      list.push({
+        ...defaults,
+        id: CAPABILITY.WRITEABLE_CONFIG,
+        status: 'warning',
+        title: <Text>{t('capabilities.WRITEABLE_CONFIG.title')} </Text>,
+        description: <Text>{t('capabilities.WRITEABLE_CONFIG.description')} </Text>,
+        onCloseComplete: () => handleReadNotification(CAPABILITY.WRITEABLE_CONFIG),
+      })
+    }
+
     if (configuration && releases && releases.length > 0) {
       const { name, html_url } = releases[0]
       const currentVersion = configuration.environment?.properties?.version

From 37347773f9c76380c2dd922aa33a0b49e6ee3c3e Mon Sep 17 00:00:00 2001
From: Nicolas Van Labeke <nicolas.vanlabeke@hivemq.com>
Date: Tue, 28 Jan 2025 18:51:53 +0000
Subject: [PATCH 3/8] feat(29319): add translations

---
 hivemq-edge/src/frontend/src/locales/en/translation.json | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/hivemq-edge/src/frontend/src/locales/en/translation.json b/hivemq-edge/src/frontend/src/locales/en/translation.json
index b0730e8ce4..cb239b4ae6 100755
--- a/hivemq-edge/src/frontend/src/locales/en/translation.json
+++ b/hivemq-edge/src/frontend/src/locales/en/translation.json
@@ -28,6 +28,12 @@
       "status_ERROR": "Error"
     }
   },
+  "capabilities": {
+    "WRITEABLE_CONFIG": {
+      "title": "Config cannot be manipulated via the REST API",
+      "description": "Changes to the configuration made via the web app will NOT be persisted back into the config.xml. The requests will fail with an error message highlighting this situation."
+    }
+  },
   "navigation": {
     "mainPage": "Main content",
     "gateway": {

From 0deaf0b8a19b2d4ad9c52dd19de3f067b1124583 Mon Sep 17 00:00:00 2001
From: Nicolas Van Labeke <nicolas.vanlabeke@hivemq.com>
Date: Tue, 28 Jan 2025 19:07:47 +0000
Subject: [PATCH 4/8] test(29319): fix test (but that's a wrong fix!)

---
 .../hooks/useGetManagedNotifications.spec.ts           | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.spec.ts b/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.spec.ts
index d3e4a3dfd1..f6e70116f9 100644
--- a/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.spec.ts
+++ b/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.spec.ts
@@ -29,7 +29,7 @@ describe('useGetManagedNotifications', () => {
       expect(result.current.isSuccess).toBeTruthy()
     })
 
-    expect(result.current.notifications).toHaveLength(2)
+    expect(result.current.notifications).toHaveLength(3)
     expect(result.current.readNotifications).toHaveLength(0)
   })
 
@@ -40,7 +40,7 @@ describe('useGetManagedNotifications', () => {
       expect(result.current.isSuccess).toBeTruthy()
     })
 
-    expect(result.current.notifications).toHaveLength(2)
+    expect(result.current.notifications).toHaveLength(3)
     expect(result.current.readNotifications).toHaveLength(0)
 
     // close the first notification
@@ -48,7 +48,7 @@ describe('useGetManagedNotifications', () => {
       result.current.notifications[0].onCloseComplete?.()
     })
 
-    expect(result.current.notifications).toHaveLength(1)
+    expect(result.current.notifications).toHaveLength(2)
     expect(result.current.readNotifications).toHaveLength(1)
     expect(result.current.readNotifications).toContainEqual('Default Credentials Need Changing!')
 
@@ -57,9 +57,9 @@ describe('useGetManagedNotifications', () => {
       result.current.notifications[0].onCloseComplete?.()
     })
 
-    expect(result.current.notifications).toHaveLength(0)
+    expect(result.current.notifications).toHaveLength(2)
     expect(result.current.readNotifications).toHaveLength(2)
     expect(result.current.readNotifications).toContainEqual('Default Credentials Need Changing!')
-    expect(result.current.readNotifications).toContainEqual('2023.XXX')
+    expect(result.current.readNotifications).toContainEqual('config-writeable')
   })
 })

From c6d33bd4864bac12dc9aadf156ed5f4175f9782f Mon Sep 17 00:00:00 2001
From: Nicolas Van Labeke <nicolas.vanlabeke@hivemq.com>
Date: Wed, 29 Jan 2025 08:55:51 +0000
Subject: [PATCH 5/8] test(29319): add test

---
 .../hooks/useGetManagedNotifications.spec.ts  | 37 ++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.spec.ts b/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.spec.ts
index f6e70116f9..dbdef86d61 100644
--- a/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.spec.ts
+++ b/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.spec.ts
@@ -5,7 +5,11 @@ import { act, renderHook, waitFor } from '@testing-library/react'
 import { server } from '@/__test-utils__/msw/mockServer.ts'
 import { SimpleWrapper as wrapper } from '@/__test-utils__/hooks/SimpleWrapper.tsx'
 
-import { handlers as frontendHandler } from '@/api/hooks/useFrontendServices/__handlers__'
+import {
+  handlerCapabilities,
+  handlers as frontendHandler,
+  MOCK_CAPABILITY_WRITEABLE_CONFIG,
+} from '@/api/hooks/useFrontendServices/__handlers__'
 import { handlers as gitHubHandler } from '@/api/hooks/useGitHub/__handlers__'
 
 import { useGetManagedNotifications } from './useGetManagedNotifications.tsx'
@@ -62,4 +66,35 @@ describe('useGetManagedNotifications', () => {
     expect(result.current.readNotifications).toContainEqual('Default Credentials Need Changing!')
     expect(result.current.readNotifications).toContainEqual('config-writeable')
   })
+
+  it('should handle config-writeable', async () => {
+    server.use(...handlerCapabilities({ items: [MOCK_CAPABILITY_WRITEABLE_CONFIG] }))
+
+    const { result } = renderHook(useGetManagedNotifications, { wrapper })
+
+    await waitFor(() => {
+      expect(result.current.isSuccess).toBeTruthy()
+    })
+
+    expect(result.current.notifications).toHaveLength(2)
+
+    // close the first notification
+    act(() => {
+      result.current.notifications[0].onCloseComplete?.()
+    })
+
+    expect(result.current.notifications).toHaveLength(1)
+    expect(result.current.readNotifications).toHaveLength(1)
+    expect(result.current.readNotifications).toContainEqual('Default Credentials Need Changing!')
+
+    // close the first notification
+    act(() => {
+      result.current.notifications[0].onCloseComplete?.()
+    })
+
+    expect(result.current.notifications).toHaveLength(0)
+    expect(result.current.readNotifications).toHaveLength(2)
+    expect(result.current.readNotifications).toContainEqual('Default Credentials Need Changing!')
+    expect(result.current.readNotifications).toContainEqual('2023.XXX')
+  })
 })

From cc63ecb0e9708100de13b3fc76ab1cd20a7441f0 Mon Sep 17 00:00:00 2001
From: Nicolas Van Labeke <nicolas.vanlabeke@hivemq.com>
Date: Wed, 29 Jan 2025 08:55:58 +0000
Subject: [PATCH 6/8] test(29319): fix mocks

---
 .../hooks/useFrontendServices/__handlers__/index.ts  | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/hivemq-edge/src/frontend/src/api/hooks/useFrontendServices/__handlers__/index.ts b/hivemq-edge/src/frontend/src/api/hooks/useFrontendServices/__handlers__/index.ts
index d4a97c38e2..3c73b390ec 100644
--- a/hivemq-edge/src/frontend/src/api/hooks/useFrontendServices/__handlers__/index.ts
+++ b/hivemq-edge/src/frontend/src/api/hooks/useFrontendServices/__handlers__/index.ts
@@ -127,6 +127,12 @@ export const MOCK_CAPABILITY_DATAHUB: Capability = {
     'This enables HiveMQ Edge to make use of the HiveMQ Data Hub. This includes validation and transformation of data.',
 }
 
+export const MOCK_CAPABILITY_WRITEABLE_CONFIG: Capability = {
+  id: 'config-writeable',
+  displayName: 'Config can be manipulated via the REST API',
+  description: 'Changes to the configuration made via the REST API are persisted back into the config.xml.',
+}
+
 export const MOCK_CAPABILITY_DUMMY: Capability = {
   id: 'edge',
   displayName: 'This is a test capability',
@@ -150,3 +156,9 @@ export const handlers = [
     return HttpResponse.json<CapabilityList>(MOCK_CAPABILITIES, { status: 200 })
   }),
 ]
+
+export const handlerCapabilities = (source: CapabilityList) => [
+  http.get('**/frontend/capabilities', () => {
+    return HttpResponse.json<CapabilityList>(source, { status: 200 })
+  }),
+]

From cfef877ae334b62a6f02e774a8a544ff8c3c9f06 Mon Sep 17 00:00:00 2001
From: Nicolas Van Labeke <nicolas.vanlabeke@hivemq.com>
Date: Wed, 29 Jan 2025 09:00:26 +0000
Subject: [PATCH 7/8] fix(29319): linting

---
 .../modules/Notifications/hooks/useGetManagedNotifications.tsx  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.tsx b/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.tsx
index 58ad0fb872..e68ca5d33e 100644
--- a/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.tsx
+++ b/hivemq-edge/src/frontend/src/modules/Notifications/hooks/useGetManagedNotifications.tsx
@@ -94,7 +94,7 @@ export const useGetManagedNotifications = () => {
     }
 
     return list
-  }, [notification?.items, configuration, releases, readNotifications, skip, t])
+  }, [notification?.items, isWritableConfig, skip, configuration, releases, readNotifications, t])
 
   return { notifications, isSuccess: isNotificationsSuccess && isReleasesSuccess, readNotifications }
 }

From 39c6fa860d6d00705f78e4c6c01c8cb67d7b430e Mon Sep 17 00:00:00 2001
From: Nicolas Van Labeke <nicolas.vanlabeke@hivemq.com>
Date: Wed, 29 Jan 2025 10:07:13 +0000
Subject: [PATCH 8/8] fix(29319): fix translations

---
 hivemq-edge/src/frontend/src/locales/en/translation.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hivemq-edge/src/frontend/src/locales/en/translation.json b/hivemq-edge/src/frontend/src/locales/en/translation.json
index cb239b4ae6..7214c87cc6 100755
--- a/hivemq-edge/src/frontend/src/locales/en/translation.json
+++ b/hivemq-edge/src/frontend/src/locales/en/translation.json
@@ -30,7 +30,7 @@
   },
   "capabilities": {
     "WRITEABLE_CONFIG": {
-      "title": "Config cannot be manipulated via the REST API",
+      "title": "Config cannot be manipulated via the web app",
       "description": "Changes to the configuration made via the web app will NOT be persisted back into the config.xml. The requests will fail with an error message highlighting this situation."
     }
   },