mirror of https://github.com/linkerd/linkerd2.git
Fixes #2490 The following PR adds validation to the New Service Profile popup form in the linkerd dashboard for the service and namespace user input. * Add validation to the Service Profile form (#2490) Signed-off-by: Gaurav Kumar <gaurav.kumar9825@gmail.com> * Change RegExp to handle all cases Signed-off-by: Gaurav Kumar <gaurav.kumar9825@gmail.com> * Fix link and error msg issue Signed-off-by: Gaurav Kumar <gaurav.kumar9825@gmail.com> * Refactor code Signed-off-by: Gaurav Kumar <gaurav.kumar9825@gmail.com> * Revert to onBlur and remove warning on field clear Signed-off-by: Gaurav Kumar <gaurav.kumar9825@gmail.com> * Refactor code to make it more readable Signed-off-by: Gaurav Kumar <gaurav.kumar9825@gmail.com> * Reset state to initialstate on form close Signed-off-by: Gaurav Kumar <gaurav.kumar9825@gmail.com>
This commit is contained in:
parent
9eab0e28a6
commit
23454d248d
|
@ -5,11 +5,14 @@ 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 FormControl from '@material-ui/core/FormControl';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Input from '@material-ui/core/Input';
|
||||
import InputLabel from '@material-ui/core/InputLabel';
|
||||
import NoteAddIcon from '@material-ui/icons/NoteAdd';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import _isEmpty from 'lodash/isEmpty';
|
||||
import { withContext } from './util/AppContext.jsx';
|
||||
|
@ -29,6 +32,7 @@ const styles = theme => ({
|
|||
textField: {
|
||||
marginLeft: theme.spacing.unit,
|
||||
marginRight: theme.spacing.unit,
|
||||
marginTop: 2 * theme.spacing.unit,
|
||||
width: 200,
|
||||
},
|
||||
root: {
|
||||
|
@ -36,14 +40,24 @@ const styles = theme => ({
|
|||
marginBottom: theme.spacing.unit * 3,
|
||||
},
|
||||
});
|
||||
|
||||
const dns1035ServiceFmt = '^[a-z]([-a-z0-9]*[a-z0-9])?$';
|
||||
const dns1123NamespaceFmt = '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$';
|
||||
const serviceNameRegexp = RegExp(dns1035ServiceFmt);
|
||||
const namespaceNameRegexp = RegExp(dns1123NamespaceFmt);
|
||||
|
||||
class ConfigureProfilesMsg extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
open: false,
|
||||
error: {
|
||||
service: false,
|
||||
namespace: false
|
||||
},
|
||||
query: {
|
||||
service: "",
|
||||
namespace: ""
|
||||
service: '',
|
||||
namespace: ''
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -53,7 +67,17 @@ class ConfigureProfilesMsg extends React.Component {
|
|||
};
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({ open: false });
|
||||
this.setState({
|
||||
open: false,
|
||||
error: {
|
||||
service: false,
|
||||
namespace: false
|
||||
},
|
||||
query: {
|
||||
service: '',
|
||||
namespace: ''
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = name => {
|
||||
|
@ -61,13 +85,30 @@ class ConfigureProfilesMsg extends React.Component {
|
|||
|
||||
return e => {
|
||||
state.query[name] = e.target.value;
|
||||
state.error[name] = false;
|
||||
this.setState(state);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
validateFields = (type, name) => {
|
||||
let error = this.state.error;
|
||||
|
||||
if (_isEmpty(name)) {
|
||||
error[type] = false;
|
||||
} else {
|
||||
let match = type === 'service' ?
|
||||
serviceNameRegexp.test(name) :
|
||||
namespaceNameRegexp.test(name);
|
||||
|
||||
error[type] = !match;
|
||||
}
|
||||
|
||||
this.setState({ error });
|
||||
};
|
||||
|
||||
renderDownloadProfileForm = () => {
|
||||
const { api, classes, showAsIcon } = this.props;
|
||||
let { query } = this.state;
|
||||
let { query, error } = this.state;
|
||||
|
||||
let downloadUrl = api.prefixedUrl(`/profiles/new?service=${query.service}&namespace=${query.namespace}`);
|
||||
let button;
|
||||
|
@ -89,14 +130,27 @@ class ConfigureProfilesMsg extends React.Component {
|
|||
variant="outlined"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={this.handleClickOpen}>Create Service Profile
|
||||
onClick={this.handleClickOpen}>
|
||||
Create Service Profile
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
let disableDownloadButton = _isEmpty(query.service) || _isEmpty(query.namespace) ||
|
||||
error.service || error.namespace;
|
||||
let downloadButton = (
|
||||
<Button
|
||||
disabled={disableDownloadButton}
|
||||
onClick={() => this.handleClose(downloadUrl)}
|
||||
color="primary">
|
||||
Download
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{button}
|
||||
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
|
@ -104,33 +158,55 @@ class ConfigureProfilesMsg extends React.Component {
|
|||
<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`.
|
||||
To create a service profile, download a profile and then apply it
|
||||
with `kubectl apply`.
|
||||
</DialogContentText>
|
||||
<TextField
|
||||
id="service"
|
||||
label="Service"
|
||||
<FormControl
|
||||
className={classes.textField}
|
||||
value={this.state.service}
|
||||
onChange={this.handleChange('service')}
|
||||
margin="normal" />
|
||||
<TextField
|
||||
id="namespace"
|
||||
label="Namespace"
|
||||
onBlur={() => this.validateFields('service', query.service)}
|
||||
error={error.service}>
|
||||
<InputLabel htmlFor="component-error">Service</InputLabel>
|
||||
<Input
|
||||
id="component-error"
|
||||
value={this.state.service}
|
||||
onChange={this.handleChange('service')}
|
||||
aria-describedby="component-error-text" />
|
||||
{error.service && (
|
||||
<FormHelperText id="component-error-text">
|
||||
Service name must consist of lower case alphanumeric characters or -
|
||||
start with an alphabetic character, and end with an alphanumeric character
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
<FormControl
|
||||
className={classes.textField}
|
||||
value={this.state.name}
|
||||
onChange={this.handleChange('namespace')}
|
||||
margin="normal" />
|
||||
onBlur={() => this.validateFields('namespace', query.namespace)}
|
||||
error={error.namespace}>
|
||||
<InputLabel htmlFor="component-error">Namespace</InputLabel>
|
||||
<Input
|
||||
id="component-error"
|
||||
value={this.state.name}
|
||||
onChange={this.handleChange('namespace')}
|
||||
aria-describedby="component-error-text" />
|
||||
{error.namespace && (
|
||||
<FormHelperText id="component-error-text">
|
||||
Namespace must consist of lower case alphanumeric characters or -
|
||||
and must start and end with an alphanumeric character
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
</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>
|
||||
<Button onClick={this.handleClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
{disableDownloadButton ?
|
||||
downloadButton :
|
||||
<a
|
||||
href={disableDownloadButton ? '' : downloadUrl}
|
||||
style={{ textDecoration: 'none' }}>{downloadButton}
|
||||
</a>
|
||||
}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</React.Fragment>
|
||||
|
@ -145,8 +221,9 @@ class ConfigureProfilesMsg extends React.Component {
|
|||
return (
|
||||
<CardContent>
|
||||
<Typography component="div">
|
||||
No named route traffic found. This could be because the service is not receiving any traffic,
|
||||
or because there is no service profile configured. Does the service have a service profile?
|
||||
No named route traffic found. This could be because the service is
|
||||
not receiving any traffic, or because there is no service profile
|
||||
configured. Does the service have a service profile?
|
||||
{this.renderDownloadProfileForm()}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
|
|
Loading…
Reference in New Issue