mirror of https://github.com/linkerd/linkerd2.git
388 lines
11 KiB
JavaScript
388 lines
11 KiB
JavaScript
import _ from 'lodash';
|
|
import PropTypes from 'prop-types';
|
|
import React from 'react';
|
|
import { addUrlProps, UrlQueryParamTypes } from 'react-url-query';
|
|
import {
|
|
AutoComplete,
|
|
Button,
|
|
Col,
|
|
Form,
|
|
Icon,
|
|
Input,
|
|
Row,
|
|
Select
|
|
} from 'antd';
|
|
import {
|
|
defaultMaxRps,
|
|
emptyTapQuery,
|
|
httpMethods,
|
|
tapQueryProps,
|
|
tapQueryPropType
|
|
} from './util/TapUtils.jsx';
|
|
|
|
const colSpan = 5;
|
|
const rowGutter = 16;
|
|
|
|
// you can also tap resources to tap all pods in the resource
|
|
const resourceTypes = [
|
|
"deployment",
|
|
"daemonset",
|
|
"pod",
|
|
"replicationcontroller",
|
|
"statefulset"
|
|
];
|
|
|
|
const getResourceList = (resourcesByNs, ns) => {
|
|
return resourcesByNs[ns] || _.uniq(_.flatten(_.values(resourcesByNs)));
|
|
};
|
|
|
|
const urlPropsQueryConfig = _.mapValues(tapQueryProps, () => {
|
|
return { type: UrlQueryParamTypes.string };
|
|
});
|
|
|
|
class TapQueryForm extends React.Component {
|
|
static propTypes = {
|
|
enableAdvancedForm: PropTypes.bool,
|
|
handleTapClear: PropTypes.func,
|
|
handleTapStart: PropTypes.func.isRequired,
|
|
handleTapStop: PropTypes.func.isRequired,
|
|
query: tapQueryPropType.isRequired,
|
|
tapIsClosing: PropTypes.bool,
|
|
tapRequestInProgress: PropTypes.bool.isRequired,
|
|
updateQuery: PropTypes.func.isRequired
|
|
}
|
|
|
|
static defaultProps = {
|
|
enableAdvancedForm: true,
|
|
handleTapClear: _.noop,
|
|
tapIsClosing: false
|
|
}
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
let query = _.merge({}, props.query, _.pick(this.props, _.keys(tapQueryProps)));
|
|
props.updateQuery(query);
|
|
|
|
let showAdvancedForm = _.some(
|
|
_.omit(query, ['namespace', 'resource']),
|
|
v => !_.isEmpty(v));
|
|
|
|
this.state = {
|
|
query,
|
|
showAdvancedForm,
|
|
authoritiesByNs: {},
|
|
resourcesByNs: {},
|
|
autocomplete: {
|
|
namespace: [],
|
|
resource: [],
|
|
toNamespace: [],
|
|
toResource: [],
|
|
authority: []
|
|
},
|
|
};
|
|
}
|
|
|
|
static getDerivedStateFromProps(props, state) {
|
|
if (!_.isEqual(props.resourcesByNs, state.resourcesByNs)) {
|
|
let resourcesByNs = props.resourcesByNs;
|
|
let authoritiesByNs = props.authoritiesByNs;
|
|
let namespaces = _.sortBy(_.keys(resourcesByNs));
|
|
let resourceNames = getResourceList(resourcesByNs, state.query.namespace);
|
|
let toResourceNames = getResourceList(resourcesByNs, state.query.toNamespace);
|
|
let authorities = getResourceList(authoritiesByNs, state.query.namespace);
|
|
|
|
return _.merge(state, {
|
|
resourcesByNs,
|
|
authoritiesByNs,
|
|
autocomplete: {
|
|
namespace: namespaces,
|
|
resource: resourceNames,
|
|
toNamespace: namespaces,
|
|
toResource: toResourceNames,
|
|
authority: authorities
|
|
}
|
|
});
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
toggleAdvancedForm = show => {
|
|
this.setState({
|
|
showAdvancedForm: show
|
|
});
|
|
}
|
|
|
|
handleFormChange = (name, scopeResource, shouldScopeAuthority) => {
|
|
let state = {
|
|
query: this.state.query,
|
|
autocomplete: this.state.autocomplete
|
|
};
|
|
|
|
return formVal => {
|
|
state.query[name] = formVal;
|
|
this.handleUrlUpdate(name, formVal);
|
|
|
|
if (!_.isNil(scopeResource)) {
|
|
// scope the available typeahead resources to the selected namespace
|
|
state.autocomplete[scopeResource] = this.state.resourcesByNs[formVal];
|
|
if (_.isEmpty(state.query[scopeResource]) || state.query[scopeResource].indexOf("namespace") !== -1) {
|
|
state.query[scopeResource] = `namespace/${formVal}`;
|
|
this.handleUrlUpdate(scopeResource, `namespace/${formVal}`);
|
|
}
|
|
}
|
|
|
|
if (shouldScopeAuthority) {
|
|
state.autocomplete.authority = this.state.authoritiesByNs[formVal];
|
|
}
|
|
|
|
this.setState(state);
|
|
this.props.updateQuery(state.query);
|
|
};
|
|
}
|
|
|
|
// Each time state.query is updated, this method calls the equivalent
|
|
// onChange method to reflect the update in url query params. These onChange
|
|
// methods are automatically added to props by react-url-query.
|
|
handleUrlUpdate = (name, formVal) => {
|
|
this.props[`onChange${_.upperFirst(name)}`](formVal);
|
|
}
|
|
|
|
handleFormEvent = name => {
|
|
let state = {
|
|
query: this.state.query
|
|
};
|
|
|
|
return event => {
|
|
state.query[name] = event.target.value;
|
|
this.handleUrlUpdate(name, event.target.value);
|
|
this.setState(state);
|
|
this.props.updateQuery(state.query);
|
|
};
|
|
}
|
|
|
|
autoCompleteData = name => {
|
|
return _(this.state.autocomplete[name])
|
|
.filter(d => d.indexOf(this.state.query[name]) !== -1)
|
|
.uniq()
|
|
.sortBy()
|
|
.value();
|
|
}
|
|
|
|
resetTapForm = () => {
|
|
this.setState({
|
|
query: emptyTapQuery()
|
|
});
|
|
|
|
_.each(this.state.query, (_val, name) => {
|
|
this.handleUrlUpdate(name, null);
|
|
});
|
|
|
|
this.props.updateQuery(emptyTapQuery(), true);
|
|
this.props.handleTapClear();
|
|
}
|
|
|
|
renderAdvancedTapForm = () => {
|
|
return (
|
|
<React.Fragment>
|
|
<Row gutter={rowGutter}>
|
|
<Col span={colSpan}>
|
|
<Form.Item>
|
|
<Select
|
|
showSearch
|
|
allowClear
|
|
placeholder="To Namespace"
|
|
optionFilterProp="children"
|
|
onChange={this.handleFormChange("toNamespace", "toResource")}
|
|
value={this.state.query.toNamespace}>
|
|
{
|
|
_.map(this.state.autocomplete.toNamespace, (n, i) => (
|
|
<Select.Option key={`ns-dr-${i}`} value={n}>{n}</Select.Option>
|
|
))
|
|
}
|
|
</Select>
|
|
</Form.Item>
|
|
</Col>
|
|
|
|
<Col span={colSpan}>
|
|
<Form.Item>
|
|
{this.renderResourceSelect("toResource", "toNamespace")}
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
|
|
<Row gutter={rowGutter}>
|
|
<Col span={2 * colSpan}>
|
|
<Form.Item
|
|
extra="Display requests with this :authority">
|
|
|
|
<AutoComplete
|
|
dataSource={this.autoCompleteData("authority")}
|
|
onSelect={this.handleFormChange("authority")}
|
|
onSearch={this.handleFormChange("authority")}
|
|
placeholder="Authority"
|
|
value={this.state.query.authority} />
|
|
</Form.Item>
|
|
</Col>
|
|
|
|
<Col span={2 * colSpan}>
|
|
<Form.Item
|
|
extra="Display requests with paths that start with this prefix">
|
|
<Input
|
|
placeholder="Path"
|
|
onChange={this.handleFormEvent("path")}
|
|
value={this.state.query.path} />
|
|
</Form.Item>
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
<Row gutter={rowGutter}>
|
|
<Col span={colSpan}>
|
|
<Form.Item
|
|
extra="Display requests with this scheme">
|
|
<Input
|
|
placeholder="Scheme"
|
|
onChange={this.handleFormEvent("scheme")}
|
|
value={this.state.query.scheme} />
|
|
</Form.Item>
|
|
</Col>
|
|
|
|
<Col span={colSpan}>
|
|
<Form.Item
|
|
extra={`Maximum requests per second to tap (default ${defaultMaxRps})`}>
|
|
<Input
|
|
placeholder="Max RPS"
|
|
onChange={this.handleFormEvent("maxRps")}
|
|
value={this.state.query.maxRps} />
|
|
</Form.Item>
|
|
</Col>
|
|
|
|
<Col span={colSpan}>
|
|
<Form.Item
|
|
extra="Display requests with this HTTP method">
|
|
<Select
|
|
allowClear
|
|
placeholder="HTTP method"
|
|
onChange={this.handleFormChange("method")}
|
|
value={this.state.query.method}>
|
|
{
|
|
_.map(httpMethods, m =>
|
|
<Select.Option key={`method-select-${m}`} value={m}>{m}</Select.Option>
|
|
)
|
|
}
|
|
</Select>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
|
|
renderResourceSelect = (resourceKey, namespaceKey) => {
|
|
let selectedNs = this.state.query[namespaceKey];
|
|
let nsEmpty = _.isNil(selectedNs) || _.isEmpty(selectedNs);
|
|
|
|
let resourceOptions = _.concat(
|
|
resourceTypes,
|
|
this.state.autocomplete[resourceKey] || [],
|
|
nsEmpty ? [] : [`namespace/${selectedNs}`]
|
|
);
|
|
|
|
return (
|
|
<Select
|
|
showSearch
|
|
allowClear
|
|
disabled={nsEmpty}
|
|
value={nsEmpty ? _.startCase(resourceKey) : this.state.query[resourceKey]}
|
|
placeholder={_.startCase(resourceKey)}
|
|
optionFilterProp="children"
|
|
onChange={this.handleFormChange(resourceKey)}>
|
|
{
|
|
_.map(_.sortBy(resourceOptions), resource => (
|
|
<Select.Option
|
|
key={`${resourceKey}-${resource}`}
|
|
value={resource}>{resource}
|
|
</Select.Option>
|
|
)
|
|
)
|
|
}
|
|
</Select>
|
|
);
|
|
}
|
|
|
|
renderTapButton = (tapInProgress, tapIsClosing) => {
|
|
if (tapIsClosing) {
|
|
return (<Button type="primary" className="tap-ctrl tap-stop" disabled={true}>Stop</Button>);
|
|
} else if (tapInProgress) {
|
|
return (<Button type="primary" className="tap-ctrl tap-stop" onClick={this.props.handleTapStop}>Stop</Button>);
|
|
} else {
|
|
return (
|
|
<Button
|
|
type="primary"
|
|
className="tap-ctrl tap-start"
|
|
disabled={!this.state.query.namespace || !this.state.query.resource}
|
|
onClick={this.props.handleTapStart}>
|
|
Start
|
|
</Button>);
|
|
}
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<Form className="tap-form">
|
|
<Row gutter={rowGutter}>
|
|
<Col span={colSpan}>
|
|
<Form.Item>
|
|
<Select
|
|
showSearch
|
|
allowClear
|
|
placeholder="Namespace"
|
|
optionFilterProp="children"
|
|
onChange={this.handleFormChange("namespace", "resource", true)}
|
|
value={this.state.query.namespace}>
|
|
{
|
|
_.map(this.state.autocomplete.namespace, (n, i) => (
|
|
<Select.Option key={`ns-dr-${i}`} value={n}>{n}</Select.Option>
|
|
))
|
|
}
|
|
</Select>
|
|
</Form.Item>
|
|
</Col>
|
|
|
|
<Col span={colSpan}>
|
|
<Form.Item>
|
|
{this.renderResourceSelect("resource", "namespace")}
|
|
</Form.Item>
|
|
</Col>
|
|
|
|
<Col span={colSpan}>
|
|
<Form.Item>
|
|
{ this.renderTapButton(this.props.tapRequestInProgress, this.props.tapIsClosing) }
|
|
<Button onClick={this.resetTapForm} disabled={this.props.tapRequestInProgress}>Reset</Button>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
|
|
{
|
|
!this.props.enableAdvancedForm ? null :
|
|
<React.Fragment>
|
|
<Button
|
|
className="tap-form-toggle"
|
|
onClick={() => this.toggleAdvancedForm(!this.state.showAdvancedForm)}>
|
|
{
|
|
this.state.showAdvancedForm ? "Hide filters" : "Show more request filters"
|
|
} <Icon type={this.state.showAdvancedForm ? 'up' : 'down'} />
|
|
</Button>
|
|
|
|
{ !this.state.showAdvancedForm ? null : this.renderAdvancedTapForm() }
|
|
</React.Fragment>
|
|
}
|
|
</Form>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default addUrlProps({ urlPropsQueryConfig })(TapQueryForm);
|