mirror of https://github.com/rancher/dashboard.git
Merge branch 'master' into alert-polish
This commit is contained in:
commit
997feac58d
|
|
@ -24,8 +24,8 @@
|
||||||
@import "./global/labeled-input";
|
@import "./global/labeled-input";
|
||||||
@import "./global/tooltip";
|
@import "./global/tooltip";
|
||||||
@import "./global/table";
|
@import "./global/table";
|
||||||
|
@import "./global/select";
|
||||||
|
|
||||||
/* @import 'vue-select/src/scss/vue-select.scss'; */
|
|
||||||
@import "./vendor/vue-select";
|
@import "./vendor/vue-select";
|
||||||
@import "./vendor/vue-js-modal";
|
@import "./vendor/vue-js-modal";
|
||||||
@import '@/node_modules/xterm/css/xterm.css';
|
@import '@/node_modules/xterm/css/xterm.css';
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ TEXTAREA,
|
||||||
BUTTON,
|
BUTTON,
|
||||||
.btn,
|
.btn,
|
||||||
.labeled-input,
|
.labeled-input,
|
||||||
|
.labeled-select,
|
||||||
|
.unlabeled-select,
|
||||||
.checkbox-custom,
|
.checkbox-custom,
|
||||||
.radio-custom {
|
.radio-custom {
|
||||||
&:focus, &.focused {
|
&:focus, &.focused {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ button,
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bg-transparent {
|
&.bg-transparent {
|
||||||
|
|
@ -49,10 +49,11 @@ button,
|
||||||
//btn sizes
|
//btn sizes
|
||||||
.btn-xs,
|
.btn-xs,
|
||||||
.btn-group-xs > .btn,
|
.btn-group-xs > .btn,
|
||||||
.btn-xs .btn-label {
|
.btn-xs .btn-label,
|
||||||
padding: $xs-padding;
|
.btn-xs .dd-button > .vs__dropdown-toggle {
|
||||||
font-size: .65em;
|
padding: $xs-padding;
|
||||||
line-height: 1.5em;
|
font-size: 0.65em;
|
||||||
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-group-xs > .btn {
|
.btn-group-xs > .btn {
|
||||||
|
|
@ -62,8 +63,7 @@ button,
|
||||||
.btn-sm,
|
.btn-sm,
|
||||||
.btn-group-sm > .btn,
|
.btn-group-sm > .btn,
|
||||||
.btn-sm .btn-label {
|
.btn-sm .btn-label {
|
||||||
padding: $sm-padding;
|
padding: $sm-padding;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-group-sm > .btn {
|
.btn-group-sm > .btn {
|
||||||
|
|
@ -74,8 +74,8 @@ button,
|
||||||
.btn-lg,
|
.btn-lg,
|
||||||
.btn-group-lg > .btn,
|
.btn-group-lg > .btn,
|
||||||
.btn-lg .btn-label {
|
.btn-lg .btn-label {
|
||||||
padding: $lg-padding;
|
padding: $lg-padding;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
//btn roles
|
//btn roles
|
||||||
|
|
@ -113,7 +113,6 @@ button,
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.icon-group i {
|
.icon-group i {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ INPUT[type='password'],
|
||||||
INPUT[type='number'],
|
INPUT[type='number'],
|
||||||
INPUT[type='date'],
|
INPUT[type='date'],
|
||||||
INPUT[type='email'],
|
INPUT[type='email'],
|
||||||
INPUT[type='search'],
|
INPUT[type='search']:not(.vs__search),
|
||||||
INPUT[type='tel'],
|
INPUT[type='tel'],
|
||||||
INPUT[type='url'],
|
INPUT[type='url'],
|
||||||
SELECT,
|
SELECT,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
transition-timing-function: ease-in-out;
|
transition-timing-function: ease-in-out;
|
||||||
color: var(--input-label);
|
color: var(--input-label);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: z-index('overContent');
|
|
||||||
i {
|
i {
|
||||||
pointer-events: initial;
|
pointer-events: initial;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
.labeled-select {
|
||||||
|
cursor: text;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
padding-top: 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.labeled-select,
|
||||||
|
.unlabeled-select {
|
||||||
|
min-width: 75px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.v-select {
|
||||||
|
&.inline {
|
||||||
|
.vs__search {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs__dropdown-toggle,
|
||||||
|
.vs__dropdown-toggle > * {
|
||||||
|
background-color: transparent;
|
||||||
|
border: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs__dropdown-menu {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
position: relative;
|
||||||
|
top: 1.4em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.v-select.inline.vs--single {
|
||||||
|
&.vs--searching .vs__selected {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&:not(.vs--searching) {
|
||||||
|
.vs__selected-options {
|
||||||
|
overflow: hidden;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
.vs__selected {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-select.inline:not(.vs--single) {
|
||||||
|
margin-bottom: -4px; // targets multi-select tag boxes to make the same size as rows next to it
|
||||||
|
min-height: 30px;
|
||||||
|
|
||||||
|
.vs__selected {
|
||||||
|
min-height: 25px;
|
||||||
|
padding: 0 7px;
|
||||||
|
|
||||||
|
&:not(:only-child) {
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.focused {
|
||||||
|
outline: none;
|
||||||
|
border: var(--outline-width) solid var(--outline);
|
||||||
|
box-shadow: none;
|
||||||
|
.v-select {
|
||||||
|
// Can toggle this to get full width dd - maybe make an option?
|
||||||
|
.vs__dropdown-menu {
|
||||||
|
min-width: max-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.unlabeled-select {
|
||||||
|
background-color: var(--input-bg);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: solid var(--outline-width) var(--input-border);
|
||||||
|
color: var(--input-text);
|
||||||
|
|
||||||
|
.vs--single .vs__selected-options {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-select {
|
||||||
|
&.inline {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.vs__dropdown-toggle {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.vs__actions {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.view) {
|
||||||
|
background-color: var(--input-bg);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&,
|
||||||
|
.vs__dropdown-menu {
|
||||||
|
background: var(--input-hover-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -180,134 +180,3 @@
|
||||||
transition: opacity .15s;
|
transition: opacity .15s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// load here instead of component so SSR render isn't all wonky
|
|
||||||
.dropdown-button-group {
|
|
||||||
// this matches the top/bottom padding of the default button
|
|
||||||
$trigger-padding: 15px 10px 15px 10px;
|
|
||||||
$xs-trigger-padding: 2px 4px 4px 4px;
|
|
||||||
$sm-trigger-padding: 8px 10px 10px 10px;
|
|
||||||
$lg-trigger-padding: 18px 10px 10px 10px;
|
|
||||||
|
|
||||||
.v-popover {
|
|
||||||
.text-right {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
.trigger {
|
|
||||||
height: 100%;
|
|
||||||
.icon-container {
|
|
||||||
height: 100%;
|
|
||||||
padding: $trigger-padding;
|
|
||||||
i {
|
|
||||||
transform: scale(1.5);
|
|
||||||
}
|
|
||||||
&.btn-xs {
|
|
||||||
padding: $xs-trigger-padding;
|
|
||||||
}
|
|
||||||
&.btn-sm {
|
|
||||||
padding: $sm-trigger-padding;
|
|
||||||
}
|
|
||||||
&.btn-lg {
|
|
||||||
padding: $lg-trigger-padding;
|
|
||||||
}
|
|
||||||
&:focus {
|
|
||||||
outline-style: none;
|
|
||||||
box-shadow: none;
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-button {
|
|
||||||
background: var(--tooltip-bg);
|
|
||||||
border: solid thin var(--link-text);
|
|
||||||
color: var(--link-text);
|
|
||||||
padding: 0;
|
|
||||||
display: inline-flex;
|
|
||||||
|
|
||||||
&>*, .icon-chevron-down {
|
|
||||||
color: var(--primary);
|
|
||||||
background-color: rgba(0,0,0,0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-divider {
|
|
||||||
border-right: 1px solid var(--link-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.bg-primary:hover {
|
|
||||||
background: var(--accent-btn-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.one-action {
|
|
||||||
position: relative;
|
|
||||||
&>.btn {
|
|
||||||
padding: 15px 35px 15px 15px;
|
|
||||||
}
|
|
||||||
.v-popover{
|
|
||||||
.trigger{
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
right: 0px;
|
|
||||||
left: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
BUTTON {
|
|
||||||
position: absolute;
|
|
||||||
right: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.popover {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.tooltip {
|
|
||||||
&[x-placement^="bottom"] {
|
|
||||||
.tooltip-arrow {
|
|
||||||
border-bottom-color: var(--dropdown-border);
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
border-bottom-color: var(--dropdown-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-inner {
|
|
||||||
color: var(--dropdown-text);
|
|
||||||
background-color: var(--dropdown-bg);
|
|
||||||
border: 1px solid var(--dropdown-border);
|
|
||||||
padding: 0px;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
LI {
|
|
||||||
padding: 10px 50px 10px 20px;
|
|
||||||
|
|
||||||
&.divider {
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
|
|
||||||
> .divider-inner {
|
|
||||||
padding: 0;
|
|
||||||
border-bottom: 1px solid var(--dropdown-divider);
|
|
||||||
width: 125%;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.divider):hover {
|
|
||||||
background-color: var(--dropdown-hover-bg);
|
|
||||||
color: var(--dropdown-hover-text);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//header
|
|
||||||
.user-info {
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
--body-gradient: linear-gradient(180deg, #{$dark} 0%, #{$darkest} 100%);
|
--body-gradient: linear-gradient(180deg, #{$dark} 0%, #{$darkest} 100%);
|
||||||
--body-text: #{$lightest};
|
--body-text: #{$lightest};
|
||||||
--scrollbar-thumb: #{$medium};
|
--scrollbar-thumb: #{$medium};
|
||||||
--scrollbar-thumb-dropdown: #{$darker};
|
--scrollbar-thumb-dropdown: #{$medium};
|
||||||
|
|
||||||
--nav-bg: #{$darkest};
|
--nav-bg: #{$darkest};
|
||||||
--nav-active: #{rgba($primary, 0.3)};
|
--nav-active: #{rgba($primary, 0.3)};
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.v-select,
|
.v-select,
|
||||||
.v-select * {
|
.v-select * {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
@ -23,7 +22,6 @@
|
||||||
.vs__dropdown-toggle,
|
.vs__dropdown-toggle,
|
||||||
.vs__clear,
|
.vs__clear,
|
||||||
.vs__search,
|
.vs__search,
|
||||||
// .vs__selected,
|
|
||||||
.vs__open-indicator {
|
.vs__open-indicator {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
color: var(--dropdown-disabled-text);
|
color: var(--dropdown-disabled-text);
|
||||||
|
|
@ -33,24 +31,19 @@
|
||||||
.vs__dropdown-menu {
|
.vs__dropdown-menu {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(100% - 1px);
|
left: -2px;
|
||||||
left: 0;
|
|
||||||
z-index: z-index('dropdownContent');
|
z-index: z-index('dropdownContent');
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: calc(100% + 4px);
|
||||||
max-height: 350px;
|
max-height: 350px;
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border: 1px solid var(--dropdown-border);
|
border: 1px solid var(--dropdown-border);
|
||||||
border-top-style: none;
|
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
background: var(--dropdown-bg);
|
background: var(--dropdown-bg);
|
||||||
box-shadow: 0px 8px 16px 0px var(--shadow);
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background-color: var(--scrollbar-thumb-dropdown) !important;
|
background-color: var(--scrollbar-thumb-dropdown) !important;
|
||||||
|
|
@ -60,7 +53,6 @@
|
||||||
&[data-popper-placement='top'] {
|
&[data-popper-placement='top'] {
|
||||||
border-radius: 4px 4px 0 0;
|
border-radius: 4px 4px 0 0;
|
||||||
border-top-style: solid;
|
border-top-style: solid;
|
||||||
border-bottom-style: none;
|
|
||||||
box-shadow: 0px -8px 16px 0px var(--shadow);
|
box-shadow: 0px -8px 16px 0px var(--shadow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -89,8 +81,8 @@
|
||||||
color: var(--dropdown-disabled-text);
|
color: var(--dropdown-disabled-text);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|
||||||
HR {
|
hr {
|
||||||
cursor: default
|
cursor: default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +96,7 @@
|
||||||
color: var(--dropdown-hover-text);
|
color: var(--dropdown-hover-text);
|
||||||
background: var(--dropdown-hover-bg);
|
background: var(--dropdown-hover-bg);
|
||||||
|
|
||||||
A {
|
a {
|
||||||
color: var(--dropdown-hover-text);
|
color: var(--dropdown-hover-text);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
@ -114,7 +106,6 @@
|
||||||
.vs__dropdown-toggle {
|
.vs__dropdown-toggle {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
// padding: 0 0 4px 0;
|
|
||||||
background: var(--input-bg);
|
background: var(--input-bg);
|
||||||
border: 1px solid var(--dropdown-border);
|
border: 1px solid var(--dropdown-border);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
|
@ -139,7 +130,9 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
left: -2px;
|
width: 100%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-shrink: 8;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
@ -161,32 +154,17 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs--unsearchable .vs__search {
|
$transition-timing-function: cubic-bezier(1, -0.115, 0.975, 0.855);
|
||||||
background-color: var(--default-text);
|
|
||||||
width: 0px;
|
|
||||||
padding: 0;
|
|
||||||
align-self: center;
|
|
||||||
border: 0;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vs--open .vs__dropdown-toggle {
|
|
||||||
border-bottom-color: transparent;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);
|
|
||||||
$transition-duration: 150ms;
|
$transition-duration: 150ms;
|
||||||
|
|
||||||
.vs__open-indicator {
|
.vs__action:after {
|
||||||
fill: var(--dropdown-disabled-text);
|
fill: var(--dropdown-disabled-text);
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
transition: transform $transition-duration $transition-timing-function;
|
transition: transform $transition-duration $transition-timing-function;
|
||||||
transition-timing-function: $transition-timing-function;
|
transition-timing-function: $transition-timing-function;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs--open .vs__open-indicator {
|
.vs--open .vs__actions:after {
|
||||||
transform: rotate(180deg) scale(1);
|
transform: rotate(180deg) scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,7 +177,7 @@ $transition-duration: 150ms;
|
||||||
* below, the cancel button will still appear in chrome.
|
* below, the cancel button will still appear in chrome.
|
||||||
* If it's up here on it's own, it'll hide it.
|
* If it's up here on it's own, it'll hide it.
|
||||||
*/
|
*/
|
||||||
.vs__search::-webkit-search-cancel-button {
|
.vs__search::-webkit-search-cancel-button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,12 +215,11 @@ $transition-duration: 150ms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.vs--single.vs--searching:not(.vs--open):not(.vs--loading) {
|
.vs--single.vs--searching:not(.vs--open):not(.vs--loading) {
|
||||||
.vs__search {
|
.vs__search {
|
||||||
opacity: .2;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* States */
|
/* States */
|
||||||
|
|
||||||
.vs--single {
|
.vs--single {
|
||||||
|
|
@ -263,7 +240,7 @@ $transition-duration: 150ms;
|
||||||
border: 1px solid var(--primary);
|
border: 1px solid var(--primary);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
color: var(--link-text);
|
color: var(--link-text);
|
||||||
padding: 6px;
|
margin-left: 7px;
|
||||||
|
|
||||||
&:not(:last-of-type) {
|
&:not(:last-of-type) {
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
|
|
@ -296,47 +273,49 @@ $transition-duration: 150ms;
|
||||||
.v-select.inline {
|
.v-select.inline {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
&.vs--single .vs__selected {
|
&.vs--single {
|
||||||
color: var(--input-text);
|
min-height: 25px;
|
||||||
position: absolute;
|
&.vs--open {
|
||||||
top: 0;
|
.vs__selected {
|
||||||
bottom:0;
|
position: absolute;
|
||||||
align-self: start;
|
opacity: 0.4;
|
||||||
margin: 4px;
|
}
|
||||||
|
.vs__search {
|
||||||
|
margin-left: 7px;;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.vs__selected {
|
||||||
|
color: var(--input-text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs__dropdown-menu {
|
.vs__dropdown-menu {
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
.vs__dropdown-toggle {
|
.vs__dropdown-toggle {
|
||||||
background-color: var(--input-bg);
|
background-color: var(--input-bg);
|
||||||
border:none;
|
border: none;
|
||||||
height: 100%;
|
|
||||||
padding: none;
|
padding: none;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
border: 1px solid var(--dropdown-border);
|
border: 1px solid var(--dropdown-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.vs--single .vs__selected-options {
|
&.vs--single .vs__selected-options {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
.vs__search {
|
.vs__search {
|
||||||
background-color: rgba(0,0,0,0);
|
background-color: rgba(0, 0, 0, 0);
|
||||||
width: 100%;
|
&:hover {
|
||||||
&:hover{
|
background-color: rgba(0, 0, 0, 0);
|
||||||
background-color: rgba(0,0,0,0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.vs__open-indicator{
|
.vs__open-indicator {
|
||||||
fill: var(--input-label);
|
fill: var(--input-label);
|
||||||
}
|
}
|
||||||
.vs__clear {
|
.vs__clear {
|
||||||
display:none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-select.mini {
|
.v-select.mini {
|
||||||
|
|
@ -351,15 +330,17 @@ $transition-duration: 150ms;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
INPUT {
|
input {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs__selected-options INPUT {
|
.vs__selected-options input {
|
||||||
width: auto;
|
width: 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
background-color: var(--input-bg);
|
||||||
|
color: var(--input-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
header .vs-select .vs__dropdown-toggle {
|
header .vs-select .vs__dropdown-toggle {
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ assignTo:
|
||||||
=1 { Assign Cluster To… }
|
=1 { Assign Cluster To… }
|
||||||
other { Assign {count} Clusters To… }
|
other { Assign {count} Clusters To… }
|
||||||
}
|
}
|
||||||
|
workspace: Workspace
|
||||||
|
|
||||||
asyncButton:
|
asyncButton:
|
||||||
default:
|
default:
|
||||||
|
|
@ -327,21 +328,41 @@ chartHeading:
|
||||||
|
|
||||||
cis:
|
cis:
|
||||||
addTest: Add Test ID
|
addTest: Add Test ID
|
||||||
|
alertNeeded: To receive alerts, please ensure <a tabindex="0" aria-label="Link to Rancher's Monitoring" href="{link}"> Rancher's Monitoring and Alerting app</a> is installed and the Receivers and Routes are <a target="_blank" rel='noopener nofollow' href='https://rancher.com/docs/rancher/v2.x/en/monitoring-alerting/v2.5/configuration/#alertmanager-config'> configured to send out alerts.</a>
|
||||||
|
alertNotFound: "It looks like alerting isn't configured."
|
||||||
|
alertOnComplete: Alert on scan completion
|
||||||
|
alertOnFailure: Alert on scan failure
|
||||||
benchmarkVersion: Benchmark Version
|
benchmarkVersion: Benchmark Version
|
||||||
|
cronSchedule:
|
||||||
|
label: Schedule
|
||||||
|
placeholder: "e.g. 0 * * * *"
|
||||||
|
deleteProfileWarning: |-
|
||||||
|
{count, plural,
|
||||||
|
=1 { Any scheduled scans using this profile will no longer work. }
|
||||||
|
other { Any scheduled scans using either of these profiles will no longer work. }
|
||||||
|
}
|
||||||
|
downloadAllReports: Download All Saved Reports
|
||||||
|
downloadLatestReport: Download Latest Report
|
||||||
noProfiles: There are no valid ClusterScanProfiles for this cluster type to select.
|
noProfiles: There are no valid ClusterScanProfiles for this cluster type to select.
|
||||||
profile: Profile
|
profile: Profile
|
||||||
|
retention: Retention Count
|
||||||
testID: Test ID
|
testID: Test ID
|
||||||
testsToSkip: Tests to Skip
|
testsToSkip: Tests to Skip
|
||||||
|
testsSkipped: Tests Skipped
|
||||||
scan:
|
scan:
|
||||||
description: Description
|
description: Description
|
||||||
failed: Failed
|
fail: Fail
|
||||||
lastScanTime: Last Scan Time
|
lastScanTime: Last Scan Time
|
||||||
notApplicable: 'N/A'
|
notApplicable: 'N/A'
|
||||||
number: Number
|
number: Number
|
||||||
passed: Passed
|
pass: Pass
|
||||||
scanReport: Scan Report
|
scanReport: Scan Report
|
||||||
skipped: Skipped
|
skip: Skip
|
||||||
total: Total
|
total: Total
|
||||||
|
warn: Warn
|
||||||
|
scoreWarning:
|
||||||
|
label: Scan state for "warn" results
|
||||||
|
protip: Scans with no failures will be marked "Pass" by default even if some of the tests generate "warn" output. This behavior can be changed by selecting the "fail" option from this section.
|
||||||
|
|
||||||
cluster:
|
cluster:
|
||||||
nodeDriver:
|
nodeDriver:
|
||||||
|
|
@ -871,6 +892,24 @@ monitoring:
|
||||||
fields:
|
fields:
|
||||||
name: Name
|
name: Name
|
||||||
|
|
||||||
|
monitoringRoute:
|
||||||
|
groups:
|
||||||
|
label: Group By
|
||||||
|
info: This is the top-level Route used by Alertmanager as the default destination for any Alerts that do not match any other Routes. This Route must exist and cannot be deleted.
|
||||||
|
interval:
|
||||||
|
label: Group Interval
|
||||||
|
matching:
|
||||||
|
info: The root route has to match everything so matching can't be configured.
|
||||||
|
label: Match
|
||||||
|
receiver:
|
||||||
|
label: Receiver
|
||||||
|
regex:
|
||||||
|
label: Match Regex
|
||||||
|
repeatInterval:
|
||||||
|
label: Repeat Interval
|
||||||
|
wait:
|
||||||
|
label: Group Wait
|
||||||
|
|
||||||
nameNsDescription:
|
nameNsDescription:
|
||||||
name:
|
name:
|
||||||
label: Name
|
label: Name
|
||||||
|
|
@ -887,6 +926,16 @@ nameNsDescription:
|
||||||
|
|
||||||
namespace:
|
namespace:
|
||||||
containerResourceLimit: Container Resource Limit
|
containerResourceLimit: Container Resource Limit
|
||||||
|
project:
|
||||||
|
label: Project
|
||||||
|
|
||||||
|
namespaceFilter:
|
||||||
|
selected:
|
||||||
|
label: "{total} items selected"
|
||||||
|
|
||||||
|
namespaceList:
|
||||||
|
selectLabel: Namespace
|
||||||
|
addLabel: Add Namespace
|
||||||
|
|
||||||
node:
|
node:
|
||||||
detail:
|
detail:
|
||||||
|
|
@ -957,6 +1006,39 @@ prefs:
|
||||||
hideDesc:
|
hideDesc:
|
||||||
label: Hide All Type Description Boxes
|
label: Hide All Type Description Boxes
|
||||||
|
|
||||||
|
probe:
|
||||||
|
checkInterval:
|
||||||
|
label: Check Interval
|
||||||
|
placeholder: 'Default: 10'
|
||||||
|
command:
|
||||||
|
label: Command to run
|
||||||
|
placeholder: e.g. cat /tmp/health
|
||||||
|
failureThreshold:
|
||||||
|
label: Failure Threshold
|
||||||
|
placeholder: 'Default: 3'
|
||||||
|
httpGet:
|
||||||
|
headers:
|
||||||
|
label: Request Headers
|
||||||
|
path:
|
||||||
|
label: Request Path
|
||||||
|
placeholder: e.g. /healthz
|
||||||
|
port:
|
||||||
|
label: Check Port
|
||||||
|
placeholder: e.g. 80
|
||||||
|
placeholderDuex: e.g. 25
|
||||||
|
initialDelay:
|
||||||
|
label: Initial Delay
|
||||||
|
placeholder: 'Default: 0'
|
||||||
|
successThreshold:
|
||||||
|
label: Success Threshold
|
||||||
|
placeholder: 'Default: 1'
|
||||||
|
timeout:
|
||||||
|
label: Timeout
|
||||||
|
placeholder: 'Default: 3'
|
||||||
|
type:
|
||||||
|
label: Type
|
||||||
|
placeholder: Select a check type
|
||||||
|
|
||||||
prometheusRule:
|
prometheusRule:
|
||||||
alertingRules:
|
alertingRules:
|
||||||
addLabel: Add Alert
|
addLabel: Add Alert
|
||||||
|
|
@ -1321,6 +1403,18 @@ tableHeaders:
|
||||||
version: Version
|
version: Version
|
||||||
weight: Weight
|
weight: Weight
|
||||||
|
|
||||||
|
target:
|
||||||
|
router:
|
||||||
|
label: Router
|
||||||
|
placeholder: Select a router
|
||||||
|
service:
|
||||||
|
label: Service
|
||||||
|
placeholder: Select a service
|
||||||
|
title: Target
|
||||||
|
version:
|
||||||
|
label: Version
|
||||||
|
placeholder: Select a version
|
||||||
|
|
||||||
validation:
|
validation:
|
||||||
arrayLength:
|
arrayLength:
|
||||||
between: '"{key}" should contain between {min} and {max} {max, plural, =1 {item} other {items}}'
|
between: '"{key}" should contain between {min} and {max} {max, plural, =1 {item} other {items}}'
|
||||||
|
|
@ -1489,6 +1583,7 @@ workload:
|
||||||
value:
|
value:
|
||||||
label: Value
|
label: Value
|
||||||
placeholder: e.g. BAR
|
placeholder: e.g. BAR
|
||||||
|
tty: TTY
|
||||||
workingDir: WorkingDir
|
workingDir: WorkingDir
|
||||||
stdin: Stdin
|
stdin: Stdin
|
||||||
healthCheck:
|
healthCheck:
|
||||||
|
|
@ -1669,6 +1764,7 @@ workload:
|
||||||
inNamespaces: "Pods in these namespaces:"
|
inNamespaces: "Pods in these namespaces:"
|
||||||
key: Key
|
key: Key
|
||||||
lessThan: <
|
lessThan: <
|
||||||
|
namespaces: Namespaces
|
||||||
notIn: ≠
|
notIn: ≠
|
||||||
operator: Operator
|
operator: Operator
|
||||||
value: Value
|
value: Value
|
||||||
|
|
@ -1758,6 +1854,7 @@ workload:
|
||||||
storagePolicyName: Storage Policy Name
|
storagePolicyName: Storage Policy Name
|
||||||
volumePath: Volume Path
|
volumePath: Volume Path
|
||||||
defaultMode: Default Mode
|
defaultMode: Default Mode
|
||||||
|
driver: driver
|
||||||
hostPath:
|
hostPath:
|
||||||
label: The Path on the Node must be
|
label: The Path on the Node must be
|
||||||
options:
|
options:
|
||||||
|
|
@ -2046,3 +2143,7 @@ workloadPorts:
|
||||||
|
|
||||||
podAffinity:
|
podAffinity:
|
||||||
addLabel: Add Pod Selector
|
addLabel: Add Pod Selector
|
||||||
|
|
||||||
|
keyValue:
|
||||||
|
keyPlaceholder: e.g. foo
|
||||||
|
valuePlaceholder: e.g. bar
|
||||||
|
|
|
||||||
|
|
@ -962,7 +962,7 @@ servicePorts:
|
||||||
label: 协议
|
label: 协议
|
||||||
target:
|
target:
|
||||||
label: 目标端口
|
label: 目标端口
|
||||||
placeholder: 例如:80 or http
|
placeholder: 例如:80 或 http
|
||||||
|
|
||||||
serviceTypes:
|
serviceTypes:
|
||||||
clusterip: 集群IP地址
|
clusterip: 集群IP地址
|
||||||
|
|
@ -1130,6 +1130,23 @@ tableHeaders:
|
||||||
value: 值
|
value: 值
|
||||||
version: 版本号
|
version: 版本号
|
||||||
weight: 权重 ## doublecheck,这里的权重有什么特殊意义吗?
|
weight: 权重 ## doublecheck,这里的权重有什么特殊意义吗?
|
||||||
|
Capacity: 容量
|
||||||
|
'Access Modes': 访问模式
|
||||||
|
'Reclaim Policy': 重声明策略
|
||||||
|
Status: 状态
|
||||||
|
Claim: 声明
|
||||||
|
StorageClass: 存储类
|
||||||
|
Reason: 原因
|
||||||
|
VolumeMode: 存储卷模式
|
||||||
|
Targets: 目标
|
||||||
|
MinPods: 最小Pod数量
|
||||||
|
MaxPods: 最大Pod数量
|
||||||
|
Replicas: 副本数
|
||||||
|
Provisioner: 提供商
|
||||||
|
ReclaimPolicy: 重声明策略
|
||||||
|
Role: 角色
|
||||||
|
Users: 用户
|
||||||
|
Groups: 用户组
|
||||||
|
|
||||||
validation:
|
validation:
|
||||||
arrayLength:
|
arrayLength:
|
||||||
|
|
@ -1659,6 +1676,16 @@ typeLabel:
|
||||||
persistentvolume: 持久卷
|
persistentvolume: 持久卷
|
||||||
service: 服务
|
service: 服务
|
||||||
node: 节点
|
node: 节点
|
||||||
|
autoscaling.horizontalpodautoscaler: Pod水平自动伸缩
|
||||||
|
networking.k8s.io.ingress: Ingress
|
||||||
|
networking.k8s.io.networkpolicy: 网络策略
|
||||||
|
persistentvolumeclaim: 持久卷声明
|
||||||
|
storage.k8s.io.storageclass: 存储类
|
||||||
|
secret: 密钥
|
||||||
|
rbac.authorization.k8s.io.clusterrolebinding: 集群角色绑定
|
||||||
|
rbac.authorization.k8s.io.clusterrole: 集群角色
|
||||||
|
rbac.authorization.k8s.io.rolebinding: 角色绑定
|
||||||
|
rbac.authorization.k8s.io.role: 角色
|
||||||
|
|
||||||
action:
|
action:
|
||||||
download: 下载YAML
|
download: 下载YAML
|
||||||
|
|
@ -1684,3 +1711,7 @@ workloadPorts:
|
||||||
|
|
||||||
podAffinity:
|
podAffinity:
|
||||||
addLabel: 添加 Pod Selector
|
addLabel: 添加 Pod Selector
|
||||||
|
|
||||||
|
keyValue:
|
||||||
|
keyPlaceholder: '例如: foo'
|
||||||
|
valuePlaceholder: '例如: bar'
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ export default {
|
||||||
<form>
|
<form>
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-model="moveTo"
|
v-model="moveTo"
|
||||||
label="Workspace"
|
:label="t('assignTo.workspace')"
|
||||||
:options="workspaceOptions"
|
:options="workspaceOptions"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
/>
|
/>
|
||||||
|
|
@ -141,7 +141,7 @@ export default {
|
||||||
|
|
||||||
<div slot="actions">
|
<div slot="actions">
|
||||||
<button class="btn role-secondary" @click="close">
|
<button class="btn role-secondary" @click="close">
|
||||||
Cancel
|
{{ t('generic.cancel') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<AsyncButton
|
<AsyncButton
|
||||||
|
|
|
||||||
|
|
@ -1,101 +1,235 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { get } from '@/utils/object';
|
||||||
|
import isString from 'lodash/isString';
|
||||||
|
import VueSelectOverrides from '@/mixins/vue-select-overrides';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
mixins: [VueSelectOverrides],
|
||||||
size: {
|
props: {
|
||||||
|
buttonLabel: {
|
||||||
|
default: '',
|
||||||
type: String,
|
type: String,
|
||||||
default: '' // possible values are xs, sm, lg. empty is default .btn
|
|
||||||
},
|
},
|
||||||
// whether this is a button and dropdown (default) or dropdown that looks like a button/dropdown
|
closeOnSelect: {
|
||||||
dualAction: {
|
default: true,
|
||||||
|
type: Boolean
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
default: false,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
},
|
||||||
}
|
// array of option objects containing at least a label and link, but also icon and action are available
|
||||||
|
dropdownOptions: {
|
||||||
|
// required: true,
|
||||||
|
default: () => [],
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
optionKey: {
|
||||||
|
default: null,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
optionLabel: {
|
||||||
|
default: 'label',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
// sm, null(med), lg - no xs...its so small
|
||||||
|
size: {
|
||||||
|
default: null,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
default: null,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
computed: {
|
return { focused: false };
|
||||||
buttonSize() {
|
|
||||||
const { size } = this;
|
|
||||||
let out;
|
|
||||||
|
|
||||||
switch (size) {
|
|
||||||
case '':
|
|
||||||
default:
|
|
||||||
out = 'btn';
|
|
||||||
break;
|
|
||||||
case 'xs':
|
|
||||||
out = 'btn btn-xs';
|
|
||||||
break;
|
|
||||||
case 'sm':
|
|
||||||
out = 'btn btn-sm';
|
|
||||||
break;
|
|
||||||
case 'lg':
|
|
||||||
out = 'btn btn-lg';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
hasSlot(name = 'default') {
|
ddButtonAction(option) {
|
||||||
return !!this.$slots[name] || !!this.$scopedSlots[name];
|
this.focusSearch();
|
||||||
|
this.$emit('dd-button-action', option);
|
||||||
|
},
|
||||||
|
getOptionLabel(option) {
|
||||||
|
if (isString(option)) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$attrs['get-option-label']) {
|
||||||
|
return this.$attrs['get-option-label'](option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get(option, this.optionLabel)) {
|
||||||
|
if (this.localizedLabel) {
|
||||||
|
return this.$store.getters['i18n/t'](get(option, this.optionLabel));
|
||||||
|
} else {
|
||||||
|
return get(option, this.optionLabel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// allows parent components to programmatically open the dropdown
|
onFocus() {
|
||||||
togglePopover() {
|
return this.onFocusLabeled();
|
||||||
this.$refs.popoverButton.click();
|
|
||||||
},
|
},
|
||||||
}
|
|
||||||
|
onFocusLabeled() {
|
||||||
|
this.focused = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
onBlur() {
|
||||||
|
return this.onBlurLabeled();
|
||||||
|
},
|
||||||
|
|
||||||
|
onBlurLabeled() {
|
||||||
|
this.focused = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
focusSearch() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs['button-dropdown'].searchEl.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="dropdown-button-group">
|
<v-select
|
||||||
<div
|
ref="button-dropdown"
|
||||||
class="dropdown-button bg-primary"
|
class="button-dropdown btn"
|
||||||
:class="{'one-action':!dualAction, [buttonSize]:true}"
|
:class="{
|
||||||
>
|
disabled,
|
||||||
<slot v-if="dualAction" name="button-content" :buttonSize="buttonSize">
|
focused,
|
||||||
<button
|
'btn-sm': size === 'sm',
|
||||||
class="bg-transparent"
|
'btn-lg': size === 'lg',
|
||||||
:class="buttonSize"
|
}"
|
||||||
disabled="true"
|
v-bind="$attrs"
|
||||||
type="button"
|
:searchable="false"
|
||||||
>
|
:clearable="false"
|
||||||
Button
|
:close-on-select="closeOnSelect"
|
||||||
</button>
|
:filterable="false"
|
||||||
</slot>
|
:value="buttonLabel"
|
||||||
<div
|
:options="dropdownOptions"
|
||||||
v-else
|
:map-keydown="mappedKeys"
|
||||||
:class="buttonSize"
|
:get-option-key="
|
||||||
|
(opt) => (optionKey ? get(opt, optionKey) : getOptionLabel(opt))
|
||||||
|
"
|
||||||
|
:get-option-label="(opt) => getOptionLabel(opt)"
|
||||||
|
@search:blur="onBlur"
|
||||||
|
@search:focus="onFocus"
|
||||||
|
@input="$emit('click-action', $event)"
|
||||||
|
>
|
||||||
|
<template #selected-option="option">
|
||||||
|
<button
|
||||||
|
tabindex="-1"
|
||||||
type="button"
|
type="button"
|
||||||
|
class="dropdown-button-two btn"
|
||||||
|
:class="{
|
||||||
|
'btn-sm': size === 'sm',
|
||||||
|
'btn-lg': size === 'lg',
|
||||||
|
}"
|
||||||
|
@click="ddButtonAction(option)"
|
||||||
|
@focus="focusSearch"
|
||||||
>
|
>
|
||||||
<slot name="button-content" />
|
{{ option.label }}
|
||||||
</div>
|
</button>
|
||||||
<div v-if="hasSlot('popover-content') && dualAction" class="button-divider"></div>
|
</template>
|
||||||
|
<!-- Pass down templates provided by the caller -->
|
||||||
<v-popover
|
<template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope">
|
||||||
v-if="hasSlot('popover-content')"
|
<slot v-if="slot !== 'selected-option'" :name="slot" v-bind="scope" />
|
||||||
placement="bottom"
|
</template>
|
||||||
:container="false"
|
</v-select>
|
||||||
offset="10"
|
|
||||||
:popper-options="{modifiers: { flip: { enabled: false } } }"
|
|
||||||
>
|
|
||||||
<slot name="button-toggle-content" :buttonSize="buttonSize">
|
|
||||||
<button
|
|
||||||
ref="popoverButton"
|
|
||||||
class="icon-container bg-transparent"
|
|
||||||
:class="buttonSize"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<i class="icon icon-chevron-down" />
|
|
||||||
</button>
|
|
||||||
</slot>
|
|
||||||
|
|
||||||
<template slot="popover">
|
|
||||||
<slot name="popover-content" />
|
|
||||||
</template>
|
|
||||||
</v-popover>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.button-dropdown.btn-sm {
|
||||||
|
::v-deep > .vs__dropdown-toggle {
|
||||||
|
.vs__actions {
|
||||||
|
&:after {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.button-dropdown.btn-lg {
|
||||||
|
::v-deep > .vs__dropdown-toggle {
|
||||||
|
.vs__actions {
|
||||||
|
&:after {
|
||||||
|
font-size: 2.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.button-dropdown {
|
||||||
|
background: var(--tooltip-bg);
|
||||||
|
border: solid 1px var(--link-text);
|
||||||
|
color: var(--link-text);
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&.vs--open ::v-deep {
|
||||||
|
outline: none;
|
||||||
|
border: var(--outline-width) solid var(--outline);
|
||||||
|
border-bottom: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
::v-deep .vs__dropdown-toggle .vs__actions,
|
||||||
|
::v-deep .vs__selected-options {
|
||||||
|
background: var(--accent-btn-hover);
|
||||||
|
}
|
||||||
|
::v-deep .vs__selected-options .vs__selected button {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep > .vs__dropdown-toggle {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 75% 25%;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
.vs__actions {
|
||||||
|
border-left: solid thin var(--link-text);
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
color: var(--link-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .vs__selected-options {
|
||||||
|
.vs__selected {
|
||||||
|
margin: unset;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
background: var(--tooltip-bg);
|
||||||
|
color: var(--link-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.vs__search {
|
||||||
|
// if you need to keep the dd open you can toggle these on and off
|
||||||
|
// display: none;
|
||||||
|
// visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .vs__dropdown-menu {
|
||||||
|
min-width: unset;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,17 @@
|
||||||
import { mapState, mapGetters } from 'vuex';
|
import { mapState, mapGetters } from 'vuex';
|
||||||
import { get, isEmpty } from '@/utils/object';
|
import { get, isEmpty } from '@/utils/object';
|
||||||
import { NAMESPACE, RIO } from '@/config/types';
|
import { NAMESPACE, RIO } from '@/config/types';
|
||||||
import Card from '@/components/Card';
|
import InfoBox from '@/components/InfoBox';
|
||||||
import { alternateLabel } from '@/utils/platform';
|
import { alternateLabel } from '@/utils/platform';
|
||||||
import LinkDetail from '@/components/formatter/LinkDetail';
|
import LinkDetail from '@/components/formatter/LinkDetail';
|
||||||
import { uniq } from '@/utils/array';
|
import { uniq } from '@/utils/array';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { Card, LinkDetail },
|
components: { InfoBox, LinkDetail },
|
||||||
data() {
|
data() {
|
||||||
return { confirmName: '', error: '' };
|
return {
|
||||||
|
confirmName: '', error: '', warning: '', preventDelete: false
|
||||||
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
names() {
|
names() {
|
||||||
|
|
@ -54,20 +56,6 @@ export default {
|
||||||
return (type === NAMESPACE || type === RIO.STACK) && this.toRemove.length === 1;
|
return (type === NAMESPACE || type === RIO.STACK) && this.toRemove.length === 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
preventDeletionMessage() {
|
|
||||||
const toRemoveWithWarning = this.toRemove.filter(tr => tr?.preventDeletionMessage);
|
|
||||||
|
|
||||||
if (toRemoveWithWarning.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return toRemoveWithWarning[0].preventDeletionMessage;
|
|
||||||
},
|
|
||||||
|
|
||||||
isDeleteDisabled() {
|
|
||||||
return !!this.preventDeletionMessage;
|
|
||||||
},
|
|
||||||
|
|
||||||
plusMore() {
|
plusMore() {
|
||||||
const remaining = this.toRemove.length - this.names.length;
|
const remaining = this.toRemove.length - this.names.length;
|
||||||
|
|
||||||
|
|
@ -126,6 +114,31 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
this.$modal.hide('promptRemove');
|
this.$modal.hide('promptRemove');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// check for any resources with a deletion prevention message,
|
||||||
|
// if none found (delete is allowed), then check for any resources with a warning message
|
||||||
|
toRemove(neu) {
|
||||||
|
let message;
|
||||||
|
const preventDeletionMessages = neu.filter(item => item.preventDeletionMessage);
|
||||||
|
|
||||||
|
if (!!preventDeletionMessages.length) {
|
||||||
|
this.preventDelete = true;
|
||||||
|
message = preventDeletionMessages[0].preventDeletionMessage;
|
||||||
|
} else {
|
||||||
|
const warnDeletionMessages = neu.filter(item => item.warnDeletionMessage);
|
||||||
|
|
||||||
|
if (!!warnDeletionMessages.length) {
|
||||||
|
message = warnDeletionMessages[0].warnDeletionMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof message === 'function' ) {
|
||||||
|
this.warning = message(this.toRemove);
|
||||||
|
} else if (!!message) {
|
||||||
|
this.warning = message;
|
||||||
|
} else {
|
||||||
|
this.warning = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -219,7 +232,7 @@ export default {
|
||||||
height="auto"
|
height="auto"
|
||||||
styles="background-color: var(--nav-bg); border-radius: var(--border-radius); max-height: 100vh;"
|
styles="background-color: var(--nav-bg); border-radius: var(--border-radius); max-height: 100vh;"
|
||||||
>
|
>
|
||||||
<Card>
|
<InfoBox>
|
||||||
<h4 slot="title" class="text-default-text">
|
<h4 slot="title" class="text-default-text">
|
||||||
Are you sure?
|
Are you sure?
|
||||||
</h4>
|
</h4>
|
||||||
|
|
@ -231,10 +244,13 @@ export default {
|
||||||
<span v-if="i===names.length-1" :key="resource+2">{{ plusMore }}</span><span v-else :key="resource+1">{{ i === toRemove.length-2 ? ', and ' : ', ' }}</span>
|
<span v-if="i===names.length-1" :key="resource+2">{{ plusMore }}</span><span v-else :key="resource+1">{{ i === toRemove.length-2 ? ', and ' : ', ' }}</span>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
<span class="text-warning">
|
||||||
|
{{ warning }}
|
||||||
|
</span>
|
||||||
<span v-if="needsConfirm" :key="resource">Re-enter its name below to confirm:</span>
|
<span v-if="needsConfirm" :key="resource">Re-enter its name below to confirm:</span>
|
||||||
</div>
|
</div>
|
||||||
<input v-if="needsConfirm" id="confirm" v-model="confirmName" type="text" />
|
<input v-if="needsConfirm" id="confirm" v-model="confirmName" type="text" />
|
||||||
<span class="text-warning">{{ preventDeletionMessage }}</span>
|
|
||||||
<span class="text-error">{{ error }}</span>
|
<span class="text-error">{{ error }}</span>
|
||||||
<span v-if="!needsConfirm" class="text-info mt-20">{{ protip }}</span>
|
<span v-if="!needsConfirm" class="text-info mt-20">{{ protip }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -242,11 +258,11 @@ export default {
|
||||||
<button class="btn role-secondary" @click="close">
|
<button class="btn role-secondary" @click="close">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button class="btn bg-error" :disabled="isDeleteDisabled" @click="remove">
|
<button class="btn bg-error" :disabled="preventDelete" @click="remove">
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</InfoBox>
|
||||||
</modal>
|
</modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,30 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import Favorite from '@/components/nav/Favorite';
|
import Favorite from '@/components/nav/Favorite';
|
||||||
import ButtonDropdown from '@/components/ButtonDropdown';
|
|
||||||
import TypeDescription from '@/components/TypeDescription';
|
import TypeDescription from '@/components/TypeDescription';
|
||||||
|
import { get } from '@/utils/object';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ButtonDropdown,
|
|
||||||
Favorite,
|
Favorite,
|
||||||
TypeDescription,
|
TypeDescription,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
resource: {
|
resource: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
typeDisplay: {
|
typeDisplay: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: '',
|
||||||
},
|
},
|
||||||
isCreatable: {
|
isCreatable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
isYamlCreatable: {
|
isYamlCreatable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
@ -33,19 +32,20 @@ export default {
|
||||||
},
|
},
|
||||||
createLocation: {
|
createLocation: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
yamlCreateLocation: {
|
yamlCreateLocation: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
get,
|
||||||
...mapGetters(['isExplorer']),
|
...mapGetters(['isExplorer']),
|
||||||
|
|
||||||
resourceName() {
|
resourceName() {
|
||||||
if ( this.schema ) {
|
if (this.schema) {
|
||||||
return this.$store.getters['type-map/labelFor'](this.schema);
|
return this.$store.getters['type-map/labelFor'](this.schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,64 +62,26 @@ export default {
|
||||||
{{ typeDisplay }} <Favorite v-if="isExplorer" :resource="resource" />
|
{{ typeDisplay }} <Favorite v-if="isExplorer" :resource="resource" />
|
||||||
</h1>
|
</h1>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<ButtonDropdown
|
<nuxt-link
|
||||||
v-if="isCreatable || isYamlCreatable"
|
v-if="isCreatable"
|
||||||
|
:to="createLocation"
|
||||||
|
class="btn role-primary"
|
||||||
>
|
>
|
||||||
<template #button-content="slotProps">
|
{{ t('resourceList.head.create') }}
|
||||||
<nuxt-link
|
</nuxt-link>
|
||||||
v-if="isCreatable"
|
<nuxt-link
|
||||||
:to="createLocation"
|
v-if="isYamlCreatable"
|
||||||
class="btn bg-transparent"
|
:to="yamlCreateLocation"
|
||||||
:class="slotProps.buttonSize"
|
class="btn role-primary"
|
||||||
>
|
>
|
||||||
{{ t("resourceList.head.create") }}
|
{{ t('resourceList.head.createFromYaml') }}
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<nuxt-link
|
|
||||||
v-else-if="!isCreatable && isYamlCreatable"
|
|
||||||
:to="yamlCreateLocation"
|
|
||||||
class="btn bg-transparent"
|
|
||||||
:class="slotProps.buttonSize"
|
|
||||||
>
|
|
||||||
{{ t("resourceList.head.createFromYaml") }}
|
|
||||||
</nuxt-link>
|
|
||||||
<a
|
|
||||||
v-else
|
|
||||||
href="#"
|
|
||||||
class="btn bg-transparent"
|
|
||||||
:class="slotProps.buttonSize"
|
|
||||||
disabled="true"
|
|
||||||
@click.prevent.self
|
|
||||||
>
|
|
||||||
{{ t("resourceList.head.create") }}
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
<template
|
|
||||||
v-if="isCreatable && isYamlCreatable"
|
|
||||||
slot="popover-content"
|
|
||||||
>
|
|
||||||
<ul class="list-unstyled menu" style="margin: -1px;">
|
|
||||||
<li class="hand">
|
|
||||||
<nuxt-link
|
|
||||||
v-if="isCreatable"
|
|
||||||
:to="createLocation"
|
|
||||||
>
|
|
||||||
{{ t("resourceList.head.createResource", { resourceName }) }}
|
|
||||||
</nuxt-link>
|
|
||||||
</li>
|
|
||||||
<li class="divider">
|
|
||||||
<div class="divider-inner"></div>
|
|
||||||
</li>
|
|
||||||
<li class="hand">
|
|
||||||
<nuxt-link
|
|
||||||
v-if="isYamlCreatable"
|
|
||||||
:to="yamlCreateLocation"
|
|
||||||
>
|
|
||||||
{{ t("resourceList.head.createFromYaml") }}
|
|
||||||
</nuxt-link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
</ButtonDropdown>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.actions {
|
||||||
|
grid-column: max-content;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,10 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
addLabel: {
|
addLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'Add',
|
default() {
|
||||||
|
return this.$store.getters['i18n/t']('generic.add');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
addIcon: {
|
addIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -85,8 +87,10 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
removeLabel: {
|
removeLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default() {
|
||||||
|
return this.$store.getters['i18n/t']('generic.remove');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
removeIcon: {
|
removeIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -295,7 +299,6 @@ export default {
|
||||||
<div v-if="showRemove" class="remove">
|
<div v-if="showRemove" class="remove">
|
||||||
<slot name="remove-button" :remove="() => remove(idx)">
|
<slot name="remove-button" :remove="() => remove(idx)">
|
||||||
<button type="button" class="btn role-link" @click="remove(idx)">
|
<button type="button" class="btn role-link" @click="remove(idx)">
|
||||||
Remove
|
|
||||||
{{ removeLabel }}
|
{{ removeLabel }}
|
||||||
</button>
|
</button>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export default {
|
||||||
ShellInput,
|
ShellInput,
|
||||||
LabeledSelect,
|
LabeledSelect,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
EnvVars
|
EnvVars,
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -21,30 +21,40 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
configMaps: {
|
configMaps: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
|
|
||||||
},
|
},
|
||||||
secrets: {
|
secrets: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
// container spec
|
// container spec
|
||||||
value: {
|
value: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {
|
default: () => {
|
||||||
return {};
|
return {};
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
const {
|
const {
|
||||||
command, args, workingDir, stdin = false, stdinOnce = false, tty = false
|
command,
|
||||||
|
args,
|
||||||
|
workingDir,
|
||||||
|
stdin = false,
|
||||||
|
stdinOnce = false,
|
||||||
|
tty = false,
|
||||||
} = this.value;
|
} = this.value;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
command, args, workingDir, stdin, stdinOnce, tty
|
args,
|
||||||
|
command,
|
||||||
|
commandOptions: ['No', 'Once', 'Yes'],
|
||||||
|
stdin,
|
||||||
|
stdinOnce,
|
||||||
|
tty,
|
||||||
|
workingDir,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -78,8 +88,8 @@ export default {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.update();
|
this.update();
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -93,10 +103,10 @@ export default {
|
||||||
args: this.args,
|
args: this.args,
|
||||||
workingDir: this.workingDir,
|
workingDir: this.workingDir,
|
||||||
tty: this.tty,
|
tty: this.tty,
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$emit('input', out );
|
this.$emit('input', out);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -136,18 +146,33 @@ export default {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<div :style="{'align-items':'center'}" class="row">
|
<div :style="{ 'align-items': 'center' }" class="row">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledSelect v-model="stdinSelect" :label="t('workload.container.command.stdin')" :options="['No', 'Once', 'Yes']" :mode="mode" />
|
<LabeledSelect
|
||||||
|
v-model="stdinSelect"
|
||||||
|
:label="t('workload.container.command.stdin')"
|
||||||
|
:options="commandOptions"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="stdin" class="col span-6">
|
<div v-if="stdin" class="col span-6">
|
||||||
<Checkbox v-model="tty" :mode="mode" label="TTY" @input="update" />
|
<Checkbox
|
||||||
|
v-model="tty"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('workload.container.command.stdin')"
|
||||||
|
@input="update"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<h3>{{ t('workload.container.titles.env') }}</h3>
|
<h3>{{ t('workload.container.titles.env') }}</h3>
|
||||||
<EnvVars :mode="mode" :config-maps="configMaps" :secrets="secrets" :value="value" />
|
<EnvVars
|
||||||
|
:mode="mode"
|
||||||
|
:config-maps="configMaps"
|
||||||
|
:secrets="secrets"
|
||||||
|
:value="value"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -112,8 +112,16 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='scss'>
|
<style lang='scss' scoped>
|
||||||
|
.value-from ::v-deep {
|
||||||
|
.v-select {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
INPUT:not(.vs__search) {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
.value-from, .value-from-headers {
|
.value-from, .value-from-headers {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 20% 20% 20% 5% 20% auto;
|
grid-template-columns: 20% 20% 20% 5% 20% auto;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { mapState } from 'vuex';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { findBy } from '@/utils/array';
|
import { findBy } from '@/utils/array';
|
||||||
import { EXTENDED_SCOPES } from '@/store/github';
|
import { EXTENDED_SCOPES } from '@/store/github';
|
||||||
|
import LabeledSelect from '@/components/form/LabeledSelect';
|
||||||
|
|
||||||
export const FILE_PATTERNS = {
|
export const FILE_PATTERNS = {
|
||||||
dockerfile: /^Dockerfile(\..*)?$/i,
|
dockerfile: /^Dockerfile(\..*)?$/i,
|
||||||
|
|
@ -11,7 +12,8 @@ export const FILE_PATTERNS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
components: { LabeledSelect },
|
||||||
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -280,7 +282,7 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-4">
|
<div class="col span-4">
|
||||||
<v-select
|
<LabeledSelect
|
||||||
:placeholder="repoPlaceholder"
|
:placeholder="repoPlaceholder"
|
||||||
:disabled="loadingRecentRepos"
|
:disabled="loadingRecentRepos"
|
||||||
:options="repos"
|
:options="repos"
|
||||||
|
|
@ -310,10 +312,10 @@ export default {
|
||||||
{{ option.full_name }}
|
{{ option.full_name }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</v-select>
|
</LabeledSelect>
|
||||||
</div>
|
</div>
|
||||||
<div class="col span-4">
|
<div class="col span-4">
|
||||||
<v-select
|
<LabeledSelect
|
||||||
:disabled="!selectedRepo || loadingBranches"
|
:disabled="!selectedRepo || loadingBranches"
|
||||||
:placeholder="branchPlaceholder"
|
:placeholder="branchPlaceholder"
|
||||||
:options="branches"
|
:options="branches"
|
||||||
|
|
@ -322,10 +324,10 @@ export default {
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
@input="selectBranch"
|
@input="selectBranch"
|
||||||
>
|
>
|
||||||
</v-select>
|
</LabeledSelect>
|
||||||
</div>
|
</div>
|
||||||
<div class="col span-4">
|
<div class="col span-4">
|
||||||
<v-select
|
<LabeledSelect
|
||||||
:disabled="!selectedBranch"
|
:disabled="!selectedBranch"
|
||||||
:placeholder="filePlaceholder"
|
:placeholder="filePlaceholder"
|
||||||
:options="files"
|
:options="files"
|
||||||
|
|
@ -334,7 +336,7 @@ export default {
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
@input="selectFile"
|
@input="selectFile"
|
||||||
>
|
>
|
||||||
</v-select>
|
</LabeledSelect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,17 @@
|
||||||
import labeledFormElement from '@/mixins/labeled-form-element';
|
import labeledFormElement from '@/mixins/labeled-form-element';
|
||||||
import LabeledInput from '@/components/form/LabeledInput';
|
import LabeledInput from '@/components/form/LabeledInput';
|
||||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
import LabeledSelect from '@/components/form/LabeledSelect';
|
||||||
|
import Select from '@/components/form/Select';
|
||||||
import UnitInput from '@/components/form/UnitInput';
|
import UnitInput from '@/components/form/UnitInput';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
LabeledInput, LabeledSelect, UnitInput
|
LabeledInput,
|
||||||
|
LabeledSelect,
|
||||||
|
UnitInput,
|
||||||
|
Select,
|
||||||
},
|
},
|
||||||
mixins: [labeledFormElement],
|
mixins: [labeledFormElement],
|
||||||
props: {
|
props: {
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
|
@ -26,22 +30,22 @@ export default {
|
||||||
|
|
||||||
selectLabel: {
|
selectLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
selectValue: {
|
selectValue: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
optionLabel: {
|
optionLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'label'
|
default: 'label',
|
||||||
},
|
},
|
||||||
|
|
||||||
options: {
|
options: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
selectBeforeText: {
|
selectBeforeText: {
|
||||||
|
|
@ -51,34 +55,37 @@ export default {
|
||||||
|
|
||||||
textLabel: {
|
textLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
textRequired: {
|
textRequired: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
textValue: {
|
textValue: {
|
||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
default: ''
|
default: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return { selected: this.selectValue || this.options[0], string: this.textValue };
|
return {
|
||||||
|
selected: this.selectValue || this.options[0],
|
||||||
|
string: this.textValue,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
focus() {
|
focus() {
|
||||||
const comp = this.$refs.text;
|
const comp = this.$refs.text;
|
||||||
|
|
||||||
if ( comp ) {
|
if (comp) {
|
||||||
comp.focus();
|
comp.focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -86,29 +93,57 @@ export default {
|
||||||
change() {
|
change() {
|
||||||
this.$emit('input', { selected: this.selected, text: this.string });
|
this.$emit('input', { selected: this.selected, text: this.string });
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="isView && !selectBeforeText">
|
<div v-if="isView && !selectBeforeText">
|
||||||
<UnitInput mode="view" :value="string" :label="textLabel" :suffix="selected" />
|
<UnitInput
|
||||||
|
mode="view"
|
||||||
|
:value="string"
|
||||||
|
:label="textLabel"
|
||||||
|
:suffix="selected"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else :class="{'select-after':!selectBeforeText}" class="input-container row" @input="change">
|
<div
|
||||||
|
v-else
|
||||||
|
:class="{ 'select-after': !selectBeforeText }"
|
||||||
|
class="input-container row"
|
||||||
|
@input="change"
|
||||||
|
>
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
|
v-if="selectLabel"
|
||||||
v-model="selected"
|
v-model="selected"
|
||||||
:label="selectLabel"
|
:label="selectLabel"
|
||||||
:class="{'in-input': !isView}"
|
:class="{ 'in-input': !isView }"
|
||||||
:options="options"
|
:options="options"
|
||||||
:searchable="searchable"
|
:searchable="false"
|
||||||
:disbaled="isView"
|
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
:disabled="disabled"
|
:disabled="disabled || isView"
|
||||||
:taggable="taggable"
|
:taggable="taggable"
|
||||||
:create-option="name => ({ label: name, value: name })"
|
:create-option="(name) => ({ label: name, value: name })"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:option-label="optionLabel"
|
:option-label="optionLabel"
|
||||||
|
:placement="$attrs.placement ? $attrs.placement : null"
|
||||||
|
:v-bind="$attrs"
|
||||||
|
@input="change"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
v-else
|
||||||
|
v-model="selected"
|
||||||
|
:options="options"
|
||||||
|
:searchable="searchable"
|
||||||
|
:disabled="disabled || isView"
|
||||||
|
:clearable="false"
|
||||||
|
:class="{ 'in-input': !isView }"
|
||||||
|
:taggable="taggable"
|
||||||
|
:create-option="(name) => ({ label: name, value: name })"
|
||||||
|
:multiple="false"
|
||||||
|
:mode="mode"
|
||||||
|
:option-label="optionLabel"
|
||||||
|
:placement="$attrs.placement ? $attrs.placement : null"
|
||||||
:v-bind="$attrs"
|
:v-bind="$attrs"
|
||||||
@input="change"
|
@input="change"
|
||||||
/>
|
/>
|
||||||
|
|
@ -143,112 +178,82 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='scss'>
|
<style lang='scss' scoped>
|
||||||
.input-container {
|
.input-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 52px;
|
height: 52px;
|
||||||
|
|
||||||
&.select-after {
|
&.select-after {
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
|
|
||||||
& .input-string {
|
|
||||||
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
|
||||||
border-right: 0;
|
|
||||||
border-left: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
& .in-input {
|
|
||||||
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
|
||||||
border-left: 0;
|
|
||||||
border-right: 1px solid var(--border);
|
|
||||||
|
|
||||||
&.labeled-select {
|
|
||||||
width: 20%;
|
|
||||||
|
|
||||||
.selected{
|
|
||||||
color: var(--input-text);
|
|
||||||
text-align: center;
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& .input-string {
|
& .input-string {
|
||||||
padding-right: 8px;
|
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
||||||
height: 100%;
|
border-right: 0;
|
||||||
width:60%;
|
border-left: 1px solid var(--border);
|
||||||
flex-grow: 1;
|
|
||||||
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
|
||||||
border-left: 0;
|
|
||||||
margin-left: -1px;
|
|
||||||
display: initial;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.in-input {
|
& .in-input {
|
||||||
margin-right: 0;
|
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
||||||
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
border-left: 0;
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
|
||||||
&.labeled-select {
|
&.labeled-select {
|
||||||
display: block;
|
.selected {
|
||||||
|
color: var(--input-text);
|
||||||
|
text-align: center;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .input-string {
|
||||||
|
padding-right: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 60%;
|
||||||
|
flex-grow: 1;
|
||||||
|
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
||||||
|
border-left: 0;
|
||||||
|
margin-left: -1px;
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .in-input {
|
||||||
|
margin-right: 0;
|
||||||
|
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
||||||
|
|
||||||
|
&.labeled-select.focused ::v-deep,
|
||||||
|
&.unlabeled-select.focused ::v-deep {
|
||||||
|
outline: none;
|
||||||
|
border: var(--outline-width) solid var(--outline);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.labeled-select ::v-deep,
|
||||||
|
&.unlabeled-select ::v-deep {
|
||||||
|
box-shadow: none;
|
||||||
|
width: 20%;
|
||||||
|
background-color: var(--accent-btn);
|
||||||
|
z-index: 1;
|
||||||
|
border: solid 1px var(--primary);
|
||||||
|
|
||||||
|
.vs__selected {
|
||||||
|
color: var(--input-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs__dropdown-menu {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
width: 40%;
|
.vs__dropdown-option {
|
||||||
|
padding: 3px 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs__dropdown-toggle {
|
||||||
|
color: var(--primary) !important;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: var(--accent-btn);
|
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
||||||
border: solid 1px var(--primary);
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
.vs__selected {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--input-text)
|
|
||||||
}
|
|
||||||
|
|
||||||
.vs__dropdown-menu {
|
|
||||||
width: calc(100% + 2px);
|
|
||||||
left: -1px;
|
|
||||||
box-shadow: none;
|
|
||||||
border: 1px solid var(--primary);
|
|
||||||
.vs__dropdown-option {
|
|
||||||
padding: 3px 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vs__dropdown-toggle {
|
|
||||||
color: var(--primary) !important;
|
|
||||||
height: 100%;
|
|
||||||
padding: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
|
||||||
& * {
|
|
||||||
padding: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vs__selected-options {
|
|
||||||
display: -webkit-box;
|
|
||||||
& .labeled-input {
|
|
||||||
top:10px;
|
|
||||||
& LABEL, .selected {
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vs__actions {
|
|
||||||
padding: 2px;;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vs__open-indicator {
|
|
||||||
fill: var(--primary);
|
|
||||||
transform: scale(0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.vs--open .vs__open-indicator {
|
|
||||||
transform: rotate(180deg) scale(0.75);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import ClickExpand from '@/components/formatter/ClickExpand';
|
||||||
import { get } from '@/utils/object';
|
import { get } from '@/utils/object';
|
||||||
import CodeMirror from '@/components/CodeMirror';
|
import CodeMirror from '@/components/CodeMirror';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import ButtonDropdown from '@/components/ButtonDropdown';
|
|
||||||
import FileSelector from '@/components/form/FileSelector';
|
import FileSelector from '@/components/form/FileSelector';
|
||||||
import { HIDE_SENSITIVE } from '@/store/prefs';
|
import { HIDE_SENSITIVE } from '@/store/prefs';
|
||||||
|
|
||||||
|
|
@ -22,7 +21,6 @@ export default {
|
||||||
TextAreaAutoGrow,
|
TextAreaAutoGrow,
|
||||||
ClickExpand,
|
ClickExpand,
|
||||||
CodeMirror,
|
CodeMirror,
|
||||||
ButtonDropdown,
|
|
||||||
FileSelector
|
FileSelector
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -74,8 +72,10 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
keyPlaceholder: {
|
keyPlaceholder: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'e.g. foo'
|
default() {
|
||||||
|
return this.$store.getters['i18n/t']('keyValue.valuePlaceholder');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
separatorLabel: {
|
separatorLabel: {
|
||||||
|
|
@ -95,8 +95,10 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
valuePlaceholder: {
|
valuePlaceholder: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'e.g. bar'
|
default() {
|
||||||
|
return this.$store.getters['i18n/t']('keyValue.valuePlaceholder');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
valueCanBeEmpty: {
|
valueCanBeEmpty: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
@ -524,21 +526,10 @@ export default {
|
||||||
|
|
||||||
<div v-if="!titleAdd && (showAdd || showRead)" class="footer">
|
<div v-if="!titleAdd && (showAdd || showRead)" class="footer">
|
||||||
<slot name="add" :add="add">
|
<slot name="add" :add="add">
|
||||||
<ButtonDropdown size="sm">
|
<button v-if="showAdd" type="button" class="btn btn-sm role-secondary add" @click="add()">
|
||||||
<template #button-content>
|
{{ addLabel }}
|
||||||
<button v-if="showAdd" type="button" class="btn btn-sm add" @click="add()">
|
</button>
|
||||||
{{ addLabel }}
|
<FileSelector v-if="showRead" class="btn-sm role-secondary" :label="t('generic.readFromFile')" :include-file-name="true" @selected="onFileSelected" />
|
||||||
</button>
|
|
||||||
<FileSelector v-else class="btn-sm" :label="t('generic.readFromFile')" :include-file-name="true" @selected="onFileSelected" />
|
|
||||||
</template>
|
|
||||||
<template v-if="showRead && showAdd" #popover-content>
|
|
||||||
<ul class="list-unstyled">
|
|
||||||
<li>
|
|
||||||
<FileSelector class="btn-sm role-link" :label="readLabel" :include-file-name="true" @selected="onFileSelected" />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
</ButtonDropdown>
|
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -566,7 +557,7 @@ export default {
|
||||||
-ms-word-break: break-all;
|
-ms-word-break: break-all;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
display:flex;
|
display:flex;
|
||||||
align-items:start;
|
align-items:flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.extra-column {
|
&.extra-column {
|
||||||
|
|
@ -581,7 +572,7 @@ export default {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 10px 0px 10px 0px;
|
margin: 10px 0px 10px 0px;
|
||||||
&.key {
|
&.key {
|
||||||
align-self: start;
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-monospace:not(.conceal) {
|
.text-monospace:not(.conceal) {
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ import LabeledFormElement from '@/mixins/labeled-form-element';
|
||||||
import { findBy } from '@/utils/array';
|
import { findBy } from '@/utils/array';
|
||||||
import { get } from '@/utils/object';
|
import { get } from '@/utils/object';
|
||||||
import LabeledTooltip from '@/components/form/LabeledTooltip';
|
import LabeledTooltip from '@/components/form/LabeledTooltip';
|
||||||
|
import VueSelectOverrides from '@/mixins/vue-select-overrides';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { LabeledTooltip },
|
components: { LabeledTooltip },
|
||||||
mixins: [LabeledFormElement],
|
mixins: [LabeledFormElement, VueSelectOverrides],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
|
|
@ -24,15 +25,15 @@ export default {
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
optionKey: {
|
optionKey: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
optionLabel: {
|
optionLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'label'
|
default: 'label',
|
||||||
},
|
},
|
||||||
placement: {
|
placement: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -40,30 +41,34 @@ export default {
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
hoverTooltip: {
|
hoverTooltip: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
localizedLabel: {
|
localizedLabel: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
|
},
|
||||||
|
searchable: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
reduce: {
|
reduce: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: (e) => {
|
default: (e) => {
|
||||||
if ( e && typeof e === 'object' && e.value !== undefined ) {
|
if (e && typeof e === 'object' && e.value !== undefined) {
|
||||||
return e.value;
|
return e.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -74,15 +79,15 @@ export default {
|
||||||
currentLabel() {
|
currentLabel() {
|
||||||
let entry;
|
let entry;
|
||||||
|
|
||||||
if ( this.grouped ) {
|
if (this.grouped) {
|
||||||
for ( let i = 0 ; i < this.options.length && !entry ; i++ ) {
|
for (let i = 0; i < this.options.length && !entry; i++) {
|
||||||
entry = findBy(this.options[i].items || [], 'value', this.value);
|
entry = findBy(this.options[i].items || [], 'value', this.value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
entry = findBy(this.options || [], 'value', this.value);
|
entry = findBy(this.options || [], 'value', this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( entry ) {
|
if (entry) {
|
||||||
return entry.label;
|
return entry.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,6 +96,11 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
focusSearch() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.input.searchEl.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
onFocus() {
|
onFocus() {
|
||||||
this.selectedVisibility = 'hidden';
|
this.selectedVisibility = 'hidden';
|
||||||
this.onFocusLabeled();
|
this.onFocusLabeled();
|
||||||
|
|
@ -102,6 +112,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
getOptionLabel(option) {
|
getOptionLabel(option) {
|
||||||
|
if (!option) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.$attrs['get-option-label']) {
|
if (this.$attrs['get-option-label']) {
|
||||||
return this.$attrs['get-option-label'](option);
|
return this.$attrs['get-option-label'](option);
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +151,7 @@ export default {
|
||||||
modifiers: [
|
modifiers: [
|
||||||
{
|
{
|
||||||
name: 'offset',
|
name: 'offset',
|
||||||
options: { offset: [0, -1] }
|
options: { offset: [0, 2] },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'toggleClass',
|
name: 'toggleClass',
|
||||||
|
|
@ -147,7 +160,8 @@ export default {
|
||||||
fn({ state }) {
|
fn({ state }) {
|
||||||
component.$el.setAttribute('x-placement', state.placement);
|
component.$el.setAttribute('x-placement', state.placement);
|
||||||
},
|
},
|
||||||
}]
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -163,26 +177,42 @@ export default {
|
||||||
input.open = true;
|
input.open = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
get
|
get,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="labeled-select labeled-input" :class="{disabled: disabled && !isView, focused, [mode]: true, [status]: status, taggable: $attrs.taggable, hoverable: hoverTooltip }">
|
<div
|
||||||
<div :class="{'labeled-container': true, raised, empty, [mode]: true}" :style="{border:'none'}">
|
class="labeled-select"
|
||||||
<label v-if="label">
|
:class="{
|
||||||
|
disabled: disabled && !isView,
|
||||||
|
focused,
|
||||||
|
[mode]: true,
|
||||||
|
[status]: status,
|
||||||
|
taggable: $attrs.taggable,
|
||||||
|
hoverable: hoverTooltip,
|
||||||
|
}"
|
||||||
|
@click="focusSearch"
|
||||||
|
@focus="focusSearch"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="{ 'labeled-container': true, raised, empty, [mode]: true }"
|
||||||
|
:style="{ border: 'none' }"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
<span v-if="required && !value" class="required">*</span>
|
<span v-if="required && !value" class="required">*</span>
|
||||||
</label>
|
</label>
|
||||||
<label v-if="label" class="corner">
|
<div
|
||||||
<slot name="corner" />
|
v-if="isView"
|
||||||
</label>
|
:class="{ 'no-label': !(label || '').length }"
|
||||||
<div v-if="isView" :class="{'no-label':!(label||'').length}" class="selected">
|
class="selected"
|
||||||
<span v-if="!currentLabel" class="text-muted">—</span>{{ currentLabel }}
|
>
|
||||||
</div>
|
<span v-if="!currentLabel" class="text-muted">
|
||||||
<div v-else-if="!$attrs.multiple" :class="{'no-label':!(label||'').length}" class="selected" :style="{visibility:selectedVisibility}">
|
{{ currentLabel }}
|
||||||
{{ currentLabel }}
|
</span>
|
||||||
|
<span v-else class="text-muted">—</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<v-select
|
<v-select
|
||||||
|
|
@ -192,123 +222,105 @@ export default {
|
||||||
class="inline"
|
class="inline"
|
||||||
:append-to-body="!!placement"
|
:append-to-body="!!placement"
|
||||||
:calculate-position="placement ? withPopper : undefined"
|
:calculate-position="placement ? withPopper : undefined"
|
||||||
:class="{'no-label':!(label||'').length}"
|
:class="{ 'no-label': !(label || '').length, }"
|
||||||
:disabled="isView || disabled"
|
:disabled="isView || disabled"
|
||||||
:get-option-key="opt=>optionKey ? get(opt, optionKey) : getOptionLabel(opt)"
|
:get-option-key="(opt) => (optionKey ? get(opt, optionKey) : getOptionLabel(opt))"
|
||||||
:get-option-label="opt=>getOptionLabel(opt)"
|
:get-option-label="(opt) => getOptionLabel(opt)"
|
||||||
:label="optionLabel"
|
:label="optionLabel"
|
||||||
:options="options"
|
:options="options"
|
||||||
|
:map-keydown="mappedKeys"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:reduce="x => reduce(x)"
|
:reduce="(x) => reduce(x)"
|
||||||
|
:searchable="isSearchable"
|
||||||
:value="value != null ? value : ''"
|
:value="value != null ? value : ''"
|
||||||
@input="e=>$emit('input', e)"
|
@input="(e) => $emit('input', e)"
|
||||||
@search:blur="onBlur"
|
@search:blur="onBlur"
|
||||||
@search:focus="onFocus"
|
@search:focus="onFocus"
|
||||||
>
|
>
|
||||||
<template v-if="!$attrs.multiple" v-slot:selected-option-container>
|
|
||||||
<span style="display: none"></span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Pass down templates provided by the caller -->
|
<!-- Pass down templates provided by the caller -->
|
||||||
<template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope">
|
<template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope">
|
||||||
<slot :name="slot" v-bind="scope" />
|
<slot :name="slot" v-bind="scope" />
|
||||||
</template>
|
</template>
|
||||||
</v-select>
|
</v-select>
|
||||||
<LabeledTooltip v-if="tooltip && !focused" :hover="hoverTooltip" :value="tooltip" :status="status" />
|
<LabeledTooltip
|
||||||
|
v-if="tooltip && !focused"
|
||||||
|
:hover="hoverTooltip"
|
||||||
|
:value="tooltip"
|
||||||
|
:status="status"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='scss'>
|
<style lang='scss' scoped>
|
||||||
.labeled-select {
|
.labeled-select {
|
||||||
&.hoverable .v-select *{
|
&.hoverable ::v-deep {
|
||||||
z-index: z-index('overContent')
|
.v-select * {
|
||||||
}
|
z-index: z-index('overContent');
|
||||||
.labeled-container .selected {
|
}
|
||||||
background-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.view.labeled-input .labeled-container {
|
.labeled-container {
|
||||||
padding: 0;
|
padding: 8px 0 0 8px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.view {
|
||||||
.labeled-container, .vs__dropdown-toggle, input, label {
|
&.labeled-input {
|
||||||
|
.labeled-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::v-deep .vs__selected-options {
|
||||||
|
margin-top: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .v-select:not(.vs--single) {
|
||||||
|
.vs__selected-options {
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .vs__actions {
|
||||||
|
&:after {
|
||||||
|
line-height: 1.85rem;
|
||||||
|
position: relative;
|
||||||
|
right: 3px;
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep &.disabled {
|
||||||
|
.labeled-container,
|
||||||
|
.vs__dropdown-toggle,
|
||||||
|
input,
|
||||||
|
label {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.no-label ::v-deep {
|
||||||
padding-top: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected, .vs__selected-options {
|
|
||||||
.vs__search, .vs__search:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 2px 0 0 0;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-label {
|
|
||||||
&.v-select:not(.vs--single) {
|
&.v-select:not(.vs--single) {
|
||||||
min-height: 33px;
|
min-height: 33px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
padding-top:8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 9px;
|
padding-bottom: 9px;
|
||||||
position: relative;
|
position: relative;
|
||||||
max-height:2.3em;
|
max-height: 2.3em;
|
||||||
overflow:hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs__selected-options {
|
.vs__selected-options {
|
||||||
padding:8px 0 7px 0;
|
padding: 8px 0 7px 0;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.focused .vs__dropdown-menu {
|
|
||||||
outline: none;
|
|
||||||
border: var(--outline-width) solid var(--outline);
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.taggable {
|
|
||||||
.vs__selected-options {
|
|
||||||
margin: 14px 0px 2px 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.v-select.inline {
|
|
||||||
position: initial;
|
|
||||||
|
|
||||||
&.vs--single {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
.vs__search {
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 18px 0 0 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&, .vs__dropdown-toggle, .vs__dropdown-toggle > * {
|
|
||||||
background-color: transparent;
|
|
||||||
border:transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vs__dropdown-menu {
|
|
||||||
top: calc(100% - 2px);
|
|
||||||
left: -3px;
|
|
||||||
width: calc(100% + 6px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected{
|
|
||||||
position:relative;
|
|
||||||
top: 1.4em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import { NODE, POD, NAMESPACE } from '@/config/types';
|
import { NODE, POD, NAMESPACE } from '@/config/types';
|
||||||
import LabeledInput from '@/components/form/LabeledInput';
|
import LabeledInput from '@/components/form/LabeledInput';
|
||||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
import LabeledSelect from '@/components/form/LabeledSelect';
|
||||||
|
import Select from '@/components/form/Select';
|
||||||
import { sortBy } from '@/utils/sort';
|
import { sortBy } from '@/utils/sort';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { removeObject } from '@/utils/array';
|
import { removeObject } from '@/utils/array';
|
||||||
|
|
@ -10,6 +11,7 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
LabeledInput,
|
LabeledInput,
|
||||||
LabeledSelect,
|
LabeledSelect,
|
||||||
|
Select,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
// array of match expressions
|
// array of match expressions
|
||||||
|
|
@ -232,7 +234,7 @@ export default {
|
||||||
<div v-if="isView">
|
<div v-if="isView">
|
||||||
{{ row.operator }}
|
{{ row.operator }}
|
||||||
</div>
|
</div>
|
||||||
<LabeledSelect
|
<Select
|
||||||
v-else
|
v-else
|
||||||
v-model="row.operator"
|
v-model="row.operator"
|
||||||
class="operator single"
|
class="operator single"
|
||||||
|
|
@ -253,7 +255,7 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
<input v-else v-model="row.values" :mode="mode" :disabled="row.operator==='Exists' || row.operator==='DoesNotExist'" />
|
<input v-else v-model="row.values" :mode="mode" :disabled="row.operator==='Exists' || row.operator==='DoesNotExist'" />
|
||||||
</div>
|
</div>
|
||||||
<div class="text-right">
|
<div class="remove-container">
|
||||||
<button
|
<button
|
||||||
v-if="!isView"
|
v-if="!isView"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -273,7 +275,7 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='scss'>
|
<style lang='scss' scoped>
|
||||||
$separator: 20;
|
$separator: 20;
|
||||||
$remove: 75;
|
$remove: 75;
|
||||||
$spacing: 10px;
|
$spacing: 10px;
|
||||||
|
|
@ -297,6 +299,11 @@ export default {
|
||||||
font-size:2em;
|
font-size:2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.remove-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.selector-weight {
|
.selector-weight {
|
||||||
color: var(--input-label)
|
color: var(--input-label)
|
||||||
}
|
}
|
||||||
|
|
@ -305,7 +312,6 @@ export default {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
grid-gap: $column-gutter;
|
grid-gap: $column-gutter;
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&>label{
|
&>label{
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
|
@ -315,7 +321,7 @@ export default {
|
||||||
grid-template-columns: 1fr 1fr 1fr 100px;
|
grid-template-columns: 1fr 1fr 1fr 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
INPUT {
|
INPUT:not(.vs__search) {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export default {
|
||||||
},
|
},
|
||||||
extraColumns: {
|
extraColumns: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
|
||||||
nameLabel: {
|
nameLabel: {
|
||||||
|
|
@ -106,31 +106,31 @@ export default {
|
||||||
const metadata = v.metadata;
|
const metadata = v.metadata;
|
||||||
let namespace, name, description;
|
let namespace, name, description;
|
||||||
|
|
||||||
if (this.nameKey ) {
|
if (this.nameKey) {
|
||||||
name = get(v, this.nameKey);
|
name = get(v, this.nameKey);
|
||||||
} else {
|
} else {
|
||||||
name = metadata.name;
|
name = metadata.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( this.namespaced ) {
|
if (this.namespaced) {
|
||||||
if ( this.forceNamespace ) {
|
if (this.forceNamespace) {
|
||||||
namespace = this.forceNamespace;
|
namespace = this.forceNamespace;
|
||||||
this.updateNamespace(namespace);
|
this.updateNamespace(namespace);
|
||||||
} else if ( this.namespaceKey ) {
|
} else if (this.namespaceKey) {
|
||||||
namespace = get(v, this.namespaceKey);
|
namespace = get(v, this.namespaceKey);
|
||||||
} else {
|
} else {
|
||||||
namespace = metadata?.namespace;
|
namespace = metadata?.namespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !namespace ) {
|
if (!namespace) {
|
||||||
namespace = this.$store.getters['defaultNamespace'];
|
namespace = this.$store.getters['defaultNamespace'];
|
||||||
if ( metadata ) {
|
if (metadata) {
|
||||||
metadata.namespace = namespace;
|
metadata.namespace = namespace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( this.descriptionKey ) {
|
if (this.descriptionKey) {
|
||||||
description = get(v, this.descriptionKey);
|
description = get(v, this.descriptionKey);
|
||||||
} else {
|
} else {
|
||||||
description = metadata?.annotations?.[DESCRIPTION];
|
description = metadata?.annotations?.[DESCRIPTION];
|
||||||
|
|
@ -139,34 +139,39 @@ export default {
|
||||||
return {
|
return {
|
||||||
namespace,
|
namespace,
|
||||||
name,
|
name,
|
||||||
description
|
description,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
namespaceReallyDisabled() {
|
namespaceReallyDisabled() {
|
||||||
return !!this.forceNamespace || this.namespaceDisabled || this.mode === _EDIT; // namespace is never editable
|
return (
|
||||||
|
!!this.forceNamespace || this.namespaceDisabled || this.mode === _EDIT
|
||||||
|
); // namespace is never editable
|
||||||
},
|
},
|
||||||
|
|
||||||
nameReallyDisabled() {
|
nameReallyDisabled() {
|
||||||
return this.nameDisabled || ( this.mode === _EDIT && !this.nameEditable);
|
return this.nameDisabled || (this.mode === _EDIT && !this.nameEditable);
|
||||||
},
|
},
|
||||||
|
|
||||||
namespaces() {
|
namespaces() {
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
const choices = this.$store.getters[`${ inStore }/all`](this.namespaceType);
|
const choices = this.$store.getters[`${ inStore }/all`](this.namespaceType);
|
||||||
|
|
||||||
const out = sortBy(choices.map((obj) => {
|
const out = sortBy(
|
||||||
return {
|
choices.map((obj) => {
|
||||||
label: obj.nameDisplay,
|
return {
|
||||||
value: obj.id,
|
label: obj.nameDisplay,
|
||||||
};
|
value: obj.id,
|
||||||
}), 'label');
|
};
|
||||||
|
}),
|
||||||
|
'label'
|
||||||
|
);
|
||||||
|
|
||||||
if ( this.forceNamespace ) {
|
if (this.forceNamespace) {
|
||||||
out.unshift({
|
out.unshift({
|
||||||
label: this.forceNamespace,
|
label: this.forceNamespace,
|
||||||
value: this.forceNamespace
|
value: this.forceNamespace,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,7 +194,7 @@ export default {
|
||||||
name(val) {
|
name(val) {
|
||||||
val = val.toLowerCase();
|
val = val.toLowerCase();
|
||||||
|
|
||||||
if ( this.nameKey ) {
|
if (this.nameKey) {
|
||||||
set(this.value, this.nameKey, val);
|
set(this.value, this.nameKey, val);
|
||||||
} else {
|
} else {
|
||||||
this.$set(this.value.metadata, 'name', val);
|
this.$set(this.value.metadata, 'name', val);
|
||||||
|
|
@ -203,7 +208,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
description(val) {
|
description(val) {
|
||||||
if ( this.descriptionKey ) {
|
if (this.descriptionKey) {
|
||||||
set(this.value, this.descriptionKey, val);
|
set(this.value, this.descriptionKey, val);
|
||||||
} else {
|
} else {
|
||||||
this.value.setAnnotation(DESCRIPTION, val);
|
this.value.setAnnotation(DESCRIPTION, val);
|
||||||
|
|
@ -222,11 +227,11 @@ export default {
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
updateNamespace(val) {
|
updateNamespace(val) {
|
||||||
if ( this.forceNamespace ) {
|
if (this.forceNamespace) {
|
||||||
val = this.forceNamespace;
|
val = this.forceNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( this.namespaceKey ) {
|
if (this.namespaceKey) {
|
||||||
set(this.value, this.namespaceKey, val);
|
set(this.value, this.namespaceKey, val);
|
||||||
} else {
|
} else {
|
||||||
this.value.metadata.namespace = val;
|
this.value.metadata.namespace = val;
|
||||||
|
|
@ -236,19 +241,20 @@ export default {
|
||||||
changeNameAndNamespace(e) {
|
changeNameAndNamespace(e) {
|
||||||
this.name = (e.text || '').toLowerCase();
|
this.name = (e.text || '').toLowerCase();
|
||||||
this.namespace = e.selected;
|
this.namespace = e.selected;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div v-show="!nameNsHidden" :class="{col: true, [colSpan]: true}">
|
<div v-show="!nameNsHidden" :class="{ col: true, [colSpan]: true }">
|
||||||
<slot :namespaces="namespaces" name="namespace">
|
<slot :namespaces="namespaces" name="namespace">
|
||||||
<InputWithSelect
|
<InputWithSelect
|
||||||
v-if="namespaced"
|
v-if="namespaced"
|
||||||
ref="name"
|
ref="name"
|
||||||
|
class="namespace-select"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:disabled="namespaceReallyDisabled"
|
:disabled="namespaceReallyDisabled"
|
||||||
:text-label="t(nameLabel)"
|
:text-label="t(nameLabel)"
|
||||||
|
|
@ -277,7 +283,7 @@ export default {
|
||||||
/>
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
<div :class="{col: true, [colSpan]: true}">
|
<div :class="{ col: true, [colSpan]: true }">
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
key="description"
|
key="description"
|
||||||
v-model="description"
|
v-model="description"
|
||||||
|
|
@ -287,10 +293,24 @@ export default {
|
||||||
:min-height="30"
|
:min-height="30"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="slot in extraColumns" :key="slot" :class="{col: true, [colSpan]: true}">
|
<div
|
||||||
|
v-for="slot in extraColumns"
|
||||||
|
:key="slot"
|
||||||
|
:class="{ col: true, [colSpan]: true }"
|
||||||
|
>
|
||||||
<slot :name="slot">
|
<slot :name="slot">
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.row {
|
||||||
|
.namespace-select ::v-deep {
|
||||||
|
.labeled-select {
|
||||||
|
min-width: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -156,9 +156,9 @@ export default {
|
||||||
v-if="!isView || kind!=='none'"
|
v-if="!isView || kind!=='none'"
|
||||||
v-model="kind"
|
v-model="kind"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:label="t('generic.type')"
|
:label="t('probe.type.label')"
|
||||||
:options="kindOptions"
|
:options="kindOptions"
|
||||||
placeholder="Select a check type"
|
:placeholder="t('probe.type.placeholder')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-if="kind && kind!=='none'" class="spacer-small" />
|
<div v-if="kind && kind!=='none'" class="spacer-small" />
|
||||||
|
|
@ -170,8 +170,8 @@ export default {
|
||||||
min="1"
|
min="1"
|
||||||
max="65535"
|
max="65535"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:label="t('workload.container.healthCheck.httpGet.port')"
|
:label="t('probe.httpGet.port.label')"
|
||||||
placeholder="e.g. 80"
|
:placeholder="t('probe.httpGet.port.placeholder')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="spacer-small" />
|
<div class="spacer-small" />
|
||||||
|
|
@ -179,8 +179,8 @@ export default {
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
v-model="httpGet.path"
|
v-model="httpGet.path"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:label="t('workload.container.healthCheck.httpGet.path')"
|
:label="t('probe.httpGet.path.label')"
|
||||||
placeholder="e.g. /healthz"
|
:placeholder="t('probe.httpGet.path.placeholder')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -191,8 +191,8 @@ export default {
|
||||||
min="1"
|
min="1"
|
||||||
max="65535"
|
max="65535"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:label="t('workload.container.healthCheck.httpGet.port')"
|
:label="t('probe.httpGet.port.label')"
|
||||||
placeholder="e.g. 25"
|
:placeholder="t('probe.httpGet.port.placeholderDuex')"
|
||||||
/>
|
/>
|
||||||
<div class="spacer-small" />
|
<div class="spacer-small" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -201,8 +201,8 @@ export default {
|
||||||
<div class="col span-12">
|
<div class="col span-12">
|
||||||
<ShellInput
|
<ShellInput
|
||||||
v-model="exec.command"
|
v-model="exec.command"
|
||||||
:label="t('workload.container.healthCheck.command.command')"
|
:label="t('probe.httpGet.port.command.label')"
|
||||||
placeholder="e.g. cat /tmp/health"
|
:placeholder="t('probe.httpGet.port.command.placeholder')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer-small" />
|
<div class="spacer-small" />
|
||||||
|
|
@ -219,30 +219,30 @@ export default {
|
||||||
<UnitInput
|
<UnitInput
|
||||||
v-model="probe.periodSeconds"
|
v-model="probe.periodSeconds"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:label="t('workload.container.healthCheck.checkInterval')"
|
:label="t('probe.checkInterval.label')"
|
||||||
min="1"
|
min="1"
|
||||||
:suffix="t('suffix.sec')"
|
:suffix="t('suffix.sec')"
|
||||||
placeholder="Default: 10"
|
:placeholder="t('probe.checkInterval.placeholder')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col span-4">
|
<div class="col span-4">
|
||||||
<UnitInput
|
<UnitInput
|
||||||
v-model="probe.initialDelaySeconds"
|
v-model="probe.initialDelaySeconds"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:label="t('workload.container.healthCheck.initialDelay')"
|
|
||||||
:suffix="t('suffix.sec')"
|
:suffix="t('suffix.sec')"
|
||||||
|
:label="t('probe.initialDelay.label')"
|
||||||
min="0"
|
min="0"
|
||||||
placeholder="Default: 0"
|
:placeholder="t('probe.initialDelay.placeholder')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col span-4">
|
<div class="col span-4">
|
||||||
<UnitInput
|
<UnitInput
|
||||||
v-model="probe.timeoutSeconds"
|
v-model="probe.timeoutSeconds"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:label="t('workload.container.healthCheck.timeout')"
|
|
||||||
:suffix="t('suffix.sec')"
|
:suffix="t('suffix.sec')"
|
||||||
|
:label="t('probe.timeout.placeholder')"
|
||||||
min="0"
|
min="0"
|
||||||
placeholder="Default: 3"
|
:placeholder="t('probe.timeout.placeholder')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -256,8 +256,8 @@ export default {
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:label="t('workload.container.healthCheck.successThreshold')"
|
:label="t('probe.successThreshold.label')"
|
||||||
placeholder="Default: 1"
|
:placeholder="t('probe.successThreshold.placeholder')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
|
|
@ -266,8 +266,8 @@ export default {
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:label="t('workload.container.healthCheck.failureThreshold')"
|
:label="t('probe.failureThreshold.label')"
|
||||||
placeholder="Default: 3"
|
:placeholder="t('probe.failureThreshold.label')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -284,14 +284,14 @@ export default {
|
||||||
:pad-left="false"
|
:pad-left="false"
|
||||||
:as-map="false"
|
:as-map="false"
|
||||||
:read-allowed="false"
|
:read-allowed="false"
|
||||||
:title="t('workload.container.healthCheck.httpGet.headers')"
|
:title="t('probe.httpGet.headers.label')"
|
||||||
:key-label="t('generic.name')"
|
:key-label="t('generic.name')"
|
||||||
:value-label="t('generic.value')"
|
:value-label="t('generic.value')"
|
||||||
:add-label="t('generic.add')"
|
:add-label="t('generic.add')"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<h4>
|
<h4>
|
||||||
{{ t('workload.container.healthCheck.httpGet.headers') }}
|
{{ t('probe.httpGet.headers.label') }}
|
||||||
</h4>
|
</h4>
|
||||||
</template>
|
</template>
|
||||||
</KeyValue>
|
</KeyValue>
|
||||||
|
|
@ -308,5 +308,8 @@ export default {
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
::v-deep .labeled-select {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { _VIEW } from '@/config/query-params';
|
import { _VIEW } from '@/config/query-params';
|
||||||
import ArrayList from '@/components/form/ArrayList';
|
import ArrayList from '@/components/form/ArrayList';
|
||||||
import LabeledInput from '@/components/form/LabeledInput';
|
import LabeledInput from '@/components/form/LabeledInput';
|
||||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
import Select from '@/components/form/Select';
|
||||||
|
|
||||||
const OPERATOR_VALUES = {
|
const OPERATOR_VALUES = {
|
||||||
IS_SET: 'Exists',
|
IS_SET: 'Exists',
|
||||||
|
|
@ -15,7 +15,7 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
ArrayList,
|
ArrayList,
|
||||||
LabeledInput,
|
LabeledInput,
|
||||||
LabeledSelect
|
Select
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -132,7 +132,7 @@ export default {
|
||||||
<LabeledInput v-model="scope.row.value.key" :mode="mode" />
|
<LabeledInput v-model="scope.row.value.key" :mode="mode" />
|
||||||
</div>
|
</div>
|
||||||
<div class="operator">
|
<div class="operator">
|
||||||
<LabeledSelect
|
<Select
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:value="scope.row.value.operator"
|
:value="scope.row.value.operator"
|
||||||
:options="operatorOptions"
|
:options="operatorOptions"
|
||||||
|
|
@ -147,20 +147,23 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
.rule-selector {
|
.rule-selector {
|
||||||
.box {
|
&:not(.view) table {
|
||||||
& > *:not(:last-child) {
|
table-layout: initial;
|
||||||
padding-right: 10px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.key, .value {
|
::v-deep .box {
|
||||||
width: 35%;
|
display: grid;
|
||||||
flex: none;
|
grid-template-columns: 25% 25% 25% 15%;
|
||||||
}
|
column-gap: 1.75%;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.key,
|
||||||
|
.value,
|
||||||
.operator {
|
.operator {
|
||||||
flex: 1;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,88 @@
|
||||||
<script>
|
<script>
|
||||||
import { createPopper } from '@popperjs/core';
|
import { createPopper } from '@popperjs/core';
|
||||||
|
import { get } from '@/utils/object';
|
||||||
|
import LabeledFormElement from '@/mixins/labeled-form-element';
|
||||||
|
import VueSelectOverrides from '@/mixins/vue-select-overrides';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
mixins: [LabeledFormElement, VueSelectOverrides],
|
||||||
placement: {
|
props: {
|
||||||
|
disabled: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
default: 'edit',
|
||||||
type: String,
|
type: String,
|
||||||
default: 'bottom'
|
},
|
||||||
|
optionKey: {
|
||||||
|
default: null,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
optionLabel: {
|
||||||
|
default: 'label',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
default: null,
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
placement: {
|
||||||
|
default: null,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
popperOverride: {
|
||||||
|
type: Function,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
reduce: {
|
||||||
|
default: (e) => {
|
||||||
|
if (e && typeof e === 'object' && e.value !== undefined) {
|
||||||
|
return e.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return e;
|
||||||
|
},
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
searchable: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
default: null,
|
||||||
|
type: [String, Object, Number, Array, Boolean],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
getOptionLabel(option) {
|
||||||
|
if (this.$attrs['get-option-label']) {
|
||||||
|
return this.$attrs['get-option-label'](option);
|
||||||
|
}
|
||||||
|
if (get(option, this.optionLabel)) {
|
||||||
|
if (this.localizedLabel) {
|
||||||
|
return this.$store.getters['i18n/t'](get(option, this.optionLabel));
|
||||||
|
} else {
|
||||||
|
return get(option, this.optionLabel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
},
|
||||||
withPopper(dropdownList, component, { width }) {
|
withPopper(dropdownList, component, { width }) {
|
||||||
|
if (this.popperOverride) {
|
||||||
|
return this.popperOverride(dropdownList, component, { width });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We need to explicitly define the dropdown width since
|
* We need to explicitly define the dropdown width since
|
||||||
* it is usually inherited from the parent with CSS.
|
* it is usually inherited from the parent with CSS.
|
||||||
|
|
@ -28,12 +100,11 @@ export default {
|
||||||
* above.
|
* above.
|
||||||
*/
|
*/
|
||||||
const popper = createPopper(component.$refs.toggle, dropdownList, {
|
const popper = createPopper(component.$refs.toggle, dropdownList, {
|
||||||
|
|
||||||
placement: this.placement,
|
placement: this.placement,
|
||||||
modifiers: [
|
modifiers: [
|
||||||
{
|
{
|
||||||
name: 'offset',
|
name: 'offset',
|
||||||
options: { offset: [0, -1] }
|
options: { offset: [0, 2] },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'toggleClass',
|
name: 'toggleClass',
|
||||||
|
|
@ -43,7 +114,7 @@ export default {
|
||||||
component.$el.setAttribute('x-placement', state.placement);
|
component.$el.setAttribute('x-placement', state.placement);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,17 +123,56 @@ export default {
|
||||||
*/
|
*/
|
||||||
return () => popper.destroy();
|
return () => popper.destroy();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
focusSearch() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs['select-input'].searchEl.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-select
|
<div
|
||||||
v-bind="$attrs"
|
class="unlabeled-select"
|
||||||
append-to-body
|
:class="{
|
||||||
:calculate-position="placement ? withPopper : undefined"
|
disabled: disabled && !isView,
|
||||||
v-on="$listeners"
|
focused,
|
||||||
|
[mode]: true,
|
||||||
|
[status]: status,
|
||||||
|
taggable: $attrs.taggable,
|
||||||
|
}"
|
||||||
|
@click="focusSearch"
|
||||||
|
@focus="focusSearch"
|
||||||
>
|
>
|
||||||
<slot />
|
<v-select
|
||||||
</v-select>
|
v-if="!isView"
|
||||||
|
ref="select-input"
|
||||||
|
v-bind="$attrs"
|
||||||
|
class="inline"
|
||||||
|
:autoscroll="true"
|
||||||
|
:append-to-body="!!placement"
|
||||||
|
:calculate-position="placement ? withPopper : undefined"
|
||||||
|
:disabled="isView || disabled"
|
||||||
|
:get-option-key="(opt) => (optionKey ? get(opt, optionKey) : getOptionLabel(opt))"
|
||||||
|
:get-option-label="(opt) => getOptionLabel(opt)"
|
||||||
|
:label="optionLabel"
|
||||||
|
:options="options"
|
||||||
|
:map-keydown="mappedKeys"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:reduce="(x) => reduce(x)"
|
||||||
|
:searchable="isSearchable"
|
||||||
|
:value="value != null ? value : ''"
|
||||||
|
@input="(e) => $emit('input', e)"
|
||||||
|
@search:blur="onBlur"
|
||||||
|
@search:focus="onFocus"
|
||||||
|
>
|
||||||
|
<!-- Pass down templates provided by the caller -->
|
||||||
|
<template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope">
|
||||||
|
<slot :name="slot" v-bind="scope" />
|
||||||
|
</template>
|
||||||
|
</v-select>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ import debounce from 'lodash/debounce';
|
||||||
import { _EDIT, _VIEW } from '@/config/query-params';
|
import { _EDIT, _VIEW } from '@/config/query-params';
|
||||||
import { removeAt } from '@/utils/array';
|
import { removeAt } from '@/utils/array';
|
||||||
import { clone } from '@/utils/object';
|
import { clone } from '@/utils/object';
|
||||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
import Select from '@/components/form/Select';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { LabeledSelect },
|
components: { Select },
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
|
@ -173,7 +173,7 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showProtocol" class="port-protocol">
|
<div v-if="showProtocol" class="port-protocol">
|
||||||
<span v-if="isView">{{ row.protocol }}</span>
|
<span v-if="isView">{{ row.protocol }}</span>
|
||||||
<LabeledSelect
|
<Select
|
||||||
v-else
|
v-else
|
||||||
v-model="row.protocol"
|
v-model="row.protocol"
|
||||||
:options="protocolOptions"
|
:options="protocolOptions"
|
||||||
|
|
@ -261,8 +261,20 @@ export default {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ports-row INPUT {
|
.ports-row {
|
||||||
height: 50px;
|
INPUT {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.port-protocol ::v-deep {
|
||||||
|
.unlabeled-select {
|
||||||
|
height: 50px;
|
||||||
|
|
||||||
|
.v-select.inline {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import ArrayList from '@/components/form/ArrayList';
|
import ArrayList from '@/components/form/ArrayList';
|
||||||
import { _VIEW } from '@/config/query-params';
|
import { _VIEW } from '@/config/query-params';
|
||||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
import Select from '@/components/form/Select';
|
||||||
|
|
||||||
const EFFECT_VALUES = {
|
const EFFECT_VALUES = {
|
||||||
NO_SCHEDULE: 'NoSchedule',
|
NO_SCHEDULE: 'NoSchedule',
|
||||||
|
|
@ -10,7 +10,7 @@ const EFFECT_VALUES = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { ArrayList, LabeledSelect },
|
components: { ArrayList, Select },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
|
|
@ -89,7 +89,7 @@ export default {
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="effect">
|
<td class="effect">
|
||||||
<LabeledSelect
|
<Select
|
||||||
v-model="scope.row.value.effect"
|
v-model="scope.row.value.effect"
|
||||||
:options="effectOptions"
|
:options="effectOptions"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -204,11 +204,11 @@ export default {
|
||||||
:disabled="isView"
|
:disabled="isView"
|
||||||
:options="routerOptions"
|
:options="routerOptions"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
placeholder="Select a Router..."
|
:placeholder="t('target.router.placeholder')"
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
class="inline"
|
class="inline"
|
||||||
:reduce="opt=>opt.value"
|
:reduce="opt=>opt.value"
|
||||||
label="Router"
|
:label="t('target.router.label')"
|
||||||
@input="update"
|
@input="update"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -220,11 +220,11 @@ export default {
|
||||||
:disabled="isView"
|
:disabled="isView"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:options="appOptions"
|
:options="appOptions"
|
||||||
placeholder="Select a service"
|
:placeholder="t('target.service.placeholder')"
|
||||||
:reduce="opt=>opt.value"
|
:reduce="opt=>opt.value"
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
class="inline"
|
class="inline"
|
||||||
label="Service"
|
:label="t('target.service.label')"
|
||||||
@input="update"
|
@input="update"
|
||||||
/>
|
/>
|
||||||
<Checkbox v-if="kind==='app'" v-model="pickVersion" label="Target one version" class="mt-10" />
|
<Checkbox v-if="kind==='app'" v-model="pickVersion" label="Target one version" class="mt-10" />
|
||||||
|
|
@ -235,10 +235,10 @@ export default {
|
||||||
:disabled="isView"
|
:disabled="isView"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:options="versionOptions"
|
:options="versionOptions"
|
||||||
placeholder="Select a version"
|
:placeholder="t('target.version.placeholder')"
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
class="inline"
|
class="inline"
|
||||||
label="Version"
|
:label="t('target.version.label')"
|
||||||
:reduce="opt=>opt.value"
|
:reduce="opt=>opt.value"
|
||||||
@input="update"
|
@input="update"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import LabeledInput from '@/components/form/LabeledInput';
|
import LabeledInput from '@/components/form/LabeledInput';
|
||||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
import Select from '@/components/form/Select';
|
||||||
import UnitInput from '@/components/form/UnitInput';
|
import UnitInput from '@/components/form/UnitInput';
|
||||||
import { _VIEW } from '@/config/query-params';
|
import { _VIEW } from '@/config/query-params';
|
||||||
import { random32 } from '@/utils/string';
|
import { random32 } from '@/utils/string';
|
||||||
|
|
@ -9,7 +9,7 @@ import { random32 } from '@/utils/string';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
LabeledInput,
|
LabeledInput,
|
||||||
LabeledSelect,
|
Select,
|
||||||
// SortableTable,
|
// SortableTable,
|
||||||
UnitInput
|
UnitInput
|
||||||
},
|
},
|
||||||
|
|
@ -147,7 +147,7 @@ export default {
|
||||||
<LabeledInput v-model="rule.key" :mode="mode" />
|
<LabeledInput v-model="rule.key" :mode="mode" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<LabeledSelect
|
<Select
|
||||||
id="operator"
|
id="operator"
|
||||||
v-model="rule.operator"
|
v-model="rule.operator"
|
||||||
:options="operatorOpts"
|
:options="operatorOpts"
|
||||||
|
|
@ -166,7 +166,7 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<LabeledSelect
|
<Select
|
||||||
v-model="rule.effect"
|
v-model="rule.effect"
|
||||||
:options="effectOpts"
|
:options="effectOpts"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
|
|
@ -195,24 +195,26 @@ export default {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='scss' scoped>
|
<style lang='scss' scoped>
|
||||||
.tolerations{
|
.tolerations{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rule, .toleration-headers{
|
.rule, .toleration-headers{
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 20% 10% 20% 10% 20% 10%;
|
grid-template-columns: 20% 10% 20% 10% 20% 10%;
|
||||||
grid-gap: $column-gutter;
|
grid-gap: $column-gutter;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rule {
|
.rule {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
.col {
|
||||||
|
height: 100%;
|
||||||
.toleration-headers SPAN {
|
|
||||||
color: var(--input-label);
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toleration-headers SPAN {
|
||||||
|
color: var(--input-label);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,8 @@ export default {
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:options="typeOpts"
|
:options="typeOpts"
|
||||||
|
option-label="label"
|
||||||
|
:searchable="false"
|
||||||
:reduce="e=>e.value"
|
:reduce="e=>e.value"
|
||||||
:label="t('workload.container.command.fromResource.type')"
|
:label="t('workload.container.command.fromResource.type')"
|
||||||
@input="updateRow"
|
@input="updateRow"
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,11 @@ export default {
|
||||||
// show host port column if existing port data has any host ports defined
|
// show host port column if existing port data has any host ports defined
|
||||||
const showHostPorts = !!rows.filter(row => !!row.hostPort).length;
|
const showHostPorts = !!rows.filter(row => !!row.hostPort).length;
|
||||||
|
|
||||||
return { rows, showHostPorts };
|
return {
|
||||||
|
rows,
|
||||||
|
showHostPorts,
|
||||||
|
workloadPortOptions: ['TCP', 'UDP']
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -188,9 +192,8 @@ export default {
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-else
|
v-else
|
||||||
v-model="row.protocol"
|
v-model="row.protocol"
|
||||||
:style="{'height':'50px'}"
|
|
||||||
class="inline"
|
class="inline"
|
||||||
:options="['TCP', 'UDP']"
|
:options="workloadPortOptions"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:label="t('workload.container.ports.protocol')"
|
:label="t('workload.container.ports.protocol')"
|
||||||
@input="queueUpdate"
|
@input="queueUpdate"
|
||||||
|
|
@ -248,58 +251,69 @@ export default {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$remove: 75;
|
$remove: 75;
|
||||||
$checkbox: 75;
|
$checkbox: 75;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
.read-from-file {
|
.read-from-file {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 1 unit is 8%
|
// 1 unit is 8%
|
||||||
.ports-headers, .ports-row{
|
.ports-headers, .ports-row{
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 30% 30% 18% 10% 5%;
|
grid-template-columns: 30% 30% 18% 10% 5%;
|
||||||
grid-column-gap: $column-gutter;
|
grid-column-gap: $column-gutter;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
& .port{
|
& .port{
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
|
||||||
|
|
||||||
&.show-host{
|
|
||||||
grid-template-columns: 30% 16% 8% 16% 16% 5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-host {
|
&.show-host{
|
||||||
justify-self: center;
|
grid-template-columns: 30% 16% 10% 15% 15% 5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ports-headers {
|
}
|
||||||
color: var(--input-label);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-host-ports {
|
.add-host {
|
||||||
color: var(--primary);
|
justify-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove BUTTON {
|
.protocol {
|
||||||
padding: 0px;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ports-row INPUT {
|
.ports-headers {
|
||||||
height: 50px;
|
color: var(--input-label);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.toggle-host-ports {
|
||||||
.protip {
|
color: var(--primary);
|
||||||
float: right;
|
}
|
||||||
padding: 5px 0;
|
|
||||||
}
|
.remove BUTTON {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ports-row INPUT {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
.protip {
|
||||||
|
float: right;
|
||||||
|
padding: 5px 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.ports-row .protocol ::v-deep .unlabeled-select,
|
||||||
|
.ports-row .protocol ::v-deep .unlabeled-select .v-select {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.ports-row .protocol ::v-deep .unlabeled-select .vs__dropdown-toggle {
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,27 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
|
||||||
addSuffix: {
|
addSuffix: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addPrefix: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
|
||||||
suffix: {
|
suffix: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'ago',
|
default: 'ago',
|
||||||
},
|
},
|
||||||
|
|
||||||
tooltipPlacement: {
|
tooltipPlacement: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'auto'
|
default: 'auto'
|
||||||
},
|
},
|
||||||
|
|
||||||
showTooltip: {
|
showTooltip: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
|
@ -84,7 +93,7 @@ export default {
|
||||||
const value = day(this.value);
|
const value = day(this.value);
|
||||||
const now = day();
|
const now = day();
|
||||||
let diff = value.diff(now, 'seconds');
|
let diff = value.diff(now, 'seconds');
|
||||||
const prefix = (diff < 0 ? '' : '-');
|
const prefix = (diff < 0 || !this.addPrefix ? '' : '-');
|
||||||
const suffix = '';
|
const suffix = '';
|
||||||
|
|
||||||
diff = Math.abs(diff);
|
diff = Math.abs(diff);
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,23 @@ import { MANAGEMENT } from '@/config/types';
|
||||||
import { sortBy } from '@/utils/sort';
|
import { sortBy } from '@/utils/sort';
|
||||||
import { findBy } from '@/utils/array';
|
import { findBy } from '@/utils/array';
|
||||||
import { mapState } from 'vuex';
|
import { mapState } from 'vuex';
|
||||||
|
import Select from '@/components/form/Select';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
components: { Select },
|
||||||
|
computed: {
|
||||||
...mapState(['isMultiCluster']),
|
...mapState(['isMultiCluster']),
|
||||||
|
|
||||||
value: {
|
value: {
|
||||||
get() {
|
get() {
|
||||||
const options = this.options;
|
const options = this.options;
|
||||||
const existing = findBy(options, 'id', this.$store.getters['clusterId']);
|
const existing = findBy(
|
||||||
|
options,
|
||||||
|
'id',
|
||||||
|
this.$store.getters['clusterId']
|
||||||
|
);
|
||||||
|
|
||||||
if ( existing ) {
|
if (existing) {
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,7 +28,7 @@ export default {
|
||||||
|
|
||||||
set(neu) {
|
set(neu) {
|
||||||
this.$router.push({ name: 'c-cluster', params: { cluster: neu.id } });
|
this.$router.push({ name: 'c-cluster', params: { cluster: neu.id } });
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
|
|
@ -43,22 +49,20 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
focus() {
|
focus() {
|
||||||
this.$refs.select.$refs.search.focus();
|
this.$refs.select.$refs.search.focus();
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="filter">
|
<div class="filter">
|
||||||
<v-select
|
<Select
|
||||||
ref="select"
|
ref="select"
|
||||||
key="cluster"
|
key="cluster"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
:selectable="option => option.ready"
|
:selectable="(option) => option.ready"
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
:options="options"
|
:options="options"
|
||||||
label="label"
|
|
||||||
>
|
>
|
||||||
<template #selected-option="opt">
|
<template #selected-option="opt">
|
||||||
<i class="icon icon-copy icon-lg pr-5" />
|
<i class="icon icon-copy icon-lg pr-5" />
|
||||||
|
|
@ -74,60 +78,81 @@ export default {
|
||||||
|
|
||||||
<template #option="opt">
|
<template #option="opt">
|
||||||
<b v-if="opt === value">{{ opt.label }}</b>
|
<b v-if="opt === value">{{ opt.label }}</b>
|
||||||
<nuxt-link v-else-if="opt.ready" class="cluster" :to="{name: 'c-cluster', params: { cluster: opt.id }}">
|
<nuxt-link
|
||||||
|
v-else-if="opt.ready"
|
||||||
|
class="cluster"
|
||||||
|
:to="{ name: 'c-cluster', params: { cluster: opt.id } }"
|
||||||
|
>
|
||||||
{{ opt.label }}
|
{{ opt.label }}
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<span v-else class="text-muted">{{ opt.label }}</span>
|
<span v-else class="text-muted">{{ opt.label }}</span>
|
||||||
</template>
|
</template>
|
||||||
</v-select>
|
</Select>
|
||||||
<button v-shortkey.once="['c']" class="hide" @shortkey="focus()" />
|
<button v-shortkey.once="['c']" class="hide" @shortkey="focus()" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.filter ::v-deep .v-select {
|
.filter ::v-deep .unlabeled-select .v-select {
|
||||||
max-width: 100%;
|
background: rgba(0, 0, 0, 0.05);
|
||||||
display: inline-block;
|
border-radius: var(--border-radius);
|
||||||
margin-top: 8px;
|
color: var(--header-btn-text);
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
&.vs--disabled .vs__actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs__actions:after {
|
||||||
|
fill: white !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs__dropdown-toggle {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
border: 1px solid var(--header-btn-bg);
|
border: 1px solid var(--header-btn-bg);
|
||||||
color: var(--header-btn-text);
|
color: var(--header-btn-text);
|
||||||
background: rgba(0,0,0,.05);
|
height: calc(var(--header-height) - 19px);
|
||||||
border-radius: var(--border-radius);
|
max-width: 100%;
|
||||||
|
padding-top: 0;
|
||||||
|
|
||||||
&.vs--disabled .vs__actions {
|
.vs__actions {
|
||||||
display: none;
|
margin-left: -10px;
|
||||||
}
|
|
||||||
|
|
||||||
.vs__actions:after {
|
|
||||||
fill: white !important;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vs__dropdown-toggle {
|
|
||||||
height: calc(var(--header-height) - 19px);
|
|
||||||
background-color: transparent;
|
|
||||||
border: 0;
|
|
||||||
|
|
||||||
.vs__actions {
|
|
||||||
margin-left: -10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vs__selected {
|
|
||||||
user-select: none;
|
|
||||||
cursor: default;
|
|
||||||
color: white;
|
|
||||||
line-height: calc(var(--header-height) - 32px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter ::v-deep INPUT {
|
.vs__selected {
|
||||||
width: auto;
|
border: none;
|
||||||
background-color: transparent;
|
position: absolute;
|
||||||
|
user-select: none;
|
||||||
|
cursor: default;
|
||||||
|
color: white;
|
||||||
|
line-height: calc(var(--header-height) - 32px);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.filter ::v-deep INPUT:hover {
|
.filter ::v-deep .unlabeled-select:not(.view):hover .vs__dropdown-menu {
|
||||||
background-color: transparent;
|
background: var(--dropdown-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select:not(.focused) {
|
||||||
|
border: var(--outline-width) solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select INPUT[type='search'] {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.filter ::v-deep .v-select.inline.vs--single.vs--open .vs__search {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select INPUT:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -172,19 +172,20 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
> .apps {
|
> .apps {
|
||||||
padding: 0 0 0 5px;
|
padding: 0 5px 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .cluster {
|
> .cluster {
|
||||||
grid-area: cluster;
|
grid-area: cluster;
|
||||||
background-color: var(--header-bg);
|
background-color: var(--header-bg);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
padding-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .top {
|
> .top {
|
||||||
grid-area: top;
|
grid-area: top;
|
||||||
background-color: var(--header-bg);
|
background-color: var(--header-bg);
|
||||||
padding-top: 8px;
|
padding-top: 6px;
|
||||||
|
|
||||||
INPUT[type='search']::placeholder,
|
INPUT[type='search']::placeholder,
|
||||||
.vs__open-indicator,
|
.vs__open-indicator,
|
||||||
|
|
|
||||||
|
|
@ -3,60 +3,35 @@ import { NAMESPACE_FILTERS } from '@/store/prefs';
|
||||||
import { NAMESPACE, MANAGEMENT } from '@/config/types';
|
import { NAMESPACE, MANAGEMENT } from '@/config/types';
|
||||||
import { sortBy } from '@/utils/sort';
|
import { sortBy } from '@/utils/sort';
|
||||||
import { isArray, addObjects, findBy, filterBy } from '@/utils/array';
|
import { isArray, addObjects, findBy, filterBy } from '@/utils/array';
|
||||||
|
import Select from '@/components/form/Select';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { Select },
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isHovered: false,
|
||||||
|
hoveredTimeout: null,
|
||||||
|
maskedWidth: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
value: {
|
filterIsHovered() {
|
||||||
get() {
|
return this.isHovered;
|
||||||
const values = this.$store.getters['prefs/get'](NAMESPACE_FILTERS);
|
},
|
||||||
const options = this.options;
|
|
||||||
|
|
||||||
if ( !values ) {
|
maskedDropdownWidth() {
|
||||||
return [];
|
const refs = this.$refs;
|
||||||
}
|
const select = refs.select;
|
||||||
|
|
||||||
// Remove values that are not valid options
|
if (select) {
|
||||||
const out = values.map((value) => {
|
const selectWidth = select.$el.offsetWidth;
|
||||||
return findBy(options, 'id', value);
|
|
||||||
}).filter(x => !!x);
|
|
||||||
|
|
||||||
return out;
|
return selectWidth;
|
||||||
},
|
|
||||||
|
|
||||||
set(neu) {
|
|
||||||
const old = (this.value || []).slice();
|
|
||||||
|
|
||||||
neu = neu.filter(x => !!x.id);
|
|
||||||
|
|
||||||
const last = neu[neu.length - 1];
|
|
||||||
const lastIsSpecial = last?.kind === 'special';
|
|
||||||
const hadUser = old.find(x => x.id === 'all://user');
|
|
||||||
const hadAll = old.find(x => x.id === 'all');
|
|
||||||
|
|
||||||
if ( lastIsSpecial ) {
|
|
||||||
neu = [last];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( neu.length > 1 ) {
|
|
||||||
neu = neu.filter(x => x.kind !== 'special');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( neu.find(x => x.id === 'all') ) {
|
|
||||||
neu = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let ids;
|
|
||||||
|
|
||||||
// If there as something selected and you remove it, go back to user by default
|
|
||||||
// Unless it was user or all
|
|
||||||
if (neu.length === 0 && !hadUser && !hadAll ) {
|
|
||||||
ids = ['all://user'];
|
|
||||||
} else {
|
|
||||||
ids = neu.map(x => x.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.dispatch('switchNamespaces', ids);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
|
|
@ -64,63 +39,70 @@ export default {
|
||||||
|
|
||||||
const out = [
|
const out = [
|
||||||
{
|
{
|
||||||
id: 'all',
|
id: 'all',
|
||||||
kind: 'special',
|
kind: 'special',
|
||||||
label: t('nav.ns.all'),
|
label: t('nav.ns.all'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'all://user',
|
id: 'all://user',
|
||||||
kind: 'special',
|
kind: 'special',
|
||||||
label: t('nav.ns.user'),
|
label: t('nav.ns.user'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'all://system',
|
id: 'all://system',
|
||||||
kind: 'special',
|
kind: 'special',
|
||||||
label: t('nav.ns.system'),
|
label: t('nav.ns.system'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'namespaced://true',
|
id: 'namespaced://true',
|
||||||
kind: 'special',
|
kind: 'special',
|
||||||
label: t('nav.ns.namespaced'),
|
label: t('nav.ns.namespaced'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'namespaced://false',
|
id: 'namespaced://false',
|
||||||
kind: 'special',
|
kind: 'special',
|
||||||
label: t('nav.ns.clusterLevel'),
|
label: t('nav.ns.clusterLevel'),
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
divider();
|
divider();
|
||||||
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
const namespaces = sortBy(this.$store.getters[`${ inStore }/all`](NAMESPACE), ['nameDisplay']);
|
const namespaces = sortBy(
|
||||||
|
this.$store.getters[`${ inStore }/all`](NAMESPACE),
|
||||||
|
['nameDisplay']
|
||||||
|
);
|
||||||
|
|
||||||
if ( this.$store.getters['isMultiCluster'] ) {
|
if (this.$store.getters['isMultiCluster']) {
|
||||||
const cluster = this.$store.getters['currentCluster'];
|
const cluster = this.$store.getters['currentCluster'];
|
||||||
let projects = this.$store.getters['management/all'](MANAGEMENT.PROJECT);
|
let projects = this.$store.getters['management/all'](
|
||||||
|
MANAGEMENT.PROJECT
|
||||||
|
);
|
||||||
|
|
||||||
projects = sortBy(filterBy(projects, 'spec.clusterName', cluster.id), ['nameDisplay']);
|
projects = sortBy(filterBy(projects, 'spec.clusterName', cluster.id), [
|
||||||
|
'nameDisplay',
|
||||||
|
]);
|
||||||
const projectsById = {};
|
const projectsById = {};
|
||||||
const namespacesByProject = {};
|
const namespacesByProject = {};
|
||||||
let firstProject = true;
|
let firstProject = true;
|
||||||
|
|
||||||
namespacesByProject[null] = []; // For namespaces not in a project
|
namespacesByProject[null] = []; // For namespaces not in a project
|
||||||
|
|
||||||
for ( const project of projects ) {
|
for (const project of projects) {
|
||||||
projectsById[project.metadata.name] = project;
|
projectsById[project.metadata.name] = project;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const namespace of namespaces ) {
|
for (const namespace of namespaces) {
|
||||||
let projectId = namespace.projectId;
|
let projectId = namespace.projectId;
|
||||||
|
|
||||||
if ( !projectId || !projectsById[projectId] ) {
|
if (!projectId || !projectsById[projectId]) {
|
||||||
// If there's a projectId but that project doesn't exist, treat it like no project
|
// If there's a projectId but that project doesn't exist, treat it like no project
|
||||||
projectId = null;
|
projectId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry = namespacesByProject[namespace.projectId];
|
let entry = namespacesByProject[namespace.projectId];
|
||||||
|
|
||||||
if ( !entry ) {
|
if (!entry) {
|
||||||
entry = [];
|
entry = [];
|
||||||
namespacesByProject[namespace.projectId] = entry;
|
namespacesByProject[namespace.projectId] = entry;
|
||||||
}
|
}
|
||||||
|
|
@ -128,10 +110,10 @@ export default {
|
||||||
entry.push(namespace);
|
entry.push(namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( const project of projects ) {
|
for (const project of projects) {
|
||||||
const id = project.metadata.name;
|
const id = project.metadata.name;
|
||||||
|
|
||||||
if ( firstProject ) {
|
if (firstProject) {
|
||||||
firstProject = false;
|
firstProject = false;
|
||||||
} else {
|
} else {
|
||||||
divider();
|
divider();
|
||||||
|
|
@ -140,7 +122,7 @@ export default {
|
||||||
out.push({
|
out.push({
|
||||||
id: `project://${ id }`,
|
id: `project://${ id }`,
|
||||||
kind: 'project',
|
kind: 'project',
|
||||||
label: t('nav.ns.project', { name: project.nameDisplay }),
|
label: t('nav.ns.project', { name: project.nameDisplay }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const forThisProject = namespacesByProject[id] || [];
|
const forThisProject = namespacesByProject[id] || [];
|
||||||
|
|
@ -150,8 +132,8 @@ export default {
|
||||||
|
|
||||||
const orphans = namespacesByProject[null];
|
const orphans = namespacesByProject[null];
|
||||||
|
|
||||||
if ( orphans.length ) {
|
if (orphans.length) {
|
||||||
if ( !firstProject ) {
|
if (!firstProject) {
|
||||||
divider();
|
divider();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,17 +153,20 @@ export default {
|
||||||
return out;
|
return out;
|
||||||
|
|
||||||
function addNamespace(namespaces) {
|
function addNamespace(namespaces) {
|
||||||
if ( !isArray(namespaces) ) {
|
if (!isArray(namespaces)) {
|
||||||
namespaces = [namespaces];
|
namespaces = [namespaces];
|
||||||
}
|
}
|
||||||
|
|
||||||
addObjects(out, namespaces.map((namespace) => {
|
addObjects(
|
||||||
return {
|
out,
|
||||||
id: `ns://${ namespace.id }`,
|
namespaces.map((namespace) => {
|
||||||
kind: 'namespace',
|
return {
|
||||||
label: t('nav.ns.namespace', { name: namespace.nameDisplay }),
|
id: `ns://${ namespace.id }`,
|
||||||
};
|
kind: 'namespace',
|
||||||
}));
|
label: t('nav.ns.namespace', { name: namespace.nameDisplay }),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function divider() {
|
function divider() {
|
||||||
|
|
@ -191,81 +176,147 @@ export default {
|
||||||
disabled: true,
|
disabled: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
value: {
|
||||||
|
get() {
|
||||||
|
const values = this.$store.getters['prefs/get'](NAMESPACE_FILTERS);
|
||||||
|
const options = this.options;
|
||||||
|
|
||||||
|
if (!values) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove values that are not valid options
|
||||||
|
const out = values
|
||||||
|
.map((value) => {
|
||||||
|
return findBy(options, 'id', value);
|
||||||
|
})
|
||||||
|
.filter(x => !!x);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
},
|
||||||
|
|
||||||
|
set(neu) {
|
||||||
|
const old = (this.value || []).slice();
|
||||||
|
|
||||||
|
neu = neu.filter(x => !!x.id);
|
||||||
|
|
||||||
|
const last = neu[neu.length - 1];
|
||||||
|
const lastIsSpecial = last?.kind === 'special';
|
||||||
|
const hadUser = old.find(x => x.id === 'all://user');
|
||||||
|
const hadAll = old.find(x => x.id === 'all');
|
||||||
|
|
||||||
|
if (lastIsSpecial) {
|
||||||
|
neu = [last];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (neu.length > 1) {
|
||||||
|
neu = neu.filter(x => x.kind !== 'special');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (neu.find(x => x.id === 'all')) {
|
||||||
|
neu = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let ids;
|
||||||
|
|
||||||
|
// If there as something selected and you remove it, go back to user by default
|
||||||
|
// Unless it was user or all
|
||||||
|
if (neu.length === 0 && !hadUser && !hadAll) {
|
||||||
|
ids = ['all://user'];
|
||||||
|
} else {
|
||||||
|
ids = neu.map(x => x.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.dispatch('switchNamespaces', ids);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
showGroupedOptions() {
|
||||||
|
if (this.value && this.value.length >= 2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.maskedWidth = this.maskedDropdownWidth;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
focus() {
|
focus() {
|
||||||
this.$refs.select.$refs.search.focus();
|
this.$refs.select.$refs['select-input'].searchEl.focus();
|
||||||
|
},
|
||||||
|
focusHandler(event) {
|
||||||
|
if (event === 'selectBlurred') {
|
||||||
|
this.maskedWidth = this.maskedDropdownWidth;
|
||||||
|
this.isHovered = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we dont handle blur here because the select specifically handles its blur events and emits when it does.
|
||||||
|
// we should listen to this blur event because the swapping of the masked select with real select.
|
||||||
|
if (event.type === 'focus') {
|
||||||
|
this.isHovered = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isHoveredHandler(event) {
|
||||||
|
clearTimeout(this.hoveredTimeout);
|
||||||
|
|
||||||
|
if (event.type === 'mouseenter') {
|
||||||
|
this.isHovered = true;
|
||||||
|
} else if (event.type === 'mouseleave') {
|
||||||
|
this.hoveredTimeout = setTimeout(() => {
|
||||||
|
this.maskedWidth = this.maskedDropdownWidth;
|
||||||
|
this.isHovered = false;
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style type="scss" scoped>
|
|
||||||
.filter ::v-deep .v-select {
|
|
||||||
min-width: 220px;
|
|
||||||
max-width: 100%;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep .v-select .vs__selected {
|
|
||||||
margin: 4px;
|
|
||||||
user-select: none;
|
|
||||||
cursor: default;
|
|
||||||
background: rgba(255, 255, 255, 0.25);
|
|
||||||
border: solid white thin;
|
|
||||||
color: white;
|
|
||||||
height: calc(var(--header-height) - 26px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep INPUT {
|
|
||||||
width: auto;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep .vs__search::placeholder {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep INPUT:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep .vs__dropdown-toggle {
|
|
||||||
max-width: 100%;
|
|
||||||
border: 1px solid var(--header-btn-bg);
|
|
||||||
color: var(--header-btn-text);
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
height: calc(var(--header-height) - 16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep .vs__deselect:after {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep .v-select .vs__actions:after {
|
|
||||||
fill: white !important;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep INPUT[type='search'] {
|
|
||||||
padding: 7px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="filter">
|
<div
|
||||||
<v-select
|
class="filter"
|
||||||
|
:class="{'show-masked': showGroupedOptions && !filterIsHovered}"
|
||||||
|
@mouseenter="isHoveredHandler"
|
||||||
|
@mouseleave="isHoveredHandler"
|
||||||
|
>
|
||||||
|
<div tabindex="0" class="unlabeled-select masked-dropdown" @focus="focusHandler">
|
||||||
|
<div class="v-select inline vs--searchable">
|
||||||
|
<div class="vs__dropdown-toggle">
|
||||||
|
<div class="vs__selected-options">
|
||||||
|
<div class="vs__selected">
|
||||||
|
{{ t('namespaceFilter.selected.label', { total: value.length }) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="vs__actions"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Select
|
||||||
ref="select"
|
ref="select"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
|
:class="{
|
||||||
|
'has-more': showGroupedOptions,
|
||||||
|
}"
|
||||||
multiple
|
multiple
|
||||||
:placeholder="t('nav.ns.all')"
|
:placeholder="t('nav.ns.all')"
|
||||||
:selectable="option => !option.disabled && option.id"
|
:selectable="(option) => !option.disabled && option.id"
|
||||||
:options="options"
|
:options="options"
|
||||||
label="label"
|
:close-on-select="false"
|
||||||
|
@on-blur="focusHandler('selectBlurred')"
|
||||||
>
|
>
|
||||||
<template v-slot:option="opt">
|
<template v-slot:option="opt">
|
||||||
<template v-if="opt.kind === 'namespace'">
|
<template v-if="opt.kind === 'namespace'">
|
||||||
|
|
@ -281,7 +332,99 @@ export default {
|
||||||
{{ opt.label }}
|
{{ opt.label }}
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</v-select>
|
</Select>
|
||||||
<button v-shortkey.once="['n']" class="hide" @shortkey="focus()" />
|
<button v-shortkey.once="['n']" class="hide" @shortkey="focus()" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style type="scss" scoped>
|
||||||
|
.filter {
|
||||||
|
min-width: 220px;
|
||||||
|
max-width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter.show-masked ::v-deep .unlabeled-select:not(.masked-dropdown) {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter:not(.show-masked) ::v-deep .unlabeled-select.masked-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select.has-more .v-select:not(.vs--open) .vs__dropdown-toggle {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select.has-more .v-select.vs--open .vs__dropdown-toggle {
|
||||||
|
height: max-content;
|
||||||
|
background-color: var(--header-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select:not(.focused) {
|
||||||
|
border: var(--outline-width) solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select:not(.view):hover .vs__dropdown-menu {
|
||||||
|
background: var(--dropdown-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select .v-select.inline {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select .v-select .vs__selected {
|
||||||
|
margin: 4px;
|
||||||
|
user-select: none;
|
||||||
|
cursor: default;
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
border: solid white thin;
|
||||||
|
color: white;
|
||||||
|
height: calc(var(--header-height) - 26px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select .vs__search::placeholder {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select INPUT:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select .vs__dropdown-toggle {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--header-btn-bg);
|
||||||
|
color: var(--header-btn-text);
|
||||||
|
height: calc(var(--header-height) - 16px);
|
||||||
|
max-width: 100%;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select .vs__deselect:after {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select .v-select .vs__actions:after {
|
||||||
|
fill: white !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select INPUT[type='search'] {
|
||||||
|
padding: 7px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,11 @@ import { ucFirst } from '@/utils/string';
|
||||||
import { createPopper } from '@popperjs/core';
|
import { createPopper } from '@popperjs/core';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { CATALOG } from '@/config/types';
|
import { CATALOG } from '@/config/types';
|
||||||
|
import Select from '@/components/form/Select';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { Select },
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return { previous: null };
|
return { previous: null };
|
||||||
},
|
},
|
||||||
|
|
@ -162,6 +165,10 @@ export default {
|
||||||
const popper = createPopper(component.$refs.toggle, dropdownList, {
|
const popper = createPopper(component.$refs.toggle, dropdownList, {
|
||||||
strategy: 'fixed',
|
strategy: 'fixed',
|
||||||
modifiers: [
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: 'offset',
|
||||||
|
options: { offset: [0, -2] },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'toggleClass',
|
name: 'toggleClass',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
@ -181,18 +188,17 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="filter">
|
<div class="filter">
|
||||||
<v-select
|
<Select
|
||||||
ref="select"
|
ref="select"
|
||||||
key="product"
|
key="product"
|
||||||
:value="value"
|
:value="value"
|
||||||
:clearable="false"
|
:searchable="false"
|
||||||
:selectable="option => !option.disabled"
|
:selectable="option => !option.disabled"
|
||||||
:options="options"
|
:options="options"
|
||||||
:reduce="opt=>opt.value"
|
:reduce="opt=>opt.value"
|
||||||
:calculate-position="withPopper"
|
:popper-override="withPopper"
|
||||||
:append-to-body="true"
|
:append-to-body="true"
|
||||||
|
placement="bottom"
|
||||||
label="label"
|
|
||||||
@input="change"
|
@input="change"
|
||||||
>
|
>
|
||||||
<template v-slot:option="opt">
|
<template v-slot:option="opt">
|
||||||
|
|
@ -205,7 +211,7 @@ export default {
|
||||||
<i v-if="opt.kind === 'external'" class="icon icon-external-link ml-10" />
|
<i v-if="opt.kind === 'external'" class="icon icon-external-link ml-10" />
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</v-select>
|
</Select>
|
||||||
<button v-shortkey.once="['p']" class="hide" @shortkey="focus()" />
|
<button v-shortkey.once="['p']" class="hide" @shortkey="focus()" />
|
||||||
<button v-shortkey.once="['f']" class="hide" @shortkey="switchToFleet()" />
|
<button v-shortkey.once="['f']" class="hide" @shortkey="switchToFleet()" />
|
||||||
<button v-shortkey.once="['e']" class="hide" @shortkey="switchToExplorer()" />
|
<button v-shortkey.once="['e']" class="hide" @shortkey="switchToExplorer()" />
|
||||||
|
|
@ -214,83 +220,87 @@ export default {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.filter ::v-deep .v-select {
|
.filter ::v-deep .unlabeled-select {
|
||||||
max-width: 100%;
|
background-color: transparent;
|
||||||
display: inline-block;
|
&:not(.focused) {
|
||||||
|
border: var(--outline-width) solid transparent;
|
||||||
|
}
|
||||||
|
.v-select {
|
||||||
&.vs--disabled .vs__actions {
|
&.vs--disabled .vs__actions {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs__actions:after {
|
.vs__actions {
|
||||||
fill: white !important;
|
&:after {
|
||||||
color: white !important;
|
fill: white !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs__dropdown-toggle {
|
.vs__dropdown-toggle {
|
||||||
|
margin-bottom: -4px;
|
||||||
height: var(--header-height);
|
height: var(--header-height);
|
||||||
// margin-left: 35px;
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 0;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
// left: 35px;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs__selected {
|
.vs__selected {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
color: white;
|
color: white;
|
||||||
line-height: calc(var(--header-height) - 14px);
|
line-height: calc(var(--header-height) - 7px);
|
||||||
position: relative;
|
position: relative;
|
||||||
left: 40px;
|
left: 40px;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
.vs__selected-options {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.unlabeled-select INPUT[type='search'] {
|
||||||
|
margin-left: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.filter ::v-deep INPUT {
|
|
||||||
width: auto;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep INPUT:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.product-menu {
|
// these styles exist because the dd is placed with Popper and is outside the flow of the component, product-menu gets appended to the menu
|
||||||
width: 300px;
|
.product-menu {
|
||||||
max-height: 90vh;
|
width: 300px;
|
||||||
|
max-height: 90vh;
|
||||||
|
&.vs__dropdown-menu {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.vs__dropdown-option {
|
.vs__dropdown-option {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-left: 5px solid transparent;
|
border-left: 5px solid transparent;
|
||||||
|
|
||||||
&.vs__dropdown-option--disabled {
|
&.vs__dropdown-option--disabled {
|
||||||
// The dividers
|
// The dividers
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.product-icon {
|
|
||||||
color: var(--product-icon);
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
UL {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs__dropdown-option--selected {
|
.product-icon {
|
||||||
color: var(--body-text);
|
color: var(--product-icon);
|
||||||
// font-weight: bold;
|
margin-right: 5px;
|
||||||
background: var(--nav-active);
|
}
|
||||||
border-left: 5px solid var(--primary);
|
|
||||||
|
|
||||||
.product-icon {
|
UL {
|
||||||
color: var(--product-icon-active);
|
margin: 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vs__dropdown-option--selected {
|
||||||
|
color: var(--body-text);
|
||||||
|
background: var(--nav-active);
|
||||||
|
border-left: 5px solid var(--primary);
|
||||||
|
|
||||||
|
.product-icon {
|
||||||
|
color: var(--product-icon-active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -446,7 +446,7 @@ export default {
|
||||||
v-if="containerChoices.length > 0"
|
v-if="containerChoices.length > 0"
|
||||||
v-model="container"
|
v-model="container"
|
||||||
:disabled="containerChoices.length === 1"
|
:disabled="containerChoices.length === 1"
|
||||||
class="containerPicker auto-width"
|
class="containerPicker"
|
||||||
:options="containerChoices"
|
:options="containerChoices"
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
placement="top"
|
placement="top"
|
||||||
|
|
@ -560,11 +560,11 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.containerPicker ::v-deep .vs__search,
|
.containerPicker {
|
||||||
.range ::v-deep .vs__search {
|
::v-deep &.unlabeled-select {
|
||||||
width: 0;
|
display: inline-block;
|
||||||
padding: 0;
|
min-width: 200px;
|
||||||
margin: 0;
|
}
|
||||||
opacity: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -268,8 +268,8 @@ export default {
|
||||||
:disabled="containerChoices.length === 1"
|
:disabled="containerChoices.length === 1"
|
||||||
class="containerPicker auto-width"
|
class="containerPicker auto-width"
|
||||||
:options="containerChoices"
|
:options="containerChoices"
|
||||||
:searchable="false"
|
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
|
placement="top"
|
||||||
@input="switchTo($event)"
|
@input="switchTo($event)"
|
||||||
>
|
>
|
||||||
<template #selected-option="option">
|
<template #selected-option="option">
|
||||||
|
|
@ -309,10 +309,10 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.containerPicker ::v-deep .vs__search {
|
.containerPicker {
|
||||||
width: 0;
|
::v-deep &.unlabeled-select {
|
||||||
padding: 0;
|
display: inline-block;
|
||||||
margin: 0;
|
min-width: 200px;
|
||||||
opacity: 0;
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export default {
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$store.commit('updateWorkspace', { value });
|
this.$store.commit('updateWorkspace', { value });
|
||||||
this.$store.dispatch('prefs/set', { key: WORKSPACE, value });
|
this.$store.dispatch('prefs/set', { key: WORKSPACE, value });
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
|
|
@ -29,7 +29,7 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -38,48 +38,8 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style type="scss" scoped>
|
|
||||||
.filter ::v-deep .v-select {
|
|
||||||
max-width: 100%;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep .v-select .vs__selected {
|
|
||||||
margin: 4px;
|
|
||||||
user-select: none;
|
|
||||||
color: white;
|
|
||||||
height: calc(var(--header-height) - 26px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep .vs__dropdown-toggle {
|
|
||||||
max-width: 100%;
|
|
||||||
border: 1px solid var(--header-btn-bg);
|
|
||||||
color: var(--header-btn-text);
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
height: calc(var(--header-height) - 16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep .vs__deselect:after {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep .v-select .vs__actions:after {
|
|
||||||
fill: white !important;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter ::v-deep .vs__search {
|
|
||||||
width: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="filter">
|
<div class="filter">
|
||||||
<Select
|
<Select
|
||||||
|
|
@ -88,8 +48,69 @@ export default {
|
||||||
label="label"
|
label="label"
|
||||||
:options="options"
|
:options="options"
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
:reduce="opt=>opt.value"
|
:reduce="(opt) => opt.value"
|
||||||
/>
|
/>
|
||||||
<button v-shortkey.once="['w']" class="hide" @shortkey="focus()" />
|
<button v-shortkey.once="['w']" class="hide" @shortkey="focus()" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style type="scss" scoped>
|
||||||
|
.filter {
|
||||||
|
min-width: 220px;
|
||||||
|
max-width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select:not(.focused) {
|
||||||
|
border: var(--outline-width) solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select:not(.view):hover .vs__dropdown-menu {
|
||||||
|
background: var(--dropdown-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select .v-select.inline {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select INPUT {
|
||||||
|
width: auto;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select .vs__search::placeholder {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select INPUT:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select .vs__dropdown-toggle {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--header-btn-bg);
|
||||||
|
color: var(--header-btn-text);
|
||||||
|
height: calc(var(--header-height) - 16px);
|
||||||
|
max-width: 100%;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select .vs__deselect:after {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select .v-select .vs__actions:after {
|
||||||
|
fill: white !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter ::v-deep .unlabeled-select INPUT[type='search'] {
|
||||||
|
padding: 7px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -43,29 +43,44 @@ export function init(store) {
|
||||||
sort: ['status.lastRunScanProfileName'],
|
sort: ['status.lastRunScanProfileName'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'total',
|
name: 'total',
|
||||||
label: 'Total',
|
labelKey: 'cis.scan.total',
|
||||||
value: 'status.summary.total',
|
value: 'status.summary.total',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'pass',
|
name: 'pass',
|
||||||
label: 'Pass',
|
value: 'status.summary.pass',
|
||||||
value: 'status.summary.pass',
|
labelKey: 'cis.scan.pass',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'fail',
|
name: 'fail',
|
||||||
label: 'Fail',
|
labelKey: 'cis.scan.fail',
|
||||||
value: 'status.summary.fail',
|
value: 'status.summary.fail',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'skip',
|
name: 'warn',
|
||||||
label: 'Skip',
|
labelKey: 'cis.scan.warn',
|
||||||
value: 'status.summary.skip',
|
value: 'status.summary.warn',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'notApplicable',
|
name: 'skip',
|
||||||
label: 'N/A',
|
labelKey: 'cis.scan.skip',
|
||||||
value: 'status.summary.notApplicable',
|
value: 'status.summary.skip',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'notApplicable',
|
||||||
|
labelKey: 'cis.scan.notApplicable',
|
||||||
|
value: 'status.summary.notApplicable',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nextScanAt',
|
||||||
|
label: 'Next Scan',
|
||||||
|
value: 'status.NextScanAt',
|
||||||
|
formatter: 'LiveDate',
|
||||||
|
formatterOpts: { addPrefix: false },
|
||||||
|
sort: 'status.nextScanAt:desc',
|
||||||
|
width: 150,
|
||||||
|
align: 'right',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lastRunTimestamp',
|
name: 'lastRunTimestamp',
|
||||||
|
|
@ -79,4 +94,22 @@ export function init(store) {
|
||||||
defaultSort: true,
|
defaultSort: true,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
headers(CIS.CLUSTER_SCAN_PROFILE, [
|
||||||
|
STATE,
|
||||||
|
NAME_HEADER,
|
||||||
|
{
|
||||||
|
name: 'benchmarkVersion',
|
||||||
|
labelKey: 'cis.benchmarkVersion',
|
||||||
|
value: 'spec.benchmarkVersion',
|
||||||
|
formatter: 'Link',
|
||||||
|
formatterOpts: { options: { internal: true }, to: { name: 'c-cluster-product-resource-id', params: { resource: CIS.BENCHMARK } } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'skippedTests',
|
||||||
|
labelKey: 'cis.testsSkipped',
|
||||||
|
value: 'numberTestsSkipped',
|
||||||
|
sort: ['numberTestsSkipped']
|
||||||
|
}
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,20 @@
|
||||||
import Date from '@/components/formatter/Date';
|
import Date from '@/components/formatter/Date';
|
||||||
import SortableTable from '@/components/SortableTable';
|
import SortableTable from '@/components/SortableTable';
|
||||||
import Banner from '@/components/Banner';
|
import Banner from '@/components/Banner';
|
||||||
|
import LabeledSelect from '@/components/form/LabeledSelect';
|
||||||
|
import day from 'dayjs';
|
||||||
|
import { DATE_FORMAT, TIME_FORMAT } from '@/store/prefs';
|
||||||
|
import { escapeHtml, randomStr } from '@/utils/string';
|
||||||
import { defaultAsyncData } from '@/components/ResourceDetail';
|
import { defaultAsyncData } from '@/components/ResourceDetail';
|
||||||
import { CIS } from '@/config/types';
|
import { CIS } from '@/config/types';
|
||||||
import { STATE } from '@/config/table-headers';
|
import { STATE } from '@/config/table-headers';
|
||||||
import { randomStr } from '@/utils/string';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Date,
|
Date,
|
||||||
SortableTable,
|
SortableTable,
|
||||||
Banner
|
Banner,
|
||||||
|
LabeledSelect
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -24,7 +28,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetch() {
|
async fetch() {
|
||||||
this.clusterReport = await this.value.getReport();
|
this.clusterReports = await this.value.getReports();
|
||||||
},
|
},
|
||||||
|
|
||||||
asyncData(ctx) {
|
asyncData(ctx) {
|
||||||
|
|
@ -32,14 +36,12 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return { clusterReport: null };
|
return { clusterReports: [], clusterReport: null };
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
parsedReport() {
|
parsedReport() {
|
||||||
const report = this.clusterReport?.parsedReport;
|
return this.clusterReport?.parsedReport || null;
|
||||||
|
|
||||||
return report;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
reportNodes() {
|
reportNodes() {
|
||||||
|
|
@ -96,15 +98,19 @@ export default {
|
||||||
value: this.parsedReport.total
|
value: this.parsedReport.total
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: this.t('cis.scan.passed'),
|
label: this.t('cis.scan.pass'),
|
||||||
value: this.parsedReport.pass
|
value: this.parsedReport.pass
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: this.t('cis.scan.skipped'),
|
label: this.t('cis.scan.warn'),
|
||||||
|
value: this.parsedReport.warn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.t('cis.scan.skip'),
|
||||||
value: this.parsedReport.skip
|
value: this.parsedReport.skip
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: this.t('cis.scan.failed'),
|
label: this.t('cis.scan.fail'),
|
||||||
value: this.parsedReport.fail
|
value: this.parsedReport.fail
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -168,14 +174,25 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
value(neu) {
|
value(neu) {
|
||||||
try {
|
try {
|
||||||
neu.getReport().then((report) => {
|
neu.getReports().then((reports) => {
|
||||||
this.clusterReport = report;
|
this.clusterReports = reports;
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
|
},
|
||||||
|
|
||||||
|
clusterReports(neu) {
|
||||||
|
this.clusterReport = neu[0];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
reportLabel(report = {}) {
|
||||||
|
const { creationTimestamp } = report.metadata;
|
||||||
|
const dateFormat = escapeHtml( this.$store.getters['prefs/get'](DATE_FORMAT));
|
||||||
|
const timeFormat = escapeHtml( this.$store.getters['prefs/get'](TIME_FORMAT));
|
||||||
|
|
||||||
|
return day(creationTimestamp).format(`${ dateFormat } ${ timeFormat }`);
|
||||||
|
},
|
||||||
|
|
||||||
nodeState(check, node, nodes = []) {
|
nodeState(check, node, nodes = []) {
|
||||||
if (check.state === 'mixed') {
|
if (check.state === 'mixed') {
|
||||||
|
|
@ -187,11 +204,13 @@ export default {
|
||||||
|
|
||||||
testStateSort(state) {
|
testStateSort(state) {
|
||||||
const SORT_ORDER = {
|
const SORT_ORDER = {
|
||||||
fail: 1,
|
other: 7,
|
||||||
skip: 2,
|
notApplicable: 6,
|
||||||
notApplicable: 3,
|
skip: 5,
|
||||||
pass: 4,
|
pass: 4,
|
||||||
other: 5,
|
warn: 3,
|
||||||
|
mixed: 2,
|
||||||
|
fail: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
return `${ SORT_ORDER[state] || SORT_ORDER['other'] } ${ state }`;
|
return `${ SORT_ORDER[state] || SORT_ORDER['other'] } ${ state }`;
|
||||||
|
|
@ -218,8 +237,17 @@ export default {
|
||||||
<span v-else>{{ item.value }}</span>
|
<span v-else>{{ item.value }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="clusterReports.length > 1" class="table-header row mb-20">
|
||||||
|
<div class="col span-8">
|
||||||
|
<h3 class="mb-0">
|
||||||
|
{{ t('cis.scan.scanReport') }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col span-4">
|
||||||
|
<v-select v-model="clusterReport" class="inline" :options="clusterReports" :get-option-label="reportLabel" :get-option-key="report=>report.id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="results.length">
|
<div v-if="results.length">
|
||||||
<h3>{{ t('cis.scan.scanReport') }}</h3>
|
|
||||||
<SortableTable
|
<SortableTable
|
||||||
default-sort-by="state"
|
default-sort-by="state"
|
||||||
:search="false"
|
:search="false"
|
||||||
|
|
@ -269,4 +297,8 @@ export default {
|
||||||
.sub-table {
|
.sub-table {
|
||||||
padding: 0px 40px 0px 40px;
|
padding: 0px 40px 0px 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,22 @@
|
||||||
<script>
|
<script>
|
||||||
import CruResource from '@/components/CruResource';
|
import CruResource from '@/components/CruResource';
|
||||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
import LabeledSelect from '@/components/form/LabeledSelect';
|
||||||
|
import LabeledInput from '@/components/form/LabeledInput';
|
||||||
import Banner from '@/components/Banner';
|
import Banner from '@/components/Banner';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import { CIS, CONFIG_MAP } from '@/config/types';
|
import { CIS, CONFIG_MAP, ENDPOINTS } from '@/config/types';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import createEditView from '@/mixins/create-edit-view';
|
import createEditView from '@/mixins/create-edit-view';
|
||||||
import { allHash } from '@/utils/promise';
|
import { allHash } from '@/utils/promise';
|
||||||
|
import Checkbox from '@/components/form/Checkbox';
|
||||||
|
import RadioGroup from '@/components/form/RadioGroup';
|
||||||
|
import cronstrue from 'cronstrue';
|
||||||
|
|
||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CruResource, LabeledSelect, Banner, Loading
|
CruResource, LabeledSelect, Banner, Loading, Checkbox, LabeledInput, RadioGroup
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [createEditView],
|
mixins: [createEditView],
|
||||||
|
|
@ -33,9 +38,17 @@ export default {
|
||||||
const hash = await allHash({
|
const hash = await allHash({
|
||||||
profiles: this.$store.dispatch('cluster/findAll', { type: CIS.CLUSTER_SCAN_PROFILE }),
|
profiles: this.$store.dispatch('cluster/findAll', { type: CIS.CLUSTER_SCAN_PROFILE }),
|
||||||
benchmarks: this.$store.dispatch('cluster/findAll', { type: CIS.BENCHMARK }),
|
benchmarks: this.$store.dispatch('cluster/findAll', { type: CIS.BENCHMARK }),
|
||||||
defaultConfigMap: this.$store.dispatch('cluster/find', { type: CONFIG_MAP, id: 'cis-operator-system/default-clusterscanprofiles' })
|
defaultConfigMap: this.$store.dispatch('cluster/find', { type: CONFIG_MAP, id: 'cis-operator-system/default-clusterscanprofiles' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('cluster/find', { type: ENDPOINTS, id: 'cattle-monitoring-system/rancher-monitoring-alertmanager' });
|
||||||
|
|
||||||
|
this.hasAlertManager = true;
|
||||||
|
} catch {
|
||||||
|
this.hasAlertManager = false;
|
||||||
|
}
|
||||||
|
|
||||||
this.allProfiles = hash.profiles;
|
this.allProfiles = hash.profiles;
|
||||||
this.defaultConfigMap = hash.defaultConfigMap;
|
this.defaultConfigMap = hash.defaultConfigMap;
|
||||||
},
|
},
|
||||||
|
|
@ -44,18 +57,31 @@ export default {
|
||||||
if (!this.value.metadata.name) {
|
if (!this.value.metadata.name) {
|
||||||
this.value.metadata.generateName = 'scan-';
|
this.value.metadata.generateName = 'scan-';
|
||||||
}
|
}
|
||||||
if (!this.value.spec) {
|
|
||||||
this.value.spec = { scanProfileName: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allProfiles: [], defaultConfigMap: null, scanProfileName: this.value.spec.scanProfileName
|
allProfiles: [], defaultConfigMap: null, scanAlertRule: this.value.spec.scanAlertRule, hasAlertManager: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({ currentCluster: 'currentCluster', t: 'i18n/t' }),
|
...mapGetters({ currentCluster: 'currentCluster', t: 'i18n/t' }),
|
||||||
|
|
||||||
|
cronLabel() {
|
||||||
|
const { cronSchedule } = this.value.spec;
|
||||||
|
|
||||||
|
if (!cronSchedule) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const hint = cronstrue.toString(cronSchedule);
|
||||||
|
|
||||||
|
return hint;
|
||||||
|
} catch (e) {
|
||||||
|
return 'invalid cron expression';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
validProfiles() {
|
validProfiles() {
|
||||||
const profileNames = this.allProfiles.filter((profile) => {
|
const profileNames = this.allProfiles.filter((profile) => {
|
||||||
const benchmarkVersion = profile?.spec?.benchmarkVersion;
|
const benchmarkVersion = profile?.spec?.benchmarkVersion;
|
||||||
|
|
@ -82,13 +108,20 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
monitoringUrl() {
|
||||||
|
return this.$router.resolve({
|
||||||
|
name: 'c-cluster-monitoring',
|
||||||
|
params: { cluster: this.$route.params.cluster }
|
||||||
|
}).href;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
defaultProfile(neu) {
|
defaultProfile(neu) {
|
||||||
if (neu && !this.scanProfileName) {
|
if (neu && !this.value.spec.scanProfileName) {
|
||||||
this.scanProfileName = neu?.id;
|
this.value.spec.scanProfileName = neu?.id;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -122,26 +155,56 @@ export default {
|
||||||
|
|
||||||
<CruResource
|
<CruResource
|
||||||
v-else
|
v-else
|
||||||
:validation-passed="!!scanProfileName"
|
:validation-passed="!!value.spec.scanProfileName"
|
||||||
:done-route="doneRoute"
|
:done-route="doneRoute"
|
||||||
:resource="value"
|
:resource="value"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
|
:errors="errors"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
|
@error="e=>errors = e"
|
||||||
>
|
>
|
||||||
<template>
|
<template>
|
||||||
<Banner v-if="!validProfiles.length" color="warning" :label="t('cis.noProfiles')" />
|
<Banner v-if="!validProfiles.length" color="warning" :label="t('cis.noProfiles')" />
|
||||||
|
|
||||||
<div v-else class="row">
|
<div v-else class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-model="scanProfileName"
|
v-model="value.spec.scanProfileName"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:label="t('cis.profile')"
|
:label="t('cis.profile')"
|
||||||
:options="validProfiles"
|
:options="validProfiles"
|
||||||
@input="value.spec.scanProfileName = $event"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<h3>Scheduling</h3>
|
||||||
|
<div class="row mb-20">
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledInput v-model="value.spec.cronSchedule" :mode="mode" :label="t('cis.cronSchedule.label')" :placeholder="t('cis.cronSchedule.placeholder')" />
|
||||||
|
<span class="text-muted">{{ cronLabel }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledInput v-model.number="value.spec.retention" type="number" :mode="mode" :label="t('cis.retention')" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>
|
||||||
|
Alerting
|
||||||
|
</h3>
|
||||||
|
<div class="row mb-20">
|
||||||
|
<div class="col span-12">
|
||||||
|
<Banner v-if="scanAlertRule.alertOnFailure || scanAlertRule.alertOnComplete" class="mt-0" :color="hasAlertManager ? 'info' : 'warning'">
|
||||||
|
<span v-if="!hasAlertManager" v-html="t('cis.alertNotFound')" />
|
||||||
|
<span v-html="t('cis.alertNeeded', {link: monitoringUrl}, true)" />
|
||||||
|
</banner>
|
||||||
|
<Checkbox v-model="scanAlertRule.alertOnComplete" :label="t('cis.alertOnComplete')" />
|
||||||
|
<Checkbox v-model="scanAlertRule.alertOnFailure" :label="t('cis.alertOnFailure')" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<span>{{ t('cis.scoreWarning.label') }}</span> <i v-tooltip="t('cis.scoreWarning.protip')" class="icon icon-info" />
|
||||||
|
<RadioGroup v-model="value.spec.scoreWarning" name="scoreWarning" :options="['pass', 'fail']" :labels="[t('cis.scan.pass'), t('cis.scan.fail')]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</CruResource>
|
</CruResource>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,15 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<CruResource :validation-passed="!!name && !!value.spec.benchmarkVersion" :done-route="doneRoute" :resource="value" :mode="mode" @finish="save">
|
<CruResource
|
||||||
|
:validation-passed="!!name && !!value.spec.benchmarkVersion"
|
||||||
|
done-route="doneRoute"
|
||||||
|
:resource="value"
|
||||||
|
:mode="mode"
|
||||||
|
:errors="errors"
|
||||||
|
@finish="save"
|
||||||
|
@error="e=>errors = e"
|
||||||
|
>
|
||||||
<template>
|
<template>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,11 @@ export default {
|
||||||
return {
|
return {
|
||||||
selectedSeverityLabel: null,
|
selectedSeverityLabel: null,
|
||||||
ignoredAnnotations: INGORED_ANNOTATIONS,
|
ignoredAnnotations: INGORED_ANNOTATIONS,
|
||||||
|
severityOptions: [
|
||||||
|
this.t('prometheusRule.alertingRules.labels.severity.choices.critical'),
|
||||||
|
this.t('prometheusRule.alertingRules.labels.severity.choices.warning'),
|
||||||
|
this.t('prometheusRule.alertingRules.labels.severity.choices.none'),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -336,11 +341,7 @@ export default {
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:label="t('prometheusRule.alertingRules.labels.severity.choices.label')"
|
:label="t('prometheusRule.alertingRules.labels.severity.choices.label')"
|
||||||
:localized-label="false"
|
:localized-label="false"
|
||||||
:options="[
|
:options="severityOptions"
|
||||||
t('prometheusRule.alertingRules.labels.severity.choices.critical'),
|
|
||||||
t('prometheusRule.alertingRules.labels.severity.choices.warning'),
|
|
||||||
t('prometheusRule.alertingRules.labels.severity.choices.none'),
|
|
||||||
]"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -67,13 +67,13 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Banner v-if="value.isRoot" color="info">
|
<Banner v-if="value.isRoot" color="info">
|
||||||
This is the top-level Route used by Alertmanager as the default destination for any Alerts that do not match any other Routes. This Route must exist and cannot be deleted.
|
{{ t("monitoringRoute.info") }}
|
||||||
</Banner>
|
</Banner>
|
||||||
<Tabbed ref="tabbed" :side-tabs="true" default-tab="overview">
|
<Tabbed ref="tabbed" :side-tabs="true" default-tab="overview">
|
||||||
<Tab label="Receiver" :weight="2" name="receiver">
|
<Tab label="Receiver" :weight="2" name="receiver">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledSelect v-model="value.spec.receiver" :options="receiverOptions" label="Receiver" :mode="mode" />
|
<LabeledSelect v-model="value.spec.receiver" :options="receiverOptions" :label="t('monitoringRoute.receiver.label')" :mode="mode" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
@ -81,9 +81,9 @@ export default {
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<span class="label">
|
<span class="label">
|
||||||
Group By:
|
{{ t("monitoringRoute.groups.label") }}:
|
||||||
</span>
|
</span>
|
||||||
<ArrayList v-if="!isView || value.spec.group_by.length > 0" v-model="value.spec.group_by" label="Group By" :mode="mode" :initial-empty-row="true" />
|
<ArrayList v-if="!isView || value.spec.group_by.length > 0" v-model="value.spec.group_by" :label="t('monitoringRoute.groups.label')" :mode="mode" :initial-empty-row="true" />
|
||||||
<div v-else>
|
<div v-else>
|
||||||
{{ t('generic.none') }}
|
{{ t('generic.none') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -92,32 +92,33 @@ export default {
|
||||||
<hr class="divider" />
|
<hr class="divider" />
|
||||||
<div class="row mb-10">
|
<div class="row mb-10">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledInput v-model="value.spec.group_wait" label="Group Wait" :mode="mode" />
|
<LabeledInput v-model="value.spec.group_wait" :label="t('monitoringRoute.wait.label')" :mode="mode" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledInput v-model="value.spec.group_interval" label="Group Interval" :mode="mode" />
|
<LabeledInput v-model="value.spec.group_interval" :label="t('monitoringRoute.interval.label')" :mode="mode" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-10">
|
<div class="row mb-10">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledInput v-model="value.spec.repeat_interval" label="Repeat Interval" :mode="mode" />
|
<LabeledInput v-model="value.spec.repeat_interval" :label="t('monitoringRoute.repeatInterval.label')" :mode="mode" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab label="Matching" :weight="1" name="matching">
|
<Tab label="Matching" :weight="1" name="matching">
|
||||||
<Banner v-if="value.isRoot" color="info">
|
<Banner v-if="value.isRoot" color="info">
|
||||||
The root route has to match everything so matching can't be configured.
|
{{ t('monitoringRoute.matching.info') }}
|
||||||
</Banner>
|
</Banner>
|
||||||
<div v-else class="row">
|
<div v-else class="row">
|
||||||
<div class="col span-12">
|
<div class="col span-12">
|
||||||
<span class="label">
|
<span class="label">
|
||||||
Match:
|
{{ t('monitoringRoute.matching.label') }}
|
||||||
</span>
|
</span>
|
||||||
<KeyValue
|
<KeyValue
|
||||||
v-if="!isView || Object.keys(value.spec.match || {}).length > 0"
|
v-if="!isView || Object.keys(value.spec.match || {}).length > 0"
|
||||||
v-model="value.spec.match"
|
v-model="value.spec.match"
|
||||||
:disabled="value.isRoot"
|
:disabled="value.isRoot"
|
||||||
:options="receiverOptions"
|
:options="receiverOptions"
|
||||||
|
:label="t('monitoringRoute.receiver.label')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:read-allowed="false"
|
:read-allowed="false"
|
||||||
add-label="Add match"
|
add-label="Add match"
|
||||||
|
|
@ -130,13 +131,14 @@ export default {
|
||||||
<div class="row mt-40">
|
<div class="row mt-40">
|
||||||
<div class="col span-12">
|
<div class="col span-12">
|
||||||
<span class="label">
|
<span class="label">
|
||||||
Match Regex:
|
{{ t('monitoringRoute.regex.label') }}:
|
||||||
</span>
|
</span>
|
||||||
<KeyValue
|
<KeyValue
|
||||||
v-if="!isView || Object.keys(value.spec.match_re || {}).length > 0"
|
v-if="!isView || Object.keys(value.spec.match_re || {}).length > 0"
|
||||||
v-model="value.spec.match_re"
|
v-model="value.spec.match_re"
|
||||||
:disabled="value.isRoot"
|
:disabled="value.isRoot"
|
||||||
:options="receiverOptions"
|
:options="receiverOptions"
|
||||||
|
:label="t('monitoringRoute.receiver.label')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:read-allowed="false"
|
:read-allowed="false"
|
||||||
add-label="Add match regex"
|
add-label="Add match regex"
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ export default {
|
||||||
:extra-columns="extraColumns"
|
:extra-columns="extraColumns"
|
||||||
>
|
>
|
||||||
<template #project-col>
|
<template #project-col>
|
||||||
<LabeledSelect v-model="project" label="Project" :options="projectOpts" />
|
<LabeledSelect v-model="project" :label="t('namespace.project.label')" :options="projectOpts" />
|
||||||
</template>
|
</template>
|
||||||
</NameNsDescription>
|
</NameNsDescription>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import InputWithSelect from '@/components/form/InputWithSelect';
|
import InputWithSelect from '@/components/form/InputWithSelect';
|
||||||
import LabeledInput from '@/components/form/LabeledInput';
|
import LabeledInput from '@/components/form/LabeledInput';
|
||||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
import Select from '@/components/form/Select';
|
||||||
import { get, set } from '@/utils/object';
|
import { get, set } from '@/utils/object';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
InputWithSelect, LabeledInput, LabeledSelect
|
InputWithSelect, LabeledInput, Select
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
|
|
@ -53,6 +54,10 @@ export default {
|
||||||
return this.serviceTargetStatus === 'warning' ? this.t('ingress.rules.target.doesntExist') : null;
|
return this.serviceTargetStatus === 'warning' ? this.t('ingress.rules.target.doesntExist') : null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
this.queueUpdate = debounce(this.update, 500);
|
||||||
|
this.queueUpdatePathTypeAndPath = debounce(this.updatePathTypeAndPath, 500);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
update() {
|
update() {
|
||||||
const servicePort = Number.parseInt(this.servicePort) || this.servicePort;
|
const servicePort = Number.parseInt(this.servicePort) || this.servicePort;
|
||||||
|
|
@ -82,14 +87,14 @@ export default {
|
||||||
:placeholder="t('ingress.rules.path.placeholder', undefined, true)"
|
:placeholder="t('ingress.rules.path.placeholder', undefined, true)"
|
||||||
:select-value="pathType"
|
:select-value="pathType"
|
||||||
:text-value="path"
|
:text-value="path"
|
||||||
@input="updatePathTypeAndPath"
|
@input="queueUpdatePathTypeAndPath"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="col span-4">
|
<div v-else class="col span-4">
|
||||||
<input v-model="path" :placeholder="t('ingress.rules.path.placeholder', undefined, true)" @input="update" />
|
<input v-model="path" :placeholder="t('ingress.rules.path.placeholder', undefined, true)" @input="queueUpdate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col" :class="{'span-3': ingress.showPathType, 'span-4': !ingress.showPathType}">
|
<div class="col" :class="{'span-3': ingress.showPathType, 'span-4': !ingress.showPathType}">
|
||||||
<LabeledSelect
|
<Select
|
||||||
v-model="serviceName"
|
v-model="serviceName"
|
||||||
option-label="label"
|
option-label="label"
|
||||||
option-key="label"
|
option-key="label"
|
||||||
|
|
@ -98,7 +103,7 @@ export default {
|
||||||
:taggable="true"
|
:taggable="true"
|
||||||
:tooltip="serviceTargetTooltip"
|
:tooltip="serviceTargetTooltip"
|
||||||
:hover-tooltip="true"
|
:hover-tooltip="true"
|
||||||
@input="update(); servicePort = ''"
|
@input="queueUpdate(); servicePort = ''"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col" :class="{'span-2': ingress.showPathType, 'span-3': !ingress.showPathType}" :style="{'margin-right': '0px'}">
|
<div class="col" :class="{'span-2': ingress.showPathType, 'span-3': !ingress.showPathType}" :style="{'margin-right': '0px'}">
|
||||||
|
|
@ -106,14 +111,14 @@ export default {
|
||||||
v-if="portOptions.length === 0"
|
v-if="portOptions.length === 0"
|
||||||
v-model="servicePort"
|
v-model="servicePort"
|
||||||
:placeholder="t('ingress.rules.port.placeholder')"
|
:placeholder="t('ingress.rules.port.placeholder')"
|
||||||
@input="update"
|
@input="queueUpdate"
|
||||||
/>
|
/>
|
||||||
<LabeledSelect
|
<Select
|
||||||
v-else
|
v-else
|
||||||
v-model="servicePort"
|
v-model="servicePort"
|
||||||
:options="portOptions"
|
:options="portOptions"
|
||||||
:placeholder="t('ingress.rules.port.placeholder')"
|
:placeholder="t('ingress.rules.port.placeholder')"
|
||||||
@input="update"
|
@input="queueUpdate"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-sm role-link col" @click="$emit('remove')">
|
<button class="btn btn-sm role-link col" @click="$emit('remove')">
|
||||||
|
|
@ -122,12 +127,16 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.rule-path {
|
.rule-path ::v-deep {
|
||||||
button {
|
button {
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
input {
|
|
||||||
height: 55px;
|
.v-select INPUT {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
.labeled-input {
|
||||||
|
padding-top: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,7 @@ export default {
|
||||||
<Certificates v-model="value" :mode="mode" :secrets="allSecrets" />
|
<Certificates v-model="value" :mode="mode" :secrets="allSecrets" />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
|
v-if="!isView"
|
||||||
name="labels-and-annotations"
|
name="labels-and-annotations"
|
||||||
:label="t('generic.labelsAndAnnotations')"
|
:label="t('generic.labelsAndAnnotations')"
|
||||||
:weight="0"
|
:weight="0"
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { RIO } from '@/config/types';
|
import { RIO } from '@/config/types';
|
||||||
import { _VIEW } from '@/config/query-params';
|
import { _VIEW } from '@/config/query-params';
|
||||||
|
import Select from '@/components/form/Select';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { Select },
|
||||||
props: {
|
props: {
|
||||||
mode: {
|
mode: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -122,7 +124,7 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<v-select
|
<Select
|
||||||
class="inline"
|
class="inline"
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
:value="app"
|
:value="app"
|
||||||
|
|
@ -130,10 +132,10 @@ export default {
|
||||||
:placeholder="showPlaceholders ? placeholders[0] : null"
|
:placeholder="showPlaceholders ? placeholders[0] : null"
|
||||||
:disabled="isView"
|
:disabled="isView"
|
||||||
@input="setApp"
|
@input="setApp"
|
||||||
></v-select>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="pickVersion">
|
<td v-if="pickVersion">
|
||||||
<v-select
|
<Select
|
||||||
v-model="version"
|
v-model="version"
|
||||||
class="inline"
|
class="inline"
|
||||||
:options="versions"
|
:options="versions"
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import LabeledInput from '@/components/form/LabeledInput';
|
import LabeledInput from '@/components/form/LabeledInput';
|
||||||
|
import Select from '@/components/form/Select';
|
||||||
export default {
|
export default {
|
||||||
components: { LabeledInput },
|
components: { LabeledInput, Select },
|
||||||
props: {
|
props: {
|
||||||
spec: {
|
spec: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
@ -38,7 +39,10 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { all };
|
return {
|
||||||
|
all,
|
||||||
|
ruleOperatorOptions: ['add', 'set', 'remove']
|
||||||
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
format() {
|
format() {
|
||||||
|
|
@ -84,7 +88,7 @@ export default {
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(rule, i) in all" :key="i">
|
<tr v-for="(rule, i) in all" :key="i">
|
||||||
<td>
|
<td>
|
||||||
<v-select v-model="rule.op" :disabled="!enabled" :serachable="false" class="inline" :options="['add', 'set', 'remove']" />
|
<Select v-model="rule.op" :disabled="!enabled" :serachable="false" class="inline" :options="ruleOperatorOptions" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<LabeledInput v-model="rule.name" />
|
<LabeledInput v-model="rule.name" />
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ import debounce from 'lodash/debounce';
|
||||||
import { _EDIT, _VIEW } from '@/config/query-params';
|
import { _EDIT, _VIEW } from '@/config/query-params';
|
||||||
import { removeAt, sameContents } from '@/utils/array';
|
import { removeAt, sameContents } from '@/utils/array';
|
||||||
import { clone } from '@/utils/object';
|
import { clone } from '@/utils/object';
|
||||||
|
import Select from '@/components/form/Select';
|
||||||
|
|
||||||
const READ_VERBS = ['get', 'list', 'watch'];
|
const READ_VERBS = ['get', 'list', 'watch'];
|
||||||
const WRITE_VERBS = ['create', 'delete', 'get', 'list', 'patch', 'update', 'watch'];
|
const WRITE_VERBS = ['create', 'delete', 'get', 'list', 'patch', 'update', 'watch'];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
components: { Select },
|
||||||
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: null,
|
default: null,
|
||||||
|
|
@ -160,7 +162,7 @@ export default {
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<td class="verbs">
|
<td class="verbs">
|
||||||
<span v-if="isView">{{ row.verbs.join(',') }}</span>
|
<span v-if="isView">{{ row.verbs.join(',') }}</span>
|
||||||
<v-select
|
<Select
|
||||||
v-else
|
v-else
|
||||||
ref="verbs"
|
ref="verbs"
|
||||||
v-model="row.verbs"
|
v-model="row.verbs"
|
||||||
|
|
|
||||||
|
|
@ -262,7 +262,7 @@ export default {
|
||||||
|
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<ResourceTabs v-if="!isView" v-model="value" :side-tabs="true" :mode="mode">
|
<ResourceTabs v-if="!isView" v-model="value" :side-tabs="true" :mode="mode">
|
||||||
<Tab name="data" label="Data">
|
<Tab name="data" :label="t('secret.data')">
|
||||||
<template v-if="isRegistry">
|
<template v-if="isRegistry">
|
||||||
<div id="registry-type" class="row mb-10">
|
<div id="registry-type" class="row mb-10">
|
||||||
<div class="col span-12">
|
<div class="col span-12">
|
||||||
|
|
@ -348,7 +348,7 @@ export default {
|
||||||
key="data"
|
key="data"
|
||||||
v-model="value.data"
|
v-model="value.data"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
title="Data"
|
:title="t('secret.data')"
|
||||||
:initial-empty-row="true"
|
:initial-empty-row="true"
|
||||||
:value-base64="true"
|
:value-base64="true"
|
||||||
:file-modifier="fileModifier"
|
:file-modifier="fileModifier"
|
||||||
|
|
|
||||||
|
|
@ -142,15 +142,16 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: this.value?.metadata?.name || null,
|
allConfigMaps: [],
|
||||||
|
allNodes: null,
|
||||||
|
allSecrets: [],
|
||||||
|
allServices: [],
|
||||||
|
name: this.value?.metadata?.name || null,
|
||||||
|
pvcs: [],
|
||||||
|
showTabs: false,
|
||||||
|
pullPolicyOptions: ['Always', 'IfNotPresent', 'Never'],
|
||||||
spec,
|
spec,
|
||||||
type,
|
type,
|
||||||
allConfigMaps: [],
|
|
||||||
allSecrets: [],
|
|
||||||
allServices: [],
|
|
||||||
pvcs: [],
|
|
||||||
allNodes: null,
|
|
||||||
showTabs: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -640,7 +641,7 @@ export default {
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-model="container.imagePullPolicy"
|
v-model="container.imagePullPolicy"
|
||||||
:label="t('workload.container.imagePullPolicy')"
|
:label="t('workload.container.imagePullPolicy')"
|
||||||
:options="['Always', 'IfNotPresent', 'Never']"
|
:options="pullPolicyOptions"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export default {
|
||||||
<div>
|
<div>
|
||||||
<div class="row mb-10">
|
<div class="row mb-10">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledSelect v-model="value.csi.driver" :mode="mode" label="Driver" :options="driverOpts" :get-option-label="opt=>t(`workload.storage.csi.drivers.'${opt}'`)" />
|
<LabeledSelect v-model="value.csi.driver" :mode="mode" :label="t('workload.storage.driver')" :options="driverOpts" :get-option-label="opt=>t(`workload.storage.csi.drivers.'${opt}'`)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="driverComponent" class="mb-10">
|
<div v-if="driverComponent" class="mb-10">
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ export default {
|
||||||
<h3>{{ t('workload.storage.subtypes.csi') }}</h3>
|
<h3>{{ t('workload.storage.subtypes.csi') }}</h3>
|
||||||
<div class="row mb-10">
|
<div class="row mb-10">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledSelect v-model="value.csi.driver" :mode="mode" label="Driver" :options="driverOpts" :get-option-label="opt=>t(`workload.storage.csi.drivers.'${opt}'`)" />
|
<LabeledSelect v-model="value.csi.driver" :mode="mode" :label="t('workload.storage.driver')" :options="driverOpts" :get-option-label="opt=>t(`workload.storage.csi.drivers.'${opt}'`)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="value.csi.driver && driverComponent" class="mb-10">
|
<div v-if="value.csi.driver && driverComponent" class="mb-10">
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@ export default {
|
||||||
ButtonDropdown, Mount, CodeMirror, InfoBox
|
ButtonDropdown, Mount, CodeMirror, InfoBox
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
mode: {
|
mode: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'create'
|
default: 'create',
|
||||||
},
|
},
|
||||||
|
|
||||||
// pod spec
|
// pod spec
|
||||||
|
|
@ -24,23 +24,23 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {
|
default: () => {
|
||||||
return {};
|
return {};
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
namespace: {
|
namespace: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
// namespaced configmaps and secrets
|
// namespaced configmaps and secrets
|
||||||
configMaps: {
|
configMaps: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
|
||||||
secrets: {
|
secrets: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
|
||||||
registerBeforeHook: {
|
registerBeforeHook: {
|
||||||
|
|
@ -66,17 +66,53 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
opts() {
|
opts() {
|
||||||
const hasComponent = require.context('@/edit/workload/storage', false, /^.*\.vue$/).keys()
|
const hasComponent = require
|
||||||
|
.context('@/edit/workload/storage', false, /^.*\.vue$/)
|
||||||
|
.keys()
|
||||||
.map(path => path.replace(/(\.\/)|(.vue)/g, ''))
|
.map(path => path.replace(/(\.\/)|(.vue)/g, ''))
|
||||||
.filter(file => file !== 'index' && file !== 'Mount' && file !== 'PVC');
|
.filter(
|
||||||
|
file => file !== 'index' && file !== 'Mount' && file !== 'PVC'
|
||||||
|
);
|
||||||
|
|
||||||
const out = [...hasComponent, 'csi', 'configMap', 'createPVC', 'persistentVolumeClaim'];
|
const out = [
|
||||||
|
...hasComponent,
|
||||||
|
'csi',
|
||||||
|
'configMap',
|
||||||
|
'createPVC',
|
||||||
|
'persistentVolumeClaim',
|
||||||
|
];
|
||||||
|
|
||||||
out.sort();
|
out.sort();
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
opts2() {
|
||||||
|
const hasComponent = require
|
||||||
|
.context('@/edit/workload/storage', false, /^.*\.vue$/)
|
||||||
|
.keys()
|
||||||
|
.map(path => path.replace(/(\.\/)|(.vue)/g, ''))
|
||||||
|
.filter(
|
||||||
|
file => file !== 'index' && file !== 'Mount' && file !== 'PVC'
|
||||||
|
);
|
||||||
|
|
||||||
|
const out = [
|
||||||
|
...hasComponent,
|
||||||
|
'csi',
|
||||||
|
'configMap',
|
||||||
|
'createPVC',
|
||||||
|
'persistentVolumeClaim',
|
||||||
|
];
|
||||||
|
|
||||||
|
out.sort();
|
||||||
|
|
||||||
|
return out.map(opt => ({
|
||||||
|
label: opt,
|
||||||
|
action: this.addVolume,
|
||||||
|
value: opt,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
pvcNames() {
|
pvcNames() {
|
||||||
return this.pvcs.map(pvc => pvc.metadata.name);
|
return this.pvcs.map(pvc => pvc.metadata.name);
|
||||||
},
|
},
|
||||||
|
|
@ -97,15 +133,21 @@ export default {
|
||||||
addVolume(type) {
|
addVolume(type) {
|
||||||
if (type === 'createPVC') {
|
if (type === 'createPVC') {
|
||||||
this.value.volumes.push({
|
this.value.volumes.push({
|
||||||
_type: 'createPVC', persistentVolumeClaim: {}, name: `vol${ this.value.volumes.length }`
|
_type: 'createPVC',
|
||||||
|
persistentVolumeClaim: {},
|
||||||
|
name: `vol${ this.value.volumes.length }`,
|
||||||
});
|
});
|
||||||
} else if ( type === 'csi' ) {
|
} else if (type === 'csi') {
|
||||||
this.value.volumes.push({
|
this.value.volumes.push({
|
||||||
_type: type, csi: { volumeAttributes: {} }, name: `vol${ this.value.volumes.length }`
|
_type: type,
|
||||||
|
csi: { volumeAttributes: {} },
|
||||||
|
name: `vol${ this.value.volumes.length }`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.value.volumes.push({
|
this.value.volumes.push({
|
||||||
_type: type, [type]: {}, name: `vol${ this.value.volumes.length }`
|
_type: type,
|
||||||
|
[type]: {},
|
||||||
|
name: `vol${ this.value.volumes.length }`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -115,7 +157,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
volumeType(vol) {
|
volumeType(vol) {
|
||||||
const type = Object.keys(vol).filter(key => typeof vol[key] === 'object')[0];
|
const type = Object.keys(vol).filter(
|
||||||
|
key => typeof vol[key] === 'object'
|
||||||
|
)[0];
|
||||||
|
|
||||||
return type;
|
return type;
|
||||||
},
|
},
|
||||||
|
|
@ -127,7 +171,8 @@ export default {
|
||||||
return require(`@/edit/workload/storage/secret.vue`).default;
|
return require(`@/edit/workload/storage/secret.vue`).default;
|
||||||
case 'createPVC':
|
case 'createPVC':
|
||||||
case 'persistentVolumeClaim':
|
case 'persistentVolumeClaim':
|
||||||
return require(`@/edit/workload/storage/persistentVolumeClaim/index.vue`).default;
|
return require(`@/edit/workload/storage/persistentVolumeClaim/index.vue`)
|
||||||
|
.default;
|
||||||
case 'csi':
|
case 'csi':
|
||||||
return require(`@/edit/workload/storage/csi/index.vue`).default;
|
return require(`@/edit/workload/storage/csi/index.vue`).default;
|
||||||
default: {
|
default: {
|
||||||
|
|
@ -135,8 +180,7 @@ export default {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
component = require(`@/edit/workload/storage/${ type }.vue`).default;
|
component = require(`@/edit/workload/storage/${ type }.vue`).default;
|
||||||
} catch {
|
} catch {}
|
||||||
}
|
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +188,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
headerFor(type) {
|
headerFor(type) {
|
||||||
if (this.$store.getters['i18n/exists'](`workload.storage.subtypes.${ type }`)) {
|
if (
|
||||||
|
this.$store.getters['i18n/exists'](`workload.storage.subtypes.${ type }`)
|
||||||
|
) {
|
||||||
return this.t(`workload.storage.subtypes.${ type }`);
|
return this.t(`workload.storage.subtypes.${ type }`);
|
||||||
} else {
|
} else {
|
||||||
return type;
|
return type;
|
||||||
|
|
@ -164,17 +210,16 @@ export default {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
button.togglePopover();
|
button.togglePopover();
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// codemirror needs to refresh if it is in a tab that wasn't visible on page load
|
// codemirror needs to refresh if it is in a tab that wasn't visible on page load
|
||||||
refresh() {
|
refresh() {
|
||||||
if ( this.$refs.cm ) {
|
if (this.$refs.cm) {
|
||||||
this.$refs.cm.forEach(component => component.refresh());
|
this.$refs.cm.forEach(component => component.refresh());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -203,7 +248,7 @@ export default {
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
ref="cm"
|
ref="cm"
|
||||||
:value="yamlDisplay(volume)"
|
:value="yamlDisplay(volume)"
|
||||||
:options="{readOnly:true, cursorBlinkRate:-1}"
|
:options="{ readOnly: true, cursorBlinkRate: -1 }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -212,30 +257,24 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<ButtonDropdown v-if="mode!=='view'" ref="buttonDropdown" size="sm">
|
<ButtonDropdown
|
||||||
<template #button-content>
|
:button-label="t('workload.storage.addVolume')"
|
||||||
<button v-if="mode!=='view'" type="button" class="btn btn-sm text-primary bg-transparent" @click="openPopover">
|
:dropdown-options="opts"
|
||||||
{{ t('workload.storage.addVolume') }}
|
size="sm"
|
||||||
</button>
|
@click-action="addVolume"
|
||||||
</template>
|
/>
|
||||||
<template #popover-content>
|
|
||||||
<ul class="list-unstyled menu">
|
|
||||||
<li v-for="opt in opts" :key="opt" v-close-popover @click="addVolume(opt)">
|
|
||||||
{{ t(`workload.storage.subtypes.${opt}`) }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
</ButtonDropdown>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='scss' scoped>
|
<style lang='scss' scoped>
|
||||||
.volume-source{
|
.volume-source {
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0px 20px 0px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
::v-deep .code-mirror {
|
::v-deep .code-mirror {
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
background-color: var(--yaml-editor-bg);
|
background-color: var(--yaml-editor-bg);
|
||||||
& .CodeMirror-gutters {
|
& .CodeMirror-gutters {
|
||||||
|
|
@ -249,12 +288,11 @@ export default {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
padding:0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-vol:focus{
|
.add-vol:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,23 @@ export default {
|
||||||
notView() {
|
notView() {
|
||||||
return (this.mode !== _VIEW);
|
return (this.mode !== _VIEW);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isSearchable() {
|
||||||
|
const { searchable } = this;
|
||||||
|
const options = ( this.options || [] );
|
||||||
|
|
||||||
|
if (searchable || options.length >= 10) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onFocus() {
|
onFocus() {
|
||||||
|
this.$emit('on-focus');
|
||||||
|
|
||||||
return this.onFocusLabeled();
|
return this.onFocusLabeled();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -67,6 +80,8 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
onBlur() {
|
onBlur() {
|
||||||
|
this.$emit('on-blur');
|
||||||
|
|
||||||
return this.onBlurLabeled();
|
return this.onBlurLabeled();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
mappedKeys(map, vm) {
|
||||||
|
// Defaults found at - https://github.com/sagalbot/vue-select/blob/master/src/components/Select.vue#L947
|
||||||
|
const out = { ...map };
|
||||||
|
|
||||||
|
// tab
|
||||||
|
(out[9] = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const optsLen = vm.filteredOptions.length;
|
||||||
|
const typeAheadPointer = vm.typeAheadPointer;
|
||||||
|
|
||||||
|
if (e.shiftKey) {
|
||||||
|
if (typeAheadPointer === 0) {
|
||||||
|
vm.$refs.search.focus();
|
||||||
|
|
||||||
|
return vm.onEscape();
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm.typeAheadUp();
|
||||||
|
}
|
||||||
|
if (typeAheadPointer + 1 === optsLen) {
|
||||||
|
$(vm.$el).next().focus();
|
||||||
|
|
||||||
|
return vm.onEscape();
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm.typeAheadDown();
|
||||||
|
});
|
||||||
|
|
||||||
|
return out;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import { CIS } from '@/config/types';
|
import { CIS } from '@/config/types';
|
||||||
import { downloadFile } from '@/utils/download';
|
import { findBy } from '@/utils/array';
|
||||||
|
import { downloadFile, generateZip } from '@/utils/download';
|
||||||
|
import { isEmpty, set } from '@/utils/object';
|
||||||
|
import { sortBy } from '@/utils/sort';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
_availableActions() {
|
_availableActions() {
|
||||||
this.getReport();
|
|
||||||
let out = this._standardActions;
|
let out = this._standardActions;
|
||||||
|
|
||||||
const toFilter = ['cloneYaml', 'goToEditYaml', 'download'];
|
const toFilter = ['cloneYaml', 'goToEditYaml', 'download'];
|
||||||
|
|
@ -14,36 +16,67 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const t = this.$rootGetters['i18n/t'];
|
||||||
|
|
||||||
const downloadReport = {
|
const downloadReport = {
|
||||||
action: 'downloadReport',
|
action: 'downloadLatestReport',
|
||||||
enabled: this.hasReport,
|
enabled: this.hasReport,
|
||||||
icon: 'icon icon-fw icon-chevron-right',
|
icon: 'icon icon-fw icon-download',
|
||||||
label: 'Download Report',
|
label: t('cis.downloadLatestReport'),
|
||||||
total: 1,
|
total: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
out.unshift({ divider: true });
|
const downloadAllReports = {
|
||||||
out.unshift(downloadReport);
|
action: 'downloadAllReports',
|
||||||
|
enabled: this.hasReport,
|
||||||
|
icon: 'icon icon-fw icon-download',
|
||||||
|
label: t('cis.downloadAllReports'),
|
||||||
|
total: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.hasReports) {
|
||||||
|
out.unshift({ divider: true });
|
||||||
|
if (this.spec?.cronSchedule) {
|
||||||
|
out.unshift(downloadAllReports);
|
||||||
|
}
|
||||||
|
out.unshift(downloadReport);
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
},
|
},
|
||||||
|
|
||||||
hasReport: false,
|
applyDefaults() {
|
||||||
|
return () => {
|
||||||
|
const spec = this.spec || {};
|
||||||
|
|
||||||
getReport() {
|
spec.scanProfileName = null;
|
||||||
return async() => {
|
spec.scanAlertRule = {};
|
||||||
const owned = await this.getOwned();
|
spec.scoreWarning = 'pass';
|
||||||
const reportCRD = owned.filter(each => each.type === CIS.REPORT)[0];
|
set(this, 'spec', spec);
|
||||||
|
|
||||||
this.hasReport = !!reportCRD;
|
|
||||||
|
|
||||||
return reportCRD;
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
downloadReport() {
|
hasReports() {
|
||||||
|
const { relationships = [] } = this.metadata;
|
||||||
|
|
||||||
|
const reportRel = findBy(relationships, 'toType', CIS.REPORT);
|
||||||
|
|
||||||
|
return !!reportRel;
|
||||||
|
},
|
||||||
|
|
||||||
|
getReports() {
|
||||||
return async() => {
|
return async() => {
|
||||||
const report = await this.getReport();
|
const owned = await this.getOwned();
|
||||||
|
const reportCRDs = owned.filter(each => each.type === CIS.REPORT);
|
||||||
|
|
||||||
|
return reportCRDs;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
downloadLatestReport() {
|
||||||
|
return async() => {
|
||||||
|
const reports = await this.getReports() || [];
|
||||||
|
const report = sortBy(reports, 'metadata.creationTimestamp', true)[0];
|
||||||
const Papa = await import(/* webpackChunkName: "cis" */'papaparse');
|
const Papa = await import(/* webpackChunkName: "cis" */'papaparse');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -56,4 +89,29 @@ export default {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
downloadAllReports() {
|
||||||
|
return async() => {
|
||||||
|
const toZip = {};
|
||||||
|
const reports = await this.getReports() || [];
|
||||||
|
const Papa = await import(/* webpackChunkName: "cis" */'papaparse');
|
||||||
|
|
||||||
|
reports.forEach((report) => {
|
||||||
|
try {
|
||||||
|
const testResults = report.aggregatedTests;
|
||||||
|
const csv = Papa.unparse(testResults);
|
||||||
|
|
||||||
|
toZip[`${ report.id }.csv`] = csv;
|
||||||
|
} catch (err) {
|
||||||
|
this.$dispatch('growl/fromError', { title: 'Error downloading file', err }, { root: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!isEmpty(toZip)) {
|
||||||
|
generateZip(toZip).then((zip) => {
|
||||||
|
downloadFile(`${ this.id }-reports`, zip, 'application/zip');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
export default {
|
||||||
|
warnDeletionMessage() {
|
||||||
|
return (toRemove = []) => {
|
||||||
|
return this.$rootGetters['i18n/t']('cis.deleteProfileWarning', { count: toRemove.length });
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
numberTestsSkipped() {
|
||||||
|
const { skipTests = [] } = this.spec;
|
||||||
|
|
||||||
|
return skipTests.length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -803,7 +803,7 @@ export default {
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
label="Chart"
|
:label="t('catalog.install.chart')"
|
||||||
:value="chart"
|
:value="chart"
|
||||||
:options="charts"
|
:options="charts"
|
||||||
:get-option-label="opt => getOptionLabel(opt)"
|
:get-option-label="opt => getOptionLabel(opt)"
|
||||||
|
|
@ -813,7 +813,7 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
<div v-if="chart" class="col span-6">
|
<div v-if="chart" class="col span-6">
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
label="Version"
|
:label="t('catalog.install.version')"
|
||||||
:value="$route.query.version"
|
:value="$route.query.version"
|
||||||
option-label="version"
|
option-label="version"
|
||||||
option-key="version"
|
option-key="version"
|
||||||
|
|
|
||||||
|
|
@ -779,7 +779,7 @@ export const getters = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
headersFor(state) {
|
headersFor(state, getters, rootState, rootGetters) {
|
||||||
return (schema) => {
|
return (schema) => {
|
||||||
const attributes = schema.attributes || {};
|
const attributes = schema.attributes || {};
|
||||||
const columns = attributes.columns || [];
|
const columns = attributes.columns || [];
|
||||||
|
|
@ -790,7 +790,7 @@ export const getters = {
|
||||||
if ( typeof entry === 'string' ) {
|
if ( typeof entry === 'string' ) {
|
||||||
const col = findBy(columns, 'name', entry);
|
const col = findBy(columns, 'name', entry);
|
||||||
if ( col ) {
|
if ( col ) {
|
||||||
return fromSchema(col);
|
return fromSchema(col, rootGetters);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -812,7 +812,7 @@ export const getters = {
|
||||||
out.push(NAMESPACE);
|
out.push(NAMESPACE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
out.push(fromSchema(col));
|
out.push(fromSchema(col, rootGetters));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -831,7 +831,7 @@ export const getters = {
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
|
|
||||||
function fromSchema(col) {
|
function fromSchema(col, rootGetters) {
|
||||||
let formatter, width, formatterOpts;
|
let formatter, width, formatterOpts;
|
||||||
|
|
||||||
if ( (col.format === '' || col.format == 'date') && col.name === 'Age' ) {
|
if ( (col.format === '' || col.format == 'date') && col.name === 'Age' ) {
|
||||||
|
|
@ -848,9 +848,13 @@ export const getters = {
|
||||||
formatter = 'Number';
|
formatter = 'Number';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const exists = rootGetters['i18n/exists']
|
||||||
|
const t = rootGetters['i18n/t']
|
||||||
|
const labelKey = `tableHeaders.${col.name}`
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: col.name.toLowerCase(),
|
name: col.name.toLowerCase(),
|
||||||
label: col.name,
|
label: exists(labelKey) ? t(labelKey) : col.name,
|
||||||
value: col.field.startsWith('.') ? `$${ col.field }` : col.field,
|
value: col.field.startsWith('.') ? `$${ col.field }` : col.field,
|
||||||
sort: [col.field],
|
sort: [col.field],
|
||||||
formatter,
|
formatter,
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,13 @@ export async function downloadFile(fileName, content, contentType = 'text/plain;
|
||||||
return saveAs(blob, fileName);
|
return saveAs(blob, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// [{name: 'file1', file: 'data'}, {name: 'file2', file: 'data2'}]
|
// {[fileName1]:data1, [fileName2]:data2}
|
||||||
export function generateZip(files) {
|
export function generateZip(files) {
|
||||||
// Moving this to a dynamic const JSZip = import('jszip') didn't work... figure out later
|
// Moving this to a dynamic const JSZip = import('jszip') didn't work... figure out later
|
||||||
const zip = new JSZip();
|
const zip = new JSZip();
|
||||||
|
|
||||||
for ( const fileName in files) {
|
for ( const fileName in files) {
|
||||||
const file = files[fileName];
|
zip.file(fileName, files[fileName]);
|
||||||
|
|
||||||
zip.file(fileName, file.data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return zip.generateAsync({ type: 'blob' }).then((contents) => {
|
return zip.generateAsync({ type: 'blob' }).then((contents) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue