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:
Qi Shu 2019-03-21 03:38:35 -07:00 committed by Clint Wylie
parent b0271b4f4d
commit 494c1a2ef8
4 changed files with 194 additions and 3 deletions

View File

@ -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];

View File

@ -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>;
} }
} }

View File

@ -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;
}
}
}
}

View File

@ -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>;
}
}