This is an automated email from the ASF dual-hosted git repository.
vogievetsky pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 2d8eb117c0 Web console: add a button to get out of restricted mode,
make capability detection more robust (#12503)
2d8eb117c0 is described below
commit 2d8eb117c0bc59aa79016ed0e28519f6b7aa7afc
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Thu May 5 15:06:59 2022 -0700
Web console: add a button to get out of restricted mode, make capability
detection more robust (#12503)
* allow unrestrict
* update tests
---
.../__snapshots__/header-bar.spec.tsx.snap | 1 +
.../src/components/header-bar/header-bar.spec.tsx | 4 +-
.../src/components/header-bar/header-bar.tsx | 39 ++++++++++++++----
web-console/src/console-application.tsx | 14 +++++--
web-console/src/utils/capabilities.ts | 48 +++++++++++++++++-----
5 files changed, 84 insertions(+), 22 deletions(-)
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 bfd6aa4c3d..b3943e556a 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
@@ -76,6 +76,7 @@ exports[`HeaderBar matches snapshot 1`] = `
"queryType": "nativeAndSql",
}
}
+ onUnrestrict={[Function]}
/>
<Blueprint4.Popover2
boundary="clippingParents"
diff --git a/web-console/src/components/header-bar/header-bar.spec.tsx
b/web-console/src/components/header-bar/header-bar.spec.tsx
index cac4febb26..61650aa7aa 100644
--- a/web-console/src/components/header-bar/header-bar.spec.tsx
+++ b/web-console/src/components/header-bar/header-bar.spec.tsx
@@ -25,7 +25,9 @@ import { HeaderBar } from './header-bar';
describe('HeaderBar', () => {
it('matches snapshot', () => {
- const headerBar = shallow(<HeaderBar active="load-data"
capabilities={Capabilities.FULL} />);
+ const headerBar = shallow(
+ <HeaderBar active="load-data" capabilities={Capabilities.FULL}
onUnrestrict={() => {}} />,
+ );
expect(headerBar).toMatchSnapshot();
});
});
diff --git a/web-console/src/components/header-bar/header-bar.tsx
b/web-console/src/components/header-bar/header-bar.tsx
index 217ff80757..53042d91a2 100644
--- a/web-console/src/components/header-bar/header-bar.tsx
+++ b/web-console/src/components/header-bar/header-bar.tsx
@@ -85,10 +85,11 @@ const DruidLogo = React.memo(function DruidLogo() {
interface RestrictedModeProps {
capabilities: Capabilities;
+ onUnrestrict(capabilities: Capabilities): void;
}
const RestrictedMode = React.memo(function RestrictedMode(props:
RestrictedModeProps) {
- const { capabilities } = props;
+ const { capabilities, onUnrestrict } = props;
const mode = capabilities.getModeExtended();
let label: string;
@@ -136,7 +137,8 @@ const RestrictedMode = React.memo(function
RestrictedMode(props: RestrictedModeP
<p>
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 full version of the console can be accessed on the
Router service.
+ limited mode. The unrestricted version of the console can be
accessed on the Router
+ service.
</p>
);
break;
@@ -157,8 +159,8 @@ const RestrictedMode = React.memo(function
RestrictedMode(props: RestrictedModeP
message = (
<p>
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 full
- version of the console can be accessed on the Router service.
+ 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.
</p>
);
break;
@@ -168,7 +170,8 @@ const RestrictedMode = React.memo(function
RestrictedMode(props: RestrictedModeP
message = (
<p>
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.
+ limited mode. The unrestricted version of the console can be
accessed on the Router
+ service.
</p>
);
break;
@@ -187,6 +190,27 @@ const RestrictedMode = React.memo(function
RestrictedMode(props: RestrictedModeP
</ExternalLink>
.
</p>
+ <p>
+ 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.
+ </p>
+ <p>
+ <Button
+ icon={IconNames.WARNING_SIGN}
+ text={`Temporarily unrestrict console${capabilities.hasSql() ?
'' : ' (with SQL)'}`}
+ onClick={() => onUnrestrict(Capabilities.FULL)}
+ />
+ </p>
+ {!capabilities.hasSql() && (
+ <p>
+ <Button
+ icon={IconNames.WARNING_SIGN}
+ text="Temporarily unrestrict console (without SQL)"
+ onClick={() => onUnrestrict(Capabilities.NO_SQL)}
+ />
+ </p>
+ )}
</PopoverText>
}
position={Position.BOTTOM_RIGHT}
@@ -199,10 +223,11 @@ const RestrictedMode = React.memo(function
RestrictedMode(props: RestrictedModeP
export interface HeaderBarProps {
active: HeaderActiveTab;
capabilities: Capabilities;
+ onUnrestrict(capabilities: Capabilities): void;
}
export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
- const { active, capabilities } = props;
+ const { active, capabilities, onUnrestrict } = props;
const [aboutDialogOpen, setAboutDialogOpen] = useState(false);
const [doctorDialogOpen, setDoctorDialogOpen] = useState(false);
const [coordinatorDynamicConfigDialogOpen,
setCoordinatorDynamicConfigDialogOpen] =
@@ -371,7 +396,7 @@ export const HeaderBar = React.memo(function
HeaderBar(props: HeaderBarProps) {
/>
</NavbarGroup>
<NavbarGroup align={Alignment.RIGHT}>
- <RestrictedMode capabilities={capabilities} />
+ <RestrictedMode capabilities={capabilities}
onUnrestrict={onUnrestrict} />
<Popover2 content={configMenu} position={Position.BOTTOM_RIGHT}>
<Button minimal icon={IconNames.COG} />
</Popover2>
diff --git a/web-console/src/console-application.tsx
b/web-console/src/console-application.tsx
index 2283deac68..731002632c 100644
--- a/web-console/src/console-application.tsx
+++ b/web-console/src/console-application.tsx
@@ -55,7 +55,7 @@ export class ConsoleApplication extends React.PureComponent<
> {
private readonly capabilitiesQueryManager: QueryManager<null, Capabilities>;
- static shownNotifications() {
+ static shownServiceNotification() {
AppToaster.show({
icon: IconNames.ERROR,
intent: Intent.DANGER,
@@ -87,7 +87,7 @@ export class ConsoleApplication extends React.PureComponent<
this.capabilitiesQueryManager = new QueryManager({
processQuery: async () => {
const capabilities = await Capabilities.detectCapabilities();
- if (!capabilities) ConsoleApplication.shownNotifications();
+ if (!capabilities) ConsoleApplication.shownServiceNotification();
return capabilities || Capabilities.FULL;
},
onStateChange: ({ data, loading }) => {
@@ -107,6 +107,10 @@ export class ConsoleApplication extends
React.PureComponent<
this.capabilitiesQueryManager.terminate();
}
+ private readonly handleUnrestrict = (capabilities: Capabilities) => {
+ this.setState({ capabilities });
+ };
+
private resetInitialsWithDelay() {
setTimeout(() => {
this.taskId = undefined;
@@ -168,7 +172,11 @@ export class ConsoleApplication extends
React.PureComponent<
return (
<>
- <HeaderBar active={active} capabilities={capabilities} />
+ <HeaderBar
+ active={active}
+ capabilities={capabilities}
+ onUnrestrict={this.handleUnrestrict}
+ />
<div className={classNames('view-container', classType)}>{el}</div>
</>
);
diff --git a/web-console/src/utils/capabilities.ts
b/web-console/src/utils/capabilities.ts
index 406115dfc9..033b398425 100644
--- a/web-console/src/utils/capabilities.ts
+++ b/web-console/src/utils/capabilities.ts
@@ -40,8 +40,9 @@ export interface CapabilitiesOptions {
}
export class Capabilities {
- static STATUS_TIMEOUT = 2000;
+ static STATUS_TIMEOUT = 15000;
static FULL: Capabilities;
+ static NO_SQL: Capabilities;
static COORDINATOR_OVERLORD: Capabilities;
static COORDINATOR: Capabilities;
static OVERLORD: Capabilities;
@@ -55,7 +56,7 @@ export class Capabilities {
// Check SQL endpoint
try {
await Api.instance.post(
- '/druid/v2/sql',
+ '/druid/v2/sql?capabilities',
{ query: 'SELECT 1337', context: { timeout:
Capabilities.STATUS_TIMEOUT } },
{ timeout: Capabilities.STATUS_TIMEOUT },
);
@@ -65,7 +66,7 @@ export class Capabilities {
return; // other failure
}
try {
- await Api.instance.get('/status', { timeout:
Capabilities.STATUS_TIMEOUT });
+ await Api.instance.get('/status?capabilities', { timeout:
Capabilities.STATUS_TIMEOUT });
} catch (e) {
return; // total failure
}
@@ -73,7 +74,7 @@ export class Capabilities {
try {
await Api.instance.post(
- '/druid/v2',
+ '/druid/v2?capabilities',
{
queryType: 'dataSourceMetadata',
dataSource: '__web_console_probe__',
@@ -95,9 +96,9 @@ export class Capabilities {
return 'nativeAndSql';
}
- static async detectNode(node: 'coordinator' | 'overlord'): Promise<boolean |
undefined> {
+ static async detectManagementProxy(): Promise<boolean> {
try {
- await Api.instance.get(`/proxy/${node}/status`, {
+ await Api.instance.get(`/proxy/coordinator/status?capabilities`, {
timeout: Capabilities.STATUS_TIMEOUT,
});
} catch (e) {
@@ -107,6 +108,21 @@ export class Capabilities {
return true;
}
+ static async detectNode(node: 'coordinator' | 'overlord'): Promise<boolean> {
+ try {
+ await Api.instance.get(
+ `/druid/${node === 'overlord' ? 'indexer' :
node}/v1/isLeader?capabilities`,
+ {
+ timeout: Capabilities.STATUS_TIMEOUT,
+ },
+ );
+ } catch (e) {
+ return false;
+ }
+
+ return true;
+ }
+
static async detectCapabilities(): Promise<Capabilities | undefined> {
const capabilitiesOverride =
localStorageGetJson(LocalStorageKeys.CAPABILITIES_OVERRIDE);
if (capabilitiesOverride) return new Capabilities(capabilitiesOverride);
@@ -114,11 +130,16 @@ export class Capabilities {
const queryType = await Capabilities.detectQueryType();
if (typeof queryType === 'undefined') return;
- const coordinator = await Capabilities.detectNode('coordinator');
- if (typeof coordinator === 'undefined') return;
-
- const overlord = await Capabilities.detectNode('overlord');
- if (typeof overlord === 'undefined') return;
+ let coordinator: boolean;
+ let overlord: boolean;
+ if (queryType === 'none') {
+ // must not be running on the router, figure out what node the console
is on (or both?)
+ coordinator = await Capabilities.detectNode('coordinator');
+ overlord = await Capabilities.detectNode('overlord');
+ } else {
+ // must be running on the router, figure out if the management proxy is
working
+ coordinator = overlord = await Capabilities.detectManagementProxy();
+ }
return new Capabilities({
queryType,
@@ -204,6 +225,11 @@ Capabilities.FULL = new Capabilities({
coordinator: true,
overlord: true,
});
+Capabilities.NO_SQL = new Capabilities({
+ queryType: 'nativeOnly',
+ coordinator: true,
+ overlord: true,
+});
Capabilities.COORDINATOR_OVERLORD = new Capabilities({
queryType: 'none',
coordinator: true,
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]