diff --git a/package.json b/package.json
index 5bdf13c020631..d6f2bffd002e8 100644
--- a/package.json
+++ b/package.json
@@ -76,7 +76,7 @@
"url": "https://github.com/elastic/kibana.git"
},
"dependencies": {
- "@elastic/eui": "v0.0.47",
+ "@elastic/eui": "v0.0.51",
"@elastic/filesaver": "1.1.2",
"@elastic/numeral": "2.3.2",
"@elastic/ui-ace": "0.2.3",
@@ -205,8 +205,8 @@
"validate-npm-package-name": "2.2.2",
"vega-lib": "^3.3.1",
"vega-lite": "^2.4.0",
- "vega-tooltip": "^0.9.14",
"vega-schema-url-parser": "1.0.0",
+ "vega-tooltip": "^0.9.14",
"vision": "4.1.0",
"webpack": "3.6.0",
"webpack-merge": "4.1.0",
diff --git a/packages/kbn-ui-framework/dist/ui_framework.css b/packages/kbn-ui-framework/dist/ui_framework.css
index 0d013535e3555..101f718149647 100644
--- a/packages/kbn-ui-framework/dist/ui_framework.css
+++ b/packages/kbn-ui-framework/dist/ui_framework.css
@@ -78,15 +78,12 @@ main {
overflow: hidden; }
.kuiActionItem {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between; }
@@ -105,15 +102,12 @@ main {
background-color: rgba(0, 0, 0, 0.1); }
.kuiBar {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
@@ -121,22 +115,18 @@ main {
/* 1 */ }
.kuiBarSection {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
margin-left: 25px;
margin-right: 25px; }
.kuiBarSection:not(:first-child):not(:last-child):not(:only-child) {
- -webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
@@ -145,12 +135,10 @@ main {
margin-left: 0; }
.kuiBarSection:last-child {
margin-right: 0;
- -webkit-box-flex: 0;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
/* 4 */
- -webkit-box-pack: end;
-webkit-justify-content: flex-end;
-ms-flex-pack: end;
justify-content: flex-end;
@@ -201,12 +189,10 @@ main {
* 1. Solves whitespace problems introduced by inline elements.
*/
.kuiButton__inner {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
/* 1 */
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
@@ -513,11 +499,9 @@ main {
background-color: rgba(165, 231, 255, 0.5); }
.kuiButtonGroup {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center; }
@@ -545,24 +529,19 @@ main {
margin-left: 2px; }
.kuiButtonGroup--fullWidth {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex; }
.kuiButtonGroup--fullWidth > .kuiButton {
- -webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
text-align: center; }
.kuiCard {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-orient: vertical;
- -webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
@@ -572,20 +551,15 @@ main {
line-height: 1.5; }
.kuiCard__description {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-orient: vertical;
- -webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
@@ -613,7 +587,6 @@ main {
* 2. Offset the spacing between wrapped cards.
*/
.kuiCardGroup {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
@@ -628,7 +601,6 @@ main {
* 2. Use an even margin all around the card so that the spacing is still even when wrapped.
*/ }
.kuiCardGroup .kuiCard {
- -webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
@@ -636,7 +608,6 @@ main {
margin: 15px;
/* 2 */ }
.kuiCardGroup .kuiCard .kuiCard__description {
- -webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto; }
@@ -687,20 +658,15 @@ main {
right: 0;
left: 0;
background: rgba(255, 255, 255, 0.7);
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-orient: vertical;
- -webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
- -webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
@@ -736,11 +702,9 @@ main {
cursor: pointer; }
.kuiColorPicker__preview {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center; }
@@ -890,11 +854,9 @@ main {
margin-right: 8px; }
.kuiContextMenu__itemLayout {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center; }
@@ -1071,13 +1033,11 @@ main {
color: #ffffff; }
.kuiContextMenuItem__inner {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex; }
.kuiContextMenuItem__text {
- -webkit-box-flex: 1;
-webkit-flex-grow: 1;
-ms-flex-positive: 1;
flex-grow: 1; }
@@ -1096,13 +1056,11 @@ main {
text-decoration: none; }
.kuiEvent {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex; }
.kuiEventSymbol {
- -webkit-box-flex: 0;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
@@ -1111,7 +1069,6 @@ main {
padding-right: 8px; }
.kuiEventBody {
- -webkit-box-flex: 1;
-webkit-flex: 1 1 0%;
-ms-flex: 1 1 0%;
flex: 1 1 0%; }
@@ -1150,16 +1107,13 @@ main {
border-bottom: solid 2px #00A69B; }
.kuiFlexGroup {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
.kuiFlexGroup .kuiFlexItem {
- -webkit-box-flex: 1;
-webkit-flex-grow: 1;
-ms-flex-positive: 1;
flex-grow: 1; }
@@ -1185,13 +1139,11 @@ main {
margin: 20px; }
.kuiFlexGroup--justifyContentSpaceEvenly {
- -webkit-box-pack: space-evenly;
-webkit-justify-content: space-evenly;
-ms-flex-pack: space-evenly;
justify-content: space-evenly; }
.kuiFlexGroup--justifyContentSpaceBetween {
- -webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between; }
@@ -1202,31 +1154,26 @@ main {
justify-content: space-around; }
.kuiFlexGroup--justifyContentCenter {
- -webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center; }
.kuiFlexGroup--justifyContentFlexEnd {
- -webkit-box-pack: end;
-webkit-justify-content: flex-end;
-ms-flex-pack: end;
justify-content: flex-end; }
.kuiFlexGroup--alignItemsStart {
- -webkit-box-align: start;
-webkit-align-items: flex-start;
-ms-flex-align: start;
align-items: flex-start; }
.kuiFlexGroup--alignItemsCenter {
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center; }
.kuiFlexGroup--alignItemsEnd {
- -webkit-box-align: end;
-webkit-align-items: flex-end;
-ms-flex-align: end;
align-items: flex-end; }
@@ -1243,7 +1190,6 @@ main {
flex-wrap: wrap; } }
.kuiFlexGrid {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
@@ -1252,12 +1198,10 @@ main {
flex-wrap: wrap;
margin-bottom: 0; }
.kuiFlexGrid > .kuiFlexItem {
- -webkit-box-flex: 0;
-webkit-flex-grow: 0;
-ms-flex-positive: 0;
flex-grow: 0; }
.kuiFlexGrid > .kuiFlexItem.kuiFlexItem--flexGrowZero {
- -webkit-box-flex: 0 !important;
-webkit-flex-grow: 0 !important;
-ms-flex-positive: 0 !important;
flex-grow: 0 !important;
@@ -1274,7 +1218,6 @@ main {
.kuiFlexGrid--gutterSmall {
margin: -4px;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -1285,7 +1228,6 @@ main {
.kuiFlexGrid--gutterSmall {
margin: -4px;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -1296,7 +1238,6 @@ main {
.kuiFlexGrid--gutterSmall {
margin: -4px;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -1314,7 +1255,6 @@ main {
.kuiFlexGrid--gutterMedium {
margin: -8px;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -1325,7 +1265,6 @@ main {
.kuiFlexGrid--gutterMedium {
margin: -8px;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -1336,7 +1275,6 @@ main {
.kuiFlexGrid--gutterMedium {
margin: -8px;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -1354,7 +1292,6 @@ main {
.kuiFlexGrid--gutterLarge {
margin: -12px;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -1365,7 +1302,6 @@ main {
.kuiFlexGrid--gutterLarge {
margin: -12px;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -1376,7 +1312,6 @@ main {
.kuiFlexGrid--gutterLarge {
margin: -12px;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -1394,7 +1329,6 @@ main {
.kuiFlexGrid--gutterXLarge {
margin: -16px;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -1405,7 +1339,6 @@ main {
.kuiFlexGrid--gutterXLarge {
margin: -16px;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -1416,7 +1349,6 @@ main {
.kuiFlexGrid--gutterXLarge {
margin: -16px;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -1429,13 +1361,10 @@ main {
* 1. Allow KuiPanels to expand to fill the item.
*/
.kuiFlexItem {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
/* 1 */
- -webkit-box-orient: vertical;
- -webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
@@ -1446,7 +1375,6 @@ main {
*/ }
.kuiFlexItem.kuiFlexItem--flexGrowZero {
/* 1 */
- -webkit-box-flex: 0;
-webkit-flex-grow: 0;
-ms-flex-positive: 0;
flex-grow: 0;
@@ -1456,52 +1384,42 @@ main {
flex-basis: auto;
/* 2 */ }
.kuiFlexItem.kuiFlexItem--flexGrow1 {
- -webkit-box-flex: 1;
-webkit-flex-grow: 1;
-ms-flex-positive: 1;
flex-grow: 1; }
.kuiFlexItem.kuiFlexItem--flexGrow2 {
- -webkit-box-flex: 2;
-webkit-flex-grow: 2;
-ms-flex-positive: 2;
flex-grow: 2; }
.kuiFlexItem.kuiFlexItem--flexGrow3 {
- -webkit-box-flex: 3;
-webkit-flex-grow: 3;
-ms-flex-positive: 3;
flex-grow: 3; }
.kuiFlexItem.kuiFlexItem--flexGrow4 {
- -webkit-box-flex: 4;
-webkit-flex-grow: 4;
-ms-flex-positive: 4;
flex-grow: 4; }
.kuiFlexItem.kuiFlexItem--flexGrow5 {
- -webkit-box-flex: 5;
-webkit-flex-grow: 5;
-ms-flex-positive: 5;
flex-grow: 5; }
.kuiFlexItem.kuiFlexItem--flexGrow6 {
- -webkit-box-flex: 6;
-webkit-flex-grow: 6;
-ms-flex-positive: 6;
flex-grow: 6; }
.kuiFlexItem.kuiFlexItem--flexGrow7 {
- -webkit-box-flex: 7;
-webkit-flex-grow: 7;
-ms-flex-positive: 7;
flex-grow: 7; }
.kuiFlexItem.kuiFlexItem--flexGrow8 {
- -webkit-box-flex: 8;
-webkit-flex-grow: 8;
-ms-flex-positive: 8;
flex-grow: 8; }
.kuiFlexItem.kuiFlexItem--flexGrow9 {
- -webkit-box-flex: 9;
-webkit-flex-grow: 9;
-ms-flex-positive: 9;
flex-grow: 9; }
.kuiFlexItem.kuiFlexItem--flexGrow10 {
- -webkit-box-flex: 10;
-webkit-flex-grow: 10;
-ms-flex-positive: 10;
flex-grow: 10; }
@@ -1592,11 +1510,9 @@ main {
background-color: #0079a5; }
.kuiCheckBoxLabel {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
@@ -1860,18 +1776,15 @@ main {
* 1. We may want to put elements in here which have different heights.
*/
.kuiFieldGroup {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
/* 1 */ }
.kuiFieldGroup--alignTop {
- -webkit-box-align: start;
-webkit-align-items: flex-start;
-ms-flex-align: start;
align-items: flex-start; }
@@ -1882,7 +1795,6 @@ main {
margin-left: 10px; }
.kuiFieldGroupSection--wide {
- -webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto; }
@@ -1890,7 +1802,6 @@ main {
width: 100%; }
.kuiGallery {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
@@ -1899,20 +1810,15 @@ main {
flex-wrap: wrap; }
.kuiGalleryItem {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-orient: vertical;
- -webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
@@ -1931,15 +1837,12 @@ main {
border-color: #00A6FF; }
.kuiGalleryItem__image {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
@@ -1970,15 +1873,12 @@ main {
color: #666; }
.kuiHeaderBar {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
@@ -1990,22 +1890,18 @@ main {
* 1. Align a single section to the left by default.
*/
.kuiHeaderBarSection {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
margin-left: 25px;
margin-right: 25px; }
.kuiHeaderBarSection:not(:first-child):not(:last-child):not(:only-child) {
- -webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
@@ -2014,12 +1910,10 @@ main {
margin-left: 0; }
.kuiHeaderBarSection:last-child {
margin-right: 0;
- -webkit-box-flex: 0;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
/* 4 */
- -webkit-box-pack: end;
-webkit-justify-content: flex-end;
-ms-flex-pack: end;
justify-content: flex-end;
@@ -2118,11 +2012,9 @@ main {
* 1. Align with first line of title text if it wraps.
*/
.kuiInfoPanelHeader {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: baseline;
-webkit-align-items: baseline;
-ms-flex-align: baseline;
align-items: baseline;
@@ -2179,11 +2071,9 @@ main {
* a bit.
*/
.kuiLocalBreadcrumbs {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
@@ -2253,15 +2143,12 @@ main {
padding: 0; }
.kuiDatePickerNavigation {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
@@ -2425,13 +2312,11 @@ main {
/* 1 */ }
.kuiLocalDropdownPanels {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex; }
.kuiLocalDropdownPanel {
- -webkit-box-flex: 1;
-webkit-flex: 1 1 0%;
-ms-flex: 1 1 0%;
flex: 1 1 0%; }
@@ -2460,15 +2345,12 @@ main {
margin-bottom: 0; }
.kuiLocalDropdownHeader {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
@@ -2487,7 +2369,6 @@ main {
color: #cecece; }
.kuiLocalDropdownHeader__actions {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex; }
@@ -2549,21 +2430,17 @@ main {
color: #9e9e9e; }
.kuiLocalMenu {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
.kuiLocalMenuItem {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
@@ -2615,16 +2492,12 @@ main {
* dropdown.
*/
.kuiLocalNav {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-orient: vertical;
- -webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
- -webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
@@ -2643,15 +2516,12 @@ main {
* 1. Allow row to expand if the content is so long that it wraps.
*/
.kuiLocalNavRow {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch;
- -webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
@@ -2659,11 +2529,9 @@ main {
/* 1 */ }
.kuiLocalNavRow__section {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: stretch;
-webkit-align-items: stretch;
-ms-flex-align: stretch;
align-items: stretch; }
@@ -2678,14 +2546,12 @@ main {
.kuiLocalNavRow--secondary {
padding: 0 10px;
/* 1 */
- -webkit-box-align: start;
-webkit-align-items: flex-start;
-ms-flex-align: start;
align-items: flex-start;
/* 1 */ }
.kuiLocalSearch {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
@@ -2708,7 +2574,6 @@ main {
transition: border-color 0.1s linear;
min-height: 30px;
/* 1 */
- -webkit-box-flex: 1;
-webkit-flex: 1 1 100%;
-ms-flex: 1 1 100%;
flex: 1 1 100%;
@@ -2738,7 +2603,6 @@ main {
.kuiLocalSearchInput--secondary {
height: 30px;
- -webkit-box-flex: 0;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
@@ -2750,11 +2614,9 @@ main {
border-right-color: #333333; }
.kuiLocalSearchAssistedInput {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-flex: 1;
-webkit-flex: 1 1 100%;
-ms-flex: 1 1 100%;
flex: 1 1 100%;
@@ -2857,11 +2719,9 @@ main {
* 1. We want the bottom border on selected tabs to be flush with the bottom of the container.
*/
.kuiLocalTabs {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: end;
-webkit-align-items: flex-end;
-ms-flex-align: end;
align-items: flex-end;
@@ -2906,11 +2766,9 @@ main {
color: #dedede; }
.kuiLocalTitle {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
@@ -3042,7 +2900,6 @@ main {
/* 3 */ }
.kuiMenuButtonGroup {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex; }
@@ -3050,7 +2907,6 @@ main {
margin-left: 4px; }
.kuiMenuButtonGroup--alignRight {
- -webkit-box-pack: end;
-webkit-justify-content: flex-end;
-ms-flex-pack: end;
justify-content: flex-end; }
@@ -3073,7 +2929,6 @@ main {
color: #191E23; }
.kuiMicroButtonGroup {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex; }
@@ -3087,15 +2942,12 @@ main {
left: 0;
right: 0;
bottom: 0;
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
@@ -3121,15 +2973,12 @@ main {
min-width: auto; }
.kuiModalHeader {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
@@ -3170,11 +3019,9 @@ main {
color: #cecece; }
.kuiModalFooter {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-pack: end;
-webkit-justify-content: flex-end;
-ms-flex-pack: end;
justify-content: flex-end;
@@ -3213,11 +3060,9 @@ main {
* 1. Put 10px of space between each child.
*/
.kuiPager {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center; }
@@ -3237,21 +3082,16 @@ main {
border-radius: 4px; }
.kuiPanel--prompt {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-orient: vertical;
- -webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
text-align: center;
- -webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
@@ -3268,29 +3108,23 @@ main {
border-radius: 0; }
.kuiPanel--centered {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center; }
.kuiPanelHeader {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
@@ -3352,22 +3186,18 @@ main {
* 1. Undo what barSection mixin does.
*/
.kuiPanelHeaderSection {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
margin-left: 25px;
margin-right: 25px; }
.kuiPanelHeaderSection:not(:first-child):not(:last-child):not(:only-child) {
- -webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
@@ -3376,12 +3206,10 @@ main {
margin-left: 0; }
.kuiPanelHeaderSection:last-child {
margin-right: 0;
- -webkit-box-flex: 0;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
/* 4 */
- -webkit-box-pack: end;
-webkit-justify-content: flex-end;
-ms-flex-pack: end;
justify-content: flex-end;
@@ -3406,7 +3234,6 @@ main {
background-color: #FFF;
border: 1px solid #D9D9D9;
border-radius: 4px;
- -webkit-box-flex: 1;
-webkit-flex-grow: 1;
-ms-flex-positive: 1;
flex-grow: 1; }
@@ -3419,7 +3246,6 @@ main {
.kuiPanelSimple.kuiPanelSimple--shadow {
box-shadow: 0 16px 16px -8px rgba(0, 0, 0, 0.1); }
.kuiPanelSimple.kuiPanelSimple--flexGrowZero {
- -webkit-box-flex: 0;
-webkit-flex-grow: 0;
-ms-flex-positive: 0;
flex-grow: 0; }
@@ -3513,16 +3339,12 @@ main {
color: #ffffff; }
.kuiEmptyTablePrompt {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-orient: vertical;
- -webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
@@ -3537,11 +3359,9 @@ main {
margin-top: 10px; }
.kuiStatusText {
- display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
- -webkit-box-align: baseline;
-webkit-align-items: baseline;
-ms-flex-align: baseline;
align-items: baseline; }
@@ -3657,11 +3477,9 @@ main {
display: block;
opacity: 1; }
.kuiTableHeaderCellButton .kuiTableHeaderCell__liner {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center; }
@@ -3762,7 +3580,6 @@ main {
line-height: 1.5; }
.kuiTabs {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
@@ -3886,15 +3703,12 @@ main {
/* 1 */ }
.kuiToolBar {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
@@ -3945,22 +3759,18 @@ main {
border-color: #0079a5; }
.kuiToolBarSection {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
margin-left: 25px;
margin-right: 25px; }
.kuiToolBarSection:not(:first-child):not(:last-child):not(:only-child) {
- -webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
@@ -3969,12 +3779,10 @@ main {
margin-left: 0; }
.kuiToolBarSection:last-child {
margin-right: 0;
- -webkit-box-flex: 0;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
/* 4 */
- -webkit-box-pack: end;
-webkit-justify-content: flex-end;
-ms-flex-pack: end;
justify-content: flex-end;
@@ -3994,15 +3802,12 @@ main {
/* 1 */ }
.kuiToolBarFooter {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
@@ -4014,22 +3819,18 @@ main {
border: 1px solid #D9D9D9; }
.kuiToolBarFooterSection {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
- -webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
margin-left: 25px;
margin-right: 25px; }
.kuiToolBarFooterSection:not(:first-child):not(:last-child):not(:only-child) {
- -webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
@@ -4038,12 +3839,10 @@ main {
margin-left: 0; }
.kuiToolBarFooterSection:last-child {
margin-right: 0;
- -webkit-box-flex: 0;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
/* 4 */
- -webkit-box-pack: end;
-webkit-justify-content: flex-end;
-ms-flex-pack: end;
justify-content: flex-end;
@@ -4061,17 +3860,14 @@ main {
* kuiToolBarSection sibling.
*/
.kuiToolBarSearch {
- display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
margin-left: 25px;
margin-right: 25px;
- -webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
@@ -4087,7 +3883,6 @@ main {
/* 1 */ }
.kuiToolBarSearchBox {
- -webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
@@ -4210,7 +4005,6 @@ main {
.kuiView {
background-color: #FFF;
- -webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto; }
diff --git a/x-pack/package.json b/x-pack/package.json
index bfcb1dd5a9905..95aa06158b637 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -75,7 +75,7 @@
"yargs": "4.7.1"
},
"dependencies": {
- "@elastic/eui": "0.0.47",
+ "@elastic/eui": "v0.0.51",
"@elastic/node-crypto": "0.1.2",
"@elastic/node-phantom-simple": "2.2.4",
"@elastic/numeral": "2.3.2",
diff --git a/x-pack/plugins/security/public/documentation_links.js b/x-pack/plugins/security/public/documentation_links.js
index 8d9bb3c2256b4..d357451d48ac7 100644
--- a/x-pack/plugins/security/public/documentation_links.js
+++ b/x-pack/plugins/security/public/documentation_links.js
@@ -7,5 +7,8 @@
import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links';
export const documentationLinks = {
- dashboardViewMode: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/xpack-view-modes.html`
+ dashboardViewMode: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/xpack-view-modes.html`,
+ esClusterPrivileges: `${ELASTIC_WEBSITE_URL}guide/en/x-pack/${DOC_LINK_VERSION}/security-privileges.html#security-privileges`,
+ esIndicesPrivileges: `${ELASTIC_WEBSITE_URL}guide/en/x-pack/${DOC_LINK_VERSION}/security-privileges.html#privileges-list-indices`,
+ esRunAsPrivileges: `${ELASTIC_WEBSITE_URL}guide/en/x-pack/${DOC_LINK_VERSION}/security-privileges.html#_run_as_privilege`,
};
diff --git a/x-pack/plugins/security/public/lib/__tests__/role.js b/x-pack/plugins/security/public/lib/__tests__/role.js
deleted file mode 100644
index efb22152ef7b1..0000000000000
--- a/x-pack/plugins/security/public/lib/__tests__/role.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 expect from 'expect.js';
-import { isRoleEnabled } from '../role';
-
-describe('role', () => {
- describe('isRoleEnabled', () => {
- it('should return false if role is explicitly not enabled', () => {
- const testRole = {
- transient_metadata: {
- enabled: false
- }
- };
- expect(isRoleEnabled(testRole)).to.be(false);
- });
-
- it('should return true if role is explicitly enabled', () => {
- const testRole = {
- transient_metadata: {
- enabled: true
- }
- };
- expect(isRoleEnabled(testRole)).to.be(true);
- });
-
- it('should return true if role is NOT explicitly enabled or disabled', () => {
- const testRole = {};
- expect(isRoleEnabled(testRole)).to.be(true);
- });
- });
-});
diff --git a/x-pack/plugins/security/public/lib/role.js b/x-pack/plugins/security/public/lib/role.js
index 89eade0f0584e..96057aa55f595 100644
--- a/x-pack/plugins/security/public/lib/role.js
+++ b/x-pack/plugins/security/public/lib/role.js
@@ -14,4 +14,13 @@ import { get } from 'lodash';
*/
export function isRoleEnabled(role) {
return get(role, 'transient_metadata.enabled', true);
-}
\ No newline at end of file
+}
+
+/**
+ * Returns whether given role is reserved or not.
+ *
+ * @param {role} the Role as returned by roles API
+ */
+export function isReservedRole(role) {
+ return get(role, 'metadata._reserved', false);
+}
diff --git a/x-pack/plugins/security/public/lib/role.test.js b/x-pack/plugins/security/public/lib/role.test.js
new file mode 100644
index 0000000000000..4c6e9fb896dcf
--- /dev/null
+++ b/x-pack/plugins/security/public/lib/role.test.js
@@ -0,0 +1,59 @@
+/*
+ * 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 { isRoleEnabled, isReservedRole } from './role';
+
+describe('role', () => {
+ describe('isRoleEnabled', () => {
+ test('should return false if role is explicitly not enabled', () => {
+ const testRole = {
+ transient_metadata: {
+ enabled: false
+ }
+ };
+ expect(isRoleEnabled(testRole)).toBe(false);
+ });
+
+ test('should return true if role is explicitly enabled', () => {
+ const testRole = {
+ transient_metadata: {
+ enabled: true
+ }
+ };
+ expect(isRoleEnabled(testRole)).toBe(true);
+ });
+
+ test('should return true if role is NOT explicitly enabled or disabled', () => {
+ const testRole = {};
+ expect(isRoleEnabled(testRole)).toBe(true);
+ });
+ });
+
+ describe('isReservedRole', () => {
+ test('should return false if role is explicitly not reserved', () => {
+ const testRole = {
+ metadata: {
+ _reserved: false
+ }
+ };
+ expect(isReservedRole(testRole)).toBe(false);
+ });
+
+ test('should return true if role is explicitly reserved', () => {
+ const testRole = {
+ metadata: {
+ _reserved: true
+ }
+ };
+ expect(isReservedRole(testRole)).toBe(true);
+ });
+
+ test('should return false if role is NOT explicitly reserved or not reserved', () => {
+ const testRole = {};
+ expect(isReservedRole(testRole)).toBe(false);
+ });
+ });
+});
diff --git a/x-pack/plugins/security/public/objects/index.js b/x-pack/plugins/security/public/objects/index.js
new file mode 100644
index 0000000000000..a6238ca879901
--- /dev/null
+++ b/x-pack/plugins/security/public/objects/index.js
@@ -0,0 +1,9 @@
+/*
+ * 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 { saveRole, deleteRole } from './lib/roles';
+
+export { getFields } from './lib/get_fields';
diff --git a/x-pack/plugins/security/public/objects/lib/get_fields.js b/x-pack/plugins/security/public/objects/lib/get_fields.js
new file mode 100644
index 0000000000000..a80f6bfe8eed1
--- /dev/null
+++ b/x-pack/plugins/security/public/objects/lib/get_fields.js
@@ -0,0 +1,14 @@
+/*
+ * 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 chrome from 'ui/chrome';
+
+const apiBase = chrome.addBasePath(`/api/security/v1/fields`);
+
+export async function getFields($http, query) {
+ return await $http
+ .get(`${apiBase}/${query}`)
+ .then(response => response.data || []);
+}
diff --git a/x-pack/plugins/security/public/objects/lib/roles.js b/x-pack/plugins/security/public/objects/lib/roles.js
new file mode 100644
index 0000000000000..5fdd95a7ccb64
--- /dev/null
+++ b/x-pack/plugins/security/public/objects/lib/roles.js
@@ -0,0 +1,16 @@
+/*
+ * 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 chrome from 'ui/chrome';
+
+const apiBase = chrome.addBasePath(`/api/security/v1/roles`);
+
+export async function saveRole($http, role) {
+ return await $http.post(`${apiBase}/${role.name}`, role);
+}
+
+export async function deleteRole($http, name) {
+ return await $http.delete(`${apiBase}/${name}`);
+}
diff --git a/x-pack/plugins/security/public/objects/role.js b/x-pack/plugins/security/public/objects/role.js
new file mode 100644
index 0000000000000..98448db207cea
--- /dev/null
+++ b/x-pack/plugins/security/public/objects/role.js
@@ -0,0 +1,13 @@
+/*
+ * 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 class Role {
+ name = null;
+ cluster = [];
+ indices = [];
+ run_as = []; //eslint-disable-line camelcase
+ applications = [];
+}
diff --git a/x-pack/plugins/security/public/services/role_privileges.js b/x-pack/plugins/security/public/services/role_privileges.js
new file mode 100644
index 0000000000000..794a4b30674e5
--- /dev/null
+++ b/x-pack/plugins/security/public/services/role_privileges.js
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+const clusterPrivileges = [
+ 'all',
+ 'monitor',
+ 'manage',
+ 'manage_security',
+ 'manage_index_templates',
+ 'manage_pipeline',
+ 'manage_ingest_pipelines',
+ 'transport_client',
+ 'manage_ml',
+ 'monitor_ml',
+ 'manage_watcher',
+ 'monitor_watcher',
+];
+const indexPrivileges = [
+ 'all',
+ 'manage',
+ 'monitor',
+ 'read',
+ 'index',
+ 'create',
+ 'delete',
+ 'write',
+ 'delete_index',
+ 'create_index',
+ 'view_index_metadata',
+ 'read_cross_cluster',
+];
+
+export function getClusterPrivileges() {
+ return [...clusterPrivileges];
+}
+
+export function getIndexPrivileges() {
+ return [...indexPrivileges];
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role.html b/x-pack/plugins/security/public/views/management/edit_role.html
deleted file mode 100644
index 8c8c5d692cce2..0000000000000
--- a/x-pack/plugins/security/public/views/management/edit_role.html
+++ /dev/null
@@ -1,194 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- New Role
-
-
- “{{ role.name }}” Role
-
-
-
-
-
-
-
-
-
- Delete role
-
-
-
-
- Reserved
-
-
-
-
-
-
-
-
-
-
diff --git a/x-pack/plugins/security/public/views/management/edit_role.js b/x-pack/plugins/security/public/views/management/edit_role.js
deleted file mode 100644
index 3f0ab02129dbb..0000000000000
--- a/x-pack/plugins/security/public/views/management/edit_role.js
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * 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 _ from 'lodash';
-import routes from 'ui/routes';
-import { fatalError, toastNotifications } from 'ui/notify';
-import { toggle } from 'plugins/security/lib/util';
-import { isRoleEnabled } from 'plugins/security/lib/role';
-import template from 'plugins/security/views/management/edit_role.html';
-import 'angular-ui-select';
-import 'plugins/security/services/application_privilege';
-import 'plugins/security/services/shield_user';
-import 'plugins/security/services/shield_role';
-import 'plugins/security/services/shield_privileges';
-import 'plugins/security/services/shield_indices';
-
-import { IndexPatternsProvider } from 'ui/index_patterns/index_patterns';
-import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
-import { checkLicenseError } from 'plugins/security/lib/check_license_error';
-import { EDIT_ROLES_PATH, ROLES_PATH } from './management_urls';
-import { DEFAULT_RESOURCE } from '../../../common/constants';
-
-const getKibanaPrivileges = (kibanaApplicationPrivilege, role, application) => {
- const kibanaPrivileges = kibanaApplicationPrivilege.reduce((acc, p) => {
- acc[p.name] = false;
- return acc;
- }, {});
-
- if (!role.applications || role.applications.length === 0) {
- return kibanaPrivileges;
- }
-
- const applications = role.applications.filter(x => x.application === application);
-
- const assigned = _.uniq(_.flatten(_.pluck(applications, 'privileges')));
- assigned.forEach(a => {
- kibanaPrivileges[a] = true;
- });
-
- return kibanaPrivileges;
-};
-
-const setApplicationPrivileges = (kibanaPrivileges, role, application) => {
- if (!role.applications) {
- role.applications = [];
- }
-
- // we first remove the matching application entries
- role.applications = role.applications.filter(x => {
- return x.application !== application;
- });
-
- const privileges = Object.keys(kibanaPrivileges).filter(key => kibanaPrivileges[key]);
-
- // if we still have them, put the application entry back
- if (privileges.length > 0) {
- role.applications = [...role.applications, {
- application,
- privileges,
- resources: [ DEFAULT_RESOURCE ]
- }];
- }
-};
-
-const getOtherApplications = (kibanaPrivileges, role, application) => {
- if (!role.applications || role.applications.length === 0) {
- return [];
- }
-
- return role.applications.map(x => x.application).filter(x => x !== application);
-};
-
-routes.when(`${EDIT_ROLES_PATH}/:name?`, {
- template,
- resolve: {
- role($route, ShieldRole, kbnUrl, Promise, Notifier) {
- const name = $route.current.params.name;
- if (name != null) {
- return ShieldRole.get({ name }).$promise
- .catch((response) => {
-
- if (response.status !== 404) {
- return fatalError(response);
- }
-
- const notifier = new Notifier();
- notifier.error(`No "${name}" role found.`);
- kbnUrl.redirect(ROLES_PATH);
- return Promise.halt();
- });
- }
- return new ShieldRole({
- cluster: [],
- indices: [],
- run_as: [],
- applications: []
- });
- },
- kibanaApplicationPrivilege(ApplicationPrivilege, kbnUrl, Promise, Private) {
- return ApplicationPrivilege.query().$promise
- .catch(checkLicenseError(kbnUrl, Promise, Private));
- },
- users(ShieldUser, kbnUrl, Promise, Private) {
- // $promise is used here because the result is an ngResource, not a promise itself
- return ShieldUser.query().$promise
- .then(users => _.map(users, 'username'))
- .catch(checkLicenseError(kbnUrl, Promise, Private));
- },
- indexPatterns(Private) {
- const indexPatterns = Private(IndexPatternsProvider);
- return indexPatterns.getTitles();
- }
- },
- controllerAs: 'editRole',
- controller($injector, $scope, rbacEnabled, rbacApplication) {
- const $route = $injector.get('$route');
- const kbnUrl = $injector.get('kbnUrl');
- const shieldPrivileges = $injector.get('shieldPrivileges');
- const Notifier = $injector.get('Notifier');
- const Private = $injector.get('Private');
- const confirmModal = $injector.get('confirmModal');
- const shieldIndices = $injector.get('shieldIndices');
-
- $scope.role = $route.current.locals.role;
- $scope.users = $route.current.locals.users;
- $scope.indexPatterns = $route.current.locals.indexPatterns;
- $scope.privileges = shieldPrivileges;
-
- $scope.rbacEnabled = rbacEnabled;
- const kibanaApplicationPrivilege = $route.current.locals.kibanaApplicationPrivilege;
- const role = $route.current.locals.role;
- $scope.kibanaPrivileges = getKibanaPrivileges(kibanaApplicationPrivilege, role, rbacApplication);
- $scope.otherApplications = getOtherApplications(kibanaApplicationPrivilege, role, rbacApplication);
-
- $scope.rolesHref = `#${ROLES_PATH}`;
-
- this.isNewRole = $route.current.params.name == null;
- this.fieldOptions = {};
-
- const notifier = new Notifier();
-
- $scope.deleteRole = (role) => {
- const doDelete = () => {
- role.$delete()
- .then(() => toastNotifications.addSuccess('Deleted role'))
- .then($scope.goToRoleList)
- .catch(error => notifier.error(_.get(error, 'data.message')));
- };
- const confirmModalOptions = {
- confirmButtonText: 'Delete role',
- onConfirm: doDelete
- };
- confirmModal('Are you sure you want to delete this role? This action is irreversible!', confirmModalOptions);
- };
-
- $scope.saveRole = (role) => {
- role.indices = role.indices.filter((index) => index.names.length);
- role.indices.forEach((index) => index.query || delete index.query);
-
- setApplicationPrivileges($scope.kibanaPrivileges, role, rbacApplication);
-
- return role.$save()
- .then(() => toastNotifications.addSuccess('Updated role'))
- .then($scope.goToRoleList)
- .catch(error => notifier.error(_.get(error, 'data.message')));
- };
-
- $scope.goToRoleList = () => {
- kbnUrl.redirect(ROLES_PATH);
- };
-
- $scope.addIndex = indices => {
- indices.push({ names: [], privileges: [], field_security: { grant: ['*'] } });
- };
-
- $scope.areIndicesValid = (indices) => {
- return indices
- .filter((index) => index.names.length)
- .find((index) => index.privileges.length === 0) == null;
- };
-
- $scope.fetchFieldOptions = (index) => {
- const indices = index.names.join(',');
- const fieldOptions = this.fieldOptions;
- if (indices && fieldOptions[indices] == null) {
- shieldIndices.getFields(indices)
- .then((fields) => fieldOptions[indices] = fields)
- .catch(() => fieldOptions[indices] = []);
- }
- };
-
- $scope.isRoleEnabled = isRoleEnabled;
-
- const xpackInfo = Private(XPackInfoProvider);
- $scope.allowDocumentLevelSecurity = xpackInfo.get('features.security.allowRoleDocumentLevelSecurity');
- $scope.allowFieldLevelSecurity = xpackInfo.get('features.security.allowRoleFieldLevelSecurity');
-
- $scope.$watch('role.indices', (indices) => {
- if (!indices.length) $scope.addIndex(indices);
- else indices.forEach($scope.fetchFieldOptions);
- }, true);
-
- $scope.toggle = toggle;
- $scope.includes = _.includes;
- $scope.togglePermission = (role, permission) => {
- const shouldRemove = $scope.hasPermission(role, permission);
- const rolePermissions = role.applications || [];
- if (shouldRemove) {
- role.applications = rolePermissions.filter(rolePermission => rolePermission.name === permission.name);
- } else {
- role.applications = rolePermissions.concat([permission]);
- }
- };
-
- $scope.union = _.flow(_.union, _.compact);
- }
-});
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/__snapshots__/collapsible_panel.test.js.snap b/x-pack/plugins/security/public/views/management/edit_role/components/__snapshots__/collapsible_panel.test.js.snap
new file mode 100644
index 0000000000000..d946357354fe2
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/__snapshots__/collapsible_panel.test.js.snap
@@ -0,0 +1,59 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`it renders without blowing up 1`] = `
+
+
+
+
+
+
+
+ Elasticsearch
+
+
+
+
+
+ hide
+
+
+
+
+
+
+ child
+
+
+
+`;
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/__snapshots__/page_header.test.js.snap b/x-pack/plugins/security/public/views/management/edit_role/components/__snapshots__/page_header.test.js.snap
new file mode 100644
index 0000000000000..628a5c068f83a
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/__snapshots__/page_header.test.js.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`it renders without crashing 1`] = `
+
+
+
+
+`;
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/collapsible_panel.js b/x-pack/plugins/security/public/views/management/edit_role/components/collapsible_panel.js
new file mode 100644
index 0000000000000..f2bac7a02b99c
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/collapsible_panel.js
@@ -0,0 +1,74 @@
+/*
+ * 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 React, { Component, Fragment } from 'react';
+import PropTypes from 'prop-types';
+import './collapsible_panel.less';
+import {
+ EuiPanel,
+ EuiLink,
+ EuiIcon,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiTitle,
+ EuiSpacer,
+} from '@elastic/eui';
+
+export class CollapsiblePanel extends Component {
+ static propTypes = {
+ iconType: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ }
+
+ state = {
+ collapsed: false
+ }
+
+ render() {
+ return (
+
+ {this.getTitle()}
+ {this.getForm()}
+
+ );
+ }
+
+ getTitle = () => {
+ return (
+
+
+
+
+ {this.props.title}
+
+
+
+
+ {this.state.collapsed ? 'show' : 'hide'}
+
+
+ );
+ };
+
+ getForm = () => {
+ if (this.state.collapsed) {
+ return null;
+ }
+
+ return (
+
+
+ {this.props.children}
+
+ );
+ }
+
+ toggleCollapsed = () => {
+ this.setState({
+ collapsed: !this.state.collapsed
+ });
+ }
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/collapsible_panel.less b/x-pack/plugins/security/public/views/management/edit_role/components/collapsible_panel.less
new file mode 100644
index 0000000000000..ffb065880c560
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/collapsible_panel.less
@@ -0,0 +1,4 @@
+.collapsiblePanel__logo {
+ margin-right: 8px;
+ vertical-align: text-bottom;
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/collapsible_panel.test.js b/x-pack/plugins/security/public/views/management/edit_role/components/collapsible_panel.test.js
new file mode 100644
index 0000000000000..ec0182101653a
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/collapsible_panel.test.js
@@ -0,0 +1,57 @@
+/*
+ * 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 React from 'react';
+import { shallow, mount } from 'enzyme';
+import { CollapsiblePanel } from './collapsible_panel';
+import { EuiLink } from '@elastic/eui';
+
+test('it renders without blowing up', () => {
+ const wrapper = shallow(
+
+ child
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('it renders children by default', () => {
+ const wrapper = mount(
+
+ child 1
+ child 2
+
+ );
+
+ expect(wrapper.find(CollapsiblePanel)).toHaveLength(1);
+ expect(wrapper.find('.child')).toHaveLength(2);
+});
+
+test('it hides children when the "hide" link is clicked', () => {
+ const wrapper = mount(
+
+ child 1
+ child 2
+
+ );
+
+ expect(wrapper.find(CollapsiblePanel)).toHaveLength(1);
+ expect(wrapper.find('.child')).toHaveLength(2);
+
+ wrapper.find(EuiLink).simulate('click');
+
+ expect(wrapper.find('.child')).toHaveLength(0);
+});
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/delete_role_button.js b/x-pack/plugins/security/public/views/management/edit_role/components/delete_role_button.js
new file mode 100644
index 0000000000000..805dfb232fff4
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/delete_role_button.js
@@ -0,0 +1,78 @@
+/*
+ * 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 React, { Component, Fragment } from 'react';
+import PropTypes from 'prop-types';
+import {
+ EuiButton,
+ EuiOverlayMask,
+ EuiConfirmModal,
+} from '@elastic/eui';
+
+export class DeleteRoleButton extends Component {
+ static propTypes = {
+ canDelete: PropTypes.bool.isRequired,
+ onDelete: PropTypes.func.isRequired
+ }
+
+ state = {
+ showModal: false
+ }
+
+ render() {
+ if (!this.props.canDelete) {
+ return null;
+ }
+
+ return (
+
+
+ Delete Role
+
+ {this.maybeShowModal()}
+
+ );
+ }
+
+ maybeShowModal = () => {
+ if (!this.state.showModal) {
+ return null;
+ }
+ return (
+
+
+ Are you sure you want to delete this role?
+ This action cannot be undone!
+
+
+ );
+ }
+
+ closeModal = () => {
+ this.setState({
+ showModal: false
+ });
+ }
+
+ showModal = () => {
+ this.setState({
+ showModal: true
+ });
+ }
+
+ onConfirmDelete = () => {
+ this.closeModal();
+ this.props.onDelete();
+ }
+}
+
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/delete_role_button.test.js b/x-pack/plugins/security/public/views/management/edit_role/components/delete_role_button.test.js
new file mode 100644
index 0000000000000..477ffbebf8699
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/delete_role_button.test.js
@@ -0,0 +1,41 @@
+/*
+ * 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 React from 'react';
+import {
+ EuiButton,
+ EuiConfirmModal,
+} from '@elastic/eui';
+import { DeleteRoleButton } from './delete_role_button';
+import {
+ shallow,
+ mount
+} from 'enzyme';
+
+test('it renders without crashing', () => {
+ const deleteHandler = jest.fn();
+ const wrapper = shallow( );
+ expect(wrapper.find(EuiButton)).toHaveLength(1);
+ expect(deleteHandler).toHaveBeenCalledTimes(0);
+});
+
+test('it shows a confirmation dialog when clicked', () => {
+ const deleteHandler = jest.fn();
+ const wrapper = mount( );
+
+ wrapper.find(EuiButton).simulate('click');
+
+ expect(wrapper.find(EuiConfirmModal)).toHaveLength(1);
+
+ expect(deleteHandler).toHaveBeenCalledTimes(0);
+});
+
+test('it renders nothing when canDelete is false', () => {
+ const deleteHandler = jest.fn();
+ const wrapper = shallow( );
+ expect(wrapper.find('*')).toHaveLength(0);
+ expect(deleteHandler).toHaveBeenCalledTimes(0);
+});
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/edit_role_page.js b/x-pack/plugins/security/public/views/management/edit_role/components/edit_role_page.js
new file mode 100644
index 0000000000000..13cd7a4f1823b
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/edit_role_page.js
@@ -0,0 +1,274 @@
+/*
+ * 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 React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { get } from 'lodash';
+import { toastNotifications } from 'ui/notify';
+import {
+ EuiPanel,
+ EuiTitle,
+ EuiSpacer,
+ EuiPage,
+ EuiButtonEmpty,
+ EuiForm,
+ EuiFormRow,
+ EuiFieldText,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButton,
+} from '@elastic/eui';
+import { PageHeader } from './page_header';
+import { saveRole, deleteRole } from '../../../../objects';
+import { isReservedRole } from '../../../../lib/role';
+import { RoleValidator } from '../lib/validate_role';
+import { ReservedRoleBadge } from './reserved_role_badge';
+import { ROLES_PATH } from '../../management_urls';
+import { DeleteRoleButton } from './delete_role_button';
+import { ElasticsearchPrivileges, KibanaPrivileges } from './privileges';
+
+export class EditRolePage extends Component {
+ static propTypes = {
+ role: PropTypes.object.isRequired,
+ runAsUsers: PropTypes.array.isRequired,
+ indexPatterns: PropTypes.array.isRequired,
+ httpClient: PropTypes.func.isRequired,
+ rbacEnabled: PropTypes.bool.isRequired,
+ rbacApplication: PropTypes.string,
+ allowDocumentLevelSecurity: PropTypes.bool.isRequired,
+ allowFieldLevelSecurity: PropTypes.bool.isRequired,
+ kibanaAppPrivileges: PropTypes.array.isRequired,
+ notifier: PropTypes.func.isRequired,
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ role: props.role,
+ formError: null
+ };
+ this.validator = new RoleValidator({ shouldValidate: false });
+ }
+
+ render() {
+ return (
+
+
+
+ {this.getFormTitle()}
+
+
+
+ {this.getRoleName()}
+
+ {this.getElasticsearchPrivileges()}
+
+ {this.getKibanaPrivileges()}
+
+
+
+ {this.getFormButtons()}
+
+
+ );
+ }
+
+ getFormTitle = () => {
+ let titleText;
+ if (isReservedRole(this.props.role)) {
+ titleText = 'Viewing role';
+ } else if (this.editingExistingRole()) {
+ titleText = 'Edit role';
+ } else {
+ titleText = 'Create role';
+ }
+
+ return (
+ {titleText}
+ );
+ };
+
+ getActionButton = () => {
+ if (this.editingExistingRole() && !isReservedRole(this.props.role)) {
+ return (
+
+
+
+ );
+ }
+
+ return null;
+ };
+
+ getRoleName = () => {
+ return (
+
+
+
+
+
+
+ );
+ }
+
+ onNameChange = (e) => {
+ const rawValue = e.target.value;
+ const name = rawValue.replace(/\s/g, '-');
+
+ this.setState({
+ role: {
+ ...this.state.role,
+ name
+ }
+ });
+ }
+
+ getElasticsearchPrivileges() {
+ return (
+
+
+
+
+ );
+ }
+
+ onRoleChange = (role) => {
+ this.setState({
+ role
+ });
+ }
+
+ getKibanaPrivileges = () => {
+ if (!this.props.rbacEnabled) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ );
+ };
+
+ getFormButtons = () => {
+ if (isReservedRole(this.props.role)) {
+ return (
+
+ Return to role list
+
+ );
+ }
+
+ const saveText = this.editingExistingRole() ? 'Update role' : 'Create role';
+
+ return (
+
+
+ {saveText}
+
+
+
+ Cancel
+
+
+
+ {this.getActionButton()}
+
+ );
+ };
+
+ editingExistingRole = () => {
+ return !!this.props.role.name;
+ };
+
+ isPlaceholderPrivilege = (indexPrivilege) => {
+ return indexPrivilege.names.length === 0;
+ };
+
+ saveRole = () => {
+ this.validator.enableValidation();
+
+ const result = this.validator.validateForSave(this.state.role);
+ if (result.isInvalid) {
+ this.setState({
+ formError: result
+ });
+ } else {
+ this.setState({
+ formError: null
+ });
+
+ const {
+ httpClient,
+ notifier,
+ } = this.props;
+
+ const role = {
+ ...this.state.role
+ };
+
+ role.indices = role.indices.filter(i => !this.isPlaceholderPrivilege(i));
+ role.indices.forEach((index) => index.query || delete index.query);
+
+ saveRole(httpClient, role)
+ .then(() => {
+ toastNotifications.addSuccess('Saved role');
+ this.backToRoleList();
+ })
+ .catch(error => {
+ notifier.error(get(error, 'data.message'));
+ });
+ }
+ };
+
+ handleDeleteRole = () => {
+ const {
+ httpClient,
+ role,
+ notifier,
+ } = this.props;
+
+ deleteRole(httpClient, role.name)
+ .then(() => {
+ toastNotifications.addSuccess('Deleted role');
+ this.backToRoleList();
+ })
+ .catch(error => {
+ notifier.error(get(error, 'data.message'));
+ });
+ };
+
+ backToRoleList = () => {
+ window.location.hash = ROLES_PATH;
+ };
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/index.js b/x-pack/plugins/security/public/views/management/edit_role/components/index.js
new file mode 100644
index 0000000000000..1a0afb37c4791
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/index.js
@@ -0,0 +1,7 @@
+/*
+ * 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 { EditRolePage } from './edit_role_page';
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/page_header.js b/x-pack/plugins/security/public/views/management/edit_role/components/page_header.js
new file mode 100644
index 0000000000000..87aac82490817
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/page_header.js
@@ -0,0 +1,35 @@
+/*
+ * 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 React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+import {
+ EuiBreadcrumbs,
+ EuiSpacer,
+} from '@elastic/eui';
+
+export class PageHeader extends Component {
+ render() {
+ return (
+
+
+
+
+ );
+ }
+
+ buildBreadcrumb = (breadcrumb) => {
+ return {
+ text: breadcrumb.display,
+ href: breadcrumb.href,
+ };
+ }
+}
+
+PageHeader.propTypes = {
+ breadcrumbs: PropTypes.array.isRequired
+};
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/page_header.test.js b/x-pack/plugins/security/public/views/management/edit_role/components/page_header.test.js
new file mode 100644
index 0000000000000..99f6994ea9898
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/page_header.test.js
@@ -0,0 +1,32 @@
+/*
+ * 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 React from 'react';
+import { shallow, mount } from 'enzyme';
+import { PageHeader } from './page_header';
+import { EuiBreadcrumbs } from '@elastic/eui';
+
+test('it renders without crashing', () => {
+ const wrapper = shallow( );
+ expect(wrapper.find(EuiBreadcrumbs)).toHaveLength(1);
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('it renders breadcrumbs', () => {
+ const breadcrumbs = [{
+ display: 'item-1',
+ href: '#item-1',
+ }, {
+ display: 'item-2',
+ href: '#item-2',
+ }, {
+ display: 'item-3',
+ href: '#item-3',
+ }];
+
+ const wrapper = mount( );
+ expect(wrapper.find(EuiBreadcrumbs)).toHaveLength(1);
+});
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/__snapshots__/cluster_privileges.test.js.snap b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/__snapshots__/cluster_privileges.test.js.snap
new file mode 100644
index 0000000000000..5315e277468ed
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/__snapshots__/cluster_privileges.test.js.snap
@@ -0,0 +1,92 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`it renders without crashing 1`] = `
+
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/__snapshots__/elasticsearch_privileges.test.js.snap b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/__snapshots__/elasticsearch_privileges.test.js.snap
new file mode 100644
index 0000000000000..dfc6b43dff636
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/__snapshots__/elasticsearch_privileges.test.js.snap
@@ -0,0 +1,160 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`it renders without crashing 1`] = `
+
+
+
+ Manage the actions this role can perform against your cluster.
+
+ Learn more
+
+
+ }
+ fullWidth={false}
+ gutterSize="l"
+ title={
+
+ Cluster privileges
+
+ }
+ titleSize="xs"
+ >
+
+
+
+
+
+
+ Allow requests to be submitted on the behalf of other users.
+
+ Learn more
+
+
+ }
+ fullWidth={false}
+ gutterSize="l"
+ title={
+
+ Run As privileges
+
+ }
+ titleSize="xs"
+ >
+
+
+
+
+
+
+
+ Index privileges
+
+
+
+
+
+ Control access to the data in your cluster.
+
+ Learn more
+
+
+
+
+
+
+ Add index privilege
+
+
+
+`;
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/__snapshots__/index_privilege_form.test.js.snap b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/__snapshots__/index_privilege_form.test.js.snap
new file mode 100644
index 0000000000000..b84dcc659d5f9
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/__snapshots__/index_privilege_form.test.js.snap
@@ -0,0 +1,184 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`it renders without crashing 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/__snapshots__/index_privileges.test.js.snap b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/__snapshots__/index_privileges.test.js.snap
new file mode 100644
index 0000000000000..189c4766c29f9
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/__snapshots__/index_privileges.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`it renders without crashing 1`] = `Array []`;
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/cluster_privileges.js b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/cluster_privileges.js
new file mode 100644
index 0000000000000..e67ddd16a7fb3
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/cluster_privileges.js
@@ -0,0 +1,75 @@
+/*
+ * 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 React, { Component } from 'react';
+import _ from 'lodash';
+import PropTypes from 'prop-types';
+import { getClusterPrivileges } from '../../../../../services/role_privileges';
+import { isReservedRole } from '../../../../../lib/role';
+import {
+ EuiCheckboxGroup,
+ EuiFlexGroup,
+ EuiFlexItem,
+} from '@elastic/eui';
+
+export class ClusterPrivileges extends Component {
+ static propTypes = {
+ role: PropTypes.object.isRequired,
+ onChange: PropTypes.func.isRequired,
+ };
+
+ render() {
+
+ const clusterPrivileges = getClusterPrivileges();
+ const privilegeGroups = _.chunk(clusterPrivileges, clusterPrivileges.length / 2);
+
+ return (
+
+ {privilegeGroups.map(this.buildCheckboxGroup)}
+
+ );
+ }
+
+ buildCheckboxGroup = (items, key) => {
+ const role = this.props.role;
+
+ const checkboxes = items.map(i => ({
+ id: i,
+ label: i
+ }));
+
+ const selectionMap = (role.cluster || [])
+ .map(k => ({ [k]: true }))
+ .reduce((acc, o) => ({ ...acc, ...o }), {});
+
+ return (
+
+
+
+ );
+ };
+
+ onClusterPrivilegesChange = (privilege) => {
+ const { cluster } = this.props.role;
+ const indexOfExistingPrivilege = cluster.indexOf(privilege);
+
+ const shouldRemove = indexOfExistingPrivilege >= 0;
+
+ const newClusterPrivs = [...cluster];
+ if (shouldRemove) {
+ newClusterPrivs.splice(indexOfExistingPrivilege, 1);
+ } else {
+ newClusterPrivs.push(privilege);
+ }
+
+ this.props.onChange(newClusterPrivs);
+ }
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/cluster_privileges.test.js b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/cluster_privileges.test.js
new file mode 100644
index 0000000000000..f75cf0ade71a9
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/cluster_privileges.test.js
@@ -0,0 +1,20 @@
+/*
+ * 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 React from 'react';
+import { shallow, mount } from 'enzyme';
+import { ClusterPrivileges } from './cluster_privileges';
+import { EuiCheckboxGroup } from '@elastic/eui';
+
+test('it renders without crashing', () => {
+ const wrapper = shallow( );
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('it renders 2 checkbox groups of privileges', () => {
+ const wrapper = mount( );
+ expect(wrapper.find(EuiCheckboxGroup)).toHaveLength(2);
+});
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/elasticsearch_privileges.js b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/elasticsearch_privileges.js
new file mode 100644
index 0000000000000..a0c6eca6f0c6e
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/elasticsearch_privileges.js
@@ -0,0 +1,163 @@
+/*
+ * 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 React, { Component, Fragment } from 'react';
+import PropTypes from 'prop-types';
+import {
+ EuiText,
+ EuiSpacer,
+ EuiComboBox,
+ EuiFormRow,
+ EuiButton,
+ EuiDescribedFormGroup,
+ EuiTitle,
+ EuiHorizontalRule,
+ EuiLink,
+} from '@elastic/eui';
+import './elasticsearch_privileges.less';
+import { ClusterPrivileges } from './cluster_privileges';
+import { IndexPrivileges } from './index_privileges';
+import { CollapsiblePanel } from '../collapsible_panel';
+import { documentationLinks } from '../../../../../documentation_links';
+
+export class ElasticsearchPrivileges extends Component {
+ static propTypes = {
+ role: PropTypes.object.isRequired,
+ editable: PropTypes.bool.isRequired,
+ httpClient: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ runAsUsers: PropTypes.array.isRequired,
+ validator: PropTypes.object.isRequired,
+ indexPatterns: PropTypes.array.isRequired,
+ allowDocumentLevelSecurity: PropTypes.bool.isRequired,
+ allowFieldLevelSecurity: PropTypes.bool.isRequired,
+ };
+
+ render() {
+ return (
+
+ {this.getForm()}
+
+ );
+ }
+
+ getForm = () => {
+ const {
+ role,
+ httpClient,
+ validator,
+ onChange,
+ indexPatterns,
+ allowDocumentLevelSecurity,
+ allowFieldLevelSecurity,
+ } = this.props;
+
+ const indexProps = {
+ role,
+ httpClient,
+ validator,
+ indexPatterns,
+ allowDocumentLevelSecurity,
+ allowFieldLevelSecurity,
+ onChange,
+ };
+
+ return (
+
+ Cluster privileges}
+ description={
+
+ Manage the actions this role can perform against your cluster. {this.learnMore(documentationLinks.esClusterPrivileges)}
+
+ }
+ >
+
+
+
+
+
+
+
+ Run As privileges}
+ description={
+
+ Allow requests to be submitted on the behalf of other users. {this.learnMore(documentationLinks.esRunAsPrivileges)}
+
+ }
+ >
+
+ ({ id: username, label: username }))}
+ selectedOptions={this.props.role.run_as.map(u => ({ label: u }))}
+ onChange={this.onRunAsUserChange}
+ isDisabled={!this.props.editable}
+ />
+
+
+
+
+
+ Index privileges
+
+
+ Control access to the data in your cluster. {this.learnMore(documentationLinks.esIndicesPrivileges)}
+
+
+
+
+
+
+ {this.props.editable && (
+ Add index privilege
+ )}
+
+ );
+ }
+
+ learnMore = (href) => (
+
+ Learn more
+
+ );
+
+ addIndexPrivilege = () => {
+ const { role } = this.props;
+
+ const newIndices = [...role.indices, {
+ names: [],
+ privileges: [],
+ field_security: {
+ grant: ['*']
+ }
+ }];
+
+ this.props.onChange({
+ ...this.props.role,
+ indices: newIndices
+ });
+ };
+
+ onClusterPrivilegesChange = (cluster) => {
+ const role = {
+ ...this.props.role,
+ cluster
+ };
+
+ this.props.onChange(role);
+ }
+
+ onRunAsUserChange = (users) => {
+ const role = {
+ ...this.props.role,
+ run_as: users.map(u => u.label)
+ };
+
+ this.props.onChange(role);
+ }
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/elasticsearch_privileges.less b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/elasticsearch_privileges.less
new file mode 100644
index 0000000000000..776ef72a4627e
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/elasticsearch_privileges.less
@@ -0,0 +1,3 @@
+.editRole__learnMore {
+ margin-left: 5px;
+}
\ No newline at end of file
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/elasticsearch_privileges.test.js b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/elasticsearch_privileges.test.js
new file mode 100644
index 0000000000000..7a2af5723e543
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/elasticsearch_privileges.test.js
@@ -0,0 +1,72 @@
+/*
+ * 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 React from 'react';
+import { shallow, mount } from 'enzyme';
+import { IndexPrivileges } from './index_privileges';
+import { ClusterPrivileges } from './cluster_privileges';
+import { ElasticsearchPrivileges } from './elasticsearch_privileges';
+import { RoleValidator } from '../../lib/validate_role';
+
+test('it renders without crashing', () => {
+ const props = {
+ role: {
+ cluster: [],
+ indices: [],
+ run_as: []
+ },
+ editable: true,
+ httpClient: jest.fn(),
+ onChange: jest.fn(),
+ runAsUsers: [],
+ indexPatterns: [],
+ allowDocumentLevelSecurity: true,
+ allowFieldLevelSecurity: true,
+ validator: new RoleValidator(),
+ };
+ const wrapper = shallow( );
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('it renders ClusterPrivileges', () => {
+ const props = {
+ role: {
+ cluster: [],
+ indices: [],
+ run_as: []
+ },
+ editable: true,
+ httpClient: jest.fn(),
+ onChange: jest.fn(),
+ runAsUsers: [],
+ indexPatterns: [],
+ allowDocumentLevelSecurity: true,
+ allowFieldLevelSecurity: true,
+ validator: new RoleValidator(),
+ };
+ const wrapper = mount( );
+ expect(wrapper.find(ClusterPrivileges)).toHaveLength(1);
+});
+
+test('it renders IndexPrivileges', () => {
+ const props = {
+ role: {
+ cluster: [],
+ indices: [],
+ run_as: []
+ },
+ editable: true,
+ httpClient: jest.fn(),
+ onChange: jest.fn(),
+ runAsUsers: [],
+ indexPatterns: [],
+ allowDocumentLevelSecurity: true,
+ allowFieldLevelSecurity: true,
+ validator: new RoleValidator(),
+ };
+ const wrapper = mount( );
+ expect(wrapper.find(IndexPrivileges)).toHaveLength(1);
+});
\ No newline at end of file
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index.js b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index.js
new file mode 100644
index 0000000000000..d142af394b155
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index.js
@@ -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 { ElasticsearchPrivileges } from './elasticsearch_privileges';
+export { KibanaPrivileges } from './kibana_privileges';
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index_privilege_form.js b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index_privilege_form.js
new file mode 100644
index 0000000000000..ee62b80c5e282
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index_privilege_form.js
@@ -0,0 +1,260 @@
+/*
+ * 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 React, { Component, Fragment } from 'react';
+import PropTypes from 'prop-types';
+import {
+ EuiComboBox,
+ EuiTextArea,
+ EuiFormRow,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSwitch,
+ EuiSpacer,
+ EuiHorizontalRule,
+ EuiButtonIcon,
+} from '@elastic/eui';
+import { getIndexPrivileges } from '../../../../../services/role_privileges';
+
+const fromOption = (option) => option.label;
+const toOption = (value) => ({ label: value });
+
+export class IndexPrivilegeForm extends Component {
+ static propTypes = {
+ indexPrivilege: PropTypes.object.isRequired,
+ indexPatterns: PropTypes.array.isRequired,
+ availableFields: PropTypes.array,
+ onChange: PropTypes.func.isRequired,
+ isReservedRole: PropTypes.bool.isRequired,
+ allowDelete: PropTypes.bool.isRequired,
+ allowDocumentLevelSecurity: PropTypes.bool.isRequired,
+ allowFieldLevelSecurity: PropTypes.bool.isRequired,
+ validator: PropTypes.object.isRequired,
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ queryExpanded: !!props.indexPrivilege.query,
+ documentQuery: props.indexPrivilege.query
+ };
+ }
+
+ render() {
+ return (
+
+
+
+
+ {this.getPrivilegeForm()}
+
+ {this.props.allowDelete && (
+
+
+
+
+
+ )}
+
+
+ );
+ }
+
+ getPrivilegeForm = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {this.getGrantedFieldsControl()}
+
+
+
+
+ {this.getGrantedDocumentsControl()}
+
+ );
+ };
+
+ getGrantedFieldsControl = () => {
+ const {
+ allowFieldLevelSecurity,
+ availableFields,
+ indexPrivilege,
+ isReservedRole,
+ } = this.props;
+
+ if (!allowFieldLevelSecurity) {
+ return null;
+ }
+
+ const { grant = [] } = indexPrivilege.field_security || {};
+
+ if (allowFieldLevelSecurity) {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+
+ return null;
+ }
+
+ getGrantedDocumentsControl = () => {
+ const {
+ allowDocumentLevelSecurity,
+ indexPrivilege,
+ } = this.props;
+
+ if (!allowDocumentLevelSecurity) {
+ return null;
+ }
+
+ return (
+
+ {!this.props.isReservedRole &&
+
+
+
+ }
+ {this.state.queryExpanded &&
+
+
+
+
+
+ }
+
+ );
+ };
+
+ toggleDocumentQuery = () => {
+ const willToggleOff = this.state.queryExanded;
+ const willToggleOn = !willToggleOff;
+
+ // If turning off, then save the current query in state so that we can restore it if the user changes their mind.
+ this.setState({
+ queryExpanded: !this.state.queryExpanded,
+ documentQuery: willToggleOff ? this.props.indexPrivilege.query : this.state.documentQuery
+ });
+
+ // If turning off, then remove the query from the Index Privilege
+ if (willToggleOff) {
+ this.props.onChange({
+ ...this.props.indexPrivilege,
+ query: '',
+ });
+ }
+
+ // If turning on, then restore the saved query if available
+ if (willToggleOn && !this.props.indexPrivilege.query && this.state.documentQuery) {
+ this.props.onChange({
+ ...this.props.indexPrivilege,
+ query: this.state.documentQuery,
+ });
+ }
+ };
+
+ onCreateIndexPatternOption = (option) => {
+ const newIndexPatterns = this.props.indexPrivilege.names.concat([option]);
+
+ this.props.onChange({
+ ...this.props.indexPrivilege,
+ names: newIndexPatterns,
+ });
+ };
+
+ onIndexPatternsChange = (newPatterns) => {
+ this.props.onChange({
+ ...this.props.indexPrivilege,
+ names: newPatterns.map(fromOption),
+ });
+ };
+
+ onPrivilegeChange = (newPrivileges) => {
+ this.props.onChange({
+ ...this.props.indexPrivilege,
+ privileges: newPrivileges.map(fromOption),
+ });
+ };
+
+ onQueryChange = (e) => {
+ this.props.onChange({
+ ...this.props.indexPrivilege,
+ query: e.target.value,
+ });
+ };
+
+ onCreateGrantedField = (grant) => {
+ const newGrants = this.props.indexPrivilege.field_security.grant.concat([grant]);
+
+ this.props.onChange({
+ ...this.props.indexPrivilege,
+ field_security: {
+ ...this.props.indexPrivilege.field_security,
+ grant: newGrants,
+ },
+ });
+ };
+
+ onGrantedFieldsChange = (grantedFields) => {
+ this.props.onChange({
+ ...this.props.indexPrivilege,
+ field_security: {
+ ...this.props.indexPrivilege.field_security,
+ grant: grantedFields.map(fromOption),
+ },
+ });
+ };
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index_privilege_form.test.js b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index_privilege_form.test.js
new file mode 100644
index 0000000000000..1fed539d2526d
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index_privilege_form.test.js
@@ -0,0 +1,206 @@
+/*
+ * 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 React from 'react';
+import { shallow, mount } from 'enzyme';
+import { IndexPrivilegeForm } from './index_privilege_form';
+import { RoleValidator } from '../../lib/validate_role';
+import { EuiSwitch, EuiTextArea, EuiButtonIcon } from '@elastic/eui';
+
+test('it renders without crashing', () => {
+ const props = {
+ indexPrivilege: {
+ names: [],
+ privileges: [],
+ query: null,
+ field_security: {
+ grant: []
+ }
+ },
+ indexPatterns: [],
+ availableFields: [],
+ isReservedRole: false,
+ allowDelete: true,
+ allowDocumentLevelSecurity: true,
+ allowFieldLevelSecurity: true,
+ validator: new RoleValidator(),
+ onChange: jest.fn()
+ };
+
+ const wrapper = shallow( );
+ expect(wrapper).toMatchSnapshot();
+});
+
+describe('delete button', () => {
+ const props = {
+ indexPrivilege: {
+ names: [],
+ privileges: [],
+ query: null,
+ field_security: {
+ grant: []
+ }
+ },
+ indexPatterns: [],
+ availableFields: [],
+ isReservedRole: false,
+ allowDelete: true,
+ allowDocumentLevelSecurity: true,
+ allowFieldLevelSecurity: true,
+ validator: new RoleValidator(),
+ onChange: jest.fn(),
+ onDelete: jest.fn()
+ };
+
+ test('it is hidden when allowDelete is false', () => {
+ const testProps = {
+ ...props,
+ allowDelete: false
+ };
+ const wrapper = mount( );
+ expect(wrapper.find(EuiButtonIcon)).toHaveLength(0);
+ });
+
+ test('it is shown when allowDelete is true', () => {
+ const testProps = {
+ ...props,
+ allowDelete: true
+ };
+ const wrapper = mount( );
+ expect(wrapper.find(EuiButtonIcon)).toHaveLength(1);
+ });
+
+ test('it invokes onDelete when clicked', () => {
+ const testProps = {
+ ...props,
+ allowDelete: true
+ };
+ const wrapper = mount( );
+ wrapper.find(EuiButtonIcon).simulate('click');
+ expect(testProps.onDelete).toHaveBeenCalledTimes(1);
+ });
+});
+
+describe(`document level security`, () => {
+ const props = {
+ indexPrivilege: {
+ names: [],
+ privileges: [],
+ query: "some query",
+ field_security: {
+ grant: []
+ }
+ },
+ indexPatterns: [],
+ availableFields: [],
+ isReservedRole: false,
+ allowDelete: true,
+ allowDocumentLevelSecurity: true,
+ allowFieldLevelSecurity: true,
+ validator: new RoleValidator(),
+ onChange: jest.fn()
+ };
+
+ test(`inputs are hidden when DLS is not allowed`, () => {
+ const testProps = {
+ ...props,
+ allowDocumentLevelSecurity: false
+ };
+
+ const wrapper = mount( );
+ expect(wrapper.find(EuiSwitch)).toHaveLength(0);
+ expect(wrapper.find(EuiTextArea)).toHaveLength(0);
+ });
+
+ test('only the switch is shown when allowed, and query is empty', () => {
+ const testProps = {
+ ...props,
+ indexPrivilege: {
+ ...props.indexPrivilege,
+ query: null
+ }
+ };
+
+ const wrapper = mount( );
+ expect(wrapper.find(EuiSwitch)).toHaveLength(1);
+ expect(wrapper.find(EuiTextArea)).toHaveLength(0);
+ });
+
+ test('both inputs are shown when allowed, and query is not empty', () => {
+ const testProps = {
+ ...props,
+ };
+
+ const wrapper = mount( );
+ expect(wrapper.find(EuiSwitch)).toHaveLength(1);
+ expect(wrapper.find(EuiTextArea)).toHaveLength(1);
+ });
+});
+
+describe('field level security', () => {
+ const props = {
+ indexPrivilege: {
+ names: [],
+ privileges: [],
+ query: null,
+ field_security: {
+ grant: ["foo*"]
+ }
+ },
+ indexPatterns: [],
+ availableFields: [],
+ isReservedRole: false,
+ allowDelete: true,
+ allowDocumentLevelSecurity: true,
+ allowFieldLevelSecurity: true,
+ validator: new RoleValidator(),
+ onChange: jest.fn()
+ };
+
+ test(`input is hidden when FLS is not allowed`, () => {
+ const testProps = {
+ ...props,
+ allowFieldLevelSecurity: false
+ };
+
+ const wrapper = mount( );
+ expect(wrapper.find(".indexPrivilegeForm__grantedFieldsRow")).toHaveLength(0);
+ });
+
+ test('input is shown when allowed', () => {
+ const testProps = {
+ ...props,
+ };
+
+ const wrapper = mount( );
+ expect(wrapper.find("div.indexPrivilegeForm__grantedFieldsRow")).toHaveLength(1);
+ });
+
+ test('it displays a warning when no fields are granted', () => {
+ const testProps = {
+ ...props,
+ indexPrivilege: {
+ ...props.indexPrivilege,
+ field_security: {
+ grant: []
+ }
+ }
+ };
+
+ const wrapper = mount( );
+ expect(wrapper.find("div.indexPrivilegeForm__grantedFieldsRow")).toHaveLength(1);
+ expect(wrapper.find(".euiFormHelpText")).toHaveLength(1);
+ });
+
+ test('it does not display a warning when fields are granted', () => {
+ const testProps = {
+ ...props
+ };
+
+ const wrapper = mount( );
+ expect(wrapper.find("div.indexPrivilegeForm__grantedFieldsRow")).toHaveLength(1);
+ expect(wrapper.find(".euiFormHelpText")).toHaveLength(0);
+ });
+});
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index_privileges.js b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index_privileges.js
new file mode 100644
index 0000000000000..cb72ecd7aa4bf
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index_privileges.js
@@ -0,0 +1,158 @@
+/*
+ * 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 React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import _ from 'lodash';
+import { isReservedRole, isRoleEnabled } from '../../../../../lib/role';
+import { IndexPrivilegeForm } from './index_privilege_form';
+import { getFields } from '../../../../../objects';
+
+export class IndexPrivileges extends Component {
+ static propTypes = {
+ role: PropTypes.object.isRequired,
+ indexPatterns: PropTypes.array.isRequired,
+ allowDocumentLevelSecurity: PropTypes.bool.isRequired,
+ allowFieldLevelSecurity: PropTypes.bool.isRequired,
+ httpClient: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ validator: PropTypes.object.isRequired,
+ }
+
+ state = {
+ availableFields: {}
+ }
+
+ componentDidMount() {
+ this.loadAvailableFields(this.props.role.indices);
+ }
+
+ render() {
+ const { indices = [] } = this.props.role;
+
+ const {
+ indexPatterns,
+ allowDocumentLevelSecurity,
+ allowFieldLevelSecurity
+ } = this.props;
+
+ const props = {
+ indexPatterns,
+ // If editing an existing role while that has been disabled, always show the FLS/DLS fields because currently
+ // a role is only marked as disabled if it has FLS/DLS setup (usually before the user changed to a license that
+ // doesn't permit FLS/DLS).
+ allowDocumentLevelSecurity: allowDocumentLevelSecurity || !isRoleEnabled(this.props.role),
+ allowFieldLevelSecurity: allowFieldLevelSecurity || !isRoleEnabled(this.props.role),
+ isReservedRole: isReservedRole(this.props.role)
+ };
+
+ const forms = indices.map((indexPrivilege, idx) => (
+
+ ));
+
+ return forms;
+ }
+
+ addIndexPrivilege = () => {
+ const { role } = this.props;
+
+ const newIndices = [...role.indices, {
+ names: [],
+ privileges: [],
+ field_security: {
+ grant: ['*']
+ }
+ }];
+
+ this.props.onChange({
+ ...this.props.role,
+ indices: newIndices
+ });
+ };
+
+ onIndexPrivilegeChange = (privilegeIndex) => {
+ return (updatedPrivilege) => {
+ const { role } = this.props;
+ const { indices } = role;
+
+ const newIndices = [...indices];
+ newIndices[privilegeIndex] = updatedPrivilege;
+
+ this.props.onChange({
+ ...this.props.role,
+ indices: newIndices
+ });
+
+ this.loadAvailableFields(newIndices);
+ };
+ };
+
+ onIndexPrivilegeDelete = (privilegeIndex) => {
+ return () => {
+ const { role } = this.props;
+
+ const newIndices = [...role.indices];
+ newIndices.splice(privilegeIndex, 1);
+
+ this.props.onChange({
+ ...this.props.role,
+ indices: newIndices
+ });
+ };
+ }
+
+ isPlaceholderPrivilege = (indexPrivilege) => {
+ return indexPrivilege.names.length === 0;
+ };
+
+ loadAvailableFields(indices) {
+ // Reserved roles cannot be edited, and therefore do not need to fetch available fields.
+ if (isReservedRole(this.props.role)) {
+ return;
+ }
+
+ const patterns = indices.map(index => index.names.join(','));
+
+ const cachedPatterns = Object.keys(this.state.availableFields);
+ const patternsToFetch = _.difference(patterns, cachedPatterns);
+
+ const fetchRequests = patternsToFetch.map(this.loadFieldsForPattern);
+
+ Promise.all(fetchRequests)
+ .then(response => {
+
+ this.setState({
+ availableFields: {
+ ...this.state.availableFields,
+ ...response.reduce((acc, o) => ({ ...acc, ...o }), {})
+ }
+ });
+ });
+ }
+
+ loadFieldsForPattern = async (pattern) => {
+ if (!pattern) return { [pattern]: [] };
+
+ try {
+ return {
+ [pattern]: await getFields(this.props.httpClient, pattern)
+ };
+
+ } catch (e) {
+ return {
+ [pattern]: []
+ };
+ }
+ }
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index_privileges.test.js b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index_privileges.test.js
new file mode 100644
index 0000000000000..221d301ceef0f
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/index_privileges.test.js
@@ -0,0 +1,54 @@
+/*
+ * 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 React from 'react';
+import { shallow, mount } from 'enzyme';
+import { IndexPrivileges } from './index_privileges';
+import { IndexPrivilegeForm } from './index_privilege_form';
+import { RoleValidator } from '../../lib/validate_role';
+
+test('it renders without crashing', () => {
+ const props = {
+ role: {
+ cluster: [],
+ indices: [],
+ run_as: []
+ },
+ httpClient: jest.fn(),
+ onChange: jest.fn(),
+ indexPatterns: [],
+ allowDocumentLevelSecurity: true,
+ allowFieldLevelSecurity: true,
+ validator: new RoleValidator(),
+ };
+ const wrapper = shallow( );
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('it renders a IndexPrivilegeForm for each privilege on the role', () => {
+ const props = {
+ role: {
+ cluster: [],
+ indices: [{
+ names: ['foo*'],
+ privileges: ['all'],
+ query: '*',
+ field_security: {
+ grant: ['some_field']
+ }
+ }],
+ run_as: []
+ },
+ httpClient: jest.fn(),
+ onChange: jest.fn(),
+ indexPatterns: [],
+ allowDocumentLevelSecurity: true,
+ allowFieldLevelSecurity: true,
+ validator: new RoleValidator(),
+ };
+ const wrapper = mount( );
+ expect(wrapper.find(IndexPrivilegeForm)).toHaveLength(1);
+});
\ No newline at end of file
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana_privileges.js b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana_privileges.js
new file mode 100644
index 0000000000000..a8c2a2c52cbe0
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana_privileges.js
@@ -0,0 +1,101 @@
+/*
+ * 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 React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { isReservedRole } from '../../../../../lib/role';
+import { getKibanaPrivileges } from '../../lib/get_application_privileges';
+import { setApplicationPrivileges } from '../../lib/set_application_privileges';
+
+import { CollapsiblePanel } from '../collapsible_panel';
+import {
+ EuiSelect,
+ EuiDescribedFormGroup,
+ EuiFormRow,
+} from '@elastic/eui';
+
+const noPrivilegeValue = '-none-';
+
+export class KibanaPrivileges extends Component {
+ static propTypes = {
+ role: PropTypes.object.isRequired,
+ spaces: PropTypes.array,
+ kibanaAppPrivileges: PropTypes.array.isRequired,
+ onChange: PropTypes.func.isRequired,
+ };
+
+ idPrefix = () => `${this.props.rbacApplication}_`;
+
+ privilegeToId = (privilege) => `${this.idPrefix()}${privilege}`;
+
+ idToPrivilege = (id) => id.split(this.idPrefix())[1];
+
+ render() {
+ return (
+
+ {this.getForm()}
+
+ );
+ }
+
+ getForm = () => {
+ const {
+ kibanaAppPrivileges,
+ role,
+ rbacApplication
+ } = this.props;
+
+ const kibanaPrivileges = getKibanaPrivileges(kibanaAppPrivileges, role, rbacApplication);
+
+ const options = [
+ { value: noPrivilegeValue, text: 'none' },
+ ...Object.keys(kibanaPrivileges).map(p => ({
+ value: p,
+ text: p
+ }))
+ ];
+
+ const value = Object.keys(kibanaPrivileges).find(p => kibanaPrivileges[p]) || noPrivilegeValue;
+
+ return (
+ Application privileges}
+ description={Manage the actions this role can perform within Kibana.
}
+ >
+
+
+
+
+ );
+ }
+
+ onKibanaPrivilegesChange = (e) => {
+ const role = {
+ ...this.props.role,
+ applications: [...this.props.role.applications]
+ };
+
+ const privilege = e.target.value;
+
+ if (privilege === noPrivilegeValue) {
+ // unsetting all privileges -- only necessary until RBAC Phase 3
+ const noPrivileges = {};
+ setApplicationPrivileges(noPrivileges, role, this.props.rbacApplication);
+ } else {
+ const newPrivileges = {
+ [privilege]: true
+ };
+ setApplicationPrivileges(newPrivileges, role, this.props.rbacApplication);
+ }
+
+ this.props.onChange(role);
+ }
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/reserved_role_badge.js b/x-pack/plugins/security/public/views/management/edit_role/components/reserved_role_badge.js
new file mode 100644
index 0000000000000..8b750b19338ed
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/reserved_role_badge.js
@@ -0,0 +1,34 @@
+/*
+ * 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 React from 'react';
+import PropTypes from 'prop-types';
+
+import { isReservedRole } from '../../../../lib/role';
+import {
+ EuiIcon,
+ EuiToolTip,
+} from '@elastic/eui';
+
+
+export const ReservedRoleBadge = (props) => {
+ const {
+ role
+ } = props;
+
+ if (isReservedRole(role)) {
+ return (
+
+
+
+ );
+ }
+ return null;
+};
+
+ReservedRoleBadge.propTypes = {
+ role: PropTypes.object.isRequired
+};
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/reserved_role_badge.test.js b/x-pack/plugins/security/public/views/management/edit_role/components/reserved_role_badge.test.js
new file mode 100644
index 0000000000000..939348661a741
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/reserved_role_badge.test.js
@@ -0,0 +1,32 @@
+/*
+ * 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 React from 'react';
+import {
+ EuiIcon
+} from '@elastic/eui';
+import { ReservedRoleBadge } from './reserved_role_badge';
+import {
+ shallow
+} from 'enzyme';
+
+const reservedRole = {
+ metadata: {
+ _reserved: true
+ }
+};
+
+const unreservedRole = {};
+
+test('it renders without crashing', () => {
+ const wrapper = shallow( );
+ expect(wrapper.find(EuiIcon)).toHaveLength(1);
+});
+
+test('it renders nothing for an unreserved role', () => {
+ const wrapper = shallow( );
+ expect(wrapper.find('*')).toHaveLength(0);
+});
diff --git a/x-pack/plugins/security/public/views/management/edit_role/edit_role.html b/x-pack/plugins/security/public/views/management/edit_role/edit_role.html
new file mode 100644
index 0000000000000..5539f0521dd79
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/edit_role.html
@@ -0,0 +1 @@
+
diff --git a/x-pack/plugins/security/public/views/management/edit_role/edit_role.less b/x-pack/plugins/security/public/views/management/edit_role/edit_role.less
new file mode 100644
index 0000000000000..d4f7ac04880d6
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/edit_role.less
@@ -0,0 +1,10 @@
+#editRoleReactRoot {
+ background: #f5f5f5;
+ flex-grow: 1;
+}
+
+.editRolePage {
+ max-width: 1000px;
+ margin-left: auto;
+ margin-right: auto;
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/index.js b/x-pack/plugins/security/public/views/management/edit_role/index.js
new file mode 100644
index 0000000000000..e356b9d7c01b6
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/index.js
@@ -0,0 +1,134 @@
+/*
+ * 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 _ from 'lodash';
+import routes from 'ui/routes';
+import { fatalError } from 'ui/notify';
+import template from 'plugins/security/views/management/edit_role/edit_role.html';
+import 'plugins/security/views/management/edit_role/edit_role.less';
+import 'angular-ui-select';
+import 'plugins/security/services/application_privilege';
+import 'plugins/security/services/shield_user';
+import 'plugins/security/services/shield_role';
+import 'plugins/security/services/shield_privileges';
+import 'plugins/security/services/shield_indices';
+
+import { IndexPatternsProvider } from 'ui/index_patterns/index_patterns';
+import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
+import { checkLicenseError } from 'plugins/security/lib/check_license_error';
+import { EDIT_ROLES_PATH, ROLES_PATH } from '../management_urls';
+
+import { EditRolePage } from './components';
+
+import React from 'react';
+import { render, unmountComponentAtNode } from 'react-dom';
+
+routes.when(`${EDIT_ROLES_PATH}/:name?`, {
+ template,
+ resolve: {
+ role($route, ShieldRole, kbnUrl, Promise, Notifier) {
+ const name = $route.current.params.name;
+
+ let role;
+
+ if (name != null) {
+ role = ShieldRole.get({ name }).$promise
+ .catch((response) => {
+
+ if (response.status !== 404) {
+ return fatalError(response);
+ }
+
+ const notifier = new Notifier();
+ notifier.error(`No "${name}" role found.`);
+ kbnUrl.redirect(ROLES_PATH);
+ return Promise.halt();
+ });
+
+ } else {
+ role = Promise.resolve(new ShieldRole({
+ cluster: [],
+ indices: [],
+ run_as: [],
+ applications: []
+ }));
+ }
+
+ return role.then(res => res.toJSON());
+ },
+ kibanaApplicationPrivilege(ApplicationPrivilege, kbnUrl, Promise, Private) {
+ return ApplicationPrivilege.query().$promise
+ .then(privileges => privileges.map(p => p.toJSON()))
+ .catch(checkLicenseError(kbnUrl, Promise, Private));
+ },
+ users(ShieldUser, kbnUrl, Promise, Private) {
+ // $promise is used here because the result is an ngResource, not a promise itself
+ return ShieldUser.query().$promise
+ .then(users => _.map(users, 'username'))
+ .catch(checkLicenseError(kbnUrl, Promise, Private));
+ },
+ indexPatterns(Private) {
+ const indexPatterns = Private(IndexPatternsProvider);
+ return indexPatterns.getTitles();
+ }
+ },
+ controllerAs: 'editRole',
+ controller($injector, $scope, $http, rbacEnabled, rbacApplication) {
+ const $route = $injector.get('$route');
+ const Private = $injector.get('Private');
+
+ const Notifier = $injector.get('Notifier');
+
+ const kibanaApplicationPrivilege = $route.current.locals.kibanaApplicationPrivilege;
+ const role = $route.current.locals.role;
+
+ const xpackInfo = Private(XPackInfoProvider);
+ const allowDocumentLevelSecurity = xpackInfo.get('features.security.allowRoleDocumentLevelSecurity');
+ const allowFieldLevelSecurity = xpackInfo.get('features.security.allowRoleFieldLevelSecurity');
+
+ const domNode = document.getElementById('editRoleReactRoot');
+
+ const {
+ users,
+ indexPatterns,
+ } = $route.current.locals;
+
+ const routeBreadcrumbs = routes.getBreadcrumbs();
+
+ render( , domNode);
+
+ // unmount react on controller destroy
+ $scope.$on('$destroy', () => {
+ unmountComponentAtNode(domNode);
+ });
+ }
+});
+
+function transformBreadcrumbs(routeBreadcrumbs) {
+ const indexOfEdit = routeBreadcrumbs.findIndex(b => b.id === 'edit');
+
+ const hasEntryAfterEdit = indexOfEdit >= 0 && indexOfEdit < (routeBreadcrumbs.length - 1);
+
+ if (hasEntryAfterEdit) {
+ // The entry after 'edit' is the name of the role being edited (if any). We don't want to use the "humanized" version of the role name here
+ const roleName = routeBreadcrumbs[indexOfEdit + 1];
+ roleName.display = roleName.id;
+ }
+
+ return routeBreadcrumbs.filter(b => b.id !== 'edit');
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/lib/__snapshots__/validate_role.test.js.snap b/x-pack/plugins/security/public/views/management/edit_role/lib/__snapshots__/validate_role.test.js.snap
new file mode 100644
index 0000000000000..20b4280b7f493
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/lib/__snapshots__/validate_role.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`validateIndexPrivileges it throws when indices is not an array 1`] = `"Expected role.indices to be an array"`;
diff --git a/x-pack/plugins/security/public/views/management/edit_role/lib/get_application_privileges.js b/x-pack/plugins/security/public/views/management/edit_role/lib/get_application_privileges.js
new file mode 100644
index 0000000000000..1ee8d201806b6
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/lib/get_application_privileges.js
@@ -0,0 +1,26 @@
+/*
+ * 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 _ from 'lodash';
+
+export function getKibanaPrivileges(kibanaApplicationPrivilege, role, application) {
+ const kibanaPrivileges = kibanaApplicationPrivilege.reduce((acc, p) => {
+ acc[p.name] = false;
+ return acc;
+ }, {});
+
+ if (!role.applications || role.applications.length === 0) {
+ return kibanaPrivileges;
+ }
+
+ const applications = role.applications.filter(x => x.application === application);
+
+ const assigned = _.uniq(_.flatten(_.pluck(applications, 'privileges')));
+ assigned.forEach(a => {
+ kibanaPrivileges[a] = true;
+ });
+
+ return kibanaPrivileges;
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/lib/set_application_privileges.js b/x-pack/plugins/security/public/views/management/edit_role/lib/set_application_privileges.js
new file mode 100644
index 0000000000000..e6e133fbf69bd
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/lib/set_application_privileges.js
@@ -0,0 +1,49 @@
+/*
+ * 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 { DEFAULT_RESOURCE } from '../../../../../common/constants';
+
+export function setApplicationPrivileges(kibanaPrivileges, role, application) {
+ if (!role.applications) {
+ role.applications = [];
+ }
+
+ // we first remove the matching application entries
+ role.applications = role.applications.filter(x => {
+ return x.application !== application;
+ });
+
+ const privileges = Object.keys(kibanaPrivileges).filter(key => kibanaPrivileges[key]);
+
+ // if we still have them, put the application entry back
+ if (privileges.length > 0) {
+ role.applications = [...role.applications, {
+ application,
+ privileges,
+ resources: [DEFAULT_RESOURCE]
+ }];
+ }
+}
+
+export function togglePrivilege(role, application, permission) {
+ const appPermissions = role.applications
+ .find(a => a.application === application && a.resources[0] === DEFAULT_RESOURCE);
+
+ if (!appPermissions) {
+ role.applications.push({
+ application,
+ privileges: [permission],
+ resources: [DEFAULT_RESOURCE]
+ });
+ } else {
+ const indexOfExisting = appPermissions.privileges.indexOf(permission);
+ if (indexOfExisting >= 0) {
+ appPermissions.privileges.splice(indexOfExisting, 1);
+ } else {
+ appPermissions.privileges.push(permission);
+ }
+ }
+}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/lib/validate_role.js b/x-pack/plugins/security/public/views/management/edit_role/lib/validate_role.js
new file mode 100644
index 0000000000000..3fc638a83782c
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/lib/validate_role.js
@@ -0,0 +1,86 @@
+/*
+ * 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 class RoleValidator {
+ constructor(options = {}) {
+ this._shouldValidate = options.shouldValidate;
+ }
+
+ enableValidation() {
+ this._shouldValidate = true;
+ }
+
+ disableValidation() {
+ this._shouldValidate = false;
+ }
+
+ validateRoleName(role) {
+ if (!this._shouldValidate) return valid();
+
+ if (!role.name) {
+ return invalid(`Please provide a role name`);
+ }
+ if (role.name.length > 1024) {
+ return invalid(`Name must not exceed 1024 characters`);
+ }
+ if (!role.name.match(/^[a-zA-Z_][a-zA-Z0-9_@\-\$\.]*$/)) {
+ return invalid(`Name must begin with a letter or underscore and contain only letters, underscores, and numbers.`);
+ }
+ return valid();
+ }
+
+ validateIndexPrivileges(role) {
+ if (!this._shouldValidate) return valid();
+
+ if (!Array.isArray(role.indices)) {
+ throw new TypeError(`Expected role.indices to be an array`);
+ }
+
+ const areIndicesValid = role.indices
+ .map(this.validateIndexPrivilege.bind(this))
+ .find((result) => result.isInvalid) == null;
+
+ if (areIndicesValid) {
+ return valid();
+ }
+ return invalid();
+ }
+
+ validateIndexPrivilege(indexPrivilege) {
+ if (!this._shouldValidate) return valid();
+
+ if (indexPrivilege.names.length && !indexPrivilege.privileges.length) {
+ return invalid(`At least one privilege is required`);
+ }
+ return valid();
+ }
+
+ validateForSave(role) {
+ const { isInvalid: isNameInvalid } = this.validateRoleName(role);
+ const { isInvalid: areIndicesInvalid } = this.validateIndexPrivileges(role);
+
+ if (isNameInvalid || areIndicesInvalid) {
+ return invalid();
+ }
+
+ return valid();
+ }
+
+}
+
+function invalid(error) {
+ return {
+ isInvalid: true,
+ error
+ };
+}
+
+function valid() {
+ return {
+ isInvalid: false
+ };
+}
+
diff --git a/x-pack/plugins/security/public/views/management/edit_role/lib/validate_role.test.js b/x-pack/plugins/security/public/views/management/edit_role/lib/validate_role.test.js
new file mode 100644
index 0000000000000..47d2bfe59a37e
--- /dev/null
+++ b/x-pack/plugins/security/public/views/management/edit_role/lib/validate_role.test.js
@@ -0,0 +1,94 @@
+/*
+ * 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 { RoleValidator } from "./validate_role";
+
+let validator;
+
+describe('validateRoleName', () => {
+ beforeEach(() => {
+ validator = new RoleValidator({ shouldValidate: true });
+ });
+
+ test('it allows an alphanumeric role name', () => {
+ const role = {
+ name: 'This-is-30-character-role-name'
+ };
+
+ expect(validator.validateRoleName(role)).toEqual({ isInvalid: false });
+ });
+
+ test('it requires a non-empty value', () => {
+ const role = {
+ name: ''
+ };
+
+ expect(validator.validateRoleName(role)).toEqual({ isInvalid: true, error: `Please provide a role name` });
+ });
+
+ test('it cannot exceed 1024 characters', () => {
+ const role = {
+ name: new Array(1026).join('A')
+ };
+
+ expect(validator.validateRoleName(role)).toEqual({ isInvalid: true, error: `Name must not exceed 1024 characters` });
+ });
+
+ const charList = `!#%^&*()+=[]{}\|';:"/,<>?`.split('');
+ charList.forEach(element => {
+ test(`it cannot support the "${element}" character`, () => {
+ const role = {
+ name: `role-${element}`
+ };
+
+ expect(validator.validateRoleName(role)).toEqual(
+ {
+ isInvalid: true,
+ error: `Name must begin with a letter or underscore and contain only letters, underscores, and numbers.`
+ }
+ );
+ });
+ });
+});
+
+describe('validateIndexPrivileges', () => {
+ beforeEach(() => {
+ validator = new RoleValidator({ shouldValidate: true });
+ });
+
+ test('it ignores privilegs with no indices defined', () => {
+ const role = {
+ indices: [{
+ names: [],
+ privileges: []
+ }]
+ };
+
+ expect(validator.validateIndexPrivileges(role)).toEqual({
+ isInvalid: false
+ });
+ });
+
+ test('it requires privilges when an index is defined', () => {
+ const role = {
+ indices: [{
+ names: ['index-*'],
+ privileges: []
+ }]
+ };
+
+ expect(validator.validateIndexPrivileges(role)).toEqual({
+ isInvalid: true
+ });
+ });
+
+ test('it throws when indices is not an array', () => {
+ const role = {
+ indices: null
+ };
+
+ expect(() => validator.validateIndexPrivileges(role)).toThrowErrorMatchingSnapshot();
+ });
+});
diff --git a/x-pack/plugins/security/public/views/management/index_privileges_form/index_privileges_form.html b/x-pack/plugins/security/public/views/management/index_privileges_form/index_privileges_form.html
deleted file mode 100644
index 829b0dfaefa92..0000000000000
--- a/x-pack/plugins/security/public/views/management/index_privileges_form/index_privileges_form.html
+++ /dev/null
@@ -1,128 +0,0 @@
-
diff --git a/x-pack/plugins/security/public/views/management/index_privileges_form/index_privileges_form.js b/x-pack/plugins/security/public/views/management/index_privileges_form/index_privileges_form.js
deleted file mode 100644
index 1bb058f56d096..0000000000000
--- a/x-pack/plugins/security/public/views/management/index_privileges_form/index_privileges_form.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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 _ from 'lodash';
-import { uiModules } from 'ui/modules';
-import template from './index_privileges_form.html';
-
-const module = uiModules.get('security', ['kibana']);
-module.directive('kbnIndexPrivilegesForm', function () {
- return {
- template,
- scope: {
- isNewRole: '=',
- indices: '=',
- indexPatterns: '=',
- privileges: '=',
- fieldOptions: '=',
- isReserved: '=',
- isEnabled: '=',
- allowDocumentLevelSecurity: '=',
- allowFieldLevelSecurity: '=',
- addIndex: '&',
- removeIndex: '&',
- },
- restrict: 'E',
- replace: true,
- controllerAs: 'indexPrivilegesController',
- controller: function ($scope) {
- this.addIndex = function addIndex() {
- $scope.addIndex({ indices: $scope.indices });
- };
-
- this.removeIndex = function removeIndex(index) {
- $scope.removeIndex({ indices: $scope.indices, index });
- };
-
- this.getIndexTitle = function getIndexTitle(index) {
- const indices = index.names.length ? index.names.join(', ') : 'No indices';
- const privileges = index.privileges.length ? index.privileges.join(', ') : 'No privileges';
- return `${indices} (${privileges})`;
- };
-
- this.union = _.flow(_.union, _.compact);
-
- // If editing an existing role while that has been disabled, always show the FLS/DLS fields because currently
- // a role is only marked as disabled if it has FLS/DLS setup (usually before the user changed to a license that
- // doesn't permit FLS/DLS).
- if (!$scope.isNewRole && !$scope.isEnabled) {
- this.showDocumentLevelSecurity = true;
- this.showFieldLevelSecurity = true;
- } else {
- this.showDocumentLevelSecurity = $scope.allowDocumentLevelSecurity;
- this.showFieldLevelSecurity = $scope.allowFieldLevelSecurity;
- }
- },
- };
-});
diff --git a/x-pack/plugins/security/public/views/management/index_privileges_form/index_privileges_form.less b/x-pack/plugins/security/public/views/management/index_privileges_form/index_privileges_form.less
deleted file mode 100644
index edd7a4898f45a..0000000000000
--- a/x-pack/plugins/security/public/views/management/index_privileges_form/index_privileges_form.less
+++ /dev/null
@@ -1,7 +0,0 @@
-.indexPrivilegesForm {
- height: 550px;
-}
-
-.indexPrivilegesList {
- flex: 0 0 400px;
-}
diff --git a/x-pack/plugins/security/public/views/management/management.js b/x-pack/plugins/security/public/views/management/management.js
index b2bd09e67e9bf..f7000478cbfc7 100644
--- a/x-pack/plugins/security/public/views/management/management.js
+++ b/x-pack/plugins/security/public/views/management/management.js
@@ -5,12 +5,11 @@
*/
import 'plugins/security/views/management/change_password_form/change_password_form';
-import 'plugins/security/views/management/index_privileges_form/index_privileges_form';
import 'plugins/security/views/management/password_form/password_form';
import 'plugins/security/views/management/users';
import 'plugins/security/views/management/roles';
import 'plugins/security/views/management/edit_user';
-import 'plugins/security/views/management/edit_role';
+import 'plugins/security/views/management/edit_role/index';
import 'plugins/security/views/management/management.less';
import routes from 'ui/routes';
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
diff --git a/x-pack/plugins/security/public/views/management/management.less b/x-pack/plugins/security/public/views/management/management.less
index d9e16650aeecc..14fc8f4fa1324 100644
--- a/x-pack/plugins/security/public/views/management/management.less
+++ b/x-pack/plugins/security/public/views/management/management.less
@@ -1,5 +1,4 @@
@import '~plugins/xpack_main/style/main.less';
-@import './index_privileges_form/index_privileges_form';
.kuiFormFooter {
display: flex;
diff --git a/x-pack/plugins/security/server/lib/check_user_permission.js b/x-pack/plugins/security/server/lib/check_user_permission.js
deleted file mode 100644
index 6890a6fc79092..0000000000000
--- a/x-pack/plugins/security/server/lib/check_user_permission.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * 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. */
-
-
-export async function checkUserPermission(permission, hasPermission) {
- // POC Stub. TODO: Use ES Permissions API once implemented.
- return Promise.resolve(hasPermission);
-}
diff --git a/x-pack/plugins/security/server/lib/privileges/privileges.js b/x-pack/plugins/security/server/lib/privileges/privileges.js
index e05be3117f934..129d4b3abfb3e 100644
--- a/x-pack/plugins/security/server/lib/privileges/privileges.js
+++ b/x-pack/plugins/security/server/lib/privileges/privileges.js
@@ -45,7 +45,7 @@ function buildSavedObjectsReadPrivileges() {
}
function buildSavedObjectsPrivileges(actions) {
- const objectTypes = ['config', 'dashboard', 'index-pattern', 'search', 'visualization', 'graph-workspace'];
+ const objectTypes = ['config', 'dashboard', 'graph-workspace', 'index-pattern', 'search', 'timelion-sheet', 'url', 'visualization'];
return objectTypes
.map(type => actions.map(action => `action:saved-objects/${type}/${action}`))
.reduce((acc, types) => [...acc, ...types], []);
diff --git a/x-pack/plugins/spaces/common/index.js b/x-pack/plugins/spaces/common/index.js
new file mode 100644
index 0000000000000..1cd6907d3d64c
--- /dev/null
+++ b/x-pack/plugins/spaces/common/index.js
@@ -0,0 +1,7 @@
+/*
+ * 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 { isReservedSpace } from './is_reserved_space';
\ No newline at end of file
diff --git a/x-pack/plugins/spaces/index.js b/x-pack/plugins/spaces/index.js
index 603b8ddb22e11..38d2e84cc992f 100644
--- a/x-pack/plugins/spaces/index.js
+++ b/x-pack/plugins/spaces/index.js
@@ -52,7 +52,7 @@ export const spaces = (kibana) => new kibana.Plugin({
valid: true,
space: await getActiveSpace(request.getSavedObjectsClient(), request.getBasePath())
};
- } catch(e) {
+ } catch (e) {
vars.activeSpace = {
valid: false,
error: wrapError(e).output.payload
diff --git a/x-pack/plugins/spaces/public/views/management/components/__snapshots__/page_header.test.js.snap b/x-pack/plugins/spaces/public/views/management/components/__snapshots__/page_header.test.js.snap
index 936f678055d41..c7cbfdb53fd73 100644
--- a/x-pack/plugins/spaces/public/views/management/components/__snapshots__/page_header.test.js.snap
+++ b/x-pack/plugins/spaces/public/views/management/components/__snapshots__/page_header.test.js.snap
@@ -1,15 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`it renders without crashing 1`] = `
-
+
+
`;
diff --git a/x-pack/plugins/spaces/public/views/management/components/manage_space_page.js b/x-pack/plugins/spaces/public/views/management/components/manage_space_page.js
index 2262e597fb433..2207076c3e695 100644
--- a/x-pack/plugins/spaces/public/views/management/components/manage_space_page.js
+++ b/x-pack/plugins/spaces/public/views/management/components/manage_space_page.js
@@ -25,6 +25,8 @@ import { DeleteSpacesButton } from './delete_spaces_button';
import { Notifier, toastNotifications } from 'ui/notify';
import { UrlContext } from './url_context';
import { toUrlContext, isValidUrlContext } from '../lib/url_context_utils';
+import { isReservedSpace } from '../../../../common';
+import { ReservedSpaceBadge } from './reserved_space_badge';
export class ManageSpacePage extends React.Component {
state = {
@@ -71,12 +73,7 @@ export class ManageSpacePage extends React.Component {
-
-
- {this.getTitle()}
-
- {this.getActionButton()}
-
+ {this.getFormHeading()}
@@ -108,7 +105,7 @@ export class ManageSpacePage extends React.Component {
@@ -128,6 +125,19 @@ export class ManageSpacePage extends React.Component {
);
}
+ getFormHeading = () => {
+ const isReserved = isReservedSpace(this.state.space);
+
+ return (
+
+
+ {this.getTitle()}
+
+ {isReserved ? this.getReservedBadge() : this.getActionButton()}
+
+ );
+ };
+
getTitle = () => {
if (this.editingExistingSpace()) {
return `Edit space`;
@@ -135,8 +145,10 @@ export class ManageSpacePage extends React.Component {
return `Create a space`;
};
+ getReservedBadge = () => ;
+
getActionButton = () => {
- if (this.editingExistingSpace()) {
+ if (this.editingExistingSpace() && !isReservedSpace(this.state.space)) {
return (
-
- {this.props.breadcrumbs.map(this.buildBreadcrumb)}
-
+
);
}
buildBreadcrumb = (breadcrumb) => {
- return (
-
- {breadcrumb.display}
-
- );
+ return {
+ text: breadcrumb.display,
+ href: breadcrumb.href,
+ };
}
}
-
-
PageHeader.propTypes = {
breadcrumbs: PropTypes.array.isRequired
-};
+};
\ No newline at end of file
diff --git a/x-pack/plugins/spaces/public/views/management/components/page_header.test.js b/x-pack/plugins/spaces/public/views/management/components/page_header.test.js
index b960175b38deb..706a6de4a0abc 100644
--- a/x-pack/plugins/spaces/public/views/management/components/page_header.test.js
+++ b/x-pack/plugins/spaces/public/views/management/components/page_header.test.js
@@ -6,30 +6,28 @@
import React from 'react';
import { PageHeader } from './page_header';
-import { render } from 'enzyme';
-import renderer from 'react-test-renderer';
+import { mount, shallow } from 'enzyme';
test('it renders without crashing', () => {
- const component = renderer.create(
+ const component = shallow(
);
expect(component).toMatchSnapshot();
});
test('it renders breadcrumbs', () => {
- const component = render(
+ const component = mount(
);
- expect(component.find('a')).toHaveLength(2);
+ expect(component.find('a.euiBreadcrumb')).toHaveLength(1);
+ expect(component.find('span.euiBreadcrumb')).toHaveLength(1);
});
diff --git a/x-pack/plugins/spaces/public/views/management/components/reserved_space_badge.js b/x-pack/plugins/spaces/public/views/management/components/reserved_space_badge.js
new file mode 100644
index 0000000000000..0ea7e05d77e12
--- /dev/null
+++ b/x-pack/plugins/spaces/public/views/management/components/reserved_space_badge.js
@@ -0,0 +1,37 @@
+/*
+ * 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 React from 'react';
+import PropTypes from 'prop-types';
+
+import { isReservedSpace } from '../../../../common';
+import {
+ EuiBadge,
+ EuiToolTip,
+ EuiFlexItem,
+} from '@elastic/eui';
+
+
+export const ReservedSpaceBadge = (props) => {
+ const {
+ space
+ } = props;
+
+ if (isReservedSpace(space)) {
+ return (
+
+
+ Reserved Space
+
+
+ );
+ }
+ return null;
+};
+
+ReservedSpaceBadge.propTypes = {
+ space: PropTypes.object.isRequired
+};
diff --git a/x-pack/plugins/spaces/public/views/management/components/reserved_space_badge.test.js b/x-pack/plugins/spaces/public/views/management/components/reserved_space_badge.test.js
new file mode 100644
index 0000000000000..e45926b859824
--- /dev/null
+++ b/x-pack/plugins/spaces/public/views/management/components/reserved_space_badge.test.js
@@ -0,0 +1,30 @@
+/*
+ * 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 React from 'react';
+import {
+ EuiBadge
+} from '@elastic/eui';
+import { ReservedSpaceBadge } from './reserved_space_badge';
+import {
+ shallow
+} from 'enzyme';
+
+const reservedSpace = {
+ _reserved: true
+};
+
+const unreservedSpace = {};
+
+test('it renders without crashing', () => {
+ const wrapper = shallow( );
+ expect(wrapper.find(EuiBadge)).toHaveLength(1);
+});
+
+test('it renders nothing for an unreserved space', () => {
+ const wrapper = shallow( );
+ expect(wrapper.find('*')).toHaveLength(0);
+});
diff --git a/x-pack/plugins/spaces/public/views/management/components/url_context.js b/x-pack/plugins/spaces/public/views/management/components/url_context.js
index 418a51b29347e..6253e36d0f9e2 100644
--- a/x-pack/plugins/spaces/public/views/management/components/url_context.js
+++ b/x-pack/plugins/spaces/public/views/management/components/url_context.js
@@ -35,7 +35,7 @@ export class UrlContext extends Component {
this.textFieldRef = ref}
@@ -48,6 +48,10 @@ export class UrlContext extends Component {
}
getLabel = () => {
+ if (!this.props.editable) {
+ return (URL Context
);
+ }
+
const editLinkText = this.state.editing ? `[stop editing]` : `[edit]`;
return (URL Context {editLinkText}
);
};
diff --git a/x-pack/plugins/spaces/public/views/management/manage_spaces.less b/x-pack/plugins/spaces/public/views/management/manage_spaces.less
index a9696d0c2f824..ae515cbb829fa 100644
--- a/x-pack/plugins/spaces/public/views/management/manage_spaces.less
+++ b/x-pack/plugins/spaces/public/views/management/manage_spaces.less
@@ -1,7 +1,7 @@
-.application, .euiPanel {
+.manageSpaces__application, .manageSpaces__.euiPanel {
background: #f5f5f5
}
-.euiPage {
+.manageSpaces__euiPage {
padding: 0;
}
diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock
index fb4184fbbb632..7c39d6e8fae06 100644
--- a/x-pack/yarn.lock
+++ b/x-pack/yarn.lock
@@ -10,9 +10,9 @@
esutils "^2.0.2"
js-tokens "^3.0.0"
-"@elastic/eui@0.0.47":
- version "0.0.47"
- resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-0.0.47.tgz#5bae27966bb1d68bb3106853610a407509053b44"
+"@elastic/eui@v0.0.51":
+ version "0.0.51"
+ resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-0.0.51.tgz#5d809af270dd9994a609fd01eaa84e21a62fff98"
dependencies:
brace "^0.10.0"
classnames "^2.2.5"
@@ -990,8 +990,8 @@ brace@0.10.0, brace@^0.10.0:
w3c-blob "0.0.1"
brace@^0.11.0:
- version "0.11.0"
- resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.0.tgz#155cd80607687dc8cb908f0df94e62a033c1d563"
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58"
braces@^1.8.2:
version "1.8.5"
@@ -1072,6 +1072,10 @@ buffer-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe"
+buffer-from@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04"
+
buffer@^3.0.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
@@ -1400,7 +1404,16 @@ concat-stream@1.5.1:
readable-stream "~2.0.0"
typedarray "~0.0.5"
-concat-stream@^1.4.7, concat-stream@~1.6.0:
+concat-stream@^1.4.7:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
+ dependencies:
+ buffer-from "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^2.2.2"
+ typedarray "^0.0.6"
+
+concat-stream@~1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
dependencies:
@@ -1443,8 +1456,8 @@ core-js@^2.4.0, core-js@^2.5.0:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
core-js@^2.5.1:
- version "2.5.5"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.5.tgz#b14dde936c640c0579a6b50cabcc132dd6127e3b"
+ version "2.5.7"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
@@ -2470,8 +2483,8 @@ focus-trap-react@^3.0.4, focus-trap-react@^3.1.1:
focus-trap "^2.0.1"
focus-trap@^2.0.1:
- version "2.4.2"
- resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-2.4.2.tgz#44ea1c55a9c22c2b6529dcebbde6390eb2ee4c88"
+ version "2.4.5"
+ resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-2.4.5.tgz#91c9c9ffb907f8f4446d80202dda9c12c2853ddb"
dependencies:
tabbable "^1.0.3"
@@ -3316,10 +3329,16 @@ icalendar@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/icalendar/-/icalendar-0.7.1.tgz#d0d3486795f8f1c5cf4f8cafac081b4b4e7a32ae"
-iconv-lite@0.4.19, iconv-lite@^0.4.19, iconv-lite@~0.4.13:
+iconv-lite@0.4.19, iconv-lite@^0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
+iconv-lite@~0.4.13:
+ version "0.4.23"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
ieee754@^1.1.4:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
@@ -4513,7 +4532,11 @@ lodash@3.10.1, lodash@^3.10.0, lodash@^3.10.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
-lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1:
+lodash@^4.0.1:
+ version "4.17.10"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
+
+lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@@ -5539,7 +5562,7 @@ prop-types@15.5.8:
dependencies:
fbjs "^0.8.9"
-prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0:
+prop-types@^15.5.0, prop-types@^15.5.4, prop-types@^15.6.0:
version "15.6.0"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
dependencies:
@@ -5547,7 +5570,7 @@ prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8,
loose-envify "^1.3.1"
object-assign "^4.1.1"
-prop-types@^15.6.1:
+prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.1:
version "15.6.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
dependencies:
@@ -5710,8 +5733,8 @@ react-clipboard.js@^1.1.2:
prop-types "^15.5.0"
react-color@^2.13.8:
- version "2.13.8"
- resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.13.8.tgz#bcc58f79a722b9bfc37c402e68cd18f26970aee4"
+ version "2.14.1"
+ resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.14.1.tgz#db8ad4f45d81e74896fc2e1c99508927c6d084e0"
dependencies:
lodash "^4.0.1"
material-colors "^1.2.1"
@@ -5743,6 +5766,10 @@ react-input-autosize@^2.1.2, react-input-autosize@^2.2.1:
dependencies:
prop-types "^15.5.8"
+react-lifecycles-compat@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
+
react-markdown-renderer@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/react-markdown-renderer/-/react-markdown-renderer-1.4.0.tgz#f3b95bd9fc7f7bf8ab3f0150aa696b41740e7d01"
@@ -5855,14 +5882,15 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.2.0:
prop-types "^15.6.0"
react-virtualized@^9.18.5:
- version "9.18.5"
- resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.18.5.tgz#42dd390ebaa7ea809bfcaf775d39872641679b89"
+ version "9.19.1"
+ resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.19.1.tgz#84b53253df2d9df61c85ce037141edccc70a73fd"
dependencies:
babel-runtime "^6.26.0"
classnames "^2.2.3"
dom-helpers "^2.4.0 || ^3.0.0"
loose-envify "^1.3.0"
prop-types "^15.6.0"
+ react-lifecycles-compat "^3.0.4"
react-vis@^1.8.1:
version "1.8.2"
@@ -5924,7 +5952,7 @@ read-pkg@^1.0.0:
isarray "0.0.1"
string_decoder "~0.10.x"
-readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2:
+readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5:
version "2.3.3"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
dependencies:
@@ -5936,6 +5964,18 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable
string_decoder "~1.0.3"
util-deprecate "~1.0.1"
+readable-stream@^2.2.2:
+ version "2.3.6"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
readable-stream@^2.3.3, readable-stream@^2.3.5:
version "2.3.5"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d"
@@ -6335,10 +6375,18 @@ rxjs@5.3.0:
dependencies:
symbol-observable "^1.0.1"
-safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+
+"safer-buffer@>= 2.1.2 < 3":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+
samsam@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567"
@@ -6739,6 +6787,12 @@ string_decoder@~1.0.3:
dependencies:
safe-buffer "~5.1.0"
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+ dependencies:
+ safe-buffer "~5.1.0"
+
stringstream@~0.0.4, stringstream@~0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
@@ -6898,8 +6952,8 @@ tabbable@1.1.0:
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.0.tgz#2c9a9c9f09db5bb0659f587d532548dd6ef2067b"
tabbable@^1.0.3, tabbable@^1.1.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.2.tgz#b171680aea6e0a3e9281ff23532e2e5de11c0d94"
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.3.tgz#0e4ee376f3631e42d7977a074dbd2b3827843081"
tar-fs@1.13.0:
version "1.13.0"
@@ -7160,8 +7214,8 @@ typedarray@^0.0.6, typedarray@~0.0.5:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
ua-parser-js@^0.7.9:
- version "0.7.17"
- resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
+ version "0.7.18"
+ resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed"
uglify-js@^2.6:
version "2.8.29"
@@ -7293,10 +7347,14 @@ uuid@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"
-uuid@^3.0.0, uuid@^3.1.0:
+uuid@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
+uuid@^3.1.0:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
+
v8flags@^2.0.2:
version "2.1.1"
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4"
@@ -7489,8 +7547,8 @@ whatwg-encoding@^1.0.1:
iconv-lite "0.4.19"
whatwg-fetch@>=0.10.0:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
whatwg-url@^6.3.0:
version "6.4.0"
diff --git a/yarn.lock b/yarn.lock
index 2098dda85233c..459cd99a1d3ea 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -77,9 +77,9 @@
version "0.0.0"
uid ""
-"@elastic/eui@0.0.47", "@elastic/eui@v0.0.47":
- version "0.0.47"
- resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-0.0.47.tgz#5bae27966bb1d68bb3106853610a407509053b44"
+"@elastic/eui@v0.0.51":
+ version "0.0.51"
+ resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-0.0.51.tgz#5d809af270dd9994a609fd01eaa84e21a62fff98"
dependencies:
brace "^0.10.0"
classnames "^2.2.5"
@@ -1860,6 +1860,10 @@ buffer-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe"
+buffer-from@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04"
+
buffer-xor@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
@@ -2576,7 +2580,16 @@ concat-stream@1.6.0:
readable-stream "^2.2.2"
typedarray "^0.0.6"
-concat-stream@^1.4.7, concat-stream@^1.5.2, concat-stream@^1.6.0, concat-stream@~1.6.0:
+concat-stream@^1.4.7:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
+ dependencies:
+ buffer-from "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^2.2.2"
+ typedarray "^0.0.6"
+
+concat-stream@^1.5.2, concat-stream@^1.6.0, concat-stream@~1.6.0:
version "1.6.1"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.1.tgz#261b8f518301f1d834e36342b9fea095d2620a26"
dependencies:
@@ -2699,10 +2712,14 @@ core-js@^1.0.0:
version "1.2.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
-core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.1:
+core-js@^2.2.0, core-js@^2.5.0:
version "2.5.3"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
+core-js@^2.4.0, core-js@^2.5.1:
+ version "2.5.7"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
+
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -4772,8 +4789,8 @@ focus-trap-react@^3.0.4, focus-trap-react@^3.1.1:
focus-trap "^2.0.1"
focus-trap@^2.0.1:
- version "2.4.3"
- resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-2.4.3.tgz#95edc23e77829b7772cb2486d61fd6371ce112f9"
+ version "2.4.5"
+ resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-2.4.5.tgz#91c9c9ffb907f8f4446d80202dda9c12c2853ddb"
dependencies:
tabbable "^1.0.3"
@@ -5905,7 +5922,7 @@ icalendar@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/icalendar/-/icalendar-0.7.1.tgz#d0d3486795f8f1c5cf4f8cafac081b4b4e7a32ae"
-iconv-lite@0.4, iconv-lite@0.4.19, iconv-lite@^0.4.13, iconv-lite@^0.4.17, iconv-lite@^0.4.19, iconv-lite@~0.4.13:
+iconv-lite@0.4, iconv-lite@0.4.19, iconv-lite@^0.4.13, iconv-lite@^0.4.17, iconv-lite@^0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
@@ -5917,6 +5934,12 @@ iconv-lite@0.4.7:
version "0.4.7"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.7.tgz#89d32fec821bf8597f44609b4bc09bed5c209a23"
+iconv-lite@~0.4.13:
+ version "0.4.23"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
icss-replace-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
@@ -10041,7 +10064,16 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-ace@^5.5.0, react-ace@^5.9.0:
+react-ace@^5.5.0:
+ version "5.10.0"
+ resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-5.10.0.tgz#e328b37ac52759f700be5afdb86ada2f5ec84c5e"
+ dependencies:
+ brace "^0.11.0"
+ lodash.get "^4.4.2"
+ lodash.isequal "^4.1.1"
+ prop-types "^15.5.8"
+
+react-ace@^5.9.0:
version "5.9.0"
resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-5.9.0.tgz#427a1cc4869b960a6f9748aa7eb169a9269fc336"
dependencies:
@@ -10129,6 +10161,10 @@ react-input-range@^1.3.0:
autobind-decorator "^1.3.4"
prop-types "^15.5.8"
+react-lifecycles-compat@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
+
react-markdown-renderer@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/react-markdown-renderer/-/react-markdown-renderer-1.4.0.tgz#f3b95bd9fc7f7bf8ab3f0150aa696b41740e7d01"
@@ -10271,14 +10307,15 @@ react-toggle@4.0.2:
classnames "^2.2.5"
react-virtualized@^9.18.5:
- version "9.18.5"
- resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.18.5.tgz#42dd390ebaa7ea809bfcaf775d39872641679b89"
+ version "9.19.1"
+ resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.19.1.tgz#84b53253df2d9df61c85ce037141edccc70a73fd"
dependencies:
babel-runtime "^6.26.0"
classnames "^2.2.3"
dom-helpers "^2.4.0 || ^3.0.0"
loose-envify "^1.3.0"
prop-types "^15.6.0"
+ react-lifecycles-compat "^3.0.4"
react-vis@^1.8.1:
version "1.9.2"
@@ -10381,7 +10418,7 @@ read-pkg@^2.0.0:
normalize-package-data "^2.3.2"
path-type "^2.0.0"
-readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@~2.3.3:
+readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@~2.3.3:
version "2.3.5"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d"
dependencies:
@@ -10393,6 +10430,18 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable
string_decoder "~1.0.3"
util-deprecate "~1.0.1"
+readable-stream@^2.2.2:
+ version "2.3.6"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
readable-stream@~1.0.2:
version "1.0.34"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
@@ -10928,10 +10977,14 @@ rxjs@5.4.3:
dependencies:
symbol-observable "^1.0.1"
-safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+
safe-json-stringify@~1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.1.0.tgz#bd2b6dad1ebafab3c24672a395527f01804b7e19"
@@ -10949,6 +11002,10 @@ safefs@^4.0.0:
editions "^1.1.1"
graceful-fs "^4.1.4"
+"safer-buffer@>= 2.1.2 < 3":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+
samsam@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567"
@@ -11622,6 +11679,12 @@ string_decoder@~1.0.3:
dependencies:
safe-buffer "~5.1.0"
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+ dependencies:
+ safe-buffer "~5.1.0"
+
stringstream@~0.0.4, stringstream@~0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
@@ -11820,8 +11883,8 @@ tabbable@1.1.0:
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.0.tgz#2c9a9c9f09db5bb0659f587d532548dd6ef2067b"
tabbable@^1.0.3, tabbable@^1.1.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.2.tgz#b171680aea6e0a3e9281ff23532e2e5de11c0d94"
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.3.tgz#0e4ee376f3631e42d7977a074dbd2b3827843081"
table@^3.7.8:
version "3.8.3"
@@ -12276,8 +12339,8 @@ typescript@^2.8.1:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.1.tgz#6160e4f8f195d5ba81d4876f9c0cc1fbc0820624"
ua-parser-js@^0.7.9:
- version "0.7.17"
- resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
+ version "0.7.18"
+ resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed"
uc.micro@^1.0.1, uc.micro@^1.0.3:
version "1.0.5"
@@ -13116,7 +13179,11 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
dependencies:
iconv-lite "0.4.19"
-whatwg-fetch@>=0.10.0, whatwg-fetch@^2.0.3:
+whatwg-fetch@>=0.10.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
+
+whatwg-fetch@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"