mirror of https://github.com/linkerd/linkerd2.git
Add a Create Service Profile dialog (#1933)
Add the ability to create and download a service profile from the web UI. This form will be displayed in the call to action if no route metrics are found.
This commit is contained in:
parent
7169eaef27
commit
442685674b
|
@ -1,24 +1,130 @@
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
import Card from '@material-ui/core/Card';
|
import Card from '@material-ui/core/Card';
|
||||||
import CardContent from '@material-ui/core/CardContent';
|
import CardContent from '@material-ui/core/CardContent';
|
||||||
|
import Dialog from '@material-ui/core/Dialog';
|
||||||
|
import DialogActions from '@material-ui/core/DialogActions';
|
||||||
|
import DialogContent from '@material-ui/core/DialogContent';
|
||||||
|
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||||
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import TextField from '@material-ui/core/TextField';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { withContext } from './util/AppContext.jsx';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
|
button: {
|
||||||
|
margin: theme.spacing.unit,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
},
|
||||||
|
textField: {
|
||||||
|
marginLeft: theme.spacing.unit,
|
||||||
|
marginRight: theme.spacing.unit,
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
root: {
|
root: {
|
||||||
marginTop: theme.spacing.unit * 3,
|
marginTop: theme.spacing.unit * 3,
|
||||||
marginBottom: theme.spacing.unit * 3,
|
marginBottom: theme.spacing.unit * 3,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
class ConfigureProfilesMsg extends React.Component {
|
class ConfigureProfilesMsg extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
open: false,
|
||||||
|
query: {
|
||||||
|
service: "",
|
||||||
|
namespace: ""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClickOpen = () => {
|
||||||
|
this.setState({ open: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClose = () => {
|
||||||
|
this.setState({ open: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange = name => {
|
||||||
|
let state = this.state;
|
||||||
|
|
||||||
|
return e => {
|
||||||
|
state.query[name] = e.target.value;
|
||||||
|
this.setState(state);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDownloadProfileForm = () => {
|
||||||
|
const { api, classes } = this.props;
|
||||||
|
let { query } = this.state;
|
||||||
|
|
||||||
|
let downloadUrl = api.prefixedUrl(`/profiles/new?service=${query.service}&namespace=${query.namespace}`);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Button
|
||||||
|
className={classes.button}
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
onClick={this.handleClickOpen}>Create Service Profile
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={this.state.open}
|
||||||
|
onClose={this.handleClose}
|
||||||
|
aria-labelledby="form-dialog-title">
|
||||||
|
<DialogTitle id="form-dialog-title">New service profile</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
To create a service profile, download a profile and then apply it with `kubectl apply`.
|
||||||
|
</DialogContentText>
|
||||||
|
<TextField
|
||||||
|
id="service"
|
||||||
|
label="Service"
|
||||||
|
className={classes.textField}
|
||||||
|
value={this.state.service}
|
||||||
|
onChange={this.handleChange('service')}
|
||||||
|
margin="normal" />
|
||||||
|
<TextField
|
||||||
|
id="namespace"
|
||||||
|
label="Namespace"
|
||||||
|
className={classes.textField}
|
||||||
|
value={this.state.name}
|
||||||
|
onChange={this.handleChange('namespace')}
|
||||||
|
margin="normal" />
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={this.handleClose} color="primary">Cancel</Button>
|
||||||
|
<a href={downloadUrl} style={{ textDecoration: 'none' }}>
|
||||||
|
<Button
|
||||||
|
disabled={_.isEmpty(query.service) || _.isEmpty(query.namespace)}
|
||||||
|
onClick={this.handleClose}
|
||||||
|
color="primary">Download
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={classes.root}>
|
<Card className={classes.root}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography>
|
<Typography component="div">
|
||||||
No traffic found. Does the service have a service profile? You can create one with the `linkerd profile` command.
|
No traffic found. Does the service have a service profile?
|
||||||
|
{this.renderDownloadProfileForm()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -27,7 +133,10 @@ class ConfigureProfilesMsg extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigureProfilesMsg.propTypes = {
|
ConfigureProfilesMsg.propTypes = {
|
||||||
|
api: PropTypes.shape({
|
||||||
|
prefixedFetch: PropTypes.func.isRequired,
|
||||||
|
}).isRequired,
|
||||||
classes: PropTypes.shape({}).isRequired
|
classes: PropTypes.shape({}).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withStyles(styles, { withTheme: true })(ConfigureProfilesMsg);
|
export default withContext(withStyles(styles, { withTheme: true })(ConfigureProfilesMsg));
|
||||||
|
|
|
@ -64,6 +64,7 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
|
||||||
"1h": "1 hour"
|
"1h": "1 hour"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// for getting json api results
|
||||||
const apiFetch = path => {
|
const apiFetch = path => {
|
||||||
if (!_.isEmpty(pathPrefix)) {
|
if (!_.isEmpty(pathPrefix)) {
|
||||||
path = `${pathPrefix}${path}`;
|
path = `${pathPrefix}${path}`;
|
||||||
|
@ -72,6 +73,15 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
|
||||||
return makeCancelable(fetch(path), r => r.json());
|
return makeCancelable(fetch(path), r => r.json());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// for getting non-json results
|
||||||
|
const prefixedUrl = path => {
|
||||||
|
if (!_.isEmpty(pathPrefix)) {
|
||||||
|
path = `${pathPrefix}${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
const fetchMetrics = path => {
|
const fetchMetrics = path => {
|
||||||
if (path.indexOf("window") === -1) {
|
if (path.indexOf("window") === -1) {
|
||||||
if (path.indexOf("?") === -1) {
|
if (path.indexOf("?") === -1) {
|
||||||
|
@ -193,6 +203,7 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetch: apiFetch,
|
fetch: apiFetch,
|
||||||
|
prefixedUrl,
|
||||||
fetchMetrics,
|
fetchMetrics,
|
||||||
fetchPods,
|
fetchPods,
|
||||||
fetchServices,
|
fetchServices,
|
||||||
|
|
Loading…
Reference in New Issue