mirror of https://github.com/apache/druid.git
Add overlord edit dialog to allow user to change overlord dynamic config on the fly (#7308)
* Add overlord edit dialog to allow user to edit overlord dynmamic config on the fly * Set config to {} if druid return nothing for overlord config
This commit is contained in:
parent
b0271b4f4d
commit
494c1a2ef8
|
@ -53,7 +53,8 @@ export const IconNames = {
|
||||||
ARROW_UP: "arrow-up" as "arrow-up",
|
ARROW_UP: "arrow-up" as "arrow-up",
|
||||||
ARROW_DOWN: "arrow-down" as "arrow-down",
|
ARROW_DOWN: "arrow-down" as "arrow-down",
|
||||||
PROPERTIES: "properties" as "properties",
|
PROPERTIES: "properties" as "properties",
|
||||||
BUILD: "build" as "build"
|
BUILD: "build" as "build",
|
||||||
|
WRENCH: "wrench" as "wrench"
|
||||||
};
|
};
|
||||||
export type IconNames = typeof IconNames[keyof typeof IconNames];
|
export type IconNames = typeof IconNames[keyof typeof IconNames];
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import * as React from 'react';
|
||||||
import { Alignment, IconNames, Navbar, NavbarDivider, NavbarGroup } from "../components/filler";
|
import { Alignment, IconNames, Navbar, NavbarDivider, NavbarGroup } from "../components/filler";
|
||||||
import { AboutDialog } from "../dialogs/about-dialog";
|
import { AboutDialog } from "../dialogs/about-dialog";
|
||||||
import { CoordinatorDynamicConfigDialog } from '../dialogs/coordinator-dynamic-config';
|
import { CoordinatorDynamicConfigDialog } from '../dialogs/coordinator-dynamic-config';
|
||||||
|
import { OverlordDynamicConfigDialog } from "../dialogs/overlord-dynamic-config";
|
||||||
import {
|
import {
|
||||||
DRUID_DOCS,
|
DRUID_DOCS,
|
||||||
DRUID_GITHUB,
|
DRUID_GITHUB,
|
||||||
|
@ -42,6 +43,7 @@ export interface HeaderBarProps extends React.Props<any> {
|
||||||
export interface HeaderBarState {
|
export interface HeaderBarState {
|
||||||
aboutDialogOpen: boolean;
|
aboutDialogOpen: boolean;
|
||||||
coordinatorDynamicConfigDialogOpen: boolean;
|
coordinatorDynamicConfigDialogOpen: boolean;
|
||||||
|
overlordDynamicConfigDialogOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HeaderBar extends React.Component<HeaderBarProps, HeaderBarState> {
|
export class HeaderBar extends React.Component<HeaderBarProps, HeaderBarState> {
|
||||||
|
@ -49,7 +51,8 @@ export class HeaderBar extends React.Component<HeaderBarProps, HeaderBarState> {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
aboutDialogOpen: false,
|
aboutDialogOpen: false,
|
||||||
coordinatorDynamicConfigDialogOpen: false
|
coordinatorDynamicConfigDialogOpen: false,
|
||||||
|
overlordDynamicConfigDialogOpen: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +107,7 @@ export class HeaderBar extends React.Component<HeaderBarProps, HeaderBarState> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { active } = this.props;
|
const { active } = this.props;
|
||||||
const { aboutDialogOpen, coordinatorDynamicConfigDialogOpen } = this.state;
|
const { aboutDialogOpen, coordinatorDynamicConfigDialogOpen, overlordDynamicConfigDialogOpen } = this.state;
|
||||||
|
|
||||||
const legacyMenu = <Menu>
|
const legacyMenu = <Menu>
|
||||||
<MenuItem iconName={IconNames.GRAPH} text="Legacy coordinator console" href={LEGACY_COORDINATOR_CONSOLE} target="_blank" />
|
<MenuItem iconName={IconNames.GRAPH} text="Legacy coordinator console" href={LEGACY_COORDINATOR_CONSOLE} target="_blank" />
|
||||||
|
@ -120,6 +123,7 @@ export class HeaderBar extends React.Component<HeaderBarProps, HeaderBarState> {
|
||||||
|
|
||||||
const configMenu = <Menu>
|
const configMenu = <Menu>
|
||||||
<MenuItem iconName={IconNames.COG} text="Coordinator dynamic config" onClick={() => this.setState({ coordinatorDynamicConfigDialogOpen: true })}/>
|
<MenuItem iconName={IconNames.COG} text="Coordinator dynamic config" onClick={() => this.setState({ coordinatorDynamicConfigDialogOpen: true })}/>
|
||||||
|
<MenuItem iconName={IconNames.WRENCH} text="Overlord dynamic config" onClick={() => this.setState({ overlordDynamicConfigDialogOpen: true })}/>
|
||||||
<MenuItem iconName={IconNames.PROPERTIES} className={classNames(Classes.MINIMAL, { 'pt-active': active === 'lookups' })} text="Lookups" href="#lookups"/>
|
<MenuItem iconName={IconNames.PROPERTIES} className={classNames(Classes.MINIMAL, { 'pt-active': active === 'lookups' })} text="Lookups" href="#lookups"/>
|
||||||
</Menu>;
|
</Menu>;
|
||||||
|
|
||||||
|
@ -153,6 +157,9 @@ export class HeaderBar extends React.Component<HeaderBarProps, HeaderBarState> {
|
||||||
{ coordinatorDynamicConfigDialogOpen ? <CoordinatorDynamicConfigDialog
|
{ coordinatorDynamicConfigDialogOpen ? <CoordinatorDynamicConfigDialog
|
||||||
onClose={() => this.setState({ coordinatorDynamicConfigDialogOpen: false })}
|
onClose={() => this.setState({ coordinatorDynamicConfigDialogOpen: false })}
|
||||||
/> : null }
|
/> : null }
|
||||||
|
{ overlordDynamicConfigDialogOpen ? <OverlordDynamicConfigDialog
|
||||||
|
onClose={() => this.setState({ overlordDynamicConfigDialogOpen: false })}
|
||||||
|
/> : null }
|
||||||
</Navbar>;
|
</Navbar>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.overlord-dynamic-config {
|
||||||
|
|
||||||
|
&.pt-dialog {
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-top: 5vh;
|
||||||
|
|
||||||
|
.pt-dialog-body {
|
||||||
|
max-height: 70vh;
|
||||||
|
|
||||||
|
.auto-form {
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.ace_editor {
|
||||||
|
height: 25vh !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.html-select {
|
||||||
|
width: 195px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-comment {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 0 15px;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
max-width: 200px;
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
* 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 { Intent } from "@blueprintjs/core";
|
||||||
|
import axios from "axios";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { AutoForm } from "../components/auto-form";
|
||||||
|
import { IconNames } from "../components/filler";
|
||||||
|
import { AppToaster } from "../singletons/toaster";
|
||||||
|
import { getDruidErrorMessage } from "../utils";
|
||||||
|
|
||||||
|
import { SnitchDialog } from "./snitch-dialog";
|
||||||
|
|
||||||
|
import "./overlord-dynamic-config.scss";
|
||||||
|
|
||||||
|
export interface OverlordDynamicConfigDialogProps extends React.Props<any> {
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OverlordDynamicConfigDialogState {
|
||||||
|
dynamicConfig: Record<string, any> | null;
|
||||||
|
allJSONValid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OverlordDynamicConfigDialog extends React.Component<OverlordDynamicConfigDialogProps, OverlordDynamicConfigDialogState> {
|
||||||
|
constructor(props: OverlordDynamicConfigDialogProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
dynamicConfig: null,
|
||||||
|
allJSONValid: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConfig() {
|
||||||
|
let config: Record<string, any> | null = null;
|
||||||
|
try {
|
||||||
|
const configResp = await axios.get("/druid/indexer/v1/worker");
|
||||||
|
config = configResp.data || {};
|
||||||
|
} catch (e) {
|
||||||
|
AppToaster.show({
|
||||||
|
iconName: IconNames.ERROR,
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
message: `Could not load overlord dynamic config: ${getDruidErrorMessage(e)}`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
dynamicConfig: config
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveConfig = async (author: string, comment: string) => {
|
||||||
|
const { onClose } = this.props;
|
||||||
|
const newState: any = this.state.dynamicConfig;
|
||||||
|
try {
|
||||||
|
await axios.post("/druid/indexer/v1/worker", newState, {
|
||||||
|
headers: {
|
||||||
|
"X-Druid-Author": author,
|
||||||
|
"X-Druid-Comment": comment
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
AppToaster.show({
|
||||||
|
iconName: IconNames.ERROR,
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
message: `Could not save overlord dynamic config: ${getDruidErrorMessage(e)}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'Saved overlord dynamic config',
|
||||||
|
intent: Intent.SUCCESS
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onClose } = this.props;
|
||||||
|
const { dynamicConfig, allJSONValid } = this.state;
|
||||||
|
|
||||||
|
return <SnitchDialog
|
||||||
|
className="overlord-dynamic-config"
|
||||||
|
isOpen
|
||||||
|
onSave={this.saveConfig}
|
||||||
|
onClose={onClose}
|
||||||
|
title="Overlord dynamic config"
|
||||||
|
saveDisabled={!allJSONValid}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Edit the overlord dynamic configuration on the fly.
|
||||||
|
For more information please refer to the <a href="http://druid.io/docs/latest/configuration/index.html#overlord-dynamic-configuration" target="_blank">documentation</a>.
|
||||||
|
</p>
|
||||||
|
<AutoForm
|
||||||
|
fields={[
|
||||||
|
{
|
||||||
|
name: "selectStrategy",
|
||||||
|
type: "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "autoScaler",
|
||||||
|
type: "json"
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
model={dynamicConfig}
|
||||||
|
onChange={m => this.setState({ dynamicConfig: m })}
|
||||||
|
updateJSONValidity={e => this.setState({allJSONValid: e})}
|
||||||
|
/>
|
||||||
|
</SnitchDialog>;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue