Skip to content

Commit fbca4cd

Browse files
Encapsulating Notifications pkg status (#2184)
* handleNotisPkg hook * notificationsPackageStatus call * installer fix * send `customNotifications`only if notifier running
1 parent b492dba commit fbca4cd

File tree

13 files changed

+244
-176
lines changed

13 files changed

+244
-176
lines changed

packages/admin-ui/src/__mock-backend__/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,13 @@ export const otherCalls: Omit<Routes, keyof typeof namedSpacedCalls> = {
394394
notificationsApplyPreviousEndpoints: async () => {
395395
return { endpoints: [], customEndpoints: [] };
396396
},
397-
notificationsIsInstalled: async () => true
397+
notificationsPackageStatus: async () => ({
398+
notificationsDnp: null,
399+
isInstalled: false,
400+
isRunning: false,
401+
isNotifierRunning: false,
402+
servicesNotRunning: [],
403+
}),
398404
};
399405

400406
export const calls: Routes = {

packages/admin-ui/src/components/topbar/dropdownMenus/Notifications.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default function Notifications() {
2121
}, [unseenNotificationsReq]);
2222

2323
useEffect(() => {
24-
if (unseenNotificationsReq.data !== undefined && unseenNotificationsReq.data !== null) {
24+
if (unseenNotificationsReq.data) {
2525
setNewNotifications(unseenNotificationsReq.data > 0);
2626
}
2727
}, [unseenNotificationsReq.data]);

packages/admin-ui/src/components/welcome/BottomButtons.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ export default function BottomButtons({
88
nextTag = "Next",
99
backVariant = "outline-secondary",
1010
nextVariant = "dappnode",
11-
nextDisabled = false
11+
nextDisabled = false,
12+
backDisabled = false
1213
}: {
1314
onBack?: () => void;
1415
onNext?: () => void;
@@ -17,11 +18,12 @@ export default function BottomButtons({
1718
backVariant?: ButtonVariant;
1819
nextVariant?: ButtonVariant;
1920
nextDisabled?: boolean;
21+
backDisabled?: boolean;
2022
}) {
2123
return (
2224
<div className="bottom-buttons">
2325
{onBack && (
24-
<Button onClick={onBack} variant={backVariant} className="back">
26+
<Button onClick={onBack} variant={backVariant} className="back" disabled={backDisabled}>
2527
{backTag}
2628
</Button>
2729
)}

packages/admin-ui/src/components/welcome/features/EnableNotifications.tsx

Lines changed: 27 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,28 @@
1-
import React, { useEffect, useState } from "react";
1+
import React, { useEffect } from "react";
22
import BottomButtons from "../BottomButtons";
33
import { docsUrl, externalUrlProps } from "params";
44
import SubTitle from "components/SubTitle";
55
import Switch from "components/Switch";
6-
import { api, useApi } from "api";
76
import { notificationsDnpName } from "params.js";
8-
import { withToast } from "components/toast/Toast";
9-
import { continueIfCalleDisconnected } from "api/utils";
107
import Loading from "components/Loading";
118
import { prettyDnpName } from "utils/format";
129
import ErrorBoundary from "components/ErrorBoundary";
10+
import { useHandleNotificationsPkg } from "hooks/useHandleNotificationsPkg";
1311

1412
export default function EnableNotifications({ onBack, onNext }: { onBack?: () => void; onNext: () => void }) {
15-
const [notificationsDisabled, setNotificationsDisabled] = useState<boolean>(false);
16-
const [notificationsNotInstalled, setNotificationsNotInstalled] = useState<boolean>(false);
17-
const [isNotificationsInstalling, setIsNotificationsInstalling] = useState<boolean>(false);
18-
const [errorInstallingNotifications, setErrorInstallingNotifications] = useState<string | null>(null);
19-
20-
const dnps = useApi.packagesGet();
21-
22-
useEffect(() => {
23-
if (dnps.data) {
24-
setNotificationsNotInstalled(dnps.data.find((dnp) => dnp.dnpName === notificationsDnpName) === undefined);
25-
// update notifications package state
26-
const notificationsPkg = dnps.data.find((dnp) => dnp.dnpName === notificationsDnpName);
27-
if (notificationsPkg) {
28-
const isStopped = notificationsPkg.containers.some((c) => c.state !== "running");
29-
setNotificationsDisabled(isStopped);
30-
} else {
31-
setNotificationsDisabled(true);
32-
}
33-
}
34-
}, [dnps.data]);
13+
const {
14+
isLoading,
15+
isInstalled,
16+
isRunning,
17+
startStopNotifications,
18+
installNotificationsPkg,
19+
isInstalling,
20+
errorInstallingNotifications
21+
} = useHandleNotificationsPkg();
3522

3623
useEffect(() => {
37-
async function installNotificationsPkg() {
38-
try {
39-
setIsNotificationsInstalling(true);
40-
await withToast(
41-
continueIfCalleDisconnected(
42-
() =>
43-
api.packageInstall({
44-
name: notificationsDnpName,
45-
options: {
46-
BYPASS_CORE_RESTRICTION: true, // allow installation even if the core version is not compatible
47-
BYPASS_SIGNED_RESTRICTION: true // allow installation even if the package is not signed
48-
}
49-
}),
50-
notificationsDnpName
51-
),
52-
{
53-
message: `Installing ${prettyDnpName(notificationsDnpName)}...`,
54-
onSuccess: `Installed ${prettyDnpName(notificationsDnpName)}`,
55-
onError: `Error while installing ${prettyDnpName(notificationsDnpName)}`
56-
}
57-
);
58-
59-
setErrorInstallingNotifications(null);
60-
setNotificationsNotInstalled(false);
61-
} catch (error) {
62-
console.error(`Error while installing notifications package: ${error}`);
63-
setErrorInstallingNotifications(`Error while installing notifications package: ${error}`);
64-
return;
65-
} finally {
66-
setIsNotificationsInstalling(false);
67-
await dnps.revalidate();
68-
}
69-
}
70-
71-
if (notificationsNotInstalled && !errorInstallingNotifications) installNotificationsPkg();
72-
}, [notificationsNotInstalled]);
73-
74-
async function startStopNotifications(): Promise<void> {
75-
try {
76-
await withToast(
77-
continueIfCalleDisconnected(
78-
() => api.packageStartStop({ dnpName: notificationsDnpName }),
79-
notificationsDnpName
80-
),
81-
{
82-
message: notificationsDisabled ? "Enabling notifications" : "Disabling notifications",
83-
onSuccess: notificationsDisabled ? "Notifications Enabled" : "Notifications disabled"
84-
}
85-
);
86-
87-
await dnps.revalidate();
88-
} catch (e) {
89-
console.error(`Error on start/stop notifications package: ${e}`);
90-
}
91-
}
24+
if (!isLoading && !isInstalled && !errorInstallingNotifications) installNotificationsPkg();
25+
}, [isLoading, isInstalled, errorInstallingNotifications]);
9226

9327
return (
9428
<div>
@@ -102,22 +36,26 @@ export default function EnableNotifications({ onBack, onNext }: { onBack?: () =>
10236
We're transitioning to a new and improved in-app Notifications experience, designed to be more reliable,
10337
configurable and scalable.
10438
</div>
105-
{dnps.isValidating || isNotificationsInstalling ? (
39+
{isLoading ? (
40+
<Loading steps={["Loading"]} />
41+
) : isInstalling ? (
10642
<Loading steps={["Installing notifications package"]} />
107-
) : notificationsNotInstalled ? (
43+
) : !isInstalled ? (
10844
errorInstallingNotifications ? (
109-
<ErrorBoundary> {errorInstallingNotifications} </ErrorBoundary>
45+
<>
46+
<br />
47+
<ErrorBoundary> {errorInstallingNotifications} </ErrorBoundary>
48+
</>
11049
) : (
111-
<>Could not install {prettyDnpName(notificationsDnpName)}. A manual installation may be required.</>
50+
<>
51+
<br />
52+
Could not install {prettyDnpName(notificationsDnpName)}. A manual installation may be required.
53+
</>
11254
)
11355
) : (
11456
<>
11557
<SubTitle>Enable new notifications</SubTitle>
116-
<Switch
117-
checked={!notificationsDisabled}
118-
disabled={dnps.isValidating}
119-
onToggle={() => startStopNotifications()}
120-
/>
58+
<Switch checked={isRunning} disabled={isLoading} onToggle={() => startStopNotifications()} />
12159
<br />
12260
<br />
12361
<p>
@@ -134,7 +72,7 @@ export default function EnableNotifications({ onBack, onNext }: { onBack?: () =>
13472
)}
13573
</div>
13674

137-
<BottomButtons onBack={onBack} onNext={() => onNext()} nextDisabled={isNotificationsInstalling} />
75+
<BottomButtons onBack={onBack} onNext={() => onNext()} nextDisabled={isInstalling} backDisabled={isInstalling} />
13876
<br />
13977
<br />
14078
</div>
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { api, useApi } from "api";
2+
import { useCallback, useEffect, useState } from "react";
3+
import { notificationsDnpName } from "params.js";
4+
import { confirm } from "components/ConfirmDialog";
5+
import { withToast } from "components/toast/Toast";
6+
import { continueIfCalleDisconnected } from "api/utils";
7+
import { InstalledPackageData } from "@dappnode/types";
8+
import { prettyDnpName } from "utils/format";
9+
10+
export function useHandleNotificationsPkg() {
11+
const [isLoading, setIsLoading] = useState<boolean>(true);
12+
const [isInstalled, setIsInstalled] = useState<boolean>(false);
13+
const [isInstalling, setIsInstalling] = useState<boolean>(false);
14+
const [isRunning, setIsRunning] = useState<boolean>(false);
15+
const [notRunningServices, setNotRunningServices] = useState<string[]>([]);
16+
const [isNotifierRunning, setIsNotifierRunning] = useState<boolean>(false);
17+
const [notificationsPkg, setNotificationsPkg] = useState<InstalledPackageData | null>(null);
18+
const [errorInstallingNotifications, setErrorInstallingNotifications] = useState<string | null>(null);
19+
20+
const notificationsStatusRequest = useApi.notificationsPackageStatus();
21+
22+
useEffect(() => {
23+
setIsLoading(notificationsStatusRequest.isValidating);
24+
}, [notificationsStatusRequest.isValidating]);
25+
26+
useEffect(() => {
27+
if (notificationsStatusRequest.data) {
28+
const {notificationsDnp, isInstalled, isRunning, servicesNotRunning, isNotifierRunning } = notificationsStatusRequest.data;
29+
setIsInstalled(isInstalled);
30+
setNotRunningServices(servicesNotRunning);
31+
setIsRunning(isRunning);
32+
setIsNotifierRunning(isNotifierRunning);
33+
setNotificationsPkg(notificationsDnp);
34+
}
35+
}, [notificationsStatusRequest.data]);
36+
37+
const startStopNotifications = useCallback(async (): Promise<void> => {
38+
try {
39+
if (isInstalled && notificationsPkg) {
40+
const notificationsRunning = notRunningServices.length === 0;
41+
42+
if (notificationsRunning) {
43+
await new Promise<void>((resolve) => {
44+
confirm({
45+
title: `Pause notifications package`,
46+
text: `Attention, the notifications package may alert you to critical issues if they arise. Pausing this package could result in missing important notifications.`,
47+
label: "Pause",
48+
onClick: resolve
49+
});
50+
});
51+
}
52+
53+
await withToast(
54+
continueIfCalleDisconnected(
55+
() =>
56+
api.packageStartStop({
57+
dnpName: notificationsDnpName,
58+
serviceNames: notificationsRunning
59+
? notificationsPkg.containers.map((c) => c.serviceName)
60+
: notRunningServices
61+
}),
62+
notificationsDnpName
63+
),
64+
{
65+
message: notificationsRunning ? "Disabling notifications" : "Enabling notifications",
66+
onSuccess: notificationsRunning ? "Notifications disabled" : "Notifications Enabled"
67+
}
68+
);
69+
70+
notificationsStatusRequest.revalidate();
71+
}
72+
} catch (e) {
73+
console.error(`Error on start/stop notifications package: ${e}`);
74+
}
75+
}, [isInstalled, notificationsPkg, notRunningServices, notificationsStatusRequest]);
76+
77+
const installNotificationsPkg = useCallback(async (): Promise<void> => {
78+
try {
79+
setIsInstalling(true);
80+
await withToast(
81+
continueIfCalleDisconnected(
82+
() =>
83+
api.packageInstall({
84+
name: notificationsDnpName,
85+
options: {
86+
BYPASS_CORE_RESTRICTION: true,
87+
BYPASS_SIGNED_RESTRICTION: true
88+
}
89+
}),
90+
notificationsDnpName
91+
),
92+
{
93+
message: `Installing ${prettyDnpName(notificationsDnpName)}...`,
94+
onSuccess: `Installed ${prettyDnpName(notificationsDnpName)}`,
95+
onError: `Error while installing ${prettyDnpName(notificationsDnpName)}`
96+
}
97+
);
98+
99+
setErrorInstallingNotifications(null);
100+
} catch (error) {
101+
console.error(`Error while installing notifications package: ${error}`);
102+
setErrorInstallingNotifications(`Error while installing notifications package: ${error}`);
103+
} finally {
104+
setIsInstalling(false);
105+
await notificationsStatusRequest.revalidate();
106+
}
107+
}, [notificationsStatusRequest]);
108+
109+
return {
110+
isLoading,
111+
isInstalled,
112+
isRunning,
113+
startStopNotifications,
114+
installNotificationsPkg,
115+
isInstalling,
116+
isNotifierRunning,
117+
errorInstallingNotifications
118+
};
119+
}

packages/admin-ui/src/pages/installer/components/InstallDnpView.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ const InstallDnpView: React.FC<InstallDnpViewProps> = ({ dnp, progressLogs }) =>
5252
const [showAdvancedEditor, setShowAdvancedEditor] = useState(false);
5353
const [showSuccess, setShowSuccess] = useState(false);
5454
const [isInstalling, setIsInstalling] = useState(false);
55+
const [notificationsPkgInstalled, setNotificationsPkgInstalled] = useState(false);
56+
5557
const dispatch = useDispatch();
5658

5759
const {
@@ -83,6 +85,16 @@ const InstallDnpView: React.FC<InstallDnpViewProps> = ({ dnp, progressLogs }) =>
8385
manifest.notifications?.customEndpoints || []
8486
);
8587

88+
const notificationsPkgStatusRequest = useApi.notificationsPackageStatus();
89+
90+
useEffect(() => {
91+
// Check if notifications package is installed and running
92+
if (notificationsPkgStatusRequest.data) {
93+
const { isInstalled } = notificationsPkgStatusRequest.data;
94+
setNotificationsPkgInstalled(isInstalled);
95+
}
96+
}, [notificationsPkgStatusRequest.data]);
97+
8698
useEffect(() => {
8799
if (notificationsSettings && notificationsSettings[dnpName]) {
88100
setEndpoints(notificationsSettings[dnpName].endpoints || []);
@@ -203,8 +215,6 @@ const InstallDnpView: React.FC<InstallDnpViewProps> = ({ dnp, progressLogs }) =>
203215
}
204216
].filter((option) => option.available);
205217

206-
const isNotificationsPkgInstalled = useApi.notificationsIsInstalled();
207-
208218
const disableInstallation =
209219
!isEmpty(progressLogs) || requiresCoreUpdate || requiresDockerUpdate || packagesToBeUninstalled.length > 0;
210220

@@ -216,7 +226,7 @@ const InstallDnpView: React.FC<InstallDnpViewProps> = ({ dnp, progressLogs }) =>
216226
const installSubPath = "install";
217227

218228
// Only display notifications step if the notifications package is installed && there are endpoints in manifest
219-
const showNotificationsStep = isNotificationsPkgInstalled.data && manifest.notifications;
229+
const showNotificationsStep = notificationsPkgInstalled && manifest.notifications;
220230

221231
const availableRoutes: {
222232
name: string;

packages/admin-ui/src/pages/notifications/NotificationsRoot.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import { NotificationsSettings } from "./tabs/Settings/Settings";
1212
import { LegacyNotifications } from "./tabs/Legacy";
1313

1414
export const NotificationsRoot: React.FC = () => {
15-
const isNotificationsPkgInstalledRequest = useApi.notificationsIsInstalled();
15+
const notificationsPkgStatusRequest = useApi.notificationsPackageStatus();
1616

17-
return renderResponse(isNotificationsPkgInstalledRequest, ["Loading notifications"], (isInstalled) => {
17+
return renderResponse(notificationsPkgStatusRequest, ["Loading notifications"], (data) => {
1818
const availableRoutes: {
1919
name: string;
2020
subPath: string;
@@ -23,12 +23,12 @@ export const NotificationsRoot: React.FC = () => {
2323
{
2424
name: "Inbox",
2525
subPath: subPaths.inbox,
26-
component: isInstalled ? Inbox : () => <InstallNotificationsPkg />
26+
component: data.isInstalled ? Inbox : () => <InstallNotificationsPkg />
2727
},
2828
{
2929
name: "Settings",
3030
subPath: subPaths.settings,
31-
component: isInstalled ? NotificationsSettings : () => <InstallNotificationsPkg />
31+
component: data.isInstalled ? NotificationsSettings : () => <InstallNotificationsPkg />
3232
},
3333
{
3434
name: "Legacy",

0 commit comments

Comments
 (0)