mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[ML] Create watch from new jobs list (#21112)
* [ML] [WIP] Create watch from new jobs list * removing comments * adding interval calculation * adding checkbox to start datafeed modal * adding proptypes check to SelectSeverity * fixing typo * changes based on review * correcting input labels
This commit is contained in:
parent
770ff205cd
commit
33bad59e1c
13 changed files with 485 additions and 16 deletions
|
@ -6,4 +6,3 @@
|
|||
|
||||
|
||||
import './select_severity_directive';
|
||||
import './styles/main.less';
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
/*
|
||||
* React component for rendering a select element with threshold levels.
|
||||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
|
@ -18,6 +19,8 @@ import {
|
|||
EuiHealth,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import './styles/main.less';
|
||||
|
||||
import { getSeverityColor } from 'plugins/ml/../common/util/anomaly_utils';
|
||||
|
||||
const OPTIONS = [
|
||||
|
@ -103,5 +106,8 @@ class SelectSeverity extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
SelectSeverity.propTypes = {
|
||||
mlSelectSeverityService: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export { SelectSeverity };
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, {
|
||||
Component,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import { loadFullJob } from '../utils';
|
||||
import { mlCreateWatchService } from '../../../../jobs/new_job/simple/components/watcher/create_watch_service';
|
||||
import { CreateWatch } from '../../../../jobs/new_job/simple/components/watcher/create_watch_view';
|
||||
|
||||
|
||||
function getSuccessToast(id, url) {
|
||||
return {
|
||||
title: `Watch ${id} created successfully`,
|
||||
text: (
|
||||
<React.Fragment>
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
href={url}
|
||||
target="_blank"
|
||||
iconType="link"
|
||||
>
|
||||
Edit watch
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</React.Fragment>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
export class CreateWatchFlyout extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
jobId: null,
|
||||
bucketSpan: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (typeof this.props.setShowFunction === 'function') {
|
||||
this.props.setShowFunction(this.showFlyout);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (typeof this.props.unsetShowFunction === 'function') {
|
||||
this.props.unsetShowFunction();
|
||||
}
|
||||
}
|
||||
|
||||
closeFlyout = () => {
|
||||
this.setState({ isFlyoutVisible: false });
|
||||
}
|
||||
|
||||
showFlyout = (jobId) => {
|
||||
loadFullJob(jobId)
|
||||
.then((job) => {
|
||||
const bucketSpan = job.analysis_config.bucket_span;
|
||||
mlCreateWatchService.config.includeInfluencers = (job.analysis_config.influencers.length > 0);
|
||||
|
||||
this.setState({
|
||||
job,
|
||||
jobId,
|
||||
bucketSpan,
|
||||
isFlyoutVisible: true,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
save = () => {
|
||||
mlCreateWatchService.createNewWatch(this.state.jobId)
|
||||
.then((resp) => {
|
||||
toastNotifications.addSuccess(getSuccessToast(resp.id, resp.url));
|
||||
this.closeFlyout();
|
||||
})
|
||||
.catch((error) => {
|
||||
toastNotifications.addDanger(`Could not save watch`);
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const {
|
||||
jobId,
|
||||
bucketSpan
|
||||
} = this.state;
|
||||
|
||||
let flyout;
|
||||
|
||||
if (this.state.isFlyoutVisible) {
|
||||
flyout = (
|
||||
<EuiFlyout
|
||||
// ownFocus
|
||||
onClose={this.closeFlyout}
|
||||
size="s"
|
||||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
Create watch for {jobId}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
|
||||
<CreateWatch
|
||||
jobId={jobId}
|
||||
bucketSpan={bucketSpan}
|
||||
/>
|
||||
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
iconType="cross"
|
||||
onClick={this.closeFlyout}
|
||||
flush="left"
|
||||
>
|
||||
Close
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={this.save}
|
||||
fill
|
||||
>
|
||||
Save
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{flyout}
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
CreateWatchFlyout.propTypes = {
|
||||
setShowFunction: PropTypes.func.isRequired,
|
||||
unsetShowFunction: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
|
||||
export { CreateWatchFlyout } from './create_watch_flyout';
|
|
@ -36,7 +36,7 @@ export class EditJobFlyout extends Component {
|
|||
this.state = {
|
||||
job: {},
|
||||
hasDatafeed: false,
|
||||
isModalVisible: false,
|
||||
isFlyoutVisible: false,
|
||||
jobDescription: '',
|
||||
jobGroups: [],
|
||||
jobModelMemoryLimit: '',
|
||||
|
@ -68,7 +68,7 @@ export class EditJobFlyout extends Component {
|
|||
}
|
||||
|
||||
closeFlyout = () => {
|
||||
this.setState({ isModalVisible: false });
|
||||
this.setState({ isFlyoutVisible: false });
|
||||
}
|
||||
|
||||
showFlyout = (jobLite) => {
|
||||
|
@ -78,7 +78,7 @@ export class EditJobFlyout extends Component {
|
|||
this.extractJob(job, hasDatafeed);
|
||||
this.setState({
|
||||
job,
|
||||
isModalVisible: true,
|
||||
isFlyoutVisible: true,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
|
@ -185,7 +185,7 @@ export class EditJobFlyout extends Component {
|
|||
render() {
|
||||
let flyout;
|
||||
|
||||
if (this.state.isModalVisible) {
|
||||
if (this.state.isFlyoutVisible) {
|
||||
const {
|
||||
job,
|
||||
jobDescription,
|
||||
|
|
|
@ -16,6 +16,7 @@ import { JobFilterBar } from '../job_filter_bar';
|
|||
import { EditJobFlyout } from '../edit_job_flyout';
|
||||
import { DeleteJobModal } from '../delete_job_modal';
|
||||
import { StartDatafeedModal } from '../start_datafeed_modal';
|
||||
import { CreateWatchFlyout } from '../create_watch_flyout';
|
||||
import { MultiJobActions } from '../multi_job_actions';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -45,6 +46,7 @@ export class JobsListView extends Component {
|
|||
this.showEditJobFlyout = () => {};
|
||||
this.showDeleteJobModal = () => {};
|
||||
this.showStartDatafeedModal = () => {};
|
||||
this.showCreateWatchFlyout = () => {};
|
||||
|
||||
this.blockRefresh = false;
|
||||
}
|
||||
|
@ -191,6 +193,17 @@ export class JobsListView extends Component {
|
|||
this.showStartDatafeedModal = () => {};
|
||||
}
|
||||
|
||||
setShowCreateWatchFlyoutFunction = (func) => {
|
||||
this.showCreateWatchFlyout = func;
|
||||
}
|
||||
unsetShowCreateWatchFlyoutFunction = () => {
|
||||
this.showCreateWatchFlyout = () => {};
|
||||
}
|
||||
getShowCreateWatchFlyoutFunction = () => {
|
||||
return this.showCreateWatchFlyout;
|
||||
}
|
||||
|
||||
|
||||
selectJobChange = (selectedJobs) => {
|
||||
this.setState({ selectedJobs });
|
||||
}
|
||||
|
@ -281,8 +294,14 @@ export class JobsListView extends Component {
|
|||
<StartDatafeedModal
|
||||
setShowFunction={this.setShowStartDatafeedModalFunction}
|
||||
unsetShowFunction={this.unsetShowDeleteJobModalFunction}
|
||||
getShowCreateWatchFlyoutFunction={this.getShowCreateWatchFlyoutFunction}
|
||||
refreshJobs={() => this.refreshJobSummaryList(true)}
|
||||
/>
|
||||
<CreateWatchFlyout
|
||||
setShowFunction={this.setShowCreateWatchFlyoutFunction}
|
||||
unsetShowFunction={this.unsetShowCreateWatchFlyoutFunction}
|
||||
compile={this.props.compile}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@ import {
|
|||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiOverlayMask,
|
||||
EuiHorizontalRule,
|
||||
EuiCheckbox,
|
||||
|
||||
} from '@elastic/eui';
|
||||
|
||||
import moment from 'moment';
|
||||
|
@ -36,20 +39,20 @@ export class StartDatafeedModal extends Component {
|
|||
isModalVisible: false,
|
||||
startTime: moment(),
|
||||
endTime: moment(),
|
||||
createWatch: false,
|
||||
allowCreateWatch: false,
|
||||
initialSpecifiedStartTime: moment()
|
||||
};
|
||||
|
||||
this.initialSpecifiedStartTime = moment();
|
||||
this.refreshJobs = this.props.refreshJobs;
|
||||
this.getShowCreateWatchFlyoutFunction = this.props.getShowCreateWatchFlyoutFunction;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (typeof this.props.setShowFunction === 'function') {
|
||||
this.props.setShowFunction(this.showModal);
|
||||
}
|
||||
if (typeof this.props.saveFunction === 'function') {
|
||||
this.externalSave = this.props.saveFunction;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -66,32 +69,52 @@ export class StartDatafeedModal extends Component {
|
|||
this.setState({ endTime: time });
|
||||
}
|
||||
|
||||
setCreateWatch = (e) => {
|
||||
this.setState({ createWatch: e.target.checked });
|
||||
}
|
||||
|
||||
closeModal = () => {
|
||||
this.setState({ isModalVisible: false });
|
||||
}
|
||||
|
||||
showModal = (jobs) => {
|
||||
showModal = (jobs, showCreateWatchFlyout) => {
|
||||
const startTime = undefined;
|
||||
const endTime = moment();
|
||||
const initialSpecifiedStartTime = getLowestLatestTime(jobs);
|
||||
const allowCreateWatch = (jobs.length === 1);
|
||||
this.setState({
|
||||
jobs,
|
||||
isModalVisible: true,
|
||||
startTime,
|
||||
endTime,
|
||||
initialSpecifiedStartTime
|
||||
initialSpecifiedStartTime,
|
||||
showCreateWatchFlyout,
|
||||
allowCreateWatch,
|
||||
createWatch: false,
|
||||
});
|
||||
}
|
||||
|
||||
save = () => {
|
||||
const { jobs } = this.state;
|
||||
const start = moment.isMoment(this.state.startTime) ? this.state.startTime.valueOf() : this.state.startTime;
|
||||
const end = moment.isMoment(this.state.endTime) ? this.state.endTime.valueOf() : this.state.endTime;
|
||||
forceStartDatafeeds(this.state.jobs, start, end, this.refreshJobs);
|
||||
forceStartDatafeeds(jobs, start, end, () => {
|
||||
if (this.state.createWatch && jobs.length === 1) {
|
||||
const jobId = jobs[0].id;
|
||||
this.getShowCreateWatchFlyoutFunction()(jobId);
|
||||
}
|
||||
this.refreshJobs();
|
||||
});
|
||||
this.closeModal();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { jobs } = this.state;
|
||||
const {
|
||||
jobs,
|
||||
initialSpecifiedStartTime,
|
||||
endTime,
|
||||
createWatch
|
||||
} = this.state;
|
||||
const startableJobs = (jobs !== undefined) ? jobs.filter(j => j.hasDatafeed) : [];
|
||||
let modal;
|
||||
|
||||
|
@ -110,11 +133,23 @@ export class StartDatafeedModal extends Component {
|
|||
|
||||
<EuiModalBody>
|
||||
<TimeRangeSelector
|
||||
startTime={this.state.initialSpecifiedStartTime}
|
||||
endTime={this.state.endTime}
|
||||
startTime={initialSpecifiedStartTime}
|
||||
endTime={endTime}
|
||||
setStartTime={this.setStartTime}
|
||||
setEndTime={this.setEndTime}
|
||||
/>
|
||||
{
|
||||
this.state.endTime === undefined &&
|
||||
<div className="create-watch">
|
||||
<EuiHorizontalRule />
|
||||
<EuiCheckbox
|
||||
id="createWatch"
|
||||
label="Create watch after datafeed has started"
|
||||
checked={createWatch}
|
||||
onChange={this.setCreateWatch}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</EuiModalBody>
|
||||
|
||||
<EuiModalFooter>
|
||||
|
|
|
@ -51,6 +51,9 @@ export class TimeRangeSelector extends Component {
|
|||
case 0:
|
||||
this.setEndTime(undefined);
|
||||
break;
|
||||
case 1:
|
||||
this.setEndTime(moment());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
</div>
|
||||
|
||||
<div ng-if="ui.watchAlreadyExists" class="watch-exists-warning">
|
||||
Warning, watch ml-{{jobId}} already exists, clicking apply with overwrite the original.
|
||||
Warning, watch ml-{{jobId}} already exists, clicking apply will overwrite the original.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -124,7 +124,10 @@ class CreateWatchService {
|
|||
this.status.watch = this.STATUS.SAVED;
|
||||
this.config.watcherEditURL =
|
||||
`${chrome.getBasePath()}/app/kibana#/management/elasticsearch/watcher/watches/watch/${id}/edit?_g=()`;
|
||||
resolve();
|
||||
resolve({
|
||||
id,
|
||||
url: this.config.watcherEditURL,
|
||||
});
|
||||
})
|
||||
.catch((resp) => {
|
||||
this.status.watch = this.STATUS.SAVE_FAILED;
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, {
|
||||
Component,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
EuiCheckbox,
|
||||
EuiFieldText,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { has } from 'lodash';
|
||||
|
||||
import { parseInterval } from 'ui/utils/parse_interval';
|
||||
|
||||
import { ml } from '../../../../../services/ml_api_service';
|
||||
import { SelectSeverity } from '../../../../../components/controls/select_severity/select_severity';
|
||||
import { mlCreateWatchService } from './create_watch_service';
|
||||
const STATUS = mlCreateWatchService.STATUS;
|
||||
|
||||
export class CreateWatch extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
mlCreateWatchService.reset();
|
||||
this.config = mlCreateWatchService.config;
|
||||
|
||||
this.state = {
|
||||
jobId: this.props.jobId,
|
||||
bucketSpan: this.props.bucketSpan,
|
||||
interval: this.config.interval,
|
||||
threshold: this.config.threshold,
|
||||
includeEmail: this.config.emailIncluded,
|
||||
email: this.config.email,
|
||||
emailEnabled: false,
|
||||
status: null,
|
||||
watchAlreadyExists: false,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// make the interval 2 times the bucket span
|
||||
if (this.state.bucketSpan) {
|
||||
const intervalObject = parseInterval(this.state.bucketSpan);
|
||||
let bs = intervalObject.asMinutes() * 2;
|
||||
if (bs < 1) {
|
||||
bs = 1;
|
||||
}
|
||||
|
||||
const interval = `${bs}m`;
|
||||
this.setState({ interval }, () => {
|
||||
this.config.interval = interval;
|
||||
});
|
||||
}
|
||||
|
||||
// load elasticsearch settings to see if email has been configured
|
||||
ml.getNotificationSettings().then((resp) => {
|
||||
if (has(resp, 'defaults.xpack.notification.email')) {
|
||||
this.setState({ emailEnabled: true });
|
||||
}
|
||||
});
|
||||
|
||||
mlCreateWatchService.loadWatch(this.state.jobId)
|
||||
.then(() => {
|
||||
this.setState({ watchAlreadyExists: true });
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({ watchAlreadyExists: false });
|
||||
});
|
||||
}
|
||||
|
||||
onThresholdChange = (threshold) => {
|
||||
this.setState({ threshold }, () => {
|
||||
this.config.threshold = threshold;
|
||||
});
|
||||
}
|
||||
|
||||
onIntervalChange = (e) => {
|
||||
const interval = e.target.value;
|
||||
this.setState({ interval }, () => {
|
||||
this.config.interval = interval;
|
||||
});
|
||||
}
|
||||
|
||||
onIncludeEmailChanged = (e) => {
|
||||
const includeEmail = e.target.checked;
|
||||
this.setState({ includeEmail }, () => {
|
||||
this.config.includeEmail = includeEmail;
|
||||
});
|
||||
}
|
||||
|
||||
onEmailChange = (e) => {
|
||||
const email = e.target.value;
|
||||
this.setState({ email }, () => {
|
||||
this.config.email = email;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const mlSelectSeverityService = {
|
||||
state: {
|
||||
set: (name, threshold) => {
|
||||
this.onThresholdChange(threshold);
|
||||
return {
|
||||
changed: () => {}
|
||||
};
|
||||
},
|
||||
get: () => {
|
||||
return this.config.threshold;
|
||||
},
|
||||
}
|
||||
};
|
||||
const { status } = this.state;
|
||||
|
||||
if (status === null || status === STATUS.SAVING || status === STATUS.SAVE_FAILED) {
|
||||
return (
|
||||
<div className="create-watch">
|
||||
<div className="form-group form-group-flex">
|
||||
<div className="sub-form-group">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="selectInterval"
|
||||
className="euiFormLabel"
|
||||
>
|
||||
Time range
|
||||
</label>
|
||||
</div>
|
||||
Now - <EuiFieldText
|
||||
id="selectInterval"
|
||||
value={this.state.interval}
|
||||
onChange={this.onIntervalChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="sub-form-group">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="selectSeverity"
|
||||
className="euiFormLabel"
|
||||
>
|
||||
Severity threshold
|
||||
</label>
|
||||
</div>
|
||||
<div className="dropdown-group">
|
||||
<SelectSeverity
|
||||
id="selectSeverity"
|
||||
mlSelectSeverityService={mlSelectSeverityService}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
this.state.emailEnabled &&
|
||||
|
||||
<div className="form-group">
|
||||
<EuiCheckbox
|
||||
id="includeEmail"
|
||||
label="Send email"
|
||||
checked={this.state.includeEmail}
|
||||
onChange={this.onIncludeEmailChanged}
|
||||
/>
|
||||
{
|
||||
this.state.includeEmail &&
|
||||
<div className="email-section">
|
||||
<EuiFieldText
|
||||
value={this.state.email}
|
||||
onChange={this.onEmailChange}
|
||||
placeholder="email address"
|
||||
aria-label="Watch email address"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
this.state.watchAlreadyExists &&
|
||||
<EuiCallOut
|
||||
title={`Warning, watch ml-${this.state.jobId} already exists, clicking apply will overwrite the original.`}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
} else if (status === STATUS.SAVED) {
|
||||
return (
|
||||
<div>Success</div>
|
||||
);
|
||||
} else {
|
||||
return (<div />);
|
||||
}
|
||||
}
|
||||
}
|
||||
CreateWatch.propTypes = {
|
||||
jobId: PropTypes.string.isRequired,
|
||||
bucketSpan: PropTypes.string.isRequired,
|
||||
};
|
|
@ -11,6 +11,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
.form-group-flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sub-form-group:first-child {
|
||||
.euiFormControlLayout {
|
||||
display: inline-block;
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
.email-section {
|
||||
padding: 10px;
|
||||
padding-left: 0px;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue