diff --git a/app/assets/css/app.css b/app/assets/css/app.css
index aa4749814..b8bd2abb4 100644
--- a/app/assets/css/app.css
+++ b/app/assets/css/app.css
@@ -94,7 +94,8 @@ body,
   font-size: 16px;
 }
 
-.form-horizontal .control-label.text-left {
+.form-horizontal .control-label.text-left,
+.form-row .control-label.text-left {
   text-align: left;
   font-size: 0.9em;
 }
diff --git a/app/kubernetes/__module.js b/app/kubernetes/__module.js
index c235645a7..9ccdbcc8a 100644
--- a/app/kubernetes/__module.js
+++ b/app/kubernetes/__module.js
@@ -174,7 +174,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
       url: '/:pod/:container/console',
       views: {
         'content@': {
-          component: 'kubernetesApplicationConsoleView',
+          component: 'kubernetesConsoleView',
         },
       },
     };
diff --git a/app/kubernetes/react/views/index.ts b/app/kubernetes/react/views/index.ts
index 3d5a8b8d8..59d9699ae 100644
--- a/app/kubernetes/react/views/index.ts
+++ b/app/kubernetes/react/views/index.ts
@@ -8,6 +8,7 @@ import { IngressesDatatableView } from '@/react/kubernetes/ingresses/IngressData
 import { CreateIngressView } from '@/react/kubernetes/ingresses/CreateIngressView';
 import { DashboardView } from '@/react/kubernetes/DashboardView';
 import { ServicesView } from '@/react/kubernetes/ServicesView';
+import { ConsoleView } from '@/react/kubernetes/applications/ConsoleView';
 
 export const viewsModule = angular
   .module('portainer.kubernetes.react.views', [])
@@ -29,4 +30,8 @@ export const viewsModule = angular
   .component(
     'kubernetesDashboardView',
     r2a(withUIRouter(withReactQuery(withCurrentUser(DashboardView))), [])
+  )
+  .component(
+    'kubernetesConsoleView',
+    r2a(withUIRouter(withReactQuery(withCurrentUser(ConsoleView))), [])
   ).name;
diff --git a/app/kubernetes/views/applications/console/console.html b/app/kubernetes/views/applications/console/console.html
deleted file mode 100644
index 83c928c5c..000000000
--- a/app/kubernetes/views/applications/console/console.html
+++ /dev/null
@@ -1,84 +0,0 @@
-<page-header
-  ng-if="ctrl.state.viewReady"
-  title="'Application console'"
-  breadcrumbs="[
-    { label:'Namespaces', link:'kubernetes.resourcePools' },
-    {
-      label:ctrl.application.ResourcePool,
-      link: 'kubernetes.resourcePools.resourcePool',
-      linkParams:{ id: ctrl.application.ResourcePool }
-    },
-    { label:'Applications', link:'kubernetes.applications' },
-    {
-      label:ctrl.application.Name,
-      link: 'kubernetes.applications.application',
-      linkParams:{ name: ctrl.application.Name, namespace: ctrl.application.ResourcePool }
-    },
-     'Pods',
-     ctrl.podName,
-     'Containers',
-     ctrl.containerName,
-     'Console'
-     ]"
-  reload="true"
->
-</page-header>
-
-<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
-
-<div ng-if="ctrl.state.viewReady">
-  <div class="row">
-    <div class="col-sm-12">
-      <rd-widget>
-        <rd-widget-body>
-          <form class="form-horizontal" autocomplete="off">
-            <div class="col-sm-12 form-section-title"> Console </div>
-            <!-- Command -->
-            <div class="form-group">
-              <label for="console_command" class="col-sm-3 col-lg-2 control-label text-left">Command</label>
-              <div class="col-sm-8 input-group">
-                <span class="input-group-addon">
-                  <pr-icon icon="'terminal'" class="mr-1"></pr-icon>
-                </span>
-                <input
-                  type="text"
-                  class="form-control"
-                  placeholder="/bin/bash"
-                  ng-model="ctrl.state.command"
-                  name="console_command"
-                  uib-typeahead="command for command in ctrl.state.availableCommands | filter:$viewValue | limitTo:5"
-                  typeahead-min-length="0"
-                  auto-focus
-                />
-              </div>
-            </div>
-            <!-- !command -->
-            <div class="form-group">
-              <div class="col-sm-12">
-                <button
-                  type="button"
-                  class="btn btn-primary btn-sm"
-                  style="margin: 0"
-                  ng-if="!ctrl.state.connected"
-                  ng-disabled="!ctrl.state.command || ctrl.state.connected"
-                  ng-click="ctrl.connectConsole()"
-                  button-spinner="ctrl.state.actionInProgress"
-                >
-                  <span ng-hide="ctrl.state.actionInProgress">Connect</span>
-                  <span ng-show="ctrl.state.actionInProgress">Connection in progress...</span>
-                </button>
-                <button type="button" class="btn btn-primary btn-sm" style="margin: 0" ng-if="ctrl.state.connected" ng-click="ctrl.disconnect()"> Disconnect </button>
-              </div>
-            </div>
-          </form>
-        </rd-widget-body>
-      </rd-widget>
-    </div>
-  </div>
-
-  <div class="row">
-    <div class="col-sm-12">
-      <div id="terminal-container" class="terminal-container"></div>
-    </div>
-  </div>
-</div>
diff --git a/app/kubernetes/views/applications/console/console.js b/app/kubernetes/views/applications/console/console.js
deleted file mode 100644
index 780e4f0ac..000000000
--- a/app/kubernetes/views/applications/console/console.js
+++ /dev/null
@@ -1,9 +0,0 @@
-angular.module('portainer.kubernetes').component('kubernetesApplicationConsoleView', {
-  templateUrl: './console.html',
-  controller: 'KubernetesApplicationConsoleController',
-  controllerAs: 'ctrl',
-  bindings: {
-    $transition$: '<',
-    endpoint: '<',
-  },
-});
diff --git a/app/kubernetes/views/applications/console/consoleController.js b/app/kubernetes/views/applications/console/consoleController.js
deleted file mode 100644
index f0912d106..000000000
--- a/app/kubernetes/views/applications/console/consoleController.js
+++ /dev/null
@@ -1,117 +0,0 @@
-import angular from 'angular';
-import { Terminal } from 'xterm';
-import { baseHref } from '@/portainer/helpers/pathHelper';
-
-class KubernetesApplicationConsoleController {
-  /* @ngInject */
-  constructor($async, $state, Notifications, KubernetesApplicationService, LocalStorage) {
-    this.$async = $async;
-    this.$state = $state;
-    this.Notifications = Notifications;
-    this.KubernetesApplicationService = KubernetesApplicationService;
-    this.LocalStorage = LocalStorage;
-
-    this.onInit = this.onInit.bind(this);
-  }
-
-  disconnect() {
-    this.state.socket.close();
-    this.state.term.dispose();
-    this.state.connected = false;
-  }
-
-  configureSocketAndTerminal(socket, term) {
-    socket.onopen = function () {
-      const terminal_container = document.getElementById('terminal-container');
-      term.open(terminal_container);
-      term.setOption('cursorBlink', true);
-      term.focus();
-    };
-
-    term.on('data', function (data) {
-      socket.send(data);
-    });
-
-    socket.onmessage = function (msg) {
-      term.write(msg.data);
-    };
-
-    socket.onerror = function (err) {
-      this.disconnect();
-      this.Notifications.error('Failure', err, 'Websocket connection error');
-    }.bind(this);
-
-    this.state.socket.onclose = function () {
-      this.disconnect();
-    }.bind(this);
-
-    this.state.connected = true;
-  }
-
-  connectConsole() {
-    const params = {
-      token: this.LocalStorage.getJWT(),
-      endpointId: this.endpoint.Id,
-      namespace: this.application.ResourcePool,
-      podName: this.podName,
-      containerName: this.containerName,
-      command: this.state.command,
-    };
-
-    const base = window.location.origin.startsWith('http') ? `${window.location.origin}${baseHref()}` : baseHref();
-
-    let url =
-      base +
-      'api/websocket/pod?' +
-      Object.keys(params)
-        .map((k) => k + '=' + params[k])
-        .join('&');
-    if (url.indexOf('https') > -1) {
-      url = url.replace('https://', 'wss://');
-    } else {
-      url = url.replace('http://', 'ws://');
-    }
-
-    this.state.socket = new WebSocket(url);
-    this.state.term = new Terminal();
-
-    this.configureSocketAndTerminal(this.state.socket, this.state.term);
-  }
-
-  async onInit() {
-    const availableCommands = ['/bin/bash', '/bin/sh'];
-
-    this.state = {
-      actionInProgress: false,
-      availableCommands: availableCommands,
-      command: availableCommands[1],
-      connected: false,
-      socket: null,
-      term: null,
-      viewReady: false,
-    };
-
-    const podName = this.$transition$.params().pod;
-    const applicationName = this.$transition$.params().name;
-    const namespace = this.$transition$.params().namespace;
-    const containerName = this.$transition$.params().container;
-
-    this.podName = podName;
-    this.containerName = containerName;
-
-    try {
-      this.application = await this.KubernetesApplicationService.get(namespace, applicationName);
-    } catch (err) {
-      this.Notifications.error('Failure', err, 'Unable to retrieve application logs');
-    } finally {
-      this.state.viewReady = true;
-    }
-  }
-
-  $onInit() {
-    return this.$async(this.onInit);
-  }
-}
-
-export default KubernetesApplicationConsoleController;
-angular.module('portainer.kubernetes').controller('KubernetesApplicationConsoleController', KubernetesApplicationConsoleController);
diff --git a/app/react/kubernetes/applications/ConsoleView/.keep b/app/react/kubernetes/applications/ConsoleView/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/app/react/kubernetes/applications/ConsoleView/ConsoleView.tsx b/app/react/kubernetes/applications/ConsoleView/ConsoleView.tsx
new file mode 100644
index 000000000..f31c7816f
--- /dev/null
+++ b/app/react/kubernetes/applications/ConsoleView/ConsoleView.tsx
@@ -0,0 +1,197 @@
+import { useState, useCallback, useEffect } from 'react';
+import { useCurrentStateAndParams } from '@uirouter/react';
+import { Terminal as TerminalIcon } from 'lucide-react';
+import { Terminal } from 'xterm';
+
+import { useLocalStorage } from '@/react/hooks/useLocalStorage';
+import { baseHref } from '@/portainer/helpers/pathHelper';
+import { notifyError } from '@/portainer/services/notifications';
+
+import { PageHeader } from '@@/PageHeader';
+import { Widget, WidgetBody } from '@@/Widget';
+import { Icon } from '@@/Icon';
+import { Button } from '@@/buttons';
+
+interface StringDictionary {
+  [index: string]: string;
+}
+
+export function ConsoleView() {
+  const {
+    params: {
+      endpointId: environmentId,
+      container,
+      name: appName,
+      namespace,
+      pod: podID,
+    },
+  } = useCurrentStateAndParams();
+
+  const [jwtToken] = useLocalStorage('JWT', '');
+  const [command, setCommand] = useState('/bin/sh');
+  const [connectionStatus, setConnectionStatus] = useState('closed');
+  const [terminal, setTerminal] = useState(null as Terminal | null);
+  const [socket, setSocket] = useState(null as WebSocket | null);
+
+  const breadcrumbs = [
+    {
+      label: 'Namespaces',
+      link: 'kubernetes.resourcePools',
+    },
+    {
+      label: namespace,
+      link: 'kubernetes.resourcePools.resourcePool',
+      linkParams: { id: namespace },
+    },
+    {
+      label: 'Applications',
+      link: 'kubernetes.applications',
+    },
+    {
+      label: appName,
+      link: 'kubernetes.applications.application',
+      linkParams: { name: appName, namespace },
+    },
+    'Pods',
+    podID,
+    'Containers',
+    container,
+    'Console',
+  ];
+
+  const disconnectConsole = useCallback(() => {
+    socket?.close();
+    terminal?.dispose();
+    setTerminal(null);
+    setSocket(null);
+    setConnectionStatus('closed');
+  }, [socket, terminal, setConnectionStatus]);
+
+  useEffect(() => {
+    if (socket) {
+      socket.onopen = () => {
+        const terminalContainer = document.getElementById('terminal-container');
+        if (terminalContainer) {
+          terminal?.open(terminalContainer);
+          terminal?.setOption('cursorBlink', true);
+          terminal?.focus();
+          setConnectionStatus('open');
+        }
+      };
+
+      socket.onmessage = (msg) => {
+        terminal?.write(msg.data);
+      };
+
+      socket.onerror = () => {
+        disconnectConsole();
+        notifyError('Websocket connection error');
+      };
+
+      socket.onclose = () => {
+        disconnectConsole();
+      };
+    }
+  }, [disconnectConsole, setConnectionStatus, socket, terminal]);
+
+  useEffect(() => {
+    terminal?.on('data', (data) => {
+      socket?.send(data);
+    });
+  }, [terminal, socket]);
+
+  return (
+    <>
+      <PageHeader
+        title="Application console"
+        breadcrumbs={breadcrumbs}
+        reload
+      />
+      <div className="row">
+        <div className="col-sm-12">
+          <Widget>
+            <WidgetBody>
+              <div className="row">
+                <div className="col-sm-12 form-section-title">Console</div>
+              </div>
+              <div className="form-row flex">
+                <label
+                  htmlFor="consoleCommand"
+                  className="col-sm-3 col-lg-2 control-label m-0 p-0 text-left"
+                >
+                  Command
+                </label>
+                <div className="col-sm-8 input-group p-0">
+                  <span className="input-group-addon">
+                    <Icon icon={TerminalIcon} className="mr-1" />
+                  </span>
+                  <input
+                    type="text"
+                    className="form-control"
+                    placeholder="/bin/bash"
+                    value={command}
+                    onChange={(e) => setCommand(e.target.value)}
+                    id="consoleCommand"
+                    auto-focus="true"
+                  />
+                </div>
+              </div>
+              <div className="row mt-4">
+                <Button
+                  className="btn btn-primary !ml-0"
+                  onClick={
+                    connectionStatus === 'closed'
+                      ? connectConsole
+                      : disconnectConsole
+                  }
+                  disabled={connectionStatus === 'connecting'}
+                >
+                  {connectionStatus === 'open' && 'Disconnect'}
+                  {connectionStatus === 'connecting' && 'Connecting'}
+                  {connectionStatus !== 'connecting' &&
+                    connectionStatus !== 'open' &&
+                    'Connect'}
+                </Button>
+              </div>
+            </WidgetBody>
+          </Widget>
+          <div className="row">
+            <div className="col-sm-12 p-0">
+              <div id="terminal-container" className="terminal-container" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </>
+  );
+
+  function connectConsole() {
+    const params: StringDictionary = {
+      token: jwtToken,
+      endpointId: environmentId,
+      namespace,
+      podName: podID,
+      containerName: container,
+      command,
+    };
+
+    const queryParams = Object.keys(params)
+      .map((k) => `${k}=${params[k]}`)
+      .join('&');
+
+    let url = `${
+      window.location.origin
+    }${baseHref()}api/websocket/pod?${queryParams}`;
+    if (url.indexOf('https') > -1) {
+      url = url.replace('https://', 'wss://');
+    } else {
+      url = url.replace('http://', 'ws://');
+    }
+
+    setConnectionStatus('connecting');
+    const term = new Terminal();
+    setTerminal(term);
+    const socket = new WebSocket(url);
+    setSocket(socket);
+  }
+}
diff --git a/app/react/kubernetes/applications/ConsoleView/index.ts b/app/react/kubernetes/applications/ConsoleView/index.ts
new file mode 100644
index 000000000..bceeb0c68
--- /dev/null
+++ b/app/react/kubernetes/applications/ConsoleView/index.ts
@@ -0,0 +1 @@
+export { ConsoleView } from './ConsoleView';