@rancher/shell 3.0.1 → 3.0.2-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/styles/base/_basic.scss +17 -5
- package/assets/styles/base/_mixins.scss +2 -1
- package/assets/styles/global/_button.scss +10 -0
- package/assets/styles/global/_form.scss +2 -2
- package/assets/translations/en-us.yaml +33 -5
- package/assets/translations/zh-hans.yaml +1 -1
- package/components/ActionMenu.vue +8 -0
- package/components/AsyncButton.vue +9 -3
- package/components/BannerGraphic.vue +10 -0
- package/components/ButtonGroup.vue +2 -0
- package/components/ButtonMultiAction.vue +6 -0
- package/components/ClusterIconMenu.vue +1 -1
- package/components/CodeMirror.vue +28 -1
- package/components/CommunityLinks.vue +13 -0
- package/components/CruResource.vue +6 -0
- package/components/GrowlManager.vue +14 -4
- package/components/LocaleSelector.vue +49 -5
- package/components/PaginatedResourceTable.vue +4 -3
- package/components/ResourceDetail/Masthead.vue +11 -4
- package/components/ResourceList/index.vue +5 -3
- package/components/ResourceTable.vue +1 -1
- package/components/SortableTable/THead.vue +19 -4
- package/components/SortableTable/index.vue +13 -9
- package/components/SortableTable/selection.js +19 -5
- package/components/YamlEditor.vue +2 -1
- package/components/auth/SelectPrincipal.vue +1 -1
- package/components/fleet/FleetBundles.vue +2 -1
- package/components/form/LabeledSelect.vue +20 -7
- package/components/form/NodeScheduling.vue +5 -1
- package/components/form/Password.vue +23 -13
- package/components/form/ResourceLabeledSelect.vue +1 -1
- package/components/form/Select.vue +28 -6
- package/components/form/SelectOrCreateAuthSecret.vue +39 -11
- package/components/form/__tests__/NodeScheduling.test.ts +44 -0
- package/components/formatter/Endpoints.vue +1 -1
- package/components/formatter/LiveExpiryDate.vue +5 -1
- package/components/formatter/ServiceTargets.vue +1 -1
- package/components/formatter/ServiceType.vue +19 -17
- package/components/nav/Pinned.vue +6 -1
- package/components/nav/TopLevelMenu.helper.ts +17 -1
- package/components/nav/TopLevelMenu.vue +154 -19
- package/config/pagination-table-headers.js +9 -1
- package/config/product/apps.js +63 -30
- package/config/product/explorer.js +182 -17
- package/config/product/settings.js +9 -1
- package/config/router/routes.js +0 -1
- package/config/settings.ts +20 -2
- package/config/table-headers.js +23 -15
- package/config/types.js +2 -1
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +12 -3
- package/edit/__tests__/management.cattle.io.setting.test.ts +37 -18
- package/edit/fleet.cattle.io.gitrepo.vue +40 -33
- package/edit/management.cattle.io.setting.vue +2 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +13 -2
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +10 -2
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +8 -2
- package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +6 -3
- package/edit/workload/mixins/workload.js +15 -7
- package/list/catalog.cattle.io.app.vue +4 -11
- package/list/catalog.cattle.io.clusterrepo.vue +59 -25
- package/list/fleet.cattle.io.bundle.vue +2 -2
- package/list/management.cattle.io.feature.vue +12 -5
- package/list/management.cattle.io.setting.vue +30 -19
- package/list/namespace.vue +4 -1
- package/list/networking.k8s.io.ingress.vue +14 -11
- package/list/node.vue +65 -63
- package/list/persistentvolume.vue +55 -20
- package/list/persistentvolumeclaim.vue +3 -15
- package/list/service.vue +16 -21
- package/list/workload.vue +35 -49
- package/mixins/resource-fetch.js +8 -1
- package/mixins/vue-select-overrides.js +10 -16
- package/models/management.cattle.io.cluster.js +6 -1
- package/models/persistentvolume.js +1 -3
- package/models/storage.k8s.io.storageclass.js +4 -0
- package/package.json +29 -29
- package/pages/c/_cluster/explorer/EventsTable.vue +58 -16
- package/pages/c/_cluster/explorer/index.vue +3 -16
- package/pages/c/_cluster/settings/performance.vue +49 -23
- package/pages/home.vue +24 -3
- package/pages/support/index.vue +1 -1
- package/plugins/floating-vue.js +1 -1
- package/plugins/steve/steve-pagination-utils.ts +85 -15
- package/rancher-components/Banner/Banner.vue +12 -0
- package/rancher-components/Form/Checkbox/Checkbox.vue +27 -5
- package/rancher-components/Form/Radio/RadioButton.vue +0 -6
- package/rancher-components/Form/Radio/RadioGroup.vue +5 -1
- package/scripts/.gitlab/workflows/build-extension-catalog.gitlab-ci.yml +2 -2
- package/scripts/test-plugins-build.sh +21 -6
- package/scripts/typegen.sh +1 -0
- package/store/index.js +16 -0
- package/store/type-map.utils.ts +14 -1
- package/types/shell/index.d.ts +467 -418
- package/types/store/vuex.d.ts +1 -1
- package/types/vue-shim.d.ts +2 -8
- package/utils/cluster.js +2 -2
- package/utils/string.js +6 -0
- package/vue.config.js +3 -4
|
@@ -54,8 +54,10 @@ export default {
|
|
|
54
54
|
this.customTypeDisplay = component.typeDisplay.apply(this);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
//
|
|
58
|
-
|
|
57
|
+
// Is the custom component responsible fetching the resources?
|
|
58
|
+
// - Component has a fetch method - legacy method. fetch will handle the requests
|
|
59
|
+
// - Component contains the PaginatedResourceTable component - go forward method. PaginatedResourceTable owns fetching the resources
|
|
60
|
+
if ( component?.fetch || component?.components?.['PaginatedResourceTable']) {
|
|
59
61
|
this.componentWillFetch = true;
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -269,7 +271,7 @@ export default {
|
|
|
269
271
|
v-else
|
|
270
272
|
:schema="schema"
|
|
271
273
|
:rows="rows"
|
|
272
|
-
:alt-loading="canPaginate"
|
|
274
|
+
:alt-loading="canPaginate && !isFirstLoad"
|
|
273
275
|
:loading="loading"
|
|
274
276
|
:headers="headers"
|
|
275
277
|
:group-by="groupBy"
|
|
@@ -167,7 +167,7 @@ export default {
|
|
|
167
167
|
default: false
|
|
168
168
|
},
|
|
169
169
|
/**
|
|
170
|
-
*
|
|
170
|
+
* Manual force the update of live and delayed cells. Change this number to kick off the update
|
|
171
171
|
*/
|
|
172
172
|
forceUpdateLiveAndDelayed: {
|
|
173
173
|
type: Number,
|
|
@@ -162,6 +162,14 @@ export default {
|
|
|
162
162
|
return col.name === this.sortBy;
|
|
163
163
|
},
|
|
164
164
|
|
|
165
|
+
ariaSort(col) {
|
|
166
|
+
if (this.isCurrent(col)) {
|
|
167
|
+
return this.descending ? this.t('generic.descending') : this.t('generic.ascending');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return this.t('generic.none');
|
|
171
|
+
},
|
|
172
|
+
|
|
165
173
|
tableColsOptionsClick(ev) {
|
|
166
174
|
// set menu position
|
|
167
175
|
const menu = document.querySelector('.table-options-container');
|
|
@@ -235,7 +243,12 @@ export default {
|
|
|
235
243
|
:align="col.align || 'left'"
|
|
236
244
|
:width="col.width"
|
|
237
245
|
:class="{ sortable: col.sort, [col.breakpoint]: !!col.breakpoint}"
|
|
246
|
+
:tabindex="col.sort ? 0 : -1"
|
|
247
|
+
class="sortable-table-head-element"
|
|
248
|
+
:aria-sort="ariaSort(col)"
|
|
238
249
|
@click.prevent="changeSort($event, col)"
|
|
250
|
+
@keyup.enter="changeSort($event, col)"
|
|
251
|
+
@keyup.space="changeSort($event, col)"
|
|
239
252
|
>
|
|
240
253
|
<div
|
|
241
254
|
class="table-header-container"
|
|
@@ -415,10 +428,7 @@ export default {
|
|
|
415
428
|
background-color: var(--sortable-table-header-bg);
|
|
416
429
|
color: var(--body-text);
|
|
417
430
|
text-align: left;
|
|
418
|
-
|
|
419
|
-
&:not(.loading) {
|
|
420
|
-
border-bottom: 1px solid var(--sortable-table-top-divider);
|
|
421
|
-
}
|
|
431
|
+
border-bottom: 1px solid var(--sortable-table-top-divider);
|
|
422
432
|
}
|
|
423
433
|
}
|
|
424
434
|
|
|
@@ -428,6 +438,11 @@ export default {
|
|
|
428
438
|
border: 0;
|
|
429
439
|
color: var(--body-text);
|
|
430
440
|
|
|
441
|
+
&.sortable-table-head-element:focus-visible {
|
|
442
|
+
@include focus-outline;
|
|
443
|
+
outline-offset: -4px;
|
|
444
|
+
}
|
|
445
|
+
|
|
431
446
|
.table-header-container {
|
|
432
447
|
display: inline-flex;
|
|
433
448
|
|
|
@@ -380,8 +380,10 @@ export default {
|
|
|
380
380
|
eventualSearchQuery = this.$route.query?.q;
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
+
const isLoading = this.loading || false;
|
|
384
|
+
|
|
383
385
|
return {
|
|
384
|
-
refreshButtonPhase: ASYNC_BUTTON_STATES.WAITING,
|
|
386
|
+
refreshButtonPhase: isLoading ? ASYNC_BUTTON_STATES.WAITING : ASYNC_BUTTON_STATES.ACTION,
|
|
385
387
|
expanded: {},
|
|
386
388
|
searchQuery,
|
|
387
389
|
eventualSearchQuery,
|
|
@@ -392,7 +394,7 @@ export default {
|
|
|
392
394
|
/**
|
|
393
395
|
* The is the bool the DOM uses to show loading state. it's proxied from `loading` to avoid blipping the indicator (see usages)
|
|
394
396
|
*/
|
|
395
|
-
isLoading
|
|
397
|
+
isLoading
|
|
396
398
|
};
|
|
397
399
|
},
|
|
398
400
|
|
|
@@ -504,7 +506,7 @@ export default {
|
|
|
504
506
|
if (neu) {
|
|
505
507
|
this._altLoadingDelayTimer = setTimeout(() => {
|
|
506
508
|
this.isLoading = true;
|
|
507
|
-
}, 200); // this should be higher than the
|
|
509
|
+
}, 200); // this should be higher than the targeted quick response
|
|
508
510
|
} else {
|
|
509
511
|
clearTimeout(this._altLoadingDelayTimer);
|
|
510
512
|
this.isLoading = false;
|
|
@@ -536,9 +538,6 @@ export default {
|
|
|
536
538
|
manualRefreshLoadingFinished() {
|
|
537
539
|
const res = !!(!this.isLoading && this._didinit && this.rows?.length && !this.isManualRefreshLoading);
|
|
538
540
|
|
|
539
|
-
// Always ensure the Refresh button phase aligns with loading state (regardless of if manualRefreshLoadingFinished has changed or not)
|
|
540
|
-
this.refreshButtonPhase = !res || this.loading ? ASYNC_BUTTON_STATES.WAITING : ASYNC_BUTTON_STATES.ACTION;
|
|
541
|
-
|
|
542
541
|
return res;
|
|
543
542
|
},
|
|
544
543
|
|
|
@@ -575,11 +574,13 @@ export default {
|
|
|
575
574
|
},
|
|
576
575
|
|
|
577
576
|
showHeaderRow() {
|
|
577
|
+
// All of these are used to show content in the header
|
|
578
578
|
return this.search ||
|
|
579
579
|
this.tableActions ||
|
|
580
|
-
this.$slots['header-left']
|
|
581
|
-
this.$slots['header-middle']
|
|
582
|
-
this.$slots['header-right']
|
|
580
|
+
this.$slots['header-left'] ||
|
|
581
|
+
this.$slots['header-middle'] ||
|
|
582
|
+
this.$slots['header-right'] ||
|
|
583
|
+
this.isTooManyItemsToAutoUpdate;
|
|
583
584
|
},
|
|
584
585
|
|
|
585
586
|
columns() {
|
|
@@ -1222,6 +1223,7 @@ export default {
|
|
|
1222
1223
|
class="sortable-table"
|
|
1223
1224
|
:class="classObject"
|
|
1224
1225
|
width="100%"
|
|
1226
|
+
role="table"
|
|
1225
1227
|
>
|
|
1226
1228
|
<THead
|
|
1227
1229
|
v-if="showHeaders"
|
|
@@ -1451,6 +1453,8 @@ export default {
|
|
|
1451
1453
|
:data-testid="componentTestid + '-' + i + '-action-button'"
|
|
1452
1454
|
:borderless="true"
|
|
1453
1455
|
@click="handleActionButtonClick(i, $event)"
|
|
1456
|
+
@keyup.enter="handleActionButtonClick(i, $event)"
|
|
1457
|
+
@keyup.space="handleActionButtonClick(i, $event)"
|
|
1454
1458
|
/>
|
|
1455
1459
|
</slot>
|
|
1456
1460
|
</td>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mapGetters } from 'vuex';
|
|
1
2
|
import { isMore, isRange, suppressContextMenu, isAlternate } from '@shell/utils/platform';
|
|
2
3
|
import { get } from '@shell/utils/object';
|
|
3
4
|
import { filterBy } from '@shell/utils/array';
|
|
@@ -29,6 +30,13 @@ export default {
|
|
|
29
30
|
},
|
|
30
31
|
|
|
31
32
|
computed: {
|
|
33
|
+
...mapGetters({
|
|
34
|
+
// Use either these Vuex getters
|
|
35
|
+
// OR the props to set the action menu state,
|
|
36
|
+
// but don't use both.
|
|
37
|
+
targetElem: 'action-menu/elem',
|
|
38
|
+
shouldShow: 'action-menu/showing',
|
|
39
|
+
}),
|
|
32
40
|
// Used for the table-level selection check-box to show checked (all selected)/intermediate (some selected)/unchecked (none selected)
|
|
33
41
|
howMuchSelected() {
|
|
34
42
|
const total = this.pagedRows.length;
|
|
@@ -270,11 +278,17 @@ export default {
|
|
|
270
278
|
}
|
|
271
279
|
}
|
|
272
280
|
|
|
273
|
-
this
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
281
|
+
if (!this.targetElem && !this.shouldShow) {
|
|
282
|
+
this.$store.commit(`action-menu/show`, {
|
|
283
|
+
resources,
|
|
284
|
+
event: e,
|
|
285
|
+
elem: actionElement
|
|
286
|
+
});
|
|
287
|
+
} else if (this.targetElem === actionElement && this.shouldShow) {
|
|
288
|
+
// this condition is needed so that we can "toggle" the action menu with
|
|
289
|
+
// the keyboard for accessibility (row action menu)
|
|
290
|
+
this.$store.commit('action-menu/hide');
|
|
291
|
+
}
|
|
278
292
|
|
|
279
293
|
return;
|
|
280
294
|
}
|
|
@@ -13,7 +13,7 @@ export const EDITOR_MODES = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export default {
|
|
16
|
-
emits: ['update:value', 'newObject', 'onInput', 'onReady', 'onChanges'],
|
|
16
|
+
emits: ['update:value', 'newObject', 'onInput', 'onReady', 'onChanges', 'validationChanged'],
|
|
17
17
|
|
|
18
18
|
components: {
|
|
19
19
|
CodeMirror,
|
|
@@ -236,6 +236,7 @@ export default {
|
|
|
236
236
|
@onInput="onInput"
|
|
237
237
|
@onReady="onReady"
|
|
238
238
|
@onChanges="onChanges"
|
|
239
|
+
@validationChanged="$emit('validationChanged', $event)"
|
|
239
240
|
/>
|
|
240
241
|
<FileDiff
|
|
241
242
|
v-else
|
|
@@ -127,7 +127,8 @@ export default {
|
|
|
127
127
|
class="text-warning"
|
|
128
128
|
>
|
|
129
129
|
{{ row.status.summary.ready }}/{{ row.status.summary.desiredReady }}</span>
|
|
130
|
-
<span v-else-if="row.status">{{ row.status.summary.desiredReady }}</span>
|
|
130
|
+
<span v-else-if="row.status && row.status.summary">{{ row.status.summary.desiredReady }}</span>
|
|
131
|
+
<span v-else>-</span>
|
|
131
132
|
</template>
|
|
132
133
|
</ResourceTable>
|
|
133
134
|
</div>
|
|
@@ -22,7 +22,7 @@ export default {
|
|
|
22
22
|
LabeledSelectPagination
|
|
23
23
|
],
|
|
24
24
|
|
|
25
|
-
emits: ['on-open', 'on-close', 'selecting', 'deselecting', 'update:validation', 'update:value'],
|
|
25
|
+
emits: ['on-open', 'on-close', 'selecting', 'deselecting', 'search', 'update:validation', 'update:value'],
|
|
26
26
|
|
|
27
27
|
props: {
|
|
28
28
|
appendToBody: {
|
|
@@ -147,18 +147,29 @@ export default {
|
|
|
147
147
|
|
|
148
148
|
// update placeholder text to inform user they can add their own opts when none are found
|
|
149
149
|
showTagPrompts() {
|
|
150
|
-
return !this.options.length && this.$attrs.taggable;
|
|
150
|
+
return !this.options.length && this.$attrs.taggable && this.isSearchable;
|
|
151
151
|
}
|
|
152
152
|
},
|
|
153
153
|
|
|
154
154
|
methods: {
|
|
155
155
|
// resizeHandler = in mixin
|
|
156
156
|
focusSearch() {
|
|
157
|
-
|
|
157
|
+
// we need this override as in a "closeOnSelect" type of component
|
|
158
|
+
// if we don't have this override, it would open again
|
|
159
|
+
if (this.overridesMixinPreventDoubleTriggerKeysOpen) {
|
|
160
|
+
this.$nextTick(() => {
|
|
161
|
+
const el = this.$refs['select'];
|
|
162
|
+
|
|
163
|
+
if ( el ) {
|
|
164
|
+
el.focus();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.overridesMixinPreventDoubleTriggerKeysOpen = false;
|
|
168
|
+
});
|
|
158
169
|
|
|
159
|
-
if (!this.focused && blurredAgo < 250) {
|
|
160
170
|
return;
|
|
161
171
|
}
|
|
172
|
+
this.$refs['select-input'].open = true;
|
|
162
173
|
|
|
163
174
|
this.$nextTick(() => {
|
|
164
175
|
const el = this.$refs['select-input']?.searchEl;
|
|
@@ -238,7 +249,7 @@ export default {
|
|
|
238
249
|
return noDrop ? false : open && shouldOpen && !mutableLoading;
|
|
239
250
|
},
|
|
240
251
|
|
|
241
|
-
onSearch(newSearchString) {
|
|
252
|
+
onSearch(newSearchString, loading) {
|
|
242
253
|
if (this.canPaginate) {
|
|
243
254
|
this.setPaginationFilter(newSearchString);
|
|
244
255
|
} else {
|
|
@@ -246,6 +257,7 @@ export default {
|
|
|
246
257
|
this.dropdownShouldOpen(this.$refs['select-input'], true);
|
|
247
258
|
}
|
|
248
259
|
}
|
|
260
|
+
this.$emit('search', newSearchString, loading);
|
|
249
261
|
},
|
|
250
262
|
|
|
251
263
|
getOptionKey(opt) {
|
|
@@ -277,8 +289,9 @@ export default {
|
|
|
277
289
|
'no-label': !hasLabel
|
|
278
290
|
}
|
|
279
291
|
]"
|
|
292
|
+
:tabindex="isView || disabled ? -1 : 0"
|
|
280
293
|
@click="focusSearch"
|
|
281
|
-
@
|
|
294
|
+
@keyup.enter.space.down="focusSearch"
|
|
282
295
|
>
|
|
283
296
|
<div
|
|
284
297
|
:class="{ 'labeled-container': true, raised, empty, [mode]: true }"
|
|
@@ -318,7 +331,7 @@ export default {
|
|
|
318
331
|
:selectable="selectable"
|
|
319
332
|
:modelValue="value != null && !loading ? value : ''"
|
|
320
333
|
:dropdown-should-open="dropdownShouldOpen"
|
|
321
|
-
|
|
334
|
+
:tabindex="-1"
|
|
322
335
|
@update:modelValue="$emit('selecting', $event); $emit('update:value', $event)"
|
|
323
336
|
@search:blur="onBlur"
|
|
324
337
|
@search:focus="onFocus"
|
|
@@ -32,6 +32,7 @@ export default {
|
|
|
32
32
|
type: String,
|
|
33
33
|
default: 'create'
|
|
34
34
|
},
|
|
35
|
+
|
|
35
36
|
loading: {
|
|
36
37
|
default: false,
|
|
37
38
|
type: Boolean
|
|
@@ -169,6 +170,7 @@ export default {
|
|
|
169
170
|
name="selectNode"
|
|
170
171
|
:options="selectNodeOptions"
|
|
171
172
|
:mode="mode"
|
|
173
|
+
:data-testid="'node-scheduling-selectNode'"
|
|
172
174
|
@input="update"
|
|
173
175
|
/>
|
|
174
176
|
</div>
|
|
@@ -182,7 +184,8 @@ export default {
|
|
|
182
184
|
:mode="mode"
|
|
183
185
|
:multiple="false"
|
|
184
186
|
:loading="loading"
|
|
185
|
-
|
|
187
|
+
:data-testid="'node-scheduling-nodeSelector'"
|
|
188
|
+
@update:value="update"
|
|
186
189
|
/>
|
|
187
190
|
</div>
|
|
188
191
|
</div>
|
|
@@ -191,6 +194,7 @@ export default {
|
|
|
191
194
|
<NodeAffinity
|
|
192
195
|
v-model:value="nodeAffinity"
|
|
193
196
|
:mode="mode"
|
|
197
|
+
:data-testid="'node-scheduling-nodeAffinity'"
|
|
194
198
|
@input="update"
|
|
195
199
|
/>
|
|
196
200
|
</template>
|
|
@@ -41,7 +41,7 @@ export default {
|
|
|
41
41
|
mode: {
|
|
42
42
|
type: String,
|
|
43
43
|
default: _CREATE,
|
|
44
|
-
}
|
|
44
|
+
}
|
|
45
45
|
},
|
|
46
46
|
data() {
|
|
47
47
|
return { reveal: false };
|
|
@@ -68,6 +68,9 @@ export default {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
return attributes;
|
|
71
|
+
},
|
|
72
|
+
hideShowLabel() {
|
|
73
|
+
return this.reveal ? this.t('action.hide') : this.t('action.show');
|
|
71
74
|
}
|
|
72
75
|
},
|
|
73
76
|
watch: {
|
|
@@ -92,6 +95,9 @@ export default {
|
|
|
92
95
|
},
|
|
93
96
|
focus() {
|
|
94
97
|
this.$refs.input.$refs.value.focus();
|
|
98
|
+
},
|
|
99
|
+
hideShowFn() {
|
|
100
|
+
this.reveal ? this.reveal = false : this.reveal = true;
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
};
|
|
@@ -127,17 +133,15 @@ export default {
|
|
|
127
133
|
class="addon"
|
|
128
134
|
>
|
|
129
135
|
<a
|
|
130
|
-
v-if="reveal"
|
|
131
|
-
tabindex="-1"
|
|
132
136
|
href="#"
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
tabindex="0"
|
|
138
|
+
class="hide-show"
|
|
139
|
+
role="button"
|
|
140
|
+
@click.prevent.stop="hideShowFn"
|
|
141
|
+
@keyup.space.prevent.stop="hideShowFn"
|
|
142
|
+
>
|
|
143
|
+
{{ hideShowLabel }}
|
|
144
|
+
</a>
|
|
141
145
|
</div>
|
|
142
146
|
</template>
|
|
143
147
|
</LabeledInput>
|
|
@@ -157,10 +161,16 @@ export default {
|
|
|
157
161
|
.password {
|
|
158
162
|
display: flex;
|
|
159
163
|
flex-direction: column;
|
|
164
|
+
|
|
160
165
|
.labeled-input {
|
|
161
166
|
.addon {
|
|
162
|
-
|
|
163
|
-
|
|
167
|
+
padding-left: 12px;
|
|
168
|
+
min-width: 65px;
|
|
169
|
+
|
|
170
|
+
.hide-show:focus-visible {
|
|
171
|
+
@include focus-outline;
|
|
172
|
+
outline-offset: 4px;
|
|
173
|
+
}
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
176
|
.genPassword {
|
|
@@ -27,7 +27,7 @@ export interface ResourceLabeledSelectPaginateSettings extends SharedSettings {
|
|
|
27
27
|
*/
|
|
28
28
|
overrideRequest?: LabelSelectPaginateFn,
|
|
29
29
|
/**
|
|
30
|
-
* Override the default settings used in the
|
|
30
|
+
* Override the default settings used in the convenience function to fetch a page of results
|
|
31
31
|
*/
|
|
32
32
|
requestSettings?: PaginateTypeOverridesFn,
|
|
33
33
|
}
|
|
@@ -114,11 +114,24 @@ export default {
|
|
|
114
114
|
calculatePosition(dropdownList, component, width, this.placement);
|
|
115
115
|
},
|
|
116
116
|
|
|
117
|
-
focus() {
|
|
118
|
-
this.focusSearch();
|
|
119
|
-
},
|
|
120
|
-
|
|
121
117
|
focusSearch() {
|
|
118
|
+
// we need this override as in a "closeOnSelect" type of component
|
|
119
|
+
// if we don't have this override, it would open again
|
|
120
|
+
if (this.overridesMixinPreventDoubleTriggerKeysOpen) {
|
|
121
|
+
this.$nextTick(() => {
|
|
122
|
+
const el = this.$refs['select'];
|
|
123
|
+
|
|
124
|
+
if ( el ) {
|
|
125
|
+
el.focus();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.overridesMixinPreventDoubleTriggerKeysOpen = false;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.$refs['select-input'].open = true;
|
|
134
|
+
|
|
122
135
|
this.$nextTick(() => {
|
|
123
136
|
const el = this.$refs['select-input']?.searchEl;
|
|
124
137
|
|
|
@@ -176,6 +189,11 @@ export default {
|
|
|
176
189
|
},
|
|
177
190
|
report(e) {
|
|
178
191
|
alert(e);
|
|
192
|
+
},
|
|
193
|
+
handleDropdownOpen(args) {
|
|
194
|
+
// function that prevents the "opening dropdown on focus"
|
|
195
|
+
// default behaviour of v-select
|
|
196
|
+
return args.noDrop || args.disabled ? false : args.open;
|
|
179
197
|
}
|
|
180
198
|
},
|
|
181
199
|
computed: {
|
|
@@ -227,7 +245,7 @@ export default {
|
|
|
227
245
|
ref="select"
|
|
228
246
|
class="unlabeled-select"
|
|
229
247
|
:class="{
|
|
230
|
-
disabled: disabled
|
|
248
|
+
disabled: disabled || isView,
|
|
231
249
|
focused,
|
|
232
250
|
[mode]: true,
|
|
233
251
|
[status]: status,
|
|
@@ -236,7 +254,9 @@ export default {
|
|
|
236
254
|
'compact-input': compact,
|
|
237
255
|
[$attrs.class]: $attrs.class
|
|
238
256
|
}"
|
|
239
|
-
|
|
257
|
+
:tabindex="disabled || isView ? -1 : 0"
|
|
258
|
+
@click="focusSearch"
|
|
259
|
+
@keyup.enter.space.down="focusSearch"
|
|
240
260
|
>
|
|
241
261
|
<v-select
|
|
242
262
|
ref="select-input"
|
|
@@ -258,6 +278,8 @@ export default {
|
|
|
258
278
|
:searchable="isSearchable"
|
|
259
279
|
:selectable="selectable"
|
|
260
280
|
:modelValue="value != null ? value : ''"
|
|
281
|
+
:dropdownShouldOpen="handleDropdownOpen"
|
|
282
|
+
:tabindex="-1"
|
|
261
283
|
|
|
262
284
|
@update:modelValue="$emit('update:value', $event)"
|
|
263
285
|
@search:blur="onBlur"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { _EDIT } from '@shell/config/query-params';
|
|
3
|
+
import { Banner } from '@components/Banner';
|
|
3
4
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
4
5
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
5
6
|
import { AUTH_TYPE, NORMAN, SECRET } from '@shell/config/types';
|
|
@@ -19,6 +20,7 @@ export default {
|
|
|
19
20
|
emits: ['inputauthval', 'update:value'],
|
|
20
21
|
|
|
21
22
|
components: {
|
|
23
|
+
Banner,
|
|
22
24
|
LabeledInput,
|
|
23
25
|
LabeledSelect,
|
|
24
26
|
},
|
|
@@ -118,7 +120,7 @@ export default {
|
|
|
118
120
|
},
|
|
119
121
|
|
|
120
122
|
/**
|
|
121
|
-
* This component is used in MultiStep
|
|
123
|
+
* This component is used in MultiStep Process
|
|
122
124
|
* So when user click through to final step and submit the form
|
|
123
125
|
* This component get recreated therefore register `doCreate` as a hook each time
|
|
124
126
|
* Also, the parent step component is not aware that credential is created
|
|
@@ -194,6 +196,7 @@ export default {
|
|
|
194
196
|
SSH: AUTH_TYPE._SSH,
|
|
195
197
|
BASIC: AUTH_TYPE._BASIC,
|
|
196
198
|
S3: AUTH_TYPE._S3,
|
|
199
|
+
RKE: AUTH_TYPE._RKE,
|
|
197
200
|
};
|
|
198
201
|
},
|
|
199
202
|
|
|
@@ -308,7 +311,7 @@ export default {
|
|
|
308
311
|
});
|
|
309
312
|
}
|
|
310
313
|
|
|
311
|
-
if (this.allowSsh || this.allowS3 || this.allowBasic) {
|
|
314
|
+
if (this.allowSsh || this.allowS3 || this.allowBasic || this.allowRke) {
|
|
312
315
|
out.unshift({
|
|
313
316
|
label: 'divider',
|
|
314
317
|
disabled: true,
|
|
@@ -340,6 +343,15 @@ export default {
|
|
|
340
343
|
});
|
|
341
344
|
}
|
|
342
345
|
|
|
346
|
+
// Note here about order
|
|
347
|
+
if ( this.allowRke ) {
|
|
348
|
+
out.unshift({
|
|
349
|
+
label: this.t('selectOrCreateAuthSecret.createRKE'),
|
|
350
|
+
value: AUTH_TYPE._RKE,
|
|
351
|
+
kind: 'highlighted'
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
343
355
|
return out;
|
|
344
356
|
},
|
|
345
357
|
|
|
@@ -348,7 +360,7 @@ export default {
|
|
|
348
360
|
return '';
|
|
349
361
|
}
|
|
350
362
|
|
|
351
|
-
if ( this.selected === AUTH_TYPE._SSH || this.selected === AUTH_TYPE._BASIC || this.selected === AUTH_TYPE._S3 ) {
|
|
363
|
+
if ( this.selected === AUTH_TYPE._SSH || this.selected === AUTH_TYPE._BASIC || this.selected === AUTH_TYPE._RKE || this.selected === AUTH_TYPE._S3 ) {
|
|
352
364
|
return 'col span-4';
|
|
353
365
|
}
|
|
354
366
|
|
|
@@ -448,7 +460,7 @@ export default {
|
|
|
448
460
|
},
|
|
449
461
|
|
|
450
462
|
updateKeyVal() {
|
|
451
|
-
if ( ![AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3].includes(this.selected) ) {
|
|
463
|
+
if ( ![AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE].includes(this.selected) ) {
|
|
452
464
|
this.privateKey = '';
|
|
453
465
|
this.publicKey = '';
|
|
454
466
|
}
|
|
@@ -461,7 +473,7 @@ export default {
|
|
|
461
473
|
},
|
|
462
474
|
|
|
463
475
|
update() {
|
|
464
|
-
if ( (!this.selected || [AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._NONE].includes(this.selected))) {
|
|
476
|
+
if ( (!this.selected || [AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE, AUTH_TYPE._NONE].includes(this.selected))) {
|
|
465
477
|
this.$emit('update:value', null);
|
|
466
478
|
} else if ( this.selected.includes(':') ) {
|
|
467
479
|
// Cloud creds
|
|
@@ -485,7 +497,7 @@ export default {
|
|
|
485
497
|
},
|
|
486
498
|
|
|
487
499
|
async doCreate() {
|
|
488
|
-
if ( ![AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3].includes(this.selected) || this.delegateCreateToParent ) {
|
|
500
|
+
if ( ![AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE].includes(this.selected) || this.delegateCreateToParent ) {
|
|
489
501
|
return;
|
|
490
502
|
}
|
|
491
503
|
|
|
@@ -521,15 +533,24 @@ export default {
|
|
|
521
533
|
publicField = 'username';
|
|
522
534
|
privateField = 'password';
|
|
523
535
|
break;
|
|
536
|
+
case AUTH_TYPE._RKE:
|
|
537
|
+
type = SECRET_TYPES.RKE_AUTH_CONFIG;
|
|
538
|
+
// Set the 'auth' key to be the base64 of the username and password concatenated with a ':' character
|
|
539
|
+
secret.data = { auth: base64Encode(`${ this.publicKey }:${ this.privateKey }`) };
|
|
540
|
+
break;
|
|
524
541
|
default:
|
|
525
542
|
throw new Error('Unknown type');
|
|
526
543
|
}
|
|
527
544
|
|
|
528
545
|
secret._type = type;
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
546
|
+
|
|
547
|
+
// Set the data if not set by one of the specific cases above
|
|
548
|
+
if (!secret.data) {
|
|
549
|
+
secret.data = {
|
|
550
|
+
[publicField]: base64Encode(this.publicKey),
|
|
551
|
+
[privateField]: base64Encode(this.privateKey),
|
|
552
|
+
};
|
|
553
|
+
}
|
|
533
554
|
}
|
|
534
555
|
|
|
535
556
|
await secret.save();
|
|
@@ -583,7 +604,14 @@ export default {
|
|
|
583
604
|
/>
|
|
584
605
|
</div>
|
|
585
606
|
</template>
|
|
586
|
-
<template v-else-if="selected === BASIC">
|
|
607
|
+
<template v-else-if="selected === BASIC || selected === RKE">
|
|
608
|
+
<Banner
|
|
609
|
+
v-if="selected === RKE"
|
|
610
|
+
color="info"
|
|
611
|
+
:class="moreCols"
|
|
612
|
+
>
|
|
613
|
+
{{ t('selectOrCreateAuthSecret.rke.info', {}, true) }}
|
|
614
|
+
</Banner>
|
|
587
615
|
<div :class="moreCols">
|
|
588
616
|
<LabeledInput
|
|
589
617
|
v-model:value="publicKey"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import NodeScheduling from '@shell/components/form/NodeScheduling.vue';
|
|
3
|
+
import { _CREATE, _EDIT, _VIEW } from '@shell/config/query-params';
|
|
4
|
+
|
|
5
|
+
const requiredSetup = () => {
|
|
6
|
+
return {
|
|
7
|
+
global: {
|
|
8
|
+
mocks: {
|
|
9
|
+
$store: {
|
|
10
|
+
getters: {
|
|
11
|
+
currentProduct: { inStore: 'cluster' },
|
|
12
|
+
'i18n/t': (text: string) => text,
|
|
13
|
+
t: (text: string) => text,
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe('component: NodeScheduling', () => {
|
|
22
|
+
const value = { nodeName: 'node-1' };
|
|
23
|
+
|
|
24
|
+
const nodes = ['node-0', 'node-1'];
|
|
25
|
+
|
|
26
|
+
it.each([
|
|
27
|
+
_VIEW,
|
|
28
|
+
_CREATE,
|
|
29
|
+
_EDIT
|
|
30
|
+
])('should show NodeName option', (mode) => {
|
|
31
|
+
const wrapper = mount(
|
|
32
|
+
NodeScheduling,
|
|
33
|
+
{
|
|
34
|
+
props: {
|
|
35
|
+
mode, loading: false, value, nodes
|
|
36
|
+
},
|
|
37
|
+
...requiredSetup(),
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
expect(wrapper.find('[data-testid="node-scheduling-selectNode"]').exists()).toBeTruthy();
|
|
42
|
+
expect(wrapper.find('[data-testid="node-scheduling-nodeSelector"]').element.textContent).toContain(value.nodeName);
|
|
43
|
+
});
|
|
44
|
+
});
|