diff --git a/ui/package-lock.json b/ui/package-lock.json index 6a779de83..b3de69e77 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -202,14 +202,6 @@ } } }, - "add-dom-event-listener": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.0.2.tgz", - "integrity": "sha1-j67SxBAIchzxEdodMNmVuFvkK+0=", - "requires": { - "object-assign": "4.1.1" - } - }, "address": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/address/-/address-1.0.3.tgz", @@ -2322,24 +2314,11 @@ "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.3.0.tgz", "integrity": "sha512-MAAAIOdi2s4Gl6rZ76PNcUa9IOYB+5ICdT41o5uMRf09aEu/F9RK+qhe8RjXNPwcTjGV7KU7h2P/fljThFVqyQ==" }, - "component-classes": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz", - "integrity": "sha1-xkI5TDYYpNiwuJGe/Mu9kw5c1pE=", - "requires": { - "component-indexof": "0.0.3" - } - }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" }, - "component-indexof": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz", - "integrity": "sha1-EdCRMSI5648yyPJa6csAL/6NPCQ=" - }, "compressible": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.14.tgz", @@ -2625,15 +2604,6 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, - "css-animation": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.4.1.tgz", - "integrity": "sha1-W4gTEl3g+7uwu+G0cq6EIhRpt6g=", - "requires": { - "babel-runtime": "6.26.0", - "component-classes": "1.2.6" - } - }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -3104,11 +3074,6 @@ "esutils": "2.0.2" } }, - "dom-align": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.8.0.tgz", - "integrity": "sha512-B85D4ef2Gj5lw0rK0KM2+D5/pH7yqNxg2mB+E8uzFaolpm7RQmsxEfjyEuNiF8UBBkffumYDeKRzTzc3LePP+w==" - }, "dom-converter": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz", @@ -6949,11 +6914,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", @@ -7002,11 +6962,6 @@ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", "dev": true }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, "lodash.isarray": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-4.0.0.tgz", @@ -7022,23 +6977,6 @@ "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=" }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "requires": { - "lodash._getnative": "3.9.1", - "lodash.isarguments": "3.1.0", - "lodash.isarray": "3.0.4" - }, - "dependencies": { - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" - } - } - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -9811,77 +9749,6 @@ } } }, - "rc-align": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-2.4.3.tgz", - "integrity": "sha512-h5KgyB5IXYR7iKpYFcMr54cuQ2eozPCZ11kbXPG5+6CWvmyJ+c0R/yjndVndiNk2G3MKcTMbJNdDv5DIckLAxQ==", - "requires": { - "babel-runtime": "6.26.0", - "dom-align": "1.8.0", - "prop-types": "15.6.2", - "rc-util": "4.5.1" - } - }, - "rc-animate": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.4.4.tgz", - "integrity": "sha512-DjJLTUQj7XKKcuS8cczN0uOLfuSmgrVXFGieP1SZc87xUUTFGh8B/KjNmEtlfvxkSrSuVfb2rrEPER4SqKUtEA==", - "requires": { - "babel-runtime": "6.26.0", - "css-animation": "1.4.1", - "prop-types": "15.6.2" - } - }, - "rc-calendar": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/rc-calendar/-/rc-calendar-9.7.0.tgz", - "integrity": "sha512-067i3TC0H/6N6ZIhtbe9o/+F955n9nujNxn6Hoi6dJNr0LXc6F7305EI8N9k3zycx9JAliFQ1LF+MeFQwWRwpw==", - "requires": { - "babel-runtime": "6.26.0", - "classnames": "2.2.6", - "create-react-class": "15.6.3", - "moment": "2.22.2", - "prop-types": "15.6.2", - "rc-trigger": "2.5.4", - "rc-util": "4.5.1" - } - }, - "rc-time-picker": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/rc-time-picker/-/rc-time-picker-3.4.0.tgz", - "integrity": "sha512-BgF0Fu/d36AK0h8jYBa01VWCm5vHWtYCh4DXBQhNazPLSH9hMP6JHLMJPSYMJ9jKttdE18O+F3j0mVQCL8JpDg==", - "requires": { - "babel-runtime": "6.26.0", - "classnames": "2.2.6", - "moment": "2.22.2", - "prop-types": "15.6.2", - "rc-trigger": "2.5.4" - } - }, - "rc-trigger": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-2.5.4.tgz", - "integrity": "sha512-clgXOdazDW2qg4vTZSAExpvOuojPNuMoamG+SxAm5Ih+rpVcrtEiDlDZWY4yUHyfEWJZBzgbrr4np/z2FK6RfA==", - "requires": { - "babel-runtime": "6.26.0", - "classnames": "2.2.6", - "prop-types": "15.6.2", - "rc-align": "2.4.3", - "rc-animate": "2.4.4", - "rc-util": "4.5.1" - } - }, - "rc-util": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.5.1.tgz", - "integrity": "sha512-PdCmHyBBodZdw6Oaikt0l+/R79IcRXpYkTrqD/Rbl4ZdoOi61t5TtEe40Q+A7rkWG5U1xjcN+h8j9H6GdtnICw==", - "requires": { - "add-dom-event-listener": "1.0.2", - "babel-runtime": "6.26.0", - "prop-types": "15.6.2", - "shallowequal": "0.2.2" - } - }, "react": { "version": "16.4.2", "resolved": "https://registry.npmjs.org/react/-/react-16.4.2.tgz", @@ -9913,6 +9780,28 @@ "section-iterator": "2.0.0" } }, + "react-datepicker": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-1.6.0.tgz", + "integrity": "sha512-wfFfw3Tn+CCTtP7PPPhSAfkfVT3UTifFb8BmuhtwWKK3g4S83EEPRQE18XFtxd22dKGqp336NVtDTTyUJ1p9+g==", + "requires": { + "classnames": "2.2.6", + "prop-types": "15.6.2", + "react-onclickoutside": "6.7.1", + "react-popper": "0.9.5" + }, + "dependencies": { + "react-popper": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-0.9.5.tgz", + "integrity": "sha1-AqJO8+7DOvnlToNYq3DrDjMe3QU=", + "requires": { + "popper.js": "1.14.4", + "prop-types": "15.6.2" + } + } + } + }, "react-dev-utils": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-5.0.1.tgz", @@ -11006,14 +10895,6 @@ "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.0.0.tgz", "integrity": "sha1-UI0YOLPeWQq4dXsBGyXkMJAJRfc=" }, - "shallowequal": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-0.2.2.tgz", - "integrity": "sha1-HjL9W8q2rWiKSBLLDMBO/HXHAU4=", - "requires": { - "lodash.keys": "3.1.2" - } - }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", diff --git a/ui/package.json b/ui/package.json index 333c02240..f742a699e 100644 --- a/ui/package.json +++ b/ui/package.json @@ -25,10 +25,9 @@ "prop-types": "15.6.2", "qs": "6.5.2", "raven-js": "3.26.4", - "rc-calendar": "9.7.0", - "rc-time-picker": "3.4.0", "react": "16.4.2", "react-autosuggest": "9.3.4", + "react-datepicker": "1.6.0", "react-dom": "16.4.2", "react-highlighter": "0.4.2", "react-input-range": "1.3.0", diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/Duration.js b/ui/src/Components/SilenceModal/DateTimeSelect/Duration.js new file mode 100644 index 000000000..5208b79aa --- /dev/null +++ b/ui/src/Components/SilenceModal/DateTimeSelect/Duration.js @@ -0,0 +1,66 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; + +import { observer } from "mobx-react"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faAngleUp } from "@fortawesome/free-solid-svg-icons/faAngleUp"; +import { faAngleDown } from "@fortawesome/free-solid-svg-icons/faAngleDown"; + +const Duration = observer( + class Duration extends Component { + static propTypes = { + value: PropTypes.number.isRequired, + label: PropTypes.string, + onInc: PropTypes.func.isRequired, + onDec: PropTypes.func.isRequired + }; + + render() { + const { value, label, onInc, onDec } = this.props; + + return ( + + + + + + + + + + + + + +
+ + + + +
+

{value}

+
+ {label ? ( + {label} + ) : null} +
+ + + + +
+ ); + } + } +); + +export { Duration }; diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js b/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js new file mode 100644 index 000000000..3a35e1856 --- /dev/null +++ b/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js @@ -0,0 +1,81 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; + +import { observer } from "mobx-react"; + +import moment from "moment"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faAngleUp } from "@fortawesome/free-solid-svg-icons/faAngleUp"; +import { faAngleDown } from "@fortawesome/free-solid-svg-icons/faAngleDown"; + +const IconTd = ({ icon, onClick }) => ( + + + + + +); +IconTd.propTypes = { + icon: PropTypes.object.isRequired, + onClick: PropTypes.func.isRequired +}; + +const HourMinute = observer( + class HourMinute extends Component { + static propTypes = { + dateValue: PropTypes.instanceOf(moment).isRequired, + onHourInc: PropTypes.func.isRequired, + onHourDec: PropTypes.func.isRequired, + onMinuteInc: PropTypes.func.isRequired, + onMinuteDec: PropTypes.func.isRequired + }; + + render() { + const { + dateValue, + onHourInc, + onHourDec, + onMinuteInc, + onMinuteDec + } = this.props; + + const hour = dateValue.hour(); + const minute = dateValue.minute(); + + return ( + + + + + + + + + + + + + + +
+ +
+

{hour > 9 ? hour : `0${hour}`}

+
+

:

+
+

{minute > 9 ? minute : `0${minute}`}

+
+ +
+ ); + } + } +); + +export { HourMinute }; diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/index.css b/ui/src/Components/SilenceModal/DateTimeSelect/index.css new file mode 100644 index 000000000..68f1e2bda --- /dev/null +++ b/ui/src/Components/SilenceModal/DateTimeSelect/index.css @@ -0,0 +1,484 @@ +.react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle, .react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle, .react-datepicker__year-read-view--down-arrow, +.react-datepicker__month-read-view--down-arrow, +.react-datepicker__month-year-read-view--down-arrow { + margin-left: -8px; + position: absolute; } + .react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle, .react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle, .react-datepicker__year-read-view--down-arrow, + .react-datepicker__month-read-view--down-arrow, + .react-datepicker__month-year-read-view--down-arrow, .react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle::before, .react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle::before, .react-datepicker__year-read-view--down-arrow::before, + .react-datepicker__month-read-view--down-arrow::before, + .react-datepicker__month-year-read-view--down-arrow::before { + box-sizing: content-box; + position: absolute; + border: 8px solid transparent; + height: 0; + width: 1px; } + .react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle::before, .react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle::before, .react-datepicker__year-read-view--down-arrow::before, + .react-datepicker__month-read-view--down-arrow::before, + .react-datepicker__month-year-read-view--down-arrow::before { + content: ""; + z-index: -1; + border-width: 8px; + left: -8px; + border-bottom-color: #dee2e6; } + +.react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle { + top: 0; + margin-top: -8px; } + .react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle, .react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle::before { + border-top: none; + border-bottom-color: #fff; } + .react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle::before { + top: -1px; + border-bottom-color: #dee2e6; } + +.react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle, .react-datepicker__year-read-view--down-arrow, +.react-datepicker__month-read-view--down-arrow, +.react-datepicker__month-year-read-view--down-arrow { + bottom: 0; + margin-bottom: -8px; } + .react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle, .react-datepicker__year-read-view--down-arrow, + .react-datepicker__month-read-view--down-arrow, + .react-datepicker__month-year-read-view--down-arrow, .react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle::before, .react-datepicker__year-read-view--down-arrow::before, + .react-datepicker__month-read-view--down-arrow::before, + .react-datepicker__month-year-read-view--down-arrow::before { + border-bottom: none; + border-top-color: #fff; } + .react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle::before, .react-datepicker__year-read-view--down-arrow::before, + .react-datepicker__month-read-view--down-arrow::before, + .react-datepicker__month-year-read-view--down-arrow::before { + bottom: -1px; + border-top-color: #dee2e6; } + +.react-datepicker-wrapper { + display: inline-block; } + +.react-datepicker { + font-family: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 0.9375rem; + background-color: #fff; + color: #000; + border: 1px solid #dee2e6; + border-radius: 0.25rem; + display: inline-block; + position: relative; } + +.react-datepicker--time-only .react-datepicker__triangle { + left: 35px; } + +.react-datepicker--time-only .react-datepicker__time-container { + border-left: 0; } + +.react-datepicker--time-only .react-datepicker__time { + border-radius: 0.3rem; } + +.react-datepicker--time-only .react-datepicker__time-box { + border-radius: 0.3rem; } + +.react-datepicker__triangle { + position: absolute; + left: 50px; } + +.react-datepicker-popper { + z-index: 1; } + .react-datepicker-popper[data-placement^="bottom"] { + margin-top: 10px; } + .react-datepicker-popper[data-placement^="top"] { + margin-bottom: 10px; } + .react-datepicker-popper[data-placement^="right"] { + margin-left: 8px; } + .react-datepicker-popper[data-placement^="right"] .react-datepicker__triangle { + left: auto; + right: 42px; } + .react-datepicker-popper[data-placement^="left"] { + margin-right: 8px; } + .react-datepicker-popper[data-placement^="left"] .react-datepicker__triangle { + left: 42px; + right: auto; } + +.react-datepicker__header { + text-align: center; + background-color: #fff; + border-bottom: 1px solid #dee2e6; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; + padding-top: 8px; + position: relative; } + .react-datepicker__header--time { + padding-bottom: 8px; + padding-left: 5px; + padding-right: 5px; } + +.react-datepicker__year-dropdown-container--select, +.react-datepicker__month-dropdown-container--select, +.react-datepicker__month-year-dropdown-container--select, +.react-datepicker__year-dropdown-container--scroll, +.react-datepicker__month-dropdown-container--scroll, +.react-datepicker__month-year-dropdown-container--scroll { + display: inline-block; + margin: 0 2px; } + +.react-datepicker__current-month, +.react-datepicker-time__header { + margin-top: 0; + color: #000; + font-weight: bold; + font-size: 1.10625rem; } + +.react-datepicker-time__header { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } + +.react-datepicker__navigation { + background: none; + line-height: 1.7rem; + text-align: center; + cursor: pointer; + position: absolute; + top: 10px; + width: 0; + padding: 0; + border: 0.45rem solid transparent; + z-index: 1; + height: 10px; + width: 10px; + text-indent: -999em; + overflow: hidden; } + .react-datepicker__navigation--previous { + left: 10px; + border-right-color: #95a5a6; } + .react-datepicker__navigation--previous:hover { + border-right-color: #798d8f; } + .react-datepicker__navigation--previous--disabled, .react-datepicker__navigation--previous--disabled:hover { + border-right-color: #95a5a6; + cursor: default; } + .react-datepicker__navigation--next { + right: 10px; + border-left-color: #95a5a6; } + .react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button) { + right: 80px; } + .react-datepicker__navigation--next:hover { + border-left-color: #798d8f; } + .react-datepicker__navigation--next--disabled, .react-datepicker__navigation--next--disabled:hover { + border-left-color: #95a5a6; + cursor: default; } + .react-datepicker__navigation--years { + position: relative; + top: 0; + display: block; + margin-left: auto; + margin-right: auto; } + .react-datepicker__navigation--years-previous { + top: 4px; + border-top-color: #95a5a6; } + .react-datepicker__navigation--years-previous:hover { + border-top-color: #798d8f; } + .react-datepicker__navigation--years-upcoming { + top: -4px; + border-bottom-color: #95a5a6; } + .react-datepicker__navigation--years-upcoming:hover { + border-bottom-color: #798d8f; } + +.react-datepicker__month-container { + float: left; } + +.react-datepicker__month { + margin: 0.4rem; + text-align: center; } + +.react-datepicker__time-container { + float: right; + border-left: 1px solid #dee2e6; + width: 70px; } + .react-datepicker__time-container--with-today-button { + display: inline; + border: 1px solid #aeaeae; + border-radius: 0.3rem; + position: absolute; + right: -72px; + top: 0; } + .react-datepicker__time-container .react-datepicker__time { + position: relative; + background: white; } + .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box { + width: 70px; + overflow-x: hidden; + margin: 0 auto; + text-align: center; } + .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list { + list-style: none; + margin: 0; + height: calc(195px + (1.7rem / 2)); + overflow-y: scroll; + padding-right: 30px; + width: 100%; + box-sizing: content-box; } + .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item { + padding: 5px 10px; } + .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item:hover { + cursor: pointer; + background-color: #fff; } + .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected { + background-color: #2C3E50; + color: white; + font-weight: bold; } + .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected:hover { + background-color: #2C3E50; } + .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled { + color: #95a5a6; } + .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled:hover { + cursor: default; + background-color: transparent; } + +.react-datepicker__week-number { + color: #95a5a6; + display: inline-block; + width: 1.7rem; + line-height: 1.7rem; + text-align: center; + margin: 0.166rem; } + .react-datepicker__week-number.react-datepicker__week-number--clickable { + cursor: pointer; } + .react-datepicker__week-number.react-datepicker__week-number--clickable:hover { + border-radius: 0.25rem; + background-color: #fff; } + +.react-datepicker__day-names, +.react-datepicker__week { + white-space: nowrap; } + +.react-datepicker__day-name, +.react-datepicker__day, +.react-datepicker__time-name { + color: #000; + display: inline-block; + width: 1.7rem; + line-height: 1.7rem; + text-align: center; + margin: 0.166rem; } + +.react-datepicker__day { + cursor: pointer; } + .react-datepicker__day:hover { + border-radius: 0.25rem; + background-color: #fff; } + .react-datepicker__day--today { + font-weight: bold; } + .react-datepicker__day--highlighted { + border-radius: 0.25rem; + background-color: #95a5a6; + color: #fff; } + .react-datepicker__day--highlighted:hover { + background-color: #87999a; } + .react-datepicker__day--highlighted-custom-1 { + color: magenta; } + .react-datepicker__day--highlighted-custom-2 { + color: green; } + .react-datepicker__day--selected, .react-datepicker__day--in-selecting-range, .react-datepicker__day--in-range { + border-radius: 0.25rem; + background-color: #2C3E50; + color: #fff; } + .react-datepicker__day--selected:hover, .react-datepicker__day--in-selecting-range:hover, .react-datepicker__day--in-range:hover { + background-color: #233140; } + .react-datepicker__day--keyboard-selected { + border-radius: 0.25rem; + background-color: #3e5871; + color: #fff; } + .react-datepicker__day--keyboard-selected:hover { + background-color: #233140; } + .react-datepicker__day--in-selecting-range:not(.react-datepicker__day--in-range) { + background-color: rgba(44, 62, 80, 0.5); } + .react-datepicker__month--selecting-range .react-datepicker__day--in-range:not(.react-datepicker__day--in-selecting-range) { + background-color: #fff; + color: #000; } + .react-datepicker__day--disabled { + cursor: default; + color: #95a5a6; } + .react-datepicker__day--disabled:hover { + background-color: transparent; } + +.react-datepicker__input-container { + position: relative; + display: inline-block; } + +.react-datepicker__year-read-view, +.react-datepicker__month-read-view, +.react-datepicker__month-year-read-view { + border: 1px solid transparent; + border-radius: 0.25rem; } + .react-datepicker__year-read-view:hover, + .react-datepicker__month-read-view:hover, + .react-datepicker__month-year-read-view:hover { + cursor: pointer; } + .react-datepicker__year-read-view:hover .react-datepicker__year-read-view--down-arrow, + .react-datepicker__year-read-view:hover .react-datepicker__month-read-view--down-arrow, + .react-datepicker__month-read-view:hover .react-datepicker__year-read-view--down-arrow, + .react-datepicker__month-read-view:hover .react-datepicker__month-read-view--down-arrow, + .react-datepicker__month-year-read-view:hover .react-datepicker__year-read-view--down-arrow, + .react-datepicker__month-year-read-view:hover .react-datepicker__month-read-view--down-arrow { + border-top-color: #798d8f; } + .react-datepicker__year-read-view--down-arrow, + .react-datepicker__month-read-view--down-arrow, + .react-datepicker__month-year-read-view--down-arrow { + border-top-color: #95a5a6; + float: right; + margin-left: 20px; + top: 8px; + position: relative; + border-width: 0.45rem; } + +.react-datepicker__year-dropdown, +.react-datepicker__month-dropdown, +.react-datepicker__month-year-dropdown { + background-color: #fff; + position: absolute; + width: 50%; + left: 25%; + top: 30px; + z-index: 1; + text-align: center; + border-radius: 0.25rem; + border: 1px solid #dee2e6; } + .react-datepicker__year-dropdown:hover, + .react-datepicker__month-dropdown:hover, + .react-datepicker__month-year-dropdown:hover { + cursor: pointer; } + .react-datepicker__year-dropdown--scrollable, + .react-datepicker__month-dropdown--scrollable, + .react-datepicker__month-year-dropdown--scrollable { + height: 150px; + overflow-y: scroll; } + +.react-datepicker__year-option, +.react-datepicker__month-option, +.react-datepicker__month-year-option { + line-height: 20px; + width: 100%; + display: block; + margin-left: auto; + margin-right: auto; } + .react-datepicker__year-option:first-of-type, + .react-datepicker__month-option:first-of-type, + .react-datepicker__month-year-option:first-of-type { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; } + .react-datepicker__year-option:last-of-type, + .react-datepicker__month-option:last-of-type, + .react-datepicker__month-year-option:last-of-type { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border-bottom-left-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; } + .react-datepicker__year-option:hover, + .react-datepicker__month-option:hover, + .react-datepicker__month-year-option:hover { + background-color: #95a5a6; } + .react-datepicker__year-option:hover .react-datepicker__navigation--years-upcoming, + .react-datepicker__month-option:hover .react-datepicker__navigation--years-upcoming, + .react-datepicker__month-year-option:hover .react-datepicker__navigation--years-upcoming { + border-bottom-color: #798d8f; } + .react-datepicker__year-option:hover .react-datepicker__navigation--years-previous, + .react-datepicker__month-option:hover .react-datepicker__navigation--years-previous, + .react-datepicker__month-year-option:hover .react-datepicker__navigation--years-previous { + border-top-color: #798d8f; } + .react-datepicker__year-option--selected, + .react-datepicker__month-option--selected, + .react-datepicker__month-year-option--selected { + position: absolute; + left: 15px; } + +.react-datepicker__close-icon { + background-color: transparent; + border: 0; + cursor: pointer; + outline: 0; + padding: 0; + vertical-align: middle; + position: absolute; + height: 16px; + width: 16px; + top: 25%; + right: 7px; } + .react-datepicker__close-icon::after { + background-color: #2C3E50; + border-radius: 50%; + bottom: 0; + box-sizing: border-box; + color: #fff; + content: "\00d7"; + cursor: pointer; + font-size: 12px; + height: 16px; + width: 16px; + line-height: 1; + margin: -8px auto 0; + padding: 2px; + position: absolute; + right: 0px; + text-align: center; } + +.react-datepicker__today-button { + background: #fff; + border-top: 1px solid #dee2e6; + cursor: pointer; + text-align: center; + font-weight: bold; + padding: 5px 0; + clear: left; } + +.react-datepicker__portal { + position: fixed; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.8); + left: 0; + top: 0; + justify-content: center; + align-items: center; + display: flex; + z-index: 2147483647; } + .react-datepicker__portal .react-datepicker__day-name, + .react-datepicker__portal .react-datepicker__day, + .react-datepicker__portal .react-datepicker__time-name { + width: 3rem; + line-height: 3rem; } + @media (max-width: 400px), (max-height: 550px) { + .react-datepicker__portal .react-datepicker__day-name, + .react-datepicker__portal .react-datepicker__day, + .react-datepicker__portal .react-datepicker__time-name { + width: 2rem; + line-height: 2rem; } } + .react-datepicker__portal .react-datepicker__current-month, + .react-datepicker__portal .react-datepicker-time__header { + font-size: 1.6875rem; } + .react-datepicker__portal .react-datepicker__navigation { + border: 0.81rem solid transparent; } + .react-datepicker__portal .react-datepicker__navigation--previous { + border-right-color: #95a5a6; } + .react-datepicker__portal .react-datepicker__navigation--previous:hover { + border-right-color: #798d8f; } + .react-datepicker__portal .react-datepicker__navigation--previous--disabled, .react-datepicker__portal .react-datepicker__navigation--previous--disabled:hover { + border-right-color: #95a5a6; + cursor: default; } + .react-datepicker__portal .react-datepicker__navigation--next { + border-left-color: #95a5a6; } + .react-datepicker__portal .react-datepicker__navigation--next:hover { + border-left-color: #798d8f; } + .react-datepicker__portal .react-datepicker__navigation--next--disabled, .react-datepicker__portal .react-datepicker__navigation--next--disabled:hover { + border-left-color: #95a5a6; + cursor: default; } + +.react-datepicker__day:not(.react-datepicker__day--disabled) { + border-width: 1px; + border-style: solid; + border-color: transparent; } + +.react-datepicker__day:hover:not(.react-datepicker__day--disabled) { + border-color: #dee2e6; } + +.react-datepicker__today-button { + border-bottom-left-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; } diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/index.js b/ui/src/Components/SilenceModal/DateTimeSelect/index.js new file mode 100644 index 000000000..922d0b2a8 --- /dev/null +++ b/ui/src/Components/SilenceModal/DateTimeSelect/index.js @@ -0,0 +1,181 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; + +import { observable, action } from "mobx"; +import { observer } from "mobx-react"; + +import moment from "moment"; + +import DatePicker from "react-datepicker"; + +import { Duration } from "./Duration"; +import { HourMinute } from "./HourMinute"; + +import "./index.css"; + +const Tab = ({ title, active, onClick }) => ( +
  • + + {title} + +
  • +); +Tab.propTypes = { + title: PropTypes.node.isRequired, + active: PropTypes.bool, + onClick: PropTypes.func.isRequired +}; + +const TabNames = Object.freeze({ + Start: "start", + End: "end", + Duration: "duration" +}); + +const TabContentStart = observer(({ silenceFormStore }) => { + return ( +
    + { + silenceFormStore.data.startsAt = moment(val); + silenceFormStore.data.verifyStarEnd(); + }} + /> + silenceFormStore.data.incStart(60)} + onHourDec={() => silenceFormStore.data.decStart(60)} + onMinuteInc={() => silenceFormStore.data.incStart(1)} + onMinuteDec={() => silenceFormStore.data.decStart(1)} + /> +
    + ); +}); + +const TabContentEnd = observer(({ silenceFormStore }) => { + return ( +
    + { + silenceFormStore.data.endsAt = moment(val); + silenceFormStore.data.verifyStarEnd(); + }} + /> + silenceFormStore.data.incEnd(60)} + onHourDec={() => silenceFormStore.data.decEnd(60)} + onMinuteInc={() => silenceFormStore.data.incEnd(1)} + onMinuteDec={() => silenceFormStore.data.decEnd(1)} + /> +
    + ); +}); + +const TabContentDuration = observer(({ silenceFormStore }) => { + return ( +
    + silenceFormStore.data.incDuration(60 * 24)} + onDec={() => silenceFormStore.data.decDuration(60 * 24)} + /> + silenceFormStore.data.incDuration(60)} + onDec={() => silenceFormStore.data.decDuration(60)} + /> + silenceFormStore.data.incDuration(1)} + onDec={() => silenceFormStore.data.decDuration(1)} + /> +
    + ); +}); +TabContentDuration.propTypes = { + silenceFormStore: PropTypes.object.isRequired +}; + +const DateTimeSelect = observer( + class DateTimeSelect extends Component { + static propTypes = { + silenceFormStore: PropTypes.object.isRequired + }; + + tab = observable( + { + current: TabNames.Duration, + setStart() { + this.current = TabNames.Start; + }, + setEnd() { + this.current = TabNames.End; + }, + setDuration() { + this.current = TabNames.Duration; + } + }, + { + setStart: action.bound, + setEnd: action.bound, + setDuration: action.bound + } + ); + + render() { + const { silenceFormStore } = this.props; + + return ( + + +
    + {this.tab.current === TabNames.Duration ? ( + + ) : null} + {this.tab.current === TabNames.Start ? ( + + ) : null} + {this.tab.current === TabNames.End ? ( + + ) : null} +
    +
    + ); + } + } +); + +export { DateTimeSelect }; diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/index.scss b/ui/src/Components/SilenceModal/DateTimeSelect/index.scss new file mode 100644 index 000000000..babb1a064 --- /dev/null +++ b/ui/src/Components/SilenceModal/DateTimeSelect/index.scss @@ -0,0 +1,29 @@ +@import "../../../../node_modules/bootswatch/dist/flatly/variables"; + +$datepicker__background-color: $white; +$datepicker__border-color: $gray-300; +$datepicker__highlighted-color: $secondary; +$datepicker__muted-color: $gray-600; +$datepicker__selected-color: $primary; +$datepicker__text-color: $black; +$datepicker__header-color: $black; +$datepicker__navigation-disabled-color: $gray-600; +$datepicker__font-size: $font-size-base; +$datepicker__font-family: $font-family-sans-serif; +$datepicker__border-radius: 0.25rem; + +@import "../../../../node_modules/react-datepicker/src/stylesheets/datepicker.scss"; + +.react-datepicker__day:not(.react-datepicker__day--disabled) { + border-width: 1px; + border-style: solid; + border-color: transparent; +} +.react-datepicker__day:hover:not(.react-datepicker__day--disabled) { + border-color: $datepicker__border-color; +} + +.react-datepicker__today-button { + border-bottom-left-radius: $datepicker__border-radius; + border-bottom-right-radius: $datepicker__border-radius; +} diff --git a/ui/src/Components/SilenceModal/SilenceForm.js b/ui/src/Components/SilenceModal/SilenceForm.js index f7e5f8751..6685975b0 100644 --- a/ui/src/Components/SilenceModal/SilenceForm.js +++ b/ui/src/Components/SilenceModal/SilenceForm.js @@ -14,7 +14,7 @@ import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown"; import { AlertManagerInput } from "./AlertManagerInput"; import { SilenceMatch } from "./SilenceMatch"; -import { SilenceStartEnd } from "./SilenceStartEnd"; +import { DateTimeSelect } from "./DateTimeSelect"; import { SilencePreview } from "./SilencePreview"; const IconInput = ({ icon, placeholder, value, onChange }) => ( @@ -124,7 +124,7 @@ const SilenceForm = observer( > - + .rc-calendar-date { - border: 0; - color: #fff; - background-color: #455a64; -} - -.rc-calendar-today .rc-calendar-date { - border-color: #7b8a8b; -} diff --git a/ui/src/Components/SilenceModal/SilenceStartEnd.js b/ui/src/Components/SilenceModal/SilenceStartEnd.js deleted file mode 100644 index 9fcd43199..000000000 --- a/ui/src/Components/SilenceModal/SilenceStartEnd.js +++ /dev/null @@ -1,129 +0,0 @@ -import React, { Component } from "react"; -import PropTypes from "prop-types"; - -import { observable, action, toJS } from "mobx"; -import { observer } from "mobx-react"; - -import moment from "moment"; - -import Picker from "rc-calendar/lib/Picker"; -import RangeCalendar from "rc-calendar/lib/RangeCalendar"; -import "rc-calendar/assets/index.css"; - -import TimePickerPanel from "rc-time-picker/lib/Panel"; -import "rc-time-picker/assets/index.css"; - -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faCalendarAlt } from "@fortawesome/free-solid-svg-icons/faCalendarAlt"; - -import "./SilenceStartEnd.css"; - -function disabledDate(current) { - if (!current) return false; - - const date = moment(); - date.hour(0); - date.minute(0); - date.second(0); - return current.isBefore(date); -} - -function isValidRange(v) { - return v && v[0] && v[1]; -} - -const format = "YYYY-MM-DD HH:mm"; -function formatTimestamp(v) { - return v ? v.format(format) : ""; -} - -const timePickerElement = ( - -); - -const SilenceStartEnd = observer( - class SilenceStartEnd extends Component { - static propTypes = { - silenceFormStore: PropTypes.object.isRequired - }; - - data = observable({ - hoverValue: [] - }); - - onChange = action(value => { - const { silenceFormStore } = this.props; - - if (value && value[0]) { - silenceFormStore.data.startsAt = value[0]; - } - if (value && value[1]) { - silenceFormStore.data.endsAt = value[1]; - } - }); - - onHoverChange = action(value => { - this.data.hoverValue = value; - }); - - render() { - const { silenceFormStore } = this.props; - - const now = moment().second(0); - - const calendar = ( - - ); - return ( - - {({ value }) => { - return ( -
    -
    - - - -
    - -
    - ); - }} -
    - ); - } - } -); - -export { SilenceStartEnd }; diff --git a/ui/src/Stores/SilenceFormStore.js b/ui/src/Stores/SilenceFormStore.js index 60adb6347..6d31b3a06 100644 --- a/ui/src/Stores/SilenceFormStore.js +++ b/ui/src/Stores/SilenceFormStore.js @@ -50,20 +50,24 @@ class SilenceFormStore { endsAt: moment().add(1, "hour"), comment: "", author: "", + resetProgress() { this.inProgress = false; }, + // append a new empty matcher to the list addEmptyMatcher() { let m = NewEmptyMatcher(); this.matchers.push(m); }, + deleteMatcher(id) { // only delete matchers if we have more than 1 if (this.matchers.length > 1) { this.matchers = this.matchers.filter(m => m.id !== id); } }, + fillMatchersFromGroup(group) { let matchers = []; @@ -108,6 +112,40 @@ class SilenceFormStore { this.matchers = matchers; }, + + verifyStarEnd() { + if (this.endsAt.isSameOrBefore(this.startsAt)) { + this.endsAt = moment(this.startsAt).add(1, "minutes"); + } + }, + incStart(minutes) { + this.startsAt = moment(this.startsAt).add(minutes, "minutes"); + this.verifyStarEnd(); + }, + decStart(minutes) { + this.startsAt = moment(this.startsAt).subtract(minutes, "minutes"); + this.verifyStarEnd(); + }, + + incEnd(minutes) { + this.endsAt = moment(this.endsAt).add(minutes, "minutes"); + this.verifyStarEnd(); + }, + decEnd(minutes) { + this.endsAt = moment(this.endsAt).subtract(minutes, "minutes"); + this.verifyStarEnd(); + }, + + incDuration(minutes) { + this.endsAt = moment(this.endsAt).add(minutes, "minutes"); + }, + decDuration(minutes) { + const newEndsAt = moment(this.endsAt).subtract(minutes, "minutes"); + if (newEndsAt.isAfter(this.startsAt)) { + this.endsAt = newEndsAt; + } + }, + get toAlertmanagerPayload() { const payload = { matchers: this.matchers.map(m => ({ @@ -132,6 +170,14 @@ class SilenceFormStore { comment: this.comment }; return payload; + }, + get toDuration() { + const data = { + days: this.endsAt.diff(this.startsAt, "days"), + hours: this.endsAt.diff(this.startsAt, "hours") % 24, + minutes: this.endsAt.diff(this.startsAt, "minutes") % 60 + }; + return data; } }, { @@ -139,7 +185,15 @@ class SilenceFormStore { addEmptyMatcher: action.bound, deleteMatcher: action.bound, fillMatchersFromGroup: action.bound, - toAlertmanagerPayload: computed + verifyStarEnd: action.bound, + incStart: action.bound, + decStart: action.bound, + incEnd: action.bound, + decEnd: action.bound, + incDuration: action.bound, + decDuration: action.bound, + toAlertmanagerPayload: computed, + toDuration: computed }, { name: "Silence form store" } );