From 48a71a4df740ce6a4de0d334196b76f1189723e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?No=C3=A9mien=20Kocher?= <nkcr.je@gmail.com>
Date: Tue, 13 Sep 2022 11:23:36 +0200
Subject: [PATCH 01/10] Fixes initialisation process on the web frontend

Fix #165
---
 .../src/pages/election/components/utils/useChangeAction.tsx   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/web/frontend/src/pages/election/components/utils/useChangeAction.tsx b/web/frontend/src/pages/election/components/utils/useChangeAction.tsx
index 7ccb4b669..5ed008ae3 100644
--- a/web/frontend/src/pages/election/components/utils/useChangeAction.tsx
+++ b/web/frontend/src/pages/election/components/utils/useChangeAction.tsx
@@ -389,8 +389,8 @@ const useChangeAction = (
 
       // TODO: can be modified such that if the majority of the node are
       // initialized than the election status can still be set to initialized
-      const promises = Array.from(nodeProxyAddresses.values()).map((proxy) => {
-        if (proxy !== '') {
+      const promises = Array.from(nodeProxyAddresses).map(([node, proxy]) => {
+        if (proxy !== '' && DKGStatuses.get(node) === NodeStatus.NotInitialized) {
           return initialize(proxy);
         }
         return undefined;

From cda589a16529c9189760b5c7ef5964ce428d32b8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?No=C3=A9mien=20Kocher?= <nkcr.je@gmail.com>
Date: Wed, 14 Sep 2022 14:14:57 +0200
Subject: [PATCH 02/10] Moves the DKG logic to DKGStatusRow

---
 web/frontend/src/mocks/handlers.ts            |   2 +-
 web/frontend/src/pages/election/Show.tsx      |  37 ++++-
 .../src/pages/election/components/Action.tsx  |   9 +-
 .../election/components/ChooseProxyModal.tsx  |   6 +-
 .../election/components/DKGStatusRow.tsx      | 153 +++++++++++++++---
 .../election/components/DKGStatusTable.tsx    |  14 +-
 .../components/utils/useChangeAction.tsx      | 134 +--------------
 web/frontend/src/types/node.ts                |  26 +++
 8 files changed, 211 insertions(+), 170 deletions(-)

diff --git a/web/frontend/src/mocks/handlers.ts b/web/frontend/src/mocks/handlers.ts
index ca53c8b75..5a06a4a67 100644
--- a/web/frontend/src/mocks/handlers.ts
+++ b/web/frontend/src/mocks/handlers.ts
@@ -63,7 +63,7 @@ export const handlers = [
       ? {
           lastname: 'Bobster',
           firstname: 'Alice',
-          role: UserRole.Voter,
+          role: UserRole.Admin,
           sciper: userId,
         }
       : {};
diff --git a/web/frontend/src/pages/election/Show.tsx b/web/frontend/src/pages/election/Show.tsx
index e88241497..5d7d671c0 100644
--- a/web/frontend/src/pages/election/Show.tsx
+++ b/web/frontend/src/pages/election/Show.tsx
@@ -9,7 +9,7 @@ import Modal from 'components/modal/Modal';
 import StatusTimeline from './components/StatusTimeline';
 import Loading from 'pages/Loading';
 import Action from './components/Action';
-import { NodeStatus } from 'types/node';
+import { InternalDKGInfo, NodeStatus } from 'types/node';
 import useGetResults from './components/utils/useGetResults';
 import UserIDTable from './components/UserIDTable';
 import DKGStatusTable from './components/DKGStatusTable';
@@ -42,7 +42,7 @@ const ElectionShow: FC = () => {
 
   const [nodeProxyAddresses, setNodeProxyAddresses] = useState<Map<string, string>>(new Map());
   const [nodeToSetup, setNodeToSetup] = useState<[string, string]>(null);
-  // The status of each node
+  // The status of each node. Key is the node's address.
   const [DKGStatuses, setDKGStatuses] = useState<Map<string, NodeStatus>>(new Map());
 
   const [nodeLoading, setNodeLoading] = useState<Map<string, boolean>>(null);
@@ -51,6 +51,25 @@ const ElectionShow: FC = () => {
   const ongoingItem = 'ongoingAction' + electionID;
   const nodeToSetupItem = 'nodeToSetup' + electionID;
 
+  const notifyDKGState = (node: string, info: InternalDKGInfo) => {
+    console.log('DKG node updated:', info);
+    switch (info.getStatus()) {
+      case NodeStatus.Failed:
+        console.log('DKG node failed');
+        setOngoingAction(OngoingAction.None);
+        break;
+      case NodeStatus.Setup:
+        setOngoingAction(OngoingAction.None);
+        setStatus(Status.Setup);
+        break;
+    }
+
+    const newDKGStatuses = new Map(DKGStatuses);
+    newDKGStatuses.set(node, info.getStatus());
+    setDKGStatuses(newDKGStatuses);
+    console.log('dkg statuses:', DKGStatuses);
+  };
+
   // Fetch result when available after a status change
   useEffect(() => {
     if (status === Status.ResultAvailable && isResultAvailable) {
@@ -76,6 +95,7 @@ const ElectionShow: FC = () => {
     const storedOngoingAction = JSON.parse(window.localStorage.getItem(ongoingItem));
 
     if (storedOngoingAction !== null) {
+      console.log('stored ongoing action:', storedOngoingAction);
       setOngoingAction(storedOngoingAction);
     }
 
@@ -141,14 +161,20 @@ const ElectionShow: FC = () => {
 
         // TODO: can be modified such that if the majority of the node are
         // initialized than the election status can still be set to initialized
-        if (statuses.includes(NodeStatus.NotInitialized)) return;
+        if (statuses.includes(NodeStatus.NotInitialized)) {
+          setOngoingAction(OngoingAction.None);
+          setStatus(Status.Initial);
+          return;
+        }
 
         if (statuses.includes(NodeStatus.Setup)) {
+          setOngoingAction(OngoingAction.None);
           setStatus(Status.Setup);
           return;
         }
 
         if (statuses.includes(NodeStatus.Unreachable)) return;
+        if (statuses.includes(NodeStatus.Failed)) return;
 
         setStatus(Status.Initialized);
 
@@ -220,8 +246,6 @@ const ElectionShow: FC = () => {
                   setOngoingAction={setOngoingAction}
                   nodeToSetup={nodeToSetup}
                   setNodeToSetup={setNodeToSetup}
-                  DKGStatuses={DKGStatuses}
-                  setDKGStatuses={setDKGStatuses}
                 />
               )}
             </div>
@@ -248,6 +272,9 @@ const ElectionShow: FC = () => {
                 setDKGStatuses={setDKGStatuses}
                 setTextModalError={setTextModalError}
                 setShowModalError={setShowModalError}
+                ongoingAction={ongoingAction}
+                notifyDKGState={notifyDKGState}
+                nodeToSetup={nodeToSetup}
               />
             </div>
           </div>
diff --git a/web/frontend/src/pages/election/components/Action.tsx b/web/frontend/src/pages/election/components/Action.tsx
index b08f0e035..ed5319dbb 100644
--- a/web/frontend/src/pages/election/components/Action.tsx
+++ b/web/frontend/src/pages/election/components/Action.tsx
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
 
 import { ID } from 'types/configuration';
 import { OngoingAction, Status } from 'types/election';
-import { NodeStatus } from 'types/node';
 import useChangeAction from './utils/useChangeAction';
 
 type ActionProps = {
@@ -19,8 +18,6 @@ type ActionProps = {
   setOngoingAction: (action: OngoingAction) => void;
   nodeToSetup: [string, string];
   setNodeToSetup: ([node, proxy]: [string, string]) => void;
-  DKGStatuses: Map<string, NodeStatus>;
-  setDKGStatuses: (dkgStatuses: Map<string, NodeStatus>) => void;
 };
 
 const Action: FC<ActionProps> = ({
@@ -36,8 +33,6 @@ const Action: FC<ActionProps> = ({
   setOngoingAction,
   nodeToSetup,
   setNodeToSetup,
-  DKGStatuses,
-  setDKGStatuses,
 }) => {
   const { getAction, modalClose, modalCancel, modalDelete, modalSetup } = useChangeAction(
     status,
@@ -51,9 +46,7 @@ const Action: FC<ActionProps> = ({
     ongoingAction,
     setOngoingAction,
     nodeToSetup,
-    setNodeToSetup,
-    DKGStatuses,
-    setDKGStatuses
+    setNodeToSetup
   );
 
   return (
diff --git a/web/frontend/src/pages/election/components/ChooseProxyModal.tsx b/web/frontend/src/pages/election/components/ChooseProxyModal.tsx
index 060420101..fe3bcc6c5 100644
--- a/web/frontend/src/pages/election/components/ChooseProxyModal.tsx
+++ b/web/frontend/src/pages/election/components/ChooseProxyModal.tsx
@@ -2,12 +2,10 @@ import { Dialog, Transition } from '@headlessui/react';
 import { CogIcon } from '@heroicons/react/outline';
 import { FC, Fragment, useRef } from 'react';
 import { useTranslation } from 'react-i18next';
-import { NodeStatus } from 'types/node';
 
 type ChooseProxyModalProps = {
   roster: string[];
   showModal: boolean;
-  DKGStatuses: Map<string, NodeStatus>;
   nodeProxyAddresses: Map<string, string>;
   nodeToSetup: [string, string];
   setNodeToSetup: (node: [string, string]) => void;
@@ -18,7 +16,6 @@ type ChooseProxyModalProps = {
 const ChooseProxyModal: FC<ChooseProxyModalProps> = ({
   roster,
   showModal,
-  DKGStatuses,
   nodeProxyAddresses,
   nodeToSetup,
   setNodeToSetup,
@@ -48,8 +45,7 @@ const ChooseProxyModal: FC<ChooseProxyModalProps> = ({
       nodeToSetup !== null &&
       roster.map((node, index) => {
         const proxy = nodeProxyAddresses.get(node);
-        const status = DKGStatuses.get(node);
-        const checkable = proxy !== '' && status === NodeStatus.Initialized;
+        const checkable = proxy !== '';
 
         return (
           <div className="flex items-center my-4 ml-4" key={node}>
diff --git a/web/frontend/src/pages/election/components/DKGStatusRow.tsx b/web/frontend/src/pages/election/components/DKGStatusRow.tsx
index 1f81eb185..0819dbd9f 100644
--- a/web/frontend/src/pages/election/components/DKGStatusRow.tsx
+++ b/web/frontend/src/pages/election/components/DKGStatusRow.tsx
@@ -2,10 +2,17 @@ import React, { FC, useEffect, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 
 import * as endpoints from 'components/utils/Endpoints';
-import { NodeProxyAddress, NodeStatus } from 'types/node';
+import { DKGInfo, InternalDKGInfo, NodeProxyAddress, NodeStatus } from 'types/node';
 import { ID } from 'types/configuration';
 import DKGStatus from 'components/utils/DKGStatus';
 import IndigoSpinnerIcon from './IndigoSpinnerIcon';
+import { OngoingAction } from 'types/election';
+import Modal from 'components/modal/Modal';
+import { ExclamationCircleIcon } from '@heroicons/react/outline';
+import { pollDKG } from './utils/PollStatus';
+
+const POLLING_INTERVAL = 1000;
+const MAX_ATTEMPTS = 10;
 
 type DKGStatusRowProps = {
   electionId: ID;
@@ -19,26 +26,34 @@ type DKGStatusRowProps = {
   setDKGStatuses: (DKFStatuses: Map<string, NodeStatus>) => void;
   setTextModalError: (error: string) => void;
   setShowModalError: (show: boolean) => void;
+  // notify to start initialization
+  ongoingAction: OngoingAction;
+  // notify the parent of the new state
+  notifyDKGState: (node: string, info: InternalDKGInfo) => void;
+  nodeToSetup: [string, string];
 };
 
 const DKGStatusRow: FC<DKGStatusRowProps> = ({
   electionId,
-  node,
+  node, // node is the node address, not the proxy
   index,
   loading,
   setLoading,
   nodeProxyAddresses,
   setNodeProxyAddresses,
-  DKGStatuses,
-  setDKGStatuses,
   setTextModalError,
   setShowModalError,
+  ongoingAction,
+  notifyDKGState,
+  nodeToSetup,
 }) => {
   const { t } = useTranslation();
   const [proxy, setProxy] = useState(null);
   const [DKGLoading, setDKGLoading] = useState(true);
   const [status, setStatus] = useState<NodeStatus>(null);
 
+  const [info, setInfo] = useState('');
+
   const abortController = new AbortController();
   const signal = abortController.signal;
   const TIMEOUT = 10000;
@@ -50,13 +65,96 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
     signal: signal,
   };
 
+  const [showModal, setShowModal] = useState(false);
+
+  // Notify the parent each time our status changes
   useEffect(() => {
-    // update status on useChangeAction (i.e. initializing and setting up)
-    if (DKGStatuses.has(node)) {
-      setStatus(DKGStatuses.get(node));
+    notifyDKGState(node, InternalDKGInfo.fromStatus(status));
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [status]);
+
+  const initializeNode = async () => {
+    const req = {
+      method: 'POST',
+      body: JSON.stringify({
+        ElectionID: electionId,
+        Proxy: proxy,
+      }),
+      headers: {
+        'Content-Type': 'application/json',
+      },
+    };
+
+    const response = await fetch(endpoints.dkgActors, req);
+    if (!response.ok) {
+      const txt = await response.text();
+      throw new Error(txt);
+    }
+  };
+
+  // Signal the start of DKG initialization
+  useEffect(() => {
+    if (
+      ongoingAction === OngoingAction.Initializing &&
+      (status === NodeStatus.NotInitialized || status === NodeStatus.Failed)
+    ) {
+      // TODO: can be modified such that if the majority of the node are
+      // initialized than the election status can still be set to initialized
+
+      setDKGLoading(true);
+
+      initializeNode()
+        .then(() => {
+          setStatus(NodeStatus.Initialized);
+        })
+        .catch((e) => {
+          setInfo(e.toString());
+          setStatus(NodeStatus.Failed);
+        });
     }
     // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [DKGStatuses]);
+  }, [ongoingAction]);
+
+  const pollDKGStatus = (statusToMatch: NodeStatus) => {
+    const req = {
+      method: 'GET',
+      signal: signal,
+    };
+
+    const match = (s: NodeStatus) => s === statusToMatch;
+
+    return pollDKG(
+      endpoints.getDKGActors(proxy, electionId),
+      req,
+      match,
+      POLLING_INTERVAL,
+      MAX_ATTEMPTS
+    );
+  };
+
+  useEffect(() => {
+    if (
+      ongoingAction === OngoingAction.SettingUp &&
+      nodeToSetup !== null &&
+      nodeToSetup[0] === node
+    ) {
+      setDKGLoading(true);
+      pollDKGStatus(NodeStatus.Setup)
+        .then(
+          () => {
+            setStatus(NodeStatus.Setup);
+          },
+          (reason: any) => {
+            setStatus(NodeStatus.Failed);
+            setInfo(reason.toString());
+          }
+        )
+        .catch((e) => {
+          console.log('error:', e);
+        });
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [ongoingAction]);
 
   // Set the mapping of the node and proxy address (only if the address was not
   // already fetched)
@@ -96,6 +194,8 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
         }
       };
 
+      setDKGLoading(true);
+
       fetchNodeProxy().then((nodeProxyAddress) => {
         setProxy(nodeProxyAddress.Proxy);
       });
@@ -116,12 +216,12 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
   // Fetch the status of the nodes
   useEffect(() => {
     if (proxy !== null && status === null) {
-      const fetchDKGStatus = async () => {
+      const fetchDKGStatus = async (): Promise<InternalDKGInfo> => {
         // If we were not able to retrieve the proxy address of the node,
         // still return a resolved promise so that promise.then() goes to onSuccess().
         // Error was already displayed, no need to throw another one.
         if (proxy === '') {
-          return NodeStatus.Unreachable;
+          return InternalDKGInfo.fromStatus(NodeStatus.Unreachable);
         }
 
         try {
@@ -132,7 +232,7 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
           const response = await fetch(endpoints.getDKGActors(proxy, electionId), request);
 
           if (response.status === 404) {
-            return NodeStatus.NotInitialized;
+            return InternalDKGInfo.fromStatus(NodeStatus.NotInitialized);
           }
 
           if (!response.ok) {
@@ -141,7 +241,8 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
           }
 
           let dataReceived = await response.json();
-          return dataReceived.Status as NodeStatus;
+          console.log('data received:', dataReceived);
+          return InternalDKGInfo.fromInfo(dataReceived as DKGInfo);
         } catch (e) {
           let errorMessage = t('errorRetrievingNodes');
 
@@ -157,12 +258,13 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
 
           // if we could not retrieve the proxy still resolve the promise
           // so that promise.then() goes to onSuccess() but display the error
-          return NodeStatus.Unreachable;
+          return InternalDKGInfo.fromStatus(NodeStatus.Unreachable);
         }
       };
 
-      fetchDKGStatus().then((nodeStatus) => {
-        setStatus(nodeStatus);
+      fetchDKGStatus().then((internalStatus) => {
+        setStatus(internalStatus.getStatus());
+        setInfo(internalStatus.getError());
       });
     }
     // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -173,11 +275,6 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
     if (status !== null) {
       setDKGLoading(false);
 
-      // notify parent
-      const newDKGStatuses = new Map(DKGStatuses);
-      newDKGStatuses.set(node, status);
-      setDKGStatuses(newDKGStatuses);
-
       const newLoading = new Map(loading);
       newLoading.set(node, false);
       setLoading(newLoading);
@@ -190,8 +287,22 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
       <td className="px-1.5 sm:px-6 py-4 font-medium text-gray-900 whitespace-nowrap truncate">
         {t('node')} {index} ({node})
       </td>
-      <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
+      <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 flex flex-row">
         {!DKGLoading ? <DKGStatus status={status} /> : <IndigoSpinnerIcon />}
+        <Modal
+          textModal={info}
+          buttonRightText="close"
+          setShowModal={setShowModal}
+          showModal={showModal}
+        />
+        {info !== '' && (
+          <button
+            onClick={() => {
+              setShowModal(true);
+            }}>
+            <ExclamationCircleIcon className="ml-3 mr-2 h-5 w-5 stroke-orange-800" />
+          </button>
+        )}
       </td>
     </tr>
   );
diff --git a/web/frontend/src/pages/election/components/DKGStatusTable.tsx b/web/frontend/src/pages/election/components/DKGStatusTable.tsx
index 4214f264a..53d451480 100644
--- a/web/frontend/src/pages/election/components/DKGStatusTable.tsx
+++ b/web/frontend/src/pages/election/components/DKGStatusTable.tsx
@@ -1,7 +1,8 @@
 import { FC } from 'react';
 import { useTranslation } from 'react-i18next';
 import { ID } from 'types/configuration';
-import { NodeStatus } from 'types/node';
+import { OngoingAction } from 'types/election';
+import { InternalDKGInfo, NodeStatus } from 'types/node';
 import DKGStatusRow from './DKGStatusRow';
 
 type DKGStatusTableProps = {
@@ -15,6 +16,11 @@ type DKGStatusTableProps = {
   setDKGStatuses: (DKFStatuses: Map<string, NodeStatus>) => void;
   setTextModalError: (error: string) => void;
   setShowModalError: (show: boolean) => void;
+  // notify to start initialization
+  ongoingAction: OngoingAction;
+  // notify the parent of the new state
+  notifyDKGState: (node: string, info: InternalDKGInfo) => void;
+  nodeToSetup: [string, string];
 };
 
 const DKGStatusTable: FC<DKGStatusTableProps> = ({
@@ -28,6 +34,9 @@ const DKGStatusTable: FC<DKGStatusTableProps> = ({
   setDKGStatuses,
   setTextModalError,
   setShowModalError,
+  ongoingAction,
+  notifyDKGState,
+  nodeToSetup,
 }) => {
   const { t } = useTranslation();
 
@@ -61,6 +70,9 @@ const DKGStatusTable: FC<DKGStatusTableProps> = ({
                   setDKGStatuses={setDKGStatuses}
                   setTextModalError={setTextModalError}
                   setShowModalError={setShowModalError}
+                  ongoingAction={ongoingAction}
+                  notifyDKGState={notifyDKGState}
+                  nodeToSetup={nodeToSetup}
                 />
               ))}
           </tbody>
diff --git a/web/frontend/src/pages/election/components/utils/useChangeAction.tsx b/web/frontend/src/pages/election/components/utils/useChangeAction.tsx
index 5ed008ae3..a6aa2e6a5 100644
--- a/web/frontend/src/pages/election/components/utils/useChangeAction.tsx
+++ b/web/frontend/src/pages/election/components/utils/useChangeAction.tsx
@@ -4,8 +4,7 @@ import { useTranslation } from 'react-i18next';
 import * as endpoints from 'components/utils/Endpoints';
 import { ID } from 'types/configuration';
 import { Action, OngoingAction, Status } from 'types/election';
-import { pollDKG, pollElection } from './PollStatus';
-import { NodeStatus } from 'types/node';
+import { pollElection } from './PollStatus';
 import { AuthContext, FlashContext, FlashLevel, ProxyContext } from 'index';
 import { useNavigate } from 'react-router';
 import { ROUTE_ELECTION_INDEX } from 'Routes';
@@ -24,7 +23,6 @@ import OpenButton from '../ActionButtons/OpenButton';
 import ResultButton from '../ActionButtons/ResultButton';
 import ShuffleButton from '../ActionButtons/ShuffleButton';
 import VoteButton from '../ActionButtons/VoteButton';
-import NoActionAvailable from '../ActionButtons/NoActionAvailable';
 import handleLogin from 'pages/session/HandleLogin';
 import { UserRole } from 'types/userRole';
 
@@ -40,12 +38,9 @@ const useChangeAction = (
   ongoingAction: OngoingAction,
   setOngoingAction: (action: OngoingAction) => void,
   nodeToSetup: [string, string],
-  setNodeToSetup: ([node, proxy]: [string, string]) => void,
-  DKGStatuses: Map<string, NodeStatus>,
-  setDKGStatuses: (dkgStatuses: Map<string, NodeStatus>) => void
+  setNodeToSetup: ([node, proxy]: [string, string]) => void
 ) => {
   const { t } = useTranslation();
-  const [isInitializing, setIsInitializing] = useState(false);
   const [, setIsPosting] = useState(false);
 
   const [showModalProxySetup, setShowModalProxySetup] = useState(false);
@@ -101,7 +96,6 @@ const useChangeAction = (
     <ChooseProxyModal
       roster={roster}
       showModal={showModalProxySetup}
-      DKGStatuses={DKGStatuses}
       nodeProxyAddresses={nodeProxyAddresses}
       nodeToSetup={nodeToSetup}
       setNodeToSetup={setNodeToSetup}
@@ -123,20 +117,6 @@ const useChangeAction = (
     return sendFetchRequest(endpoint, req, setIsPosting);
   };
 
-  const initializeNode = async (proxy: string) => {
-    const request = {
-      method: 'POST',
-      body: JSON.stringify({
-        ElectionID: electionID,
-        Proxy: proxy,
-      }),
-      headers: {
-        'Content-Type': 'application/json',
-      },
-    };
-    return sendFetchRequest(endpoints.dkgActors, request, setIsPosting);
-  };
-
   const onFullFilled = (nextStatus: Status) => {
     if (setGetError !== null && setGetError !== undefined) {
       setGetError(null);
@@ -185,80 +165,16 @@ const useChangeAction = (
       });
   };
 
-  const pollDKGStatus = (proxy: string, statusToMatch: NodeStatus) => {
-    const request = {
-      method: 'GET',
-      signal: signal,
-    };
-
-    const match = (s: NodeStatus) => s === statusToMatch;
-
-    return pollDKG(
-      endpoints.getDKGActors(proxy, electionID),
-      request,
-      match,
-      POLLING_INTERVAL,
-      MAX_ATTEMPTS
-    );
-  };
-
   // Start to poll when there is an ongoingAction
   useEffect(() => {
     // use an abortController to stop polling when the component is unmounted
 
     switch (ongoingAction) {
       case OngoingAction.Initializing:
-        if (nodeProxyAddresses !== null) {
-          // TODO: can be modified such that if the majority of the node are
-          // initialized than the election status can still be set to initialized
-          const promises = Array.from(nodeProxyAddresses.values()).map((proxy) => {
-            if (proxy !== '') {
-              return pollDKGStatus(proxy, NodeStatus.Initialized);
-            }
-            return undefined;
-          });
-
-          Promise.all(promises).then(
-            () => {
-              onFullFilled(Status.Initialized);
-              const newDKGStatuses = new Map(DKGStatuses);
-              nodeProxyAddresses.forEach((proxy, node) => {
-                if (proxy !== '') {
-                  newDKGStatuses.set(node, NodeStatus.Initialized);
-                }
-              });
-              setDKGStatuses(newDKGStatuses);
-            },
-            (reason: any) => onRejected(reason, Status.Initial)
-          );
-        }
+        // Initializing is handled by each row of the DKG table
         break;
       case OngoingAction.SettingUp:
-        if (nodeToSetup !== null) {
-          pollDKGStatus(nodeToSetup[1], NodeStatus.Setup)
-            .then(
-              () => {
-                onFullFilled(Status.Setup);
-                const newDKGStatuses = new Map(DKGStatuses);
-                newDKGStatuses.set(nodeToSetup[0], NodeStatus.Setup);
-                setDKGStatuses(newDKGStatuses);
-              },
-              (reason: any) => {
-                onRejected(reason, Status.Initialized);
-
-                if (!(reason instanceof DOMException)) {
-                  const newDKGStatuses = new Map(DKGStatuses);
-                  newDKGStatuses.set(nodeToSetup[0], NodeStatus.Failed);
-                  setDKGStatuses(newDKGStatuses);
-                }
-              }
-            )
-            .catch((e) => {
-              setStatus(Status.Initialized);
-              setGetError(e.message);
-              setShowModalError(true);
-            });
-        }
+        // Initializing is handled by each row of the DKG table
         break;
       case OngoingAction.Opening:
         pollElectionStatus(Status.Setup, Status.Open);
@@ -375,34 +291,6 @@ const useChangeAction = (
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [userConfirmedDeleting]);
 
-  useEffect(() => {
-    if (isInitializing) {
-      const initialize = async (proxy: string) => {
-        setOngoingAction(OngoingAction.Initializing);
-        const initSuccess = await initializeNode(proxy);
-
-        if (!initSuccess) {
-          setStatus(Status.Initial);
-          setOngoingAction(OngoingAction.None);
-        }
-      };
-
-      // TODO: can be modified such that if the majority of the node are
-      // initialized than the election status can still be set to initialized
-      const promises = Array.from(nodeProxyAddresses).map(([node, proxy]) => {
-        if (proxy !== '' && DKGStatuses.get(node) === NodeStatus.NotInitialized) {
-          return initialize(proxy);
-        }
-        return undefined;
-      });
-
-      Promise.all(promises).then(() => {
-        setIsInitializing(false);
-      });
-    }
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [isInitializing]);
-
   useEffect(() => {
     if (userConfirmedProxySetup) {
       const setup = async () => {
@@ -438,7 +326,7 @@ const useChangeAction = (
   }, [userConfirmedProxySetup]);
 
   const handleInitialize = () => {
-    setIsInitializing(true);
+    setOngoingAction(OngoingAction.Initializing);
   };
 
   const handleSetup = () => {
@@ -505,18 +393,6 @@ const useChangeAction = (
   };
 
   const getAction = () => {
-    // Check that more than 2/3 of the nodes are working, if not actions are
-    // disabled, since consensus cannot be achieved
-    const consensus = 2 / 3;
-
-    if (
-      Array.from(DKGStatuses.values()).filter((nodeStatus) => nodeStatus !== NodeStatus.Unreachable)
-        .length <=
-      consensus * roster.length
-    ) {
-      return <NoActionAvailable />;
-    }
-
     // Except for seeing the results, all actions at least require the users
     // to be logged in
     if (!isLogged && status !== Status.ResultAvailable) {
diff --git a/web/frontend/src/types/node.ts b/web/frontend/src/types/node.ts
index 69bd94105..acccd0ee2 100644
--- a/web/frontend/src/types/node.ts
+++ b/web/frontend/src/types/node.ts
@@ -21,4 +21,30 @@ interface NodeProxyAddress {
   Proxy: string;
 }
 
+// InternalDKGInfo is used to internally provide the status of DKG on a node.
+class InternalDKGInfo {
+  static fromStatus(status: NodeStatus): InternalDKGInfo {
+    return new InternalDKGInfo(status, undefined);
+  }
+
+  static fromInfo(info: DKGInfo): InternalDKGInfo {
+    return new InternalDKGInfo(info.Status, info.Error);
+  }
+
+  private constructor(private status: NodeStatus, private error: DKGInfo['Error']) {}
+
+  getError(): string {
+    if (this.error === undefined || this.error.Title === '') {
+      return '';
+    }
+
+    return this.error.Title + ' - ' + this.error.Code + ' - ' + this.error.Message;
+  }
+
+  getStatus(): NodeStatus {
+    return this.status;
+  }
+}
+
 export type { DKGInfo, NodeProxyAddress };
+export { InternalDKGInfo };

From 958f7021816c25eae5eec47903accd715c890de3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?No=C3=A9mien=20Kocher?= <nkcr.je@gmail.com>
Date: Thu, 15 Sep 2022 12:02:56 +0200
Subject: [PATCH 03/10] Adds new DKG states and continues the refactoring

---
 services/dkg/mod.go                           |  8 ++
 services/dkg/pedersen/handler.go              | 11 ++-
 services/dkg/pedersen/mod.go                  | 14 +--
 services/dkg/pedersen/mod_test.go             |  2 +-
 .../src/components/utils/DKGStatus.tsx        | 28 ++++++
 web/frontend/src/pages/election/Show.tsx      | 13 ++-
 .../election/components/DKGStatusRow.tsx      | 92 +++++++++----------
 .../election/components/DKGStatusTable.tsx    | 17 +---
 .../election/components/utils/PollStatus.ts   |  9 +-
 web/frontend/src/types/node.ts                |  8 ++
 10 files changed, 127 insertions(+), 75 deletions(-)

diff --git a/services/dkg/mod.go b/services/dkg/mod.go
index 54f750709..fbe43058b 100644
--- a/services/dkg/mod.go
+++ b/services/dkg/mod.go
@@ -24,6 +24,14 @@ const (
 	Setup StatusCode = 1
 	// Failed is when the actor failed to set up
 	Failed StatusCode = 2
+	// Dealing is when the actor is sending its deals
+	Dealing = 3
+	// Responding is when the actor sends its responses on the deals
+	Responding = 4
+	// Certifying is when the actor is validating its responses
+	Certifying = 5
+	// Certified is then the actor is certified
+	Certified = 6
 )
 
 // DKG defines the primitive to start a DKG protocol
diff --git a/services/dkg/pedersen/handler.go b/services/dkg/pedersen/handler.go
index 925071f9f..e440de747 100644
--- a/services/dkg/pedersen/handler.go
+++ b/services/dkg/pedersen/handler.go
@@ -23,6 +23,7 @@ import (
 
 	etypes "github.com/dedis/d-voting/contracts/evoting/types"
 	"github.com/dedis/d-voting/internal/testing/fake"
+	"github.com/dedis/d-voting/services/dkg"
 	"github.com/dedis/d-voting/services/dkg/pedersen/types"
 	"go.dedis.ch/dela"
 	"go.dedis.ch/dela/core/ordering"
@@ -70,12 +71,14 @@ type Handler struct {
 
 	log     zerolog.Logger
 	running bool
+
+	status *dkg.Status
 }
 
 // NewHandler creates a new handler
 func NewHandler(me mino.Address, service ordering.Service, pool pool.Pool,
 	txnmngr txn.Manager, pubSharesSigner crypto.Signer, handlerData HandlerData,
-	context serde.Context, electionFac serde.Factory) *Handler {
+	context serde.Context, electionFac serde.Factory, status *dkg.Status) *Handler {
 
 	privKey := handlerData.PrivKey
 	pubKey := handlerData.PubKey
@@ -101,6 +104,8 @@ func NewHandler(me mino.Address, service ordering.Service, pool pool.Pool,
 
 		log:     log,
 		running: false,
+
+		status: status,
 	}
 }
 
@@ -255,12 +260,15 @@ func (h *Handler) start(start types.Start, deals, resps *list.List, from mino.Ad
 // doDKG calls the subsequent DKG steps
 func (h *Handler) doDKG(deals, resps *list.List, out mino.Sender, from mino.Address) {
 	h.log.Info().Str("action", "deal").Msg("new state")
+	*h.status = dkg.Status{Status: dkg.Dealing}
 	h.deal(out)
 
 	h.log.Info().Str("action", "respond").Msg("new state")
+	*h.status = dkg.Status{Status: dkg.Responding}
 	h.respond(deals, out)
 
 	h.log.Info().Str("action", "certify").Msg("new state")
+	*h.status = dkg.Status{Status: dkg.Certifying}
 	err := h.certify(resps, out)
 	if err != nil {
 		dela.Logger.Error().Msgf("failed to certify: %v", err)
@@ -268,6 +276,7 @@ func (h *Handler) doDKG(deals, resps *list.List, out mino.Sender, from mino.Addr
 	}
 
 	h.log.Info().Str("action", "finalize").Msg("new state")
+	*h.status = dkg.Status{Status: dkg.Certified}
 
 	// Send back the public DKG key
 	distKey, err := h.dkg.DistKeyShare()
diff --git a/services/dkg/pedersen/mod.go b/services/dkg/pedersen/mod.go
index a69366d4e..6b914f0c5 100644
--- a/services/dkg/pedersen/mod.go
+++ b/services/dkg/pedersen/mod.go
@@ -118,9 +118,11 @@ func (s *Pedersen) NewActor(electionIDBuf []byte, pool pool.Pool, txmngr txn.Man
 
 	ctx := jsonserde.NewContext()
 
+	status := &dkg.Status{Status: dkg.Initialized}
+
 	// link the actor to an RPC by the election ID
 	h := NewHandler(s.mino.GetAddress(), s.service, pool, txmngr, s.signer,
-		handlerData, ctx, s.electionFac)
+		handlerData, ctx, s.electionFac, status)
 
 	no := s.mino.WithSegment(electionID)
 	rpc := mino.MustCreateRPC(no, RPC, h, s.factory)
@@ -135,7 +137,7 @@ func (s *Pedersen) NewActor(electionIDBuf []byte, pool pool.Pool, txmngr txn.Man
 		electionFac: s.electionFac,
 		handler:     h,
 		electionID:  electionID,
-		status:      dkg.Status{Status: dkg.Initialized},
+		status:      status,
 		log:         log,
 	}
 
@@ -167,12 +169,12 @@ type Actor struct {
 	electionFac serde.Factory
 	handler     *Handler
 	electionID  string
-	status      dkg.Status
+	status      *dkg.Status
 	log         zerolog.Logger
 }
 
 func (a *Actor) setErr(err error, args map[string]interface{}) {
-	a.status = dkg.Status{
+	*a.status = dkg.Status{
 		Status: dkg.Failed,
 		Err:    err,
 		Args:   args,
@@ -313,7 +315,7 @@ func (a *Actor) Setup() (kyber.Point, error) {
 		a.log.Info().Msgf("ok for %s", addr.String())
 	}
 
-	a.status = dkg.Status{Status: dkg.Setup}
+	*a.status = dkg.Status{Status: dkg.Setup}
 	evoting.PromElectionDkgStatus.WithLabelValues(a.electionID).Set(float64(dkg.Setup))
 
 	return dkgPubKeys[0], nil
@@ -397,7 +399,7 @@ func (a *Actor) MarshalJSON() ([]byte, error) {
 
 // Status implements dkg.Actor
 func (a *Actor) Status() dkg.Status {
-	return a.status
+	return *a.status
 }
 
 func electionExists(service ordering.Service, electionIDBuf []byte) (ordering.Proof, bool) {
diff --git a/services/dkg/pedersen/mod_test.go b/services/dkg/pedersen/mod_test.go
index 542bc92ac..cf4b70a4d 100644
--- a/services/dkg/pedersen/mod_test.go
+++ b/services/dkg/pedersen/mod_test.go
@@ -184,7 +184,7 @@ func TestPedersen_InitNonEmptyMap(t *testing.T) {
 
 		otherActor := Actor{
 			handler: NewHandler(fake.NewAddress(0), &fake.Service{}, &fake.Pool{},
-				fake.Manager{}, fake.Signer{}, handlerData, serdecontext, electionFac),
+				fake.Manager{}, fake.Signer{}, handlerData, serdecontext, electionFac, nil),
 		}
 
 		requireActorsEqual(t, actor, &otherActor)
diff --git a/web/frontend/src/components/utils/DKGStatus.tsx b/web/frontend/src/components/utils/DKGStatus.tsx
index 0e6d1a6d0..980449028 100644
--- a/web/frontend/src/components/utils/DKGStatus.tsx
+++ b/web/frontend/src/components/utils/DKGStatus.tsx
@@ -49,6 +49,34 @@ const DKGStatus: FC<DKGStatusProps> = ({ status }) => {
             <div>{t('failed')}</div>
           </div>
         );
+      case NodeStatus.Dealing:
+        return (
+          <div className="flex items-center">
+            <div className="block h-4 w-4 bg-blue-500 rounded-full mr-2"></div>
+            <div>{t('dealing')}</div>
+          </div>
+        );
+      case NodeStatus.Responding:
+        return (
+          <div className="flex items-center">
+            <div className="block h-4 w-4 bg-blue-500 rounded-full mr-2"></div>
+            <div>{t('responding')}</div>
+          </div>
+        );
+      case NodeStatus.Certifying:
+        return (
+          <div className="flex items-center">
+            <div className="block h-4 w-4 bg-blue-500 rounded-full mr-2"></div>
+            <div>{t('certifying')}</div>
+          </div>
+        );
+      case NodeStatus.Certified:
+        return (
+          <div className="flex items-center">
+            <div className="block h-4 w-4 bg-green-500 rounded-full mr-2"></div>
+            <div>{t('certified')}</div>
+          </div>
+        );
       default:
         return null;
     }
diff --git a/web/frontend/src/pages/election/Show.tsx b/web/frontend/src/pages/election/Show.tsx
index 5d7d671c0..3f9c045ec 100644
--- a/web/frontend/src/pages/election/Show.tsx
+++ b/web/frontend/src/pages/election/Show.tsx
@@ -51,6 +51,7 @@ const ElectionShow: FC = () => {
   const ongoingItem = 'ongoingAction' + electionID;
   const nodeToSetupItem = 'nodeToSetup' + electionID;
 
+  // called by a DKG row
   const notifyDKGState = (node: string, info: InternalDKGInfo) => {
     console.log('DKG node updated:', info);
     switch (info.getStatus()) {
@@ -70,6 +71,13 @@ const ElectionShow: FC = () => {
     console.log('dkg statuses:', DKGStatuses);
   };
 
+  // called by a DKG row
+  const notifyLoading = (node: string, l: boolean) => {
+    const newLoading = new Map(nodeLoading);
+    newLoading.set(node, l);
+    setNodeLoading(newLoading);
+  };
+
   // Fetch result when available after a status change
   useEffect(() => {
     if (status === Status.ResultAvailable && isResultAvailable) {
@@ -264,17 +272,14 @@ const ElectionShow: FC = () => {
               <DKGStatusTable
                 roster={roster}
                 electionId={electionId}
-                loading={nodeLoading}
-                setLoading={setNodeLoading}
                 nodeProxyAddresses={nodeProxyAddresses}
                 setNodeProxyAddresses={setNodeProxyAddresses}
-                DKGStatuses={DKGStatuses}
-                setDKGStatuses={setDKGStatuses}
                 setTextModalError={setTextModalError}
                 setShowModalError={setShowModalError}
                 ongoingAction={ongoingAction}
                 notifyDKGState={notifyDKGState}
                 nodeToSetup={nodeToSetup}
+                notifyLoading={notifyLoading}
               />
             </div>
           </div>
diff --git a/web/frontend/src/pages/election/components/DKGStatusRow.tsx b/web/frontend/src/pages/election/components/DKGStatusRow.tsx
index 0819dbd9f..1db1c57e9 100644
--- a/web/frontend/src/pages/election/components/DKGStatusRow.tsx
+++ b/web/frontend/src/pages/election/components/DKGStatusRow.tsx
@@ -12,33 +12,29 @@ import { ExclamationCircleIcon } from '@heroicons/react/outline';
 import { pollDKG } from './utils/PollStatus';
 
 const POLLING_INTERVAL = 1000;
-const MAX_ATTEMPTS = 10;
+const MAX_ATTEMPTS = 20;
 
 type DKGStatusRowProps = {
   electionId: ID;
   node: string;
   index: number;
-  loading: Map<string, boolean>;
-  setLoading: (loading: Map<string, boolean>) => void;
   nodeProxyAddresses: Map<string, string>;
   setNodeProxyAddresses: (nodeProxy: Map<string, string>) => void;
-  DKGStatuses: Map<string, NodeStatus>;
-  setDKGStatuses: (DKFStatuses: Map<string, NodeStatus>) => void;
   setTextModalError: (error: string) => void;
   setShowModalError: (show: boolean) => void;
   // notify to start initialization
   ongoingAction: OngoingAction;
   // notify the parent of the new state
   notifyDKGState: (node: string, info: InternalDKGInfo) => void;
+  // contains the node/proxy address of the node to setup
   nodeToSetup: [string, string];
+  notifyLoading: (node: string, loading: boolean) => void;
 };
 
 const DKGStatusRow: FC<DKGStatusRowProps> = ({
   electionId,
   node, // node is the node address, not the proxy
   index,
-  loading,
-  setLoading,
   nodeProxyAddresses,
   setNodeProxyAddresses,
   setTextModalError,
@@ -46,6 +42,7 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
   ongoingAction,
   notifyDKGState,
   nodeToSetup,
+  notifyLoading,
 }) => {
   const { t } = useTranslation();
   const [proxy, setProxy] = useState(null);
@@ -73,6 +70,13 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [status]);
 
+  // Notify the parent when we are loading or not
+  useEffect(() => {
+    notifyLoading(node, DKGLoading);
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [DKGLoading]);
+
+  // send the initialization request
   const initializeNode = async () => {
     const req = {
       method: 'POST',
@@ -92,67 +96,71 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
     }
   };
 
-  // Signal the start of DKG initialization
+  // Initialize the node if the initialization is ongoing and we are in a
+  // legitimate status.
   useEffect(() => {
     if (
       ongoingAction === OngoingAction.Initializing &&
       (status === NodeStatus.NotInitialized || status === NodeStatus.Failed)
     ) {
-      // TODO: can be modified such that if the majority of the node are
-      // initialized than the election status can still be set to initialized
-
       setDKGLoading(true);
 
       initializeNode()
         .then(() => {
           setStatus(NodeStatus.Initialized);
         })
-        .catch((e) => {
+        .catch((e: Error) => {
           setInfo(e.toString());
           setStatus(NodeStatus.Failed);
+        })
+        .finally(() => {
+          setDKGLoading(false);
         });
     }
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [ongoingAction]);
 
-  const pollDKGStatus = (statusToMatch: NodeStatus) => {
+  const pollDKGStatus = (statusToMatch: NodeStatus): Promise<DKGInfo> => {
     const req = {
       method: 'GET',
       signal: signal,
     };
 
     const match = (s: NodeStatus) => s === statusToMatch;
+    const statusUpdate = (s: NodeStatus) => setStatus(s);
 
     return pollDKG(
       endpoints.getDKGActors(proxy, electionId),
       req,
       match,
       POLLING_INTERVAL,
-      MAX_ATTEMPTS
+      MAX_ATTEMPTS,
+      statusUpdate
     );
   };
 
+  // Action taken when the setting up is triggered.
   useEffect(() => {
-    if (
-      ongoingAction === OngoingAction.SettingUp &&
-      nodeToSetup !== null &&
-      nodeToSetup[0] === node
-    ) {
-      setDKGLoading(true);
-      pollDKGStatus(NodeStatus.Setup)
-        .then(
-          () => {
-            setStatus(NodeStatus.Setup);
-          },
-          (reason: any) => {
-            setStatus(NodeStatus.Failed);
-            setInfo(reason.toString());
-          }
-        )
-        .catch((e) => {
-          console.log('error:', e);
-        });
+    if (ongoingAction !== OngoingAction.SettingUp || nodeToSetup === null) {
+      return;
+    }
+
+    setDKGLoading(true);
+
+    let expectedStatus = NodeStatus.Certified;
+    if (nodeToSetup[0] === node) {
+      expectedStatus = NodeStatus.Setup;
     }
+
+    pollDKGStatus(expectedStatus)
+      .then(
+        () => {},
+        (e: Error) => {
+          setStatus(NodeStatus.Failed);
+          setInfo(e.toString());
+        }
+      )
+      .finally(() => setDKGLoading(false));
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [ongoingAction]);
 
@@ -241,7 +249,7 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
           }
 
           let dataReceived = await response.json();
-          console.log('data received:', dataReceived);
+
           return InternalDKGInfo.fromInfo(dataReceived as DKGInfo);
         } catch (e) {
           let errorMessage = t('errorRetrievingNodes');
@@ -265,30 +273,20 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
       fetchDKGStatus().then((internalStatus) => {
         setStatus(internalStatus.getStatus());
         setInfo(internalStatus.getError());
+        setDKGLoading(false);
       });
     }
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [proxy, status]);
 
-  useEffect(() => {
-    // UseEffect prevents the race condition on setDKGStatuses
-    if (status !== null) {
-      setDKGLoading(false);
-
-      const newLoading = new Map(loading);
-      newLoading.set(node, false);
-      setLoading(newLoading);
-    }
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [status]);
-
   return (
     <tr key={node} className="bg-white border-b hover:bg-gray-50">
       <td className="px-1.5 sm:px-6 py-4 font-medium text-gray-900 whitespace-nowrap truncate">
         {t('node')} {index} ({node})
       </td>
       <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 flex flex-row">
-        {!DKGLoading ? <DKGStatus status={status} /> : <IndigoSpinnerIcon />}
+        {DKGLoading && <IndigoSpinnerIcon />}
+        <DKGStatus status={status} />
         <Modal
           textModal={info}
           buttonRightText="close"
diff --git a/web/frontend/src/pages/election/components/DKGStatusTable.tsx b/web/frontend/src/pages/election/components/DKGStatusTable.tsx
index 53d451480..6d28b0241 100644
--- a/web/frontend/src/pages/election/components/DKGStatusTable.tsx
+++ b/web/frontend/src/pages/election/components/DKGStatusTable.tsx
@@ -2,18 +2,14 @@ import { FC } from 'react';
 import { useTranslation } from 'react-i18next';
 import { ID } from 'types/configuration';
 import { OngoingAction } from 'types/election';
-import { InternalDKGInfo, NodeStatus } from 'types/node';
+import { InternalDKGInfo } from 'types/node';
 import DKGStatusRow from './DKGStatusRow';
 
 type DKGStatusTableProps = {
   roster: string[];
   electionId: ID;
-  loading: Map<string, boolean>;
-  setLoading: (loading: Map<string, boolean>) => void;
   nodeProxyAddresses: Map<string, string>;
   setNodeProxyAddresses: (nodeProxy: Map<string, string>) => void;
-  DKGStatuses: Map<string, NodeStatus>;
-  setDKGStatuses: (DKFStatuses: Map<string, NodeStatus>) => void;
   setTextModalError: (error: string) => void;
   setShowModalError: (show: boolean) => void;
   // notify to start initialization
@@ -21,22 +17,20 @@ type DKGStatusTableProps = {
   // notify the parent of the new state
   notifyDKGState: (node: string, info: InternalDKGInfo) => void;
   nodeToSetup: [string, string];
+  notifyLoading: (node: string, loading: boolean) => void;
 };
 
 const DKGStatusTable: FC<DKGStatusTableProps> = ({
   roster,
   electionId,
-  loading,
-  setLoading,
   nodeProxyAddresses,
   setNodeProxyAddresses,
-  DKGStatuses,
-  setDKGStatuses,
   setTextModalError,
   setShowModalError,
   ongoingAction,
   notifyDKGState,
   nodeToSetup,
+  notifyLoading,
 }) => {
   const { t } = useTranslation();
 
@@ -62,17 +56,14 @@ const DKGStatusTable: FC<DKGStatusTableProps> = ({
                   electionId={electionId}
                   node={node}
                   index={index}
-                  loading={loading}
-                  setLoading={setLoading}
                   nodeProxyAddresses={nodeProxyAddresses}
                   setNodeProxyAddresses={setNodeProxyAddresses}
-                  DKGStatuses={DKGStatuses}
-                  setDKGStatuses={setDKGStatuses}
                   setTextModalError={setTextModalError}
                   setShowModalError={setShowModalError}
                   ongoingAction={ongoingAction}
                   notifyDKGState={notifyDKGState}
                   nodeToSetup={nodeToSetup}
+                  notifyLoading={notifyLoading}
                 />
               ))}
           </tbody>
diff --git a/web/frontend/src/pages/election/components/utils/PollStatus.ts b/web/frontend/src/pages/election/components/utils/PollStatus.ts
index 243c4fde5..4157e7ca4 100644
--- a/web/frontend/src/pages/election/components/utils/PollStatus.ts
+++ b/web/frontend/src/pages/election/components/utils/PollStatus.ts
@@ -44,8 +44,9 @@ const pollDKG = (
   request: RequestInit,
   validate: (status: NodeStatus) => boolean,
   interval: number,
-  maxAttempts: number
-) => {
+  maxAttempts: number,
+  status: (status: NodeStatus) => void
+): Promise<DKGInfo> => {
   let attempts = 0;
 
   const executePoll = async (resolve, reject) => {
@@ -68,6 +69,8 @@ const pollDKG = (
         throw new Error(JSON.stringify(result));
       }
 
+      status(result.Status);
+
       if (validate(result.Status)) {
         return resolve(result);
       }
@@ -94,7 +97,7 @@ const pollDKG = (
     }
   };
 
-  return new Promise(executePoll);
+  return new Promise<DKGInfo>(executePoll);
 };
 
 export { pollElection, pollDKG };
diff --git a/web/frontend/src/types/node.ts b/web/frontend/src/types/node.ts
index acccd0ee2..100d6efcd 100644
--- a/web/frontend/src/types/node.ts
+++ b/web/frontend/src/types/node.ts
@@ -9,6 +9,14 @@ export const enum NodeStatus {
   Setup,
   // Failed is when the actor failed to set up
   Failed,
+  // Dealing is when the actor is sending its deals
+  Dealing,
+  // Responding is when the actor is sending its responses on the received deals
+  Responding,
+  // Certifying is when the actor is validating its deals based on the responses
+  Certifying,
+  // Certified is when the actor has been certified
+  Certified,
 }
 
 interface DKGInfo {

From e7b7d1ef243db4b70c2330751b239847d9cb6aa2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?No=C3=A9mien=20Kocher?= <nkcr.je@gmail.com>
Date: Thu, 15 Sep 2022 13:25:23 +0200
Subject: [PATCH 04/10] Updates the mock

---
 web/frontend/src/mocks/handlers.ts | 50 ++++++++++++++++++++++++++++--
 1 file changed, 47 insertions(+), 3 deletions(-)

diff --git a/web/frontend/src/mocks/handlers.ts b/web/frontend/src/mocks/handlers.ts
index 5a06a4a67..c7d3126ed 100644
--- a/web/frontend/src/mocks/handlers.ts
+++ b/web/frontend/src/mocks/handlers.ts
@@ -263,16 +263,60 @@ export const handlers = [
         const newDKGStatus = new Map(mockDKG.get(ElectionID as string));
         let node = '';
 
-        mockElections.get(ElectionID as string).Roster.forEach((n) => {
+        const roster = mockElections.get(ElectionID as string).Roster;
+
+        const INCREMENT = 1200;
+
+        roster.forEach((n) => {
           const p = mockNodeProxyAddresses.get(n);
           if (p === body.Proxy) {
             node = n;
           }
         });
 
-        newDKGStatus.set(node, NodeStatus.Setup);
+        const setup = () => {
+          newDKGStatus.set(node, NodeStatus.Setup);
+          mockDKG.set(ElectionID as string, newDKGStatus);
+        };
+
+        const certified = () => {
+          roster.forEach((n) => {
+            newDKGStatus.set(n, NodeStatus.Certified);
+          });
+          mockDKG.set(ElectionID as string, newDKGStatus);
+
+          setTimeout(setup, INCREMENT);
+        };
+
+        const certifying = () => {
+          roster.forEach((n) => {
+            newDKGStatus.set(n, NodeStatus.Certifying);
+          });
+          mockDKG.set(ElectionID as string, newDKGStatus);
+
+          setTimeout(certified, INCREMENT);
+        };
+
+        const responding = () => {
+          roster.forEach((n) => {
+            newDKGStatus.set(n, NodeStatus.Responding);
+          });
+          mockDKG.set(ElectionID as string, newDKGStatus);
+
+          setTimeout(certifying, INCREMENT);
+        };
+
+        const dealing = () => {
+          roster.forEach((n) => {
+            newDKGStatus.set(n, NodeStatus.Dealing);
+          });
+          mockDKG.set(ElectionID as string, newDKGStatus);
+
+          setTimeout(responding, INCREMENT);
+        };
+
+        setTimeout(dealing, INCREMENT);
 
-        setTimeout(() => mockDKG.set(ElectionID as string, newDKGStatus), SETUP_TIMER);
         break;
       case Action.BeginDecryption:
         setTimeout(

From 930019fe8aae66d57ebc2c0c5c55fd7655c0a5db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?No=C3=A9mien=20Kocher?= <nkcr.je@gmail.com>
Date: Fri, 16 Sep 2022 10:05:49 +0200
Subject: [PATCH 05/10] Fixes some edge cases

---
 web/frontend/src/pages/election/Show.tsx      | 26 +++++++------------
 .../election/components/DKGStatusRow.tsx      |  5 +++-
 2 files changed, 14 insertions(+), 17 deletions(-)

diff --git a/web/frontend/src/pages/election/Show.tsx b/web/frontend/src/pages/election/Show.tsx
index 3f9c045ec..688391d81 100644
--- a/web/frontend/src/pages/election/Show.tsx
+++ b/web/frontend/src/pages/election/Show.tsx
@@ -53,10 +53,8 @@ const ElectionShow: FC = () => {
 
   // called by a DKG row
   const notifyDKGState = (node: string, info: InternalDKGInfo) => {
-    console.log('DKG node updated:', info);
     switch (info.getStatus()) {
       case NodeStatus.Failed:
-        console.log('DKG node failed');
         setOngoingAction(OngoingAction.None);
         break;
       case NodeStatus.Setup:
@@ -68,7 +66,6 @@ const ElectionShow: FC = () => {
     const newDKGStatuses = new Map(DKGStatuses);
     newDKGStatuses.set(node, info.getStatus());
     setDKGStatuses(newDKGStatuses);
-    console.log('dkg statuses:', DKGStatuses);
   };
 
   // called by a DKG row
@@ -103,7 +100,6 @@ const ElectionShow: FC = () => {
     const storedOngoingAction = JSON.parse(window.localStorage.getItem(ongoingItem));
 
     if (storedOngoingAction !== null) {
-      console.log('stored ongoing action:', storedOngoingAction);
       setOngoingAction(storedOngoingAction);
     }
 
@@ -152,10 +148,14 @@ const ElectionShow: FC = () => {
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [roster]);
 
+  // Keep the "DKGLoading" state according to "nodeLoading". This state tells if
+  // one of the element on the map is true.
   useEffect(() => {
     if (nodeLoading !== null) {
       if (!Array.from(nodeLoading.values()).includes(true)) {
         setDKGLoading(false);
+      } else {
+        setDKGLoading(true);
       }
     }
     // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -166,24 +166,18 @@ const ElectionShow: FC = () => {
     if (status === Status.Initial) {
       if (DKGStatuses !== null && !DKGLoading) {
         const statuses = Array.from(DKGStatuses.values());
+        setOngoingAction(OngoingAction.None);
 
         // TODO: can be modified such that if the majority of the node are
         // initialized than the election status can still be set to initialized
-        if (statuses.includes(NodeStatus.NotInitialized)) {
-          setOngoingAction(OngoingAction.None);
-          setStatus(Status.Initial);
+        if (
+          statuses.includes(NodeStatus.NotInitialized) ||
+          statuses.includes(NodeStatus.Unreachable) ||
+          statuses.includes(NodeStatus.Failed)
+        ) {
           return;
         }
 
-        if (statuses.includes(NodeStatus.Setup)) {
-          setOngoingAction(OngoingAction.None);
-          setStatus(Status.Setup);
-          return;
-        }
-
-        if (statuses.includes(NodeStatus.Unreachable)) return;
-        if (statuses.includes(NodeStatus.Failed)) return;
-
         setStatus(Status.Initialized);
 
         // Status Failed is handled by useChangeAction
diff --git a/web/frontend/src/pages/election/components/DKGStatusRow.tsx b/web/frontend/src/pages/election/components/DKGStatusRow.tsx
index 1db1c57e9..3345c2aad 100644
--- a/web/frontend/src/pages/election/components/DKGStatusRow.tsx
+++ b/web/frontend/src/pages/election/components/DKGStatusRow.tsx
@@ -101,12 +101,15 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
   useEffect(() => {
     if (
       ongoingAction === OngoingAction.Initializing &&
-      (status === NodeStatus.NotInitialized || status === NodeStatus.Failed)
+      (status === NodeStatus.NotInitialized ||
+        status === NodeStatus.Failed ||
+        status === NodeStatus.Unreachable)
     ) {
       setDKGLoading(true);
 
       initializeNode()
         .then(() => {
+          setInfo('');
           setStatus(NodeStatus.Initialized);
         })
         .catch((e: Error) => {

From 6c7f680724774f346f9eec9cd3f5bff7da05854f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?No=C3=A9mien=20Kocher?= <nkcr.je@gmail.com>
Date: Fri, 16 Sep 2022 13:30:56 +0200
Subject: [PATCH 06/10] Adds missing translation

---
 web/frontend/src/language/en.json  | 4 ++++
 web/frontend/src/mocks/handlers.ts | 1 -
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/web/frontend/src/language/en.json b/web/frontend/src/language/en.json
index f8d149563..112e499f1 100644
--- a/web/frontend/src/language/en.json
+++ b/web/frontend/src/language/en.json
@@ -132,6 +132,10 @@
     "setupNode": "Setup Node",
     "statusOpen": "Open",
     "failed": "Failed",
+    "dealing": "Dealing",
+    "responding": "Responding",
+    "certifying": "Certifying",
+    "certified": "Certified",
     "opening": "Opening...",
     "statusClose": "Closed",
     "closing": "Closing...",
diff --git a/web/frontend/src/mocks/handlers.ts b/web/frontend/src/mocks/handlers.ts
index c7d3126ed..c2f3bf1f7 100644
--- a/web/frontend/src/mocks/handlers.ts
+++ b/web/frontend/src/mocks/handlers.ts
@@ -39,7 +39,6 @@ let mockUserDB = setupMockUserDB();
 const RESPONSE_TIME = 500;
 const CHANGE_STATUS_TIMER = 2000;
 const INIT_TIMER = 1000;
-const SETUP_TIMER = 2000;
 const SHUFFLE_TIMER = 2000;
 const DECRYPT_TIMER = 1000;
 

From 3e3105dfcd113905485127fd37d85680043bf01b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?No=C3=A9mien=20Kocher?= <nkcr.je@gmail.com>
Date: Tue, 20 Sep 2022 12:25:08 +0200
Subject: [PATCH 07/10] Fixes the case where a node is empty

\+ other fixes
---
 web/frontend/src/pages/election/Show.tsx      |  13 +-
 .../election/components/DKGStatusRow.tsx      | 117 ++++++++++++------
 2 files changed, 84 insertions(+), 46 deletions(-)

diff --git a/web/frontend/src/pages/election/Show.tsx b/web/frontend/src/pages/election/Show.tsx
index 688391d81..78fe9a8e0 100644
--- a/web/frontend/src/pages/election/Show.tsx
+++ b/web/frontend/src/pages/election/Show.tsx
@@ -152,11 +152,8 @@ const ElectionShow: FC = () => {
   // one of the element on the map is true.
   useEffect(() => {
     if (nodeLoading !== null) {
-      if (!Array.from(nodeLoading.values()).includes(true)) {
-        setDKGLoading(false);
-      } else {
-        setDKGLoading(true);
-      }
+      const someNodeLoading = Array.from(nodeLoading.values()).includes(true);
+      setDKGLoading(someNodeLoading);
     }
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [nodeLoading]);
@@ -166,6 +163,12 @@ const ElectionShow: FC = () => {
     if (status === Status.Initial) {
       if (DKGStatuses !== null && !DKGLoading) {
         const statuses = Array.from(DKGStatuses.values());
+
+        // We want to update only if all nodes have already set their status
+        if (statuses.length !== roster.length) {
+          return;
+        }
+
         setOngoingAction(OngoingAction.None);
 
         // TODO: can be modified such that if the majority of the node are
diff --git a/web/frontend/src/pages/election/components/DKGStatusRow.tsx b/web/frontend/src/pages/election/components/DKGStatusRow.tsx
index 3345c2aad..6e7e11fe9 100644
--- a/web/frontend/src/pages/election/components/DKGStatusRow.tsx
+++ b/web/frontend/src/pages/election/components/DKGStatusRow.tsx
@@ -70,12 +70,6 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [status]);
 
-  // Notify the parent when we are loading or not
-  useEffect(() => {
-    notifyLoading(node, DKGLoading);
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [DKGLoading]);
-
   // send the initialization request
   const initializeNode = async () => {
     const req = {
@@ -106,19 +100,6 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
         status === NodeStatus.Unreachable)
     ) {
       setDKGLoading(true);
-
-      initializeNode()
-        .then(() => {
-          setInfo('');
-          setStatus(NodeStatus.Initialized);
-        })
-        .catch((e: Error) => {
-          setInfo(e.toString());
-          setStatus(NodeStatus.Failed);
-        })
-        .finally(() => {
-          setDKGLoading(false);
-        });
     }
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [ongoingAction]);
@@ -142,6 +123,68 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
     );
   };
 
+  // performAction does the initialization or the setup if appropriate
+  const performAction = () => {
+    // Initialize ?
+    if (
+      ongoingAction === OngoingAction.Initializing &&
+      (status === NodeStatus.NotInitialized ||
+        status === NodeStatus.Failed ||
+        status === NodeStatus.Unreachable)
+    ) {
+      if (proxy === '') {
+        setStatus(NodeStatus.Unreachable);
+        setInfo('proxy empty');
+        setDKGLoading(false);
+        return;
+      }
+
+      initializeNode()
+        .then(() => {
+          setInfo('');
+          setStatus(NodeStatus.Initialized);
+        })
+        .catch((e: Error) => {
+          setInfo(e.toString());
+          setStatus(NodeStatus.Failed);
+        })
+        .finally(() => {
+          setDKGLoading(false);
+        });
+
+      return;
+    }
+
+    // Setup ?
+    if (ongoingAction === OngoingAction.SettingUp && nodeToSetup !== null) {
+      let expectedStatus = NodeStatus.Certified;
+      if (nodeToSetup[0] === node) {
+        expectedStatus = NodeStatus.Setup;
+      }
+
+      pollDKGStatus(expectedStatus)
+        .then(
+          () => {},
+          (e: Error) => {
+            setStatus(NodeStatus.Failed);
+            setInfo(e.toString());
+          }
+        )
+        .finally(() => setDKGLoading(false));
+    }
+  };
+
+  // Notify the parent when we are loading or not
+  useEffect(() => {
+    notifyLoading(node, DKGLoading);
+
+    if (DKGLoading) {
+      performAction();
+    }
+
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [DKGLoading]);
+
   // Action taken when the setting up is triggered.
   useEffect(() => {
     if (ongoingAction !== OngoingAction.SettingUp || nodeToSetup === null) {
@@ -150,20 +193,6 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
 
     setDKGLoading(true);
 
-    let expectedStatus = NodeStatus.Certified;
-    if (nodeToSetup[0] === node) {
-      expectedStatus = NodeStatus.Setup;
-    }
-
-    pollDKGStatus(expectedStatus)
-      .then(
-        () => {},
-        (e: Error) => {
-          setStatus(NodeStatus.Failed);
-          setInfo(e.toString());
-        }
-      )
-      .finally(() => setDKGLoading(false));
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [ongoingAction]);
 
@@ -196,8 +225,8 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
             errorMessage += t('error');
           }
 
-          setTextModalError(errorMessage + e.message);
-          setShowModalError(true);
+          setInfo(errorMessage + e.message);
+          setStatus(NodeStatus.Unreachable);
 
           // if we could not retrieve the proxy still resolve the promise
           // so that promise.then() goes to onSuccess() but display the error
@@ -207,9 +236,13 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
 
       setDKGLoading(true);
 
-      fetchNodeProxy().then((nodeProxyAddress) => {
-        setProxy(nodeProxyAddress.Proxy);
-      });
+      fetchNodeProxy()
+        .then((nodeProxyAddress) => {
+          setProxy(nodeProxyAddress.Proxy);
+        })
+        .finally(() => {
+          setDKGLoading(false);
+        });
     }
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [node, proxy]);
@@ -264,12 +297,14 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
             errorMessage += t('error');
           }
 
-          setTextModalError(errorMessage + e.message);
-          setShowModalError(true);
+          setInfo(errorMessage + e.message);
 
           // if we could not retrieve the proxy still resolve the promise
           // so that promise.then() goes to onSuccess() but display the error
-          return InternalDKGInfo.fromStatus(NodeStatus.Unreachable);
+          return InternalDKGInfo.fromInfo({
+            Status: NodeStatus.Failed,
+            Error: { Title: errorMessage, Message: e.message, Code: 0, Args: undefined },
+          });
         }
       };
 

From e40e1ea940a62aeba6b55b22219fc846fe3f246c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?No=C3=A9mien=20Kocher?= <nkcr.je@gmail.com>
Date: Tue, 20 Sep 2022 13:35:16 +0200
Subject: [PATCH 08/10] Adds build info on the footer

---
 web/frontend/src/layout/Footer.tsx | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/web/frontend/src/layout/Footer.tsx b/web/frontend/src/layout/Footer.tsx
index f9bd12a14..463288314 100644
--- a/web/frontend/src/layout/Footer.tsx
+++ b/web/frontend/src/layout/Footer.tsx
@@ -21,6 +21,12 @@ const Footer = () => (
           <ProxyInput />
         </div>
       </div>
+      <div className="text-center">
+        version:
+        {process.env.REACT_APP_VERSION || 'unknown'} - build{' '}
+        {process.env.REACT_APP_BUILD || 'unknown'} - on{' '}
+        {process.env.REACT_APP_BUILD_TIME || 'unknown'}
+      </div>
     </footer>
   </div>
 );

From 5e97a5f9efdc329f2a442474af2558925b76fca5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?No=C3=A9mien=20Kocher?= <nkcr.je@gmail.com>
Date: Wed, 21 Sep 2022 15:06:17 +0200
Subject: [PATCH 09/10] Fixes the setup state

---
 web/frontend/src/pages/election/Show.tsx      | 26 ++++++++-----------
 .../election/components/DKGStatusRow.tsx      | 10 +++----
 .../election/components/DKGStatusTable.tsx    |  6 -----
 3 files changed, 16 insertions(+), 26 deletions(-)

diff --git a/web/frontend/src/pages/election/Show.tsx b/web/frontend/src/pages/election/Show.tsx
index 78fe9a8e0..203151c56 100644
--- a/web/frontend/src/pages/election/Show.tsx
+++ b/web/frontend/src/pages/election/Show.tsx
@@ -53,14 +53,11 @@ const ElectionShow: FC = () => {
 
   // called by a DKG row
   const notifyDKGState = (node: string, info: InternalDKGInfo) => {
-    switch (info.getStatus()) {
-      case NodeStatus.Failed:
-        setOngoingAction(OngoingAction.None);
-        break;
-      case NodeStatus.Setup:
-        setOngoingAction(OngoingAction.None);
-        setStatus(Status.Setup);
-        break;
+    if (
+      info.getStatus() === NodeStatus.Setup &&
+      (status === Status.Initial || status === Status.Initialized)
+    ) {
+      setStatus(Status.Setup);
     }
 
     const newDKGStatuses = new Map(DKGStatuses);
@@ -154,6 +151,9 @@ const ElectionShow: FC = () => {
     if (nodeLoading !== null) {
       const someNodeLoading = Array.from(nodeLoading.values()).includes(true);
       setDKGLoading(someNodeLoading);
+      if (!someNodeLoading) {
+        setOngoingAction(OngoingAction.None);
+      }
     }
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [nodeLoading]);
@@ -169,8 +169,6 @@ const ElectionShow: FC = () => {
           return;
         }
 
-        setOngoingAction(OngoingAction.None);
-
         // TODO: can be modified such that if the majority of the node are
         // initialized than the election status can still be set to initialized
         if (
@@ -222,12 +220,12 @@ const ElectionShow: FC = () => {
             )}
           <div className="py-6 pl-2">
             <div className="font-bold uppercase text-lg text-gray-700">{t('status')}</div>
-            {DKGLoading && (
+            {DKGLoading && ongoingAction === OngoingAction.None && (
               <div className="px-2 pt-6">
                 <LoadingButton>{t('statusLoading')}</LoadingButton>
               </div>
             )}
-            {!DKGLoading && (
+            {(!DKGLoading || ongoingAction !== OngoingAction.None) && (
               <div className="px-2 pt-6 flex justify-center">
                 <StatusTimeline status={status} ongoingAction={ongoingAction} />
               </div>
@@ -237,7 +235,7 @@ const ElectionShow: FC = () => {
             <div className="font-bold uppercase text-lg text-gray-700 pb-2">{t('action')}</div>
             <div className="px-2">
               {DKGLoading && <LoadingButton>{t('actionLoading')}</LoadingButton>}{' '}
-              {!DKGLoading && (
+              {(!DKGLoading || ongoingAction !== OngoingAction.None) && (
                 <Action
                   status={status}
                   electionID={electionID}
@@ -271,8 +269,6 @@ const ElectionShow: FC = () => {
                 electionId={electionId}
                 nodeProxyAddresses={nodeProxyAddresses}
                 setNodeProxyAddresses={setNodeProxyAddresses}
-                setTextModalError={setTextModalError}
-                setShowModalError={setShowModalError}
                 ongoingAction={ongoingAction}
                 notifyDKGState={notifyDKGState}
                 nodeToSetup={nodeToSetup}
diff --git a/web/frontend/src/pages/election/components/DKGStatusRow.tsx b/web/frontend/src/pages/election/components/DKGStatusRow.tsx
index 6e7e11fe9..d42a0dfd1 100644
--- a/web/frontend/src/pages/election/components/DKGStatusRow.tsx
+++ b/web/frontend/src/pages/election/components/DKGStatusRow.tsx
@@ -20,8 +20,6 @@ type DKGStatusRowProps = {
   index: number;
   nodeProxyAddresses: Map<string, string>;
   setNodeProxyAddresses: (nodeProxy: Map<string, string>) => void;
-  setTextModalError: (error: string) => void;
-  setShowModalError: (show: boolean) => void;
   // notify to start initialization
   ongoingAction: OngoingAction;
   // notify the parent of the new state
@@ -37,8 +35,6 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
   index,
   nodeProxyAddresses,
   setNodeProxyAddresses,
-  setTextModalError,
-  setShowModalError,
   ongoingAction,
   notifyDKGState,
   nodeToSetup,
@@ -170,12 +166,16 @@ const DKGStatusRow: FC<DKGStatusRowProps> = ({
             setInfo(e.toString());
           }
         )
-        .finally(() => setDKGLoading(false));
+        .finally(() => {
+          console.log('setDKGLoading to false');
+          setDKGLoading(false);
+        });
     }
   };
 
   // Notify the parent when we are loading or not
   useEffect(() => {
+    console.log('notifyLoading', DKGLoading);
     notifyLoading(node, DKGLoading);
 
     if (DKGLoading) {
diff --git a/web/frontend/src/pages/election/components/DKGStatusTable.tsx b/web/frontend/src/pages/election/components/DKGStatusTable.tsx
index 6d28b0241..02ae64618 100644
--- a/web/frontend/src/pages/election/components/DKGStatusTable.tsx
+++ b/web/frontend/src/pages/election/components/DKGStatusTable.tsx
@@ -10,8 +10,6 @@ type DKGStatusTableProps = {
   electionId: ID;
   nodeProxyAddresses: Map<string, string>;
   setNodeProxyAddresses: (nodeProxy: Map<string, string>) => void;
-  setTextModalError: (error: string) => void;
-  setShowModalError: (show: boolean) => void;
   // notify to start initialization
   ongoingAction: OngoingAction;
   // notify the parent of the new state
@@ -25,8 +23,6 @@ const DKGStatusTable: FC<DKGStatusTableProps> = ({
   electionId,
   nodeProxyAddresses,
   setNodeProxyAddresses,
-  setTextModalError,
-  setShowModalError,
   ongoingAction,
   notifyDKGState,
   nodeToSetup,
@@ -58,8 +54,6 @@ const DKGStatusTable: FC<DKGStatusTableProps> = ({
                   index={index}
                   nodeProxyAddresses={nodeProxyAddresses}
                   setNodeProxyAddresses={setNodeProxyAddresses}
-                  setTextModalError={setTextModalError}
-                  setShowModalError={setShowModalError}
                   ongoingAction={ongoingAction}
                   notifyDKGState={notifyDKGState}
                   nodeToSetup={nodeToSetup}

From 3a6ab2065329f95e7aa4809a98fe2d304f61adb4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?No=C3=A9mien=20Kocher?= <nkcr.je@gmail.com>
Date: Wed, 21 Sep 2022 17:42:19 +0200
Subject: [PATCH 10/10] Adds missing condition while loading

---
 web/frontend/src/pages/election/Show.tsx | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/web/frontend/src/pages/election/Show.tsx b/web/frontend/src/pages/election/Show.tsx
index 203151c56..c946db97a 100644
--- a/web/frontend/src/pages/election/Show.tsx
+++ b/web/frontend/src/pages/election/Show.tsx
@@ -234,7 +234,9 @@ const ElectionShow: FC = () => {
           <div className="py-4 pl-2 pb-8">
             <div className="font-bold uppercase text-lg text-gray-700 pb-2">{t('action')}</div>
             <div className="px-2">
-              {DKGLoading && <LoadingButton>{t('actionLoading')}</LoadingButton>}{' '}
+              {DKGLoading && ongoingAction === OngoingAction.None && (
+                <LoadingButton>{t('actionLoading')}</LoadingButton>
+              )}{' '}
               {(!DKGLoading || ongoingAction !== OngoingAction.None) && (
                 <Action
                   status={status}