diff --git a/packages/web/public/i18n/locales/en/dialog.json b/packages/web/public/i18n/locales/en/dialog.json
index b443e7e3..a5fa2250 100644
--- a/packages/web/public/i18n/locales/en/dialog.json
+++ b/packages/web/public/i18n/locales/en/dialog.json
@@ -179,5 +179,10 @@
"confirmUnderstanding": "Yes, I know what I'm doing",
"title": "Are you sure?",
"description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration."
+ },
+ "clientNotification": {
+ "title": "Client Notification",
+ "TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds",
+ "Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated."
}
}
diff --git a/packages/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx b/packages/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx
new file mode 100644
index 00000000..1df5ef4f
--- /dev/null
+++ b/packages/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx
@@ -0,0 +1,69 @@
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@components/UI/Dialog.tsx";
+import { useDevice } from "@core/stores/deviceStore.ts";
+import { useTranslation } from "react-i18next";
+
+export interface ClientNotificationDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+export const ClientNotificationDialog = ({
+ open,
+ onOpenChange,
+}: ClientNotificationDialogProps) => {
+ const { t } = useTranslation("dialog");
+ const { getClientNotification, removeClientNotification } = useDevice();
+
+ const localOnOpenChange = (open: boolean) => {
+ if (!getClientNotification(1)) {
+ onOpenChange(open);
+ }
+ removeClientNotification(0);
+ };
+
+ const dialogContent = (() => {
+ if (!getClientNotification(0)) {
+ return;
+ }
+
+ switch (getClientNotification(0)?.payloadVariant.case) {
+ // TODO: Add KeyVerification logic
+ /*case "keyVerificationNumberInform":
+ return <>>;
+ case "keyVerificationNumberRequest":
+ return <>>;
+ case "keyVerificationFinal":
+ return <>>;
+ case "duplicatedPublicKey":
+ return <>>;
+ case "lowEntropyKey":
+ return <>>;*/
+
+ default:
+ return (
+
+ {t("clientNotification.title")}
+
+ {t(`clientNotification.${getClientNotification(0)?.message}`)}
+
+
+ );
+ }
+ })();
+
+ return (
+
+ );
+};
diff --git a/packages/web/src/components/Dialog/DialogManager.tsx b/packages/web/src/components/Dialog/DialogManager.tsx
index 0ba1a97f..f155523f 100644
--- a/packages/web/src/components/Dialog/DialogManager.tsx
+++ b/packages/web/src/components/Dialog/DialogManager.tsx
@@ -1,3 +1,4 @@
+import { ClientNotificationDialog } from "@components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx";
import { DeleteMessagesDialog } from "@components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx";
import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.tsx";
import { ImportDialog } from "@components/Dialog/ImportDialog.tsx";
@@ -91,6 +92,12 @@ export const DialogManager = () => {
setDialogOpen("deleteMessages", open);
}}
/>
+ {
+ setDialogOpen("clientNotification", open);
+ }}
+ />
>
);
};
diff --git a/packages/web/src/core/stores/deviceStore.mock.ts b/packages/web/src/core/stores/deviceStore.mock.ts
index d1dc70f8..22523511 100644
--- a/packages/web/src/core/stores/deviceStore.mock.ts
+++ b/packages/web/src/core/stores/deviceStore.mock.ts
@@ -45,7 +45,10 @@ export const mockDeviceStore: Device = {
refreshKeys: false,
deleteMessages: false,
managedMode: false,
+ clientNotification: false,
},
+ clientNotifications: [],
+
setStatus: vi.fn(),
setConfig: vi.fn(),
setModuleConfig: vi.fn(),
@@ -86,4 +89,9 @@ export const mockDeviceStore: Device = {
sendAdminMessage: vi.fn(),
updateFavorite: vi.fn(),
updateIgnored: vi.fn(),
+ addClientNotification: vi.fn(),
+ removeClientNotification: vi.fn(),
+ getClientNotification: vi.fn(),
+ getAllUnreadCount: vi.fn(),
+ getUnreadCount: vi.fn(),
};
diff --git a/packages/web/src/core/stores/deviceStore.ts b/packages/web/src/core/stores/deviceStore.ts
index 4cbad192..8a561b16 100644
--- a/packages/web/src/core/stores/deviceStore.ts
+++ b/packages/web/src/core/stores/deviceStore.ts
@@ -63,7 +63,9 @@ export interface Device {
refreshKeys: boolean;
deleteMessages: boolean;
managedMode: boolean;
+ clientNotification: boolean;
};
+ clientNotifications: Protobuf.Mesh.ClientNotification[];
setStatus: (status: Types.DeviceStatusEnum) => void;
setConfig: (config: Protobuf.Config.Config) => void;
@@ -126,6 +128,13 @@ export interface Device {
sendAdminMessage: (message: Protobuf.Admin.AdminMessage) => void;
updateFavorite: (nodeNum: number, isFavorite: boolean) => void;
updateIgnored: (nodeNum: number, isIgnored: boolean) => void;
+ addClientNotification: (
+ clientNotificationPacket: Protobuf.Mesh.ClientNotification,
+ ) => void;
+ removeClientNotification: (index: number) => void;
+ getClientNotification: (
+ index: number,
+ ) => Protobuf.Mesh.ClientNotification | undefined;
}
export interface DeviceState {
@@ -175,12 +184,14 @@ export const useDeviceStore = createStore((set, get) => ({
rebootOTA: false,
deleteMessages: false,
managedMode: false,
+ clientNotification: false,
},
pendingSettingsChanges: false,
messageDraft: "",
nodeErrors: new Map(),
unreadCounts: new Map(),
nodesMap: new Map(),
+ clientNotifications: [],
setStatus: (status: Types.DeviceStatusEnum) => {
set(
@@ -849,6 +860,37 @@ export const useDeviceStore = createStore((set, get) => ({
}),
);
},
+ addClientNotification: (
+ clientNotificationPacket: Protobuf.Mesh.ClientNotification,
+ ) => {
+ set(
+ produce((draft) => {
+ const device = draft.devices.get(id);
+ if (!device) {
+ return;
+ }
+ device.clientNotifications.push(clientNotificationPacket);
+ }),
+ );
+ },
+ removeClientNotification: (index: number) => {
+ set(
+ produce((draft) => {
+ const device = draft.devices.get(id);
+ if (!device) {
+ return;
+ }
+ device.clientNotifications.splice(index, 1);
+ }),
+ );
+ },
+ getClientNotification: (index: number) => {
+ const device = get().devices.get(id);
+ if (!device) {
+ return;
+ }
+ return device.clientNotifications[index];
+ },
});
}),
);
diff --git a/packages/web/src/core/subscriptions.ts b/packages/web/src/core/subscriptions.ts
index e9725688..0f685f4e 100644
--- a/packages/web/src/core/subscriptions.ts
+++ b/packages/web/src/core/subscriptions.ts
@@ -116,7 +116,8 @@ export const subscribeAll = (
connection.events.onClientNotificationPacket.subscribe(
(clientNotificationPacket) => {
- console.debug(clientNotificationPacket.message);
+ device.addClientNotification(clientNotificationPacket);
+ device.setDialogOpen("clientNotification", true);
},
);