Repository: provectus/kafka-ui
Issue: Wizard: Add a proper UI handling for image restart on Cluster config update
Labels: type/enhancement
scope/frontend
status/accepted
status/needs-attention
area/wizard

<!--

Don't forget to check for existing issues/discussions regarding your proposal. We might already have it.
https://github.com/provectus/kafka-ui/issues
https://github.com/provectus/kafka-ui/discussions

-->

### Which version of the app are you running?
<!-- Please provide docker image version or check commit hash in the top left corner in UI) -->
[dac023e](https://github.com/provectus/kafka-ui/commit/dac023e)
the 'wizard' branch

### Is your proposal related to a problem?
<!--
  Provide a clear and concise description of what the problem is.
  For example, "I'm always frustrated when..."
-->
On the update of cluster settings Kafka image is restarted and it is not reflected on the UI in any way.
There are also failed requests, that can be confusing or misleading:
![image](https://user-images.githubusercontent.com/112083452/221179407-861859bb-68a4-4b75-b1d0-c91c18b02ef5.png)


### Describe the solution you'd like
<!--
  Provide a clear and concise description of what you want to happen.
-->
1. After successful submit wizard redirects to dashboard with extra query param  (e.g. `restarting=true`)
1. Update useCluster hook: 
    - if `restarting=true` set refresh number to 10 and refresh interval = 10s. 
    - update error message. `It appears that there may be an issue. Please review the cluster configuration or logs to ensure everything is correct`
1. While the updating process is ongoing, a message will be displayed on the dashboard page: `The configuration has been updated and is currently being applied. Please wait.`. 
### Describe alternatives you've considered
<!--
  Let us know about other solutions you've tried or researched.
-->

### Additional context

<!--
  Is there anything else you can add about the proposal?
  You might want to link to related issues here, if you haven't already.
-->



Issue comments:

Pull request review comments:
i think maybe we can just make a general one cause i see this spinner code thing repeated across the application. we can make it customizable and just use that with `height` and `widths` and `colors` `props` etc and set the most common used one as a default value.
I would suggest add such spinner to Button component. So we will have button with `inProgress` state 
Yeah, I thought about it too. Update PR soon, thanks!
> I would suggest add such spinner to Button component. So we will have button with `inProgress` state
@workshur

But what if we need button with spinner but it somehow differs from other button with such spinner? I don't consider expanding button props with spinner customization props as a great move
@Mgrdich @workshur Hey, updated spinner usage
Should I add spinner to Link button?
Split border properties for easier manipulating them
We use S namespace for all styled components related to current component. 
```suggestion
    <Spinner
```
do we really need to customize all this props here? 
I think yes, but we can create default props object and customize on demand. In PageLoader it's gonna be empty in such case
so Links are for navigation , the actions are done in a sync way so maybe no.
i think we should disable the button when it is in progress,
`<StyledButton type="button" {...props} disabled={props.disabled || props.inProgress}`
Updated in https://github.com/provectus/kafka-ui/pull/3551/commits/8a6706f0dd3c26aa2fc5f6bcd0ddf91625bf1092
Updated + added test for this behavior

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/common/Button/Button.tsx b/kafka-ui-react-app/src/components/common/Button/Button.tsx
index 5e0a6609eb3..fe330a5e44f 100644
--- a/kafka-ui-react-app/src/components/common/Button/Button.tsx
+++ b/kafka-ui-react-app/src/components/common/Button/Button.tsx
@@ -3,11 +3,13 @@ import StyledButton, {
 } from 'components/common/Button/Button.styled';
 import React from 'react';
 import { Link } from 'react-router-dom';
+import Spinner from 'components/common/Spinner/Spinner';
 
 export interface Props
   extends React.ButtonHTMLAttributes<HTMLButtonElement>,
     ButtonProps {
   to?: string | object;
+  inProgress?: boolean;
 }
 
 export const Button: React.FC<Props> = ({ to, ...props }) => {
@@ -20,5 +22,16 @@ export const Button: React.FC<Props> = ({ to, ...props }) => {
       </Link>
     );
   }
-  return <StyledButton type="button" {...props} />;
+  return (
+    <StyledButton
+      type="button"
+      disabled={props.disabled || props.inProgress}
+      {...props}
+    >
+      {props.children}{' '}
+      {props.inProgress ? (
+        <Spinner size={16} borderWidth={2} marginLeft={2} emptyBorderColor />
+      ) : null}
+    </StyledButton>
+  );
 };
diff --git a/kafka-ui-react-app/src/components/common/Button/__tests__/Button.spec.tsx b/kafka-ui-react-app/src/components/common/Button/__tests__/Button.spec.tsx
index e0fb407a01a..21919eb0da7 100644
--- a/kafka-ui-react-app/src/components/common/Button/__tests__/Button.spec.tsx
+++ b/kafka-ui-react-app/src/components/common/Button/__tests__/Button.spec.tsx
@@ -58,4 +58,10 @@ describe('Button', () => {
       theme.button.primary.invertedColors.normal
     );
   });
+  it('renders disabled button and spinner when inProgress truthy', () => {
+    render(<Button buttonType="primary" buttonSize="M" inProgress />);
+    expect(screen.getByRole('button')).toBeInTheDocument();
+    expect(screen.getByRole('progressbar')).toBeInTheDocument();
+    expect(screen.getByRole('button')).toBeDisabled();
+  });
 });
diff --git a/kafka-ui-react-app/src/components/common/PageLoader/PageLoader.styled.ts b/kafka-ui-react-app/src/components/common/PageLoader/PageLoader.styled.ts
index 87f7a27fd13..f38f21c0b28 100644
--- a/kafka-ui-react-app/src/components/common/PageLoader/PageLoader.styled.ts
+++ b/kafka-ui-react-app/src/components/common/PageLoader/PageLoader.styled.ts
@@ -1,4 +1,4 @@
-import styled, { css } from 'styled-components';
+import styled from 'styled-components';
 
 export const Wrapper = styled.div`
   display: flex;
@@ -8,23 +8,3 @@ export const Wrapper = styled.div`
   height: 100%;
   width: 100%;
 `;
-
-export const Spinner = styled.div(
-  ({ theme }) => css`
-    border: 10px solid ${theme.pageLoader.borderColor};
-    border-bottom: 10px solid ${theme.pageLoader.borderBottomColor};
-    border-radius: 50%;
-    width: 80px;
-    height: 80px;
-    animation: spin 1.3s linear infinite;
-
-    @keyframes spin {
-      0% {
-        transform: rotate(0deg);
-      }
-      100% {
-        transform: rotate(360deg);
-      }
-    }
-  `
-);
diff --git a/kafka-ui-react-app/src/components/common/PageLoader/PageLoader.tsx b/kafka-ui-react-app/src/components/common/PageLoader/PageLoader.tsx
index 33348b17ea1..674ab0f0cee 100644
--- a/kafka-ui-react-app/src/components/common/PageLoader/PageLoader.tsx
+++ b/kafka-ui-react-app/src/components/common/PageLoader/PageLoader.tsx
@@ -1,10 +1,11 @@
 import React from 'react';
+import Spinner from 'components/common/Spinner/Spinner';
 
 import * as S from './PageLoader.styled';
 
 const PageLoader: React.FC = () => (
   <S.Wrapper>
-    <S.Spinner role="progressbar" />
+    <Spinner />
   </S.Wrapper>
 );
 
diff --git a/kafka-ui-react-app/src/components/common/Spinner/Spinner.styled.ts b/kafka-ui-react-app/src/components/common/Spinner/Spinner.styled.ts
new file mode 100644
index 00000000000..32edbcb11c4
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Spinner/Spinner.styled.ts
@@ -0,0 +1,26 @@
+import styled from 'styled-components';
+import { SpinnerProps } from 'components/common/Spinner/types';
+
+export const Spinner = styled.div<SpinnerProps>`
+  border-width: ${(props) => props.borderWidth}px;
+  border-style: solid;
+  border-color: ${({ theme }) => theme.pageLoader.borderColor};
+  border-bottom-color: ${(props) =>
+    props.emptyBorderColor
+      ? 'transparent'
+      : props.theme.pageLoader.borderBottomColor};
+  border-radius: 50%;
+  width: ${(props) => props.size}px;
+  height: ${(props) => props.size}px;
+  margin-left: ${(props) => props.marginLeft}px;
+  animation: spin 1.3s linear infinite;
+
+  @keyframes spin {
+    0% {
+      transform: rotate(0deg);
+    }
+    100% {
+      transform: rotate(360deg);
+    }
+  }
+`;
diff --git a/kafka-ui-react-app/src/components/common/Spinner/Spinner.tsx b/kafka-ui-react-app/src/components/common/Spinner/Spinner.tsx
new file mode 100644
index 00000000000..1d1cd597333
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Spinner/Spinner.tsx
@@ -0,0 +1,20 @@
+/* eslint-disable react/default-props-match-prop-types */
+import React from 'react';
+import { SpinnerProps } from 'components/common/Spinner/types';
+
+import * as S from './Spinner.styled';
+
+const defaultProps: SpinnerProps = {
+  size: 80,
+  borderWidth: 10,
+  emptyBorderColor: false,
+  marginLeft: 0,
+};
+
+const Spinner: React.FC<SpinnerProps> = (props) => (
+  <S.Spinner role="progressbar" {...props} />
+);
+
+Spinner.defaultProps = defaultProps;
+
+export default Spinner;
diff --git a/kafka-ui-react-app/src/components/common/Spinner/types.ts b/kafka-ui-react-app/src/components/common/Spinner/types.ts
new file mode 100644
index 00000000000..7db64b7fd30
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Spinner/types.ts
@@ -0,0 +1,6 @@
+export interface SpinnerProps {
+  size?: number;
+  borderWidth?: number;
+  emptyBorderColor?: boolean;
+  marginLeft?: number;
+}
diff --git a/kafka-ui-react-app/src/widgets/ClusterConfigForm/index.tsx b/kafka-ui-react-app/src/widgets/ClusterConfigForm/index.tsx
index 2c636c29a27..d4007cf4883 100644
--- a/kafka-ui-react-app/src/widgets/ClusterConfigForm/index.tsx
+++ b/kafka-ui-react-app/src/widgets/ClusterConfigForm/index.tsx
@@ -142,6 +142,7 @@ const ClusterConfigForm: React.FC<ClusterConfigFormProps> = ({
               buttonSize="L"
               buttonType="primary"
               disabled={isSubmitDisabled}
+              inProgress={isSubmitting}
             >
               Submit
             </Button>


Test patch: