diff --git a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
index 18a352dbbc6..6c7cf34cb70 100644
--- a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
+++ b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
@@ -267,55 +267,53 @@ exports[`HeaderBar matches snapshot 1`] = `
-
-
-
-
-
-
+
+
+
+
}
diff --git a/web-console/src/components/header-bar/header-bar.tsx b/web-console/src/components/header-bar/header-bar.tsx
index d3c58db6bb5..9a66dbaeace 100644
--- a/web-console/src/components/header-bar/header-bar.tsx
+++ b/web-console/src/components/header-bar/header-bar.tsx
@@ -32,7 +32,6 @@ import {
} from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import { Popover2 } from '@blueprintjs/popover2';
-import type { JSX } from 'react';
import React, { useState } from 'react';
import {
@@ -51,9 +50,10 @@ import {
localStorageSetJson,
oneOf,
} from '../../utils';
-import { ExternalLink } from '../external-link/external-link';
import { PopoverText } from '../popover-text/popover-text';
+import { RestrictedMode } from './restricted-mode/restricted-mode';
+
import './header-bar.scss';
const capabilitiesOverride = localStorageGetJson(LocalStorageKeys.CAPABILITIES_OVERRIDE);
@@ -92,143 +92,6 @@ const DruidLogo = React.memo(function DruidLogo() {
);
});
-interface RestrictedModeProps {
- capabilities: Capabilities;
- onUnrestrict(capabilities: Capabilities): void;
-}
-
-const RestrictedMode = React.memo(function RestrictedMode(props: RestrictedModeProps) {
- const { capabilities, onUnrestrict } = props;
- const mode = capabilities.getModeExtended();
-
- let label: string;
- let message: JSX.Element;
- switch (mode) {
- case 'full':
- return null; // Do not show anything
-
- case 'no-sql':
- label = 'No SQL mode';
- message = (
-
- It appears that the SQL endpoint is disabled. The console will fall back to{' '}
- native Druid APIs and will be
- limited in functionality. Look at{' '}
- the SQL docs to enable the SQL
- endpoint.
-
- );
- break;
-
- case 'no-proxy':
- label = 'No management proxy mode';
- message = (
-
- It appears that the management proxy is not enabled, the console will operate with limited
- functionality.
-
- );
- break;
-
- case 'no-sql-no-proxy':
- label = 'No SQL mode';
- message = (
-
- It appears that the SQL endpoint and management proxy are disabled. The console can only
- be used to make queries.
-
- );
- break;
-
- case 'coordinator-overlord':
- label = 'Coordinator/Overlord mode';
- message = (
-
- It appears that you are accessing the console on the Coordinator/Overlord shared service.
- Due to the lack of access to some APIs on this service the console will operate in a
- limited mode. The unrestricted version of the console can be accessed on the Router
- service.
-
- );
- break;
-
- case 'coordinator':
- label = 'Coordinator mode';
- message = (
-
- It appears that you are accessing the console on the Coordinator service. Due to the lack
- of access to some APIs on this service the console will operate in a limited mode. The
- full version of the console can be accessed on the Router service.
-
- );
- break;
-
- case 'overlord':
- label = 'Overlord mode';
- message = (
-
- It appears that you are accessing the console on the Overlord service. Due to the lack of
- access to some APIs on this service the console will operate in a limited mode. The
- unrestricted version of the console can be accessed on the Router service.
-
- );
- break;
-
- default:
- label = 'Restricted mode';
- message = (
-
- Due to the lack of access to some APIs on this service the console will operate in a
- limited mode. The unrestricted version of the console can be accessed on the Router
- service.
-
- );
- break;
- }
-
- return (
-
- The console is running in restricted mode.
- {message}
-
- For more info check out the{' '}
-
- web console documentation
-
- .
-
-
- It is possible that there is an issue with the capability detection. You can enable the
- unrestricted console but certain features might not work if the underlying APIs are not
- available.
-
-
- onUnrestrict(Capabilities.FULL)}
- />
-
- {!capabilities.hasSql() && (
-
- onUnrestrict(Capabilities.NO_SQL)}
- />
-
- )}
-
- }
- position={Position.BOTTOM_RIGHT}
- >
-
-
- );
-});
-
export interface HeaderBarProps {
active: HeaderActiveTab;
capabilities: Capabilities;
@@ -324,7 +187,7 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
);
- function setForcedMode(capabilities: Capabilities | undefined): void {
+ function setCapabilitiesOverride(capabilities: Capabilities | undefined): void {
if (capabilities) {
localStorageSetJson(LocalStorageKeys.CAPABILITIES_OVERRIDE, capabilities);
} else {
@@ -360,39 +223,46 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
onClick={() => setCompactionDynamicConfigDialogOpen(true)}
disabled={!capabilities.hasCoordinatorAccess()}
/>
-
-
- {capabilitiesOverride ? (
- setForcedMode(undefined)} />
- ) : (
+
+ {capabilitiesOverride && (
<>
- {capabilitiesMode !== 'coordinator-overlord' && (
- setForcedMode(Capabilities.COORDINATOR_OVERLORD)}
- />
- )}
- {capabilitiesMode !== 'coordinator' && (
- setForcedMode(Capabilities.COORDINATOR)}
- />
- )}
- {capabilitiesMode !== 'overlord' && (
- setForcedMode(Capabilities.OVERLORD)}
- />
- )}
- {capabilitiesMode !== 'no-proxy' && (
- setForcedMode(Capabilities.NO_PROXY)}
- />
- )}
+ setCapabilitiesOverride(undefined)}
+ intent={Intent.PRIMARY}
+ />
+
>
)}
+ {capabilitiesMode !== 'coordinator-overlord' && (
+ setCapabilitiesOverride(Capabilities.COORDINATOR_OVERLORD)}
+ />
+ )}
+ {capabilitiesMode !== 'coordinator' && (
+ setCapabilitiesOverride(Capabilities.COORDINATOR)}
+ />
+ )}
+ {capabilitiesMode !== 'overlord' && (
+ setCapabilitiesOverride(Capabilities.OVERLORD)}
+ />
+ )}
+ {capabilitiesMode !== 'no-proxy' && (
+ setCapabilitiesOverride(Capabilities.NO_PROXY)}
+ />
+ )}
);
@@ -495,7 +365,48 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
-
+ setCapabilitiesOverride(undefined) : undefined
+ }
+ />
+ {capabilitiesOverride && (
+
+
+ The console is running in a manual capability setting mode that assumes a limited
+ set of capabilities rather than detecting all capabilities for the cluster.
+
+
+ Manual capability setting mode is an advanced feature used for testing and for
+ working around issues with the automatic capability detecting logic.
+
+
+ If you are unsure why the console is in this mode, revert to using automatic
+ capability detection.
+
+
+ setCapabilitiesOverride(undefined)}
+ intent={Intent.PRIMARY}
+ />
+
+
+ }
+ position={Position.BOTTOM_RIGHT}
+ >
+
+
+ )}
diff --git a/web-console/src/components/header-bar/restricted-mode/__snapshots__/restricted-mode.spec.tsx.snap b/web-console/src/components/header-bar/restricted-mode/__snapshots__/restricted-mode.spec.tsx.snap
new file mode 100644
index 00000000000..ed352ecbf5f
--- /dev/null
+++ b/web-console/src/components/header-bar/restricted-mode/__snapshots__/restricted-mode.spec.tsx.snap
@@ -0,0 +1,143 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`RestrictedMode matches snapshot when in auto capability detection mode 1`] = `
+
+
+ The console is running in restricted mode.
+
+
+ You are accessing the console on the Coordinator/Overlord shared service. Because this service lacks access to some APIs, the console will operate in a limited mode. You can access the unrestricted version of the console on the Router service.
+
+
+ For more info refer to the
+
+
+ web console documentation
+
+ .
+
+
+
+ It is possible that the console is experiencing an issue with the capability detection. You can enable the unrestricted console, but certain features might not work if the underlying APIs are not available.
+
+
+
+
+
+
+
+
+
+ }
+ defaultIsOpen={false}
+ disabled={false}
+ fill={false}
+ hasBackdrop={false}
+ hoverCloseDelay={300}
+ hoverOpenDelay={150}
+ inheritDarkTheme={true}
+ interactionKind="click"
+ matchTargetWidth={false}
+ minimal={false}
+ openOnTargetFocus={true}
+ position="bottom-right"
+ positioningStrategy="absolute"
+ shouldReturnFocusOnClose={false}
+ targetTagName="span"
+ transitionDuration={300}
+ usePortal={true}
+>
+
+
+`;
+
+exports[`RestrictedMode matches snapshot when in manual capability detection mode 1`] = `
+
+
+ The console is running in restricted mode.
+
+
+ You are accessing the console on the Coordinator/Overlord shared service. Because this service lacks access to some APIs, the console will operate in a limited mode. You can access the unrestricted version of the console on the Router service.
+
+
+ For more info refer to the
+
+
+ web console documentation
+
+ .
+
+
+
+ The console did no perform its automatic capability detection because it is running in manual capability detection mode.
+
+
+
+
+
+
+
+
+
+ }
+ defaultIsOpen={false}
+ disabled={false}
+ fill={false}
+ hasBackdrop={false}
+ hoverCloseDelay={300}
+ hoverOpenDelay={150}
+ inheritDarkTheme={true}
+ interactionKind="click"
+ matchTargetWidth={false}
+ minimal={false}
+ openOnTargetFocus={true}
+ position="bottom-right"
+ positioningStrategy="absolute"
+ shouldReturnFocusOnClose={false}
+ targetTagName="span"
+ transitionDuration={300}
+ usePortal={true}
+>
+
+
+`;
diff --git a/web-console/src/components/header-bar/restricted-mode/restricted-mode.spec.tsx b/web-console/src/components/header-bar/restricted-mode/restricted-mode.spec.tsx
new file mode 100644
index 00000000000..2469c11f6ff
--- /dev/null
+++ b/web-console/src/components/header-bar/restricted-mode/restricted-mode.spec.tsx
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+
+import { Capabilities } from '../../../helpers';
+import { shallow } from '../../../utils/shallow-renderer';
+
+import { RestrictedMode } from './restricted-mode';
+
+describe('RestrictedMode', () => {
+ it('matches snapshot when in auto capability detection mode', () => {
+ const headerBar = shallow(
+ {}} />,
+ );
+ expect(headerBar).toMatchSnapshot();
+ });
+
+ it('matches snapshot when in manual capability detection mode', () => {
+ const headerBar = shallow(
+ {}}
+ onUseAutomaticCapabilityDetection={() => {}}
+ />,
+ );
+ expect(headerBar).toMatchSnapshot();
+ });
+});
diff --git a/web-console/src/components/header-bar/restricted-mode/restricted-mode.tsx b/web-console/src/components/header-bar/restricted-mode/restricted-mode.tsx
new file mode 100644
index 00000000000..072d5b10e58
--- /dev/null
+++ b/web-console/src/components/header-bar/restricted-mode/restricted-mode.tsx
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Button, Intent, Position } from '@blueprintjs/core';
+import { IconNames } from '@blueprintjs/icons';
+import { Popover2 } from '@blueprintjs/popover2';
+import React, { type JSX } from 'react';
+
+import { Capabilities } from '../../../helpers';
+import { getLink } from '../../../links';
+import { ExternalLink } from '../../external-link/external-link';
+import { PopoverText } from '../../popover-text/popover-text';
+
+export interface RestrictedModeProps {
+ capabilities: Capabilities;
+ onUnrestrict(capabilities: Capabilities): void;
+ onUseAutomaticCapabilityDetection?: () => void;
+}
+
+export const RestrictedMode = React.memo(function RestrictedMode(props: RestrictedModeProps) {
+ const { capabilities, onUnrestrict, onUseAutomaticCapabilityDetection } = props;
+ const mode = capabilities.getModeExtended();
+
+ let label: string;
+ let message: JSX.Element;
+ switch (mode) {
+ case 'full':
+ return null; // Do not show anything
+
+ case 'no-sql':
+ label = 'No SQL mode';
+ message = (
+
+ The SQL endpoint is disabled. The console will fall back to{' '}
+ native Druid APIs and will operate
+ with limited functionality. Refer to{' '}
+ the SQL docs for instructions to
+ enable the SQL endpoint.
+
+ );
+ break;
+
+ case 'no-proxy':
+ label = 'No management proxy mode';
+ message = (
+
+ The management proxy is disabled, the console will operate with limited functionality.
+
+ );
+ break;
+
+ case 'no-sql-no-proxy':
+ label = 'No SQL mode';
+ message = (
+
+ The SQL endpoint and management proxy are disabled. You can only use the console to make
+ JSON-based queries.
+
+ );
+ break;
+
+ case 'coordinator-overlord':
+ label = 'Coordinator/Overlord mode';
+ message = (
+
+ You are accessing the console on the Coordinator/Overlord shared service. Because this
+ service lacks access to some APIs, the console will operate in a limited mode. You can
+ access the unrestricted version of the console on the Router service.
+
+ );
+ break;
+
+ case 'coordinator':
+ label = 'Coordinator mode';
+ message = (
+
+ You are accessing the console on the Coordinator service. Because this service lacks
+ access to some APIs, the console will operate in a limited mode. You can access the
+ unrestricted version of the console on the Router service.
+
+ );
+ break;
+
+ case 'overlord':
+ label = 'Overlord mode';
+ message = (
+
+ You are accessing the console on the Overlord service. Because this service lacks access
+ to some APIs, the console will operate in a limited mode. You can access the unrestricted
+ version of the console on the Router service.
+
+ );
+ break;
+
+ default:
+ label = 'Restricted mode';
+ message = (
+
+ Due to the lack of access to some APIs on this service, the console will operate in a
+ limited mode. You can access the unrestricted version of the console on the Router
+ service.
+
+ );
+ break;
+ }
+
+ return (
+
+ The console is running in restricted mode.
+ {message}
+
+ For more info refer to the{' '}
+
+ web console documentation
+
+ .
+
+ {onUseAutomaticCapabilityDetection ? (
+ <>
+
+ The console did no perform its automatic capability detection because it is running
+ in manual capability detection mode.
+
+
+
+
+ >
+ ) : (
+ <>
+
+ It is possible that the console is experiencing an issue with the capability
+ detection. You can enable the unrestricted console, but certain features might not
+ work if the underlying APIs are not available.
+
+
+ onUnrestrict(Capabilities.FULL)}
+ />
+
+ >
+ )}
+ {!capabilities.hasSql() && (
+
+ onUnrestrict(Capabilities.NO_SQL)}
+ />
+
+ )}
+
+ }
+ position={Position.BOTTOM_RIGHT}
+ >
+
+
+ );
+});
diff --git a/web-console/src/console-application.tsx b/web-console/src/console-application.tsx
index 8955b1c9051..08eaf74f4b7 100644
--- a/web-console/src/console-application.tsx
+++ b/web-console/src/console-application.tsx
@@ -122,7 +122,8 @@ export class ConsoleApplication extends React.PureComponent<
return await Capabilities.detectCapacity(capabilities);
},
- onStateChange: ({ data, loading }) => {
+ onStateChange: ({ data, loading, error }) => {
+ console.error('There was an error retrieving the capabilities', error);
this.setState({
capabilities: data || Capabilities.FULL,
capabilitiesLoading: loading,
diff --git a/web-console/src/helpers/capabilities.ts b/web-console/src/helpers/capabilities.ts
index 4c5c9357cc1..ce8509072f2 100644
--- a/web-console/src/helpers/capabilities.ts
+++ b/web-console/src/helpers/capabilities.ts
@@ -65,8 +65,8 @@ export class Capabilities {
{ timeout: Capabilities.STATUS_TIMEOUT },
);
} catch (e) {
- const { response } = e;
- if (response.status !== 405 && response.status !== 404) {
+ const status = e.response?.status;
+ if (status !== 405 && status !== 404) {
return; // other failure
}
try {
@@ -87,7 +87,7 @@ export class Capabilities {
{ timeout: Capabilities.STATUS_TIMEOUT },
);
} catch (e) {
- if (response.status !== 405 && response.status !== 404) {
+ if (status !== 405 && status !== 404) {
return; // other failure
}
@@ -106,9 +106,9 @@ export class Capabilities {
timeout: Capabilities.STATUS_TIMEOUT,
});
} catch (e) {
- const { response } = e;
+ const status = e.response?.status;
// If we detect error code 400 the management proxy is enabled but just does not know about the recently added /proxy/enabled route so treat this as a win.
- return response.status === 400;
+ return status === 400;
}
return true;
diff --git a/web-console/src/helpers/capacity.ts b/web-console/src/helpers/capacity.ts
index aa29efc68e9..c990149385f 100644
--- a/web-console/src/helpers/capacity.ts
+++ b/web-console/src/helpers/capacity.ts
@@ -25,7 +25,6 @@ export async function getClusterCapacity(): Promise {
});
const usedTaskSlots = Number(workersResponse.data.usedClusterCapacity);
-
const totalTaskSlots = Number(workersResponse.data.currentClusterCapacity);
return {
diff --git a/web-console/src/singletons/api.ts b/web-console/src/singletons/api.ts
index 6067dba39ae..c7b123df76c 100644
--- a/web-console/src/singletons/api.ts
+++ b/web-console/src/singletons/api.ts
@@ -16,8 +16,8 @@
* limitations under the License.
*/
-import type { AxiosError, AxiosInstance, CreateAxiosDefaults } from 'axios';
-import axios from 'axios';
+import type { AxiosInstance, CreateAxiosDefaults } from 'axios';
+import axios, { AxiosError } from 'axios';
import * as JSONBig from 'json-bigint-native';
import { nonEmptyString } from '../utils';
@@ -36,11 +36,15 @@ export class Api {
const responseData = error.response?.data;
const message = responseData?.message;
if (nonEmptyString(message)) {
- return Promise.reject(new Error(message));
+ return Promise.reject(
+ new AxiosError(message, error.code, error.config, error.request, error.response),
+ );
}
if (error.config?.method?.toLowerCase() === 'get' && nonEmptyString(responseData)) {
- return Promise.reject(new Error(responseData));
+ return Promise.reject(
+ new AxiosError(responseData, error.code, error.config, error.request, error.response),
+ );
}
return Promise.reject(error);