@rancher/shell 0.3.16 → 0.3.17
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/images/wechat-qr-code.jpg +0 -0
- package/assets/translations/en-us.yaml +69 -14
- package/assets/translations/zh-hans.yaml +87 -7
- package/chart/__tests__/S3.test.ts +50 -0
- package/chart/rancher-backup/S3.vue +21 -0
- package/chart/rancher-backup/index.vue +4 -0
- package/components/CommunityLinks.vue +1 -0
- package/components/FileDiff.vue +92 -85
- package/components/ResourceDetail/index.vue +4 -12
- package/components/ResourceList/index.vue +1 -1
- package/components/ResourceTable.vue +50 -2
- package/components/YamlEditor.vue +1 -0
- package/components/auth/RoleDetailEdit.vue +1 -0
- package/components/form/NameNsDescription.vue +28 -12
- package/components/form/NodeAffinity.vue +2 -2
- package/components/form/PodAffinity.vue +2 -2
- package/components/form/ResourceTabs/index.vue +8 -2
- package/components/form/Select.vue +16 -0
- package/components/form/__tests__/NodeAffinity.test.ts +38 -0
- package/components/form/__tests__/PodAffinity.test.ts +46 -0
- package/components/formatter/ClusterLink.vue +8 -4
- package/components/formatter/ImageName.vue +23 -0
- package/components/formatter/PodImages.vue +7 -1
- package/components/formatter/__tests__/ClusterLink.test.ts +101 -0
- package/components/nav/Header.vue +2 -2
- package/config/__test__/home-links.test.ts +62 -0
- package/config/home-links.js +15 -3
- package/config/labels-annotations.js +5 -1
- package/config/router.js +0 -4
- package/config/settings.ts +4 -0
- package/config/table-headers.js +6 -5
- package/config/uiplugins.js +50 -5
- package/core/plugin-helpers.js +20 -12
- package/core/plugin.ts +9 -0
- package/core/plugins.js +1 -1
- package/core/types-provisioning.ts +253 -0
- package/core/types.ts +17 -3
- package/detail/autoscaling.horizontalpodautoscaler/index.vue +50 -1
- package/detail/node.vue +6 -6
- package/detail/pod.vue +2 -6
- package/detail/provisioning.cattle.io.cluster.vue +46 -7
- package/detail/workload/index.vue +9 -9
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +62 -0
- package/edit/auth/github.vue +1 -0
- package/edit/autoscaling.horizontalpodautoscaler/hpa-scaling-rule.vue +130 -0
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +79 -0
- package/edit/fleet.cattle.io.gitrepo.vue +18 -1
- package/edit/namespace.vue +9 -1
- package/edit/networking.k8s.io.ingress/RulePath.vue +0 -2
- package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +1 -30
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +79 -1
- package/edit/provisioning.cattle.io.cluster/index.vue +52 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +330 -150
- package/edit/ui.cattle.io.navlink.vue +2 -1
- package/initialize/App.js +3 -13
- package/initialize/layouts.ts +26 -0
- package/list/provisioning.cattle.io.cluster.vue +8 -1
- package/middleware/authenticated.js +93 -5
- package/mixins/brand.js +39 -3
- package/mixins/child-hook.js +2 -2
- package/mixins/create-edit-view/impl.js +2 -2
- package/models/fleet.cattle.io.gitrepo.js +1 -0
- package/models/provisioning.cattle.io.cluster.js +9 -1
- package/package.json +2 -2
- package/pages/about.vue +8 -2
- package/pages/auth/login.vue +1 -1
- package/pages/auth/logout.vue +11 -3
- package/pages/c/_cluster/apps/charts/index.vue +5 -2
- package/pages/c/_cluster/apps/charts/install.vue +5 -0
- package/pages/c/_cluster/explorer/index.vue +1 -10
- package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +177 -0
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +19 -3
- package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +90 -21
- package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +107 -37
- package/pages/c/_cluster/uiplugins/index.vue +155 -44
- package/pages/docs/_doc.vue +9 -3
- package/pages/home.vue +5 -5
- package/pages/support/index.vue +10 -4
- package/pkg/auto-import.js +1 -1
- package/plugins/clean-tooltip-directive.js +1 -1
- package/plugins/dashboard-store/resource-class.js +35 -2
- package/plugins/plugin.js +9 -1
- package/rancher-components/BadgeState/BadgeState.vue +5 -1
- package/rancher-components/Banner/Banner.test.ts +51 -1
- package/rancher-components/Banner/Banner.vue +134 -53
- package/rancher-components/Card/Card.vue +24 -7
- package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
- package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
- package/rancher-components/Form/Radio/RadioButton.vue +30 -13
- package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
- package/rancher-components/StringList/StringList.test.ts +453 -49
- package/rancher-components/StringList/StringList.vue +44 -26
- package/scripts/extension/publish +2 -2
- package/scripts/typegen.sh +1 -0
- package/server/server-middleware.js +4 -12
- package/store/index.js +13 -0
- package/store/prefs.js +0 -3
- package/store/type-map.js +17 -29
- package/types/shell/index.d.ts +236 -83
- package/utils/kube.js +9 -0
- package/utils/object.js +27 -0
- package/utils/settings.ts +2 -2
- package/vue.config.js +3 -2
- package/components/.DS_Store +0 -0
- package/components/__tests__/.DS_Store +0 -0
- package/creators/pkg/package-lock.json +0 -37
- package/pages/safeMode.vue +0 -17
- package/rancher-components/components/BadgeState/BadgeState.spec.ts +0 -12
- package/rancher-components/components/BadgeState/BadgeState.vue +0 -111
- package/rancher-components/components/BadgeState/index.ts +0 -1
- package/rancher-components/components/Banner/Banner.test.ts +0 -63
- package/rancher-components/components/Banner/Banner.vue +0 -244
- package/rancher-components/components/Banner/index.ts +0 -1
- package/rancher-components/components/Card/Card.vue +0 -167
- package/rancher-components/components/Card/index.ts +0 -1
- package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +0 -68
- package/rancher-components/components/Form/Checkbox/Checkbox.vue +0 -420
- package/rancher-components/components/Form/Checkbox/index.ts +0 -1
- package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +0 -23
- package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +0 -355
- package/rancher-components/components/Form/LabeledInput/index.ts +0 -1
- package/rancher-components/components/Form/Radio/RadioButton.vue +0 -287
- package/rancher-components/components/Form/Radio/RadioGroup.vue +0 -254
- package/rancher-components/components/Form/Radio/index.ts +0 -2
- package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +0 -170
- package/rancher-components/components/Form/TextArea/index.ts +0 -1
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +0 -94
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +0 -149
- package/rancher-components/components/Form/ToggleSwitch/index.ts +0 -1
- package/rancher-components/components/Form/index.ts +0 -5
- package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +0 -151
- package/rancher-components/components/LabeledTooltip/index.ts +0 -1
- package/rancher-components/components/StringList/StringList.test.ts +0 -484
- package/rancher-components/components/StringList/StringList.vue +0 -611
- package/rancher-components/components/StringList/index.ts +0 -1
- package/yarn-error.log +0 -196
- /package/rancher-components/{components/Card → Card}/Card.test.ts +0 -0
- /package/rancher-components/{components/Form → Form}/Radio/RadioButton.test.ts +0 -0
package/components/FileDiff.vue
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import {
|
|
2
|
+
import { Diff2HtmlUI } from 'diff2html/lib/ui/js/diff2html-ui-slim.js';
|
|
3
|
+
|
|
3
4
|
import { createPatch } from 'diff';
|
|
4
5
|
|
|
5
6
|
export default {
|
|
@@ -38,36 +39,40 @@ export default {
|
|
|
38
39
|
}
|
|
39
40
|
},
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
mounted() {
|
|
43
|
+
this.draw();
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
watch: {
|
|
47
|
+
sideBySide() {
|
|
48
|
+
this.draw();
|
|
49
|
+
}
|
|
50
|
+
},
|
|
46
51
|
|
|
52
|
+
methods: {
|
|
53
|
+
draw() {
|
|
54
|
+
const targetElement = document.getElementById('diffElement');
|
|
47
55
|
const patch = createPatch(
|
|
48
56
|
this.filename,
|
|
49
57
|
this.orig,
|
|
50
58
|
this.neu
|
|
51
59
|
);
|
|
60
|
+
const configuration = {
|
|
61
|
+
// UI
|
|
62
|
+
synchronisedScroll: true,
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
matching,
|
|
65
|
-
synchronizedScroll: true,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
},
|
|
64
|
+
// Base
|
|
65
|
+
outputFormat: this.sideBySide ? 'side-by-side' : 'line-by-line',
|
|
66
|
+
drawFileList: false,
|
|
67
|
+
matching: 'words',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const diff2htmlUi = new Diff2HtmlUI(targetElement, patch, configuration);
|
|
71
|
+
|
|
72
|
+
diff2htmlUi.draw();
|
|
73
|
+
this.fit();
|
|
74
|
+
},
|
|
69
75
|
|
|
70
|
-
methods: {
|
|
71
76
|
fit() {
|
|
72
77
|
if ( !this.autoResize ) {
|
|
73
78
|
return;
|
|
@@ -97,8 +102,8 @@ export default {
|
|
|
97
102
|
<div>
|
|
98
103
|
<resize-observer @notify="fit" />
|
|
99
104
|
<div
|
|
105
|
+
id="diffElement"
|
|
100
106
|
ref="root"
|
|
101
|
-
v-clean-html="html"
|
|
102
107
|
class="root"
|
|
103
108
|
/>
|
|
104
109
|
</div>
|
|
@@ -112,66 +117,68 @@ export default {
|
|
|
112
117
|
}
|
|
113
118
|
</style>
|
|
114
119
|
|
|
115
|
-
<style lang="scss">
|
|
116
|
-
@import 'node_modules/diff2html/
|
|
117
|
-
|
|
118
|
-
.d2h-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
.d2h-code-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
120
|
+
<style scoped lang="scss">
|
|
121
|
+
@import 'node_modules/diff2html/bundles/css/diff2html.min.css';
|
|
122
|
+
|
|
123
|
+
::v-deep .d2h-wrapper {
|
|
124
|
+
.d2h-file-header {
|
|
125
|
+
display: none;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.d2h-file-wrapper {
|
|
129
|
+
border-color: var(--diff-border);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.d2h-diff-table {
|
|
133
|
+
font-family: Menlo,Consolas,monospace;
|
|
134
|
+
font-size: 13px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.d2h-emptyplaceholder, .d2h-code-side-emptyplaceholder {
|
|
138
|
+
border-color: var(--diff-linenum-border);
|
|
139
|
+
background-color: var(--diff-empty-placeholder);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.d2h-code-linenumber,
|
|
143
|
+
.d2h-code-side-linenumber {
|
|
144
|
+
background-color: var(--diff-linenum-bg);
|
|
145
|
+
color: var(--diff-linenum);
|
|
146
|
+
border-color: var(--diff-linenum-border);
|
|
147
|
+
border-left: 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.d2h-code-line del,.d2h-code-side-line del {
|
|
151
|
+
background-color: var(--diff-line-del-bg);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.d2h-code-line ins,.d2h-code-side-line ins {
|
|
155
|
+
background-color: var(--diff-line-ins-bg);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.d2h-del {
|
|
159
|
+
background-color: var(--diff-del-bg);
|
|
160
|
+
border-color: var(--diff-del-border);
|
|
161
|
+
color: var(--body-text);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.d2h-ins {
|
|
165
|
+
background-color: var(--diff-ins-bg);
|
|
166
|
+
border-color: var(--diff-ins-border);
|
|
167
|
+
color: var(--body-text);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.d2h-info {
|
|
171
|
+
background-color: var(--diff-header-bg);
|
|
172
|
+
color: var(--diff-header);
|
|
173
|
+
border-color: var(--diff-header-border);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.d2h-file-diff .d2h-del.d2h-change {
|
|
177
|
+
background-color: var(--diff-chg-del);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.d2h-file-diff .d2h-ins.d2h-change {
|
|
181
|
+
background-color: var(--diff-chg-ins);
|
|
182
|
+
}
|
|
176
183
|
}
|
|
177
184
|
</style>
|
|
@@ -193,6 +193,9 @@ export default {
|
|
|
193
193
|
opt: { watch: true }
|
|
194
194
|
});
|
|
195
195
|
} catch (e) {
|
|
196
|
+
if (e.status === 404 || e.status === 403) {
|
|
197
|
+
store.dispatch('loadingError', new Error(this.t('nav.failWhale.resourceIdNotFound', { resource, fqid }, true)));
|
|
198
|
+
}
|
|
196
199
|
liveModel = {};
|
|
197
200
|
notFound = fqid;
|
|
198
201
|
}
|
|
@@ -367,18 +370,7 @@ export default {
|
|
|
367
370
|
</script>
|
|
368
371
|
|
|
369
372
|
<template>
|
|
370
|
-
<Loading v-if="$fetchState.pending" />
|
|
371
|
-
<div v-else-if="notFound">
|
|
372
|
-
<IconMessage icon="icon-warning">
|
|
373
|
-
<template v-slot:message>
|
|
374
|
-
{{ t('generic.notFound') }}
|
|
375
|
-
<div>
|
|
376
|
-
<div>{{ t('generic.type') }}: {{ resource }}</div>
|
|
377
|
-
<div>{{ t('generic.id') }}: {{ notFound }}</div>
|
|
378
|
-
</div>
|
|
379
|
-
</template>
|
|
380
|
-
</IconMessage>
|
|
381
|
-
</div>
|
|
373
|
+
<Loading v-if="$fetchState.pending || notFound" />
|
|
382
374
|
<div v-else>
|
|
383
375
|
<Masthead
|
|
384
376
|
v-if="showMasthead"
|
|
@@ -70,7 +70,7 @@ export default {
|
|
|
70
70
|
|
|
71
71
|
if ( !this.hasFetch ) {
|
|
72
72
|
if ( !schema ) {
|
|
73
|
-
store.dispatch('loadingError', new Error(
|
|
73
|
+
store.dispatch('loadingError', new Error(this.t('nav.failWhale.resourceListNotFound', { resource }, true)));
|
|
74
74
|
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
@@ -4,8 +4,9 @@ import { get } from '@shell/utils/object';
|
|
|
4
4
|
import { mapPref, GROUP_RESOURCES } from '@shell/store/prefs';
|
|
5
5
|
import ButtonGroup from '@shell/components/ButtonGroup';
|
|
6
6
|
import SortableTable from '@shell/components/SortableTable';
|
|
7
|
-
import { NAMESPACE } from '@shell/config/table-headers';
|
|
7
|
+
import { NAMESPACE, AGE } from '@shell/config/table-headers';
|
|
8
8
|
import { findBy } from '@shell/utils/array';
|
|
9
|
+
import { ExtensionPoint, TableColumnLocation } from '@shell/core/types';
|
|
9
10
|
|
|
10
11
|
// Default group-by in the case the group stored in the preference does not apply
|
|
11
12
|
const DEFAULT_GROUP = 'namespace';
|
|
@@ -57,6 +58,12 @@ export default {
|
|
|
57
58
|
required: false
|
|
58
59
|
},
|
|
59
60
|
|
|
61
|
+
keyField: {
|
|
62
|
+
// Field that is unique for each row.
|
|
63
|
+
type: String,
|
|
64
|
+
default: '_key',
|
|
65
|
+
},
|
|
66
|
+
|
|
60
67
|
headers: {
|
|
61
68
|
type: Array,
|
|
62
69
|
default: null,
|
|
@@ -216,6 +223,7 @@ export default {
|
|
|
216
223
|
_headers() {
|
|
217
224
|
let headers;
|
|
218
225
|
const showNamespace = this.showNamespaceColumn;
|
|
226
|
+
const type = this.schema?.id || this.$route?.params?.resource || undefined;
|
|
219
227
|
|
|
220
228
|
if ( this.headers ) {
|
|
221
229
|
headers = this.headers.slice();
|
|
@@ -223,6 +231,46 @@ export default {
|
|
|
223
231
|
headers = this.$store.getters['type-map/headersFor'](this.schema);
|
|
224
232
|
}
|
|
225
233
|
|
|
234
|
+
// add custom table columns provided by the extensions ExtensionPoint.TABLE_COL hook
|
|
235
|
+
// gate it so that we prevent errors on older versions of dashboard
|
|
236
|
+
if (this.$store.$plugin?.getUIConfig) {
|
|
237
|
+
const extensionCols = this.$store.$plugin.getUIConfig(ExtensionPoint.TABLE_COL, TableColumnLocation.RESOURCE);
|
|
238
|
+
|
|
239
|
+
// Try and insert the columns before the Age column
|
|
240
|
+
let insertPosition = headers.length;
|
|
241
|
+
|
|
242
|
+
if (headers.length > 0) {
|
|
243
|
+
const ageColIndex = headers.findIndex((h) => h.name === AGE.name);
|
|
244
|
+
|
|
245
|
+
if (ageColIndex >= 0) {
|
|
246
|
+
insertPosition = ageColIndex;
|
|
247
|
+
} else {
|
|
248
|
+
// we've found some labels with ' ', which isn't necessarily empty (explore action/button)
|
|
249
|
+
// if we are to add cols, let's push them before these so that the UI doesn't look weird
|
|
250
|
+
const lastViableColIndex = headers.findIndex((h) => (!h.label || !h.label?.trim()) && (!h.labelKey || !h.labelKey?.trim()));
|
|
251
|
+
|
|
252
|
+
if (lastViableColIndex >= 0) {
|
|
253
|
+
insertPosition = lastViableColIndex;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// adding extension defined cols to the correct header config
|
|
259
|
+
extensionCols.forEach((col) => {
|
|
260
|
+
if (col.locationConfig.resource) {
|
|
261
|
+
col.locationConfig.resource.forEach((resource) => {
|
|
262
|
+
if (resource && type === resource) {
|
|
263
|
+
// we need the 'value' prop to be populated in order for the rows to show the values
|
|
264
|
+
if (!col.value && col.getValue) {
|
|
265
|
+
col.value = col.getValue;
|
|
266
|
+
}
|
|
267
|
+
headers.splice(insertPosition, 0, col);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
226
274
|
// If only one namespace is selected, hide the namespace column
|
|
227
275
|
if ( !showNamespace ) {
|
|
228
276
|
const idx = headers.findIndex((header) => header.name === NAMESPACE.name);
|
|
@@ -447,7 +495,7 @@ export default {
|
|
|
447
495
|
:has-advanced-filtering="hasAdvancedFiltering"
|
|
448
496
|
:adv-filter-hide-labels-as-cols="advFilterHideLabelsAsCols"
|
|
449
497
|
:adv-filter-prevent-filtering-labels="advFilterPreventFilteringLabels"
|
|
450
|
-
key-field="
|
|
498
|
+
:key-field="keyField"
|
|
451
499
|
:sort-generation-fn="safeSortGenerationFn"
|
|
452
500
|
:use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
|
|
453
501
|
:force-update-live-and-delayed="forceUpdateLiveAndDelayed"
|
|
@@ -8,16 +8,7 @@ import { DESCRIPTION } from '@shell/config/labels-annotations';
|
|
|
8
8
|
import { _VIEW, _EDIT, _CREATE } from '@shell/config/query-params';
|
|
9
9
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
10
10
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
11
|
-
|
|
12
|
-
export function normalizeName(str) {
|
|
13
|
-
return (str || '')
|
|
14
|
-
.trim()
|
|
15
|
-
.toLowerCase()
|
|
16
|
-
.replace(/\s+/g, '-')
|
|
17
|
-
.replace(/-+/g, '-')
|
|
18
|
-
.replace(/^-+/, '')
|
|
19
|
-
.replace(/-+$/, '');
|
|
20
|
-
}
|
|
11
|
+
import { normalizeName } from '@shell/utils/kube';
|
|
21
12
|
|
|
22
13
|
export default {
|
|
23
14
|
name: 'NameNsDescription',
|
|
@@ -101,10 +92,20 @@ export default {
|
|
|
101
92
|
type: Boolean,
|
|
102
93
|
default: false
|
|
103
94
|
},
|
|
95
|
+
/**
|
|
96
|
+
* Use these objects instead of namespaces
|
|
97
|
+
*/
|
|
104
98
|
namespacesOverride: {
|
|
105
99
|
type: Array,
|
|
106
100
|
default: null,
|
|
107
101
|
},
|
|
102
|
+
/**
|
|
103
|
+
* User these namespaces instead of determining list within component
|
|
104
|
+
*/
|
|
105
|
+
namespaceOptions: {
|
|
106
|
+
type: Array,
|
|
107
|
+
default: null,
|
|
108
|
+
},
|
|
108
109
|
createNamespaceOverride: {
|
|
109
110
|
type: Boolean,
|
|
110
111
|
default: false,
|
|
@@ -228,8 +229,23 @@ export default {
|
|
|
228
229
|
* Map namespaces from the store to options, adding divider and create button
|
|
229
230
|
*/
|
|
230
231
|
options() {
|
|
231
|
-
|
|
232
|
-
|
|
232
|
+
let namespaces;
|
|
233
|
+
|
|
234
|
+
if (this.namespacesOverride) {
|
|
235
|
+
// Use the resources provided
|
|
236
|
+
namespaces = this.namespacesOverride;
|
|
237
|
+
} else {
|
|
238
|
+
if (this.namespaceOptions) {
|
|
239
|
+
// Use the namespaces provided
|
|
240
|
+
namespaces = (this.namespaceOptions.map((ns) => ns.name) || []).sort();
|
|
241
|
+
} else {
|
|
242
|
+
// Determine the namespaces
|
|
243
|
+
const namespaceObjs = this.isCreate ? this.allowedNamespaces() : this.namespaces();
|
|
244
|
+
|
|
245
|
+
namespaces = Object.keys(namespaceObjs);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
233
249
|
const options = namespaces
|
|
234
250
|
.map((namespace) => ({ nameDisplay: namespace, id: namespace }))
|
|
235
251
|
.map(this.namespaceMapper || ((obj) => ({
|
|
@@ -156,7 +156,7 @@ export default {
|
|
|
156
156
|
},
|
|
157
157
|
|
|
158
158
|
priorityDisplay(term) {
|
|
159
|
-
return
|
|
159
|
+
return 'weight' in term ? this.t('workload.scheduling.affinity.preferred') : this.t('workload.scheduling.affinity.required');
|
|
160
160
|
},
|
|
161
161
|
|
|
162
162
|
updateExpressions(row, expressions) {
|
|
@@ -212,7 +212,7 @@ export default {
|
|
|
212
212
|
/>
|
|
213
213
|
</div>
|
|
214
214
|
<div
|
|
215
|
-
v-if="props.row.value
|
|
215
|
+
v-if="'weight' in props.row.value"
|
|
216
216
|
class="col span-3"
|
|
217
217
|
>
|
|
218
218
|
<LabeledInput
|
|
@@ -286,7 +286,7 @@ export default {
|
|
|
286
286
|
},
|
|
287
287
|
|
|
288
288
|
priorityDisplay(term) {
|
|
289
|
-
return
|
|
289
|
+
return 'weight' in term ? this.t('workload.scheduling.affinity.preferred') : this.t('workload.scheduling.affinity.required');
|
|
290
290
|
},
|
|
291
291
|
|
|
292
292
|
changeNamespaceMode(val, term, idx) {
|
|
@@ -468,7 +468,7 @@ export default {
|
|
|
468
468
|
/>
|
|
469
469
|
</div>
|
|
470
470
|
<div
|
|
471
|
-
v-if="props.row.value
|
|
471
|
+
v-if="'weight' in props.row.value"
|
|
472
472
|
class="col span-3"
|
|
473
473
|
>
|
|
474
474
|
<LabeledInput
|
|
@@ -13,6 +13,7 @@ import { _VIEW } from '@shell/config/query-params';
|
|
|
13
13
|
import RelatedResources from '@shell/components/RelatedResources';
|
|
14
14
|
import { ExtensionPoint, TabLocation } from '@shell/core/types';
|
|
15
15
|
import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
|
|
16
|
+
import { isConditionReadyAndWaiting } from '@shell/plugins/dashboard-store/resource-class';
|
|
16
17
|
|
|
17
18
|
export default {
|
|
18
19
|
|
|
@@ -60,6 +61,11 @@ export default {
|
|
|
60
61
|
needRelated: {
|
|
61
62
|
type: Boolean,
|
|
62
63
|
default: true
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
extensionParams: {
|
|
67
|
+
type: Object,
|
|
68
|
+
default: null
|
|
63
69
|
}
|
|
64
70
|
},
|
|
65
71
|
|
|
@@ -71,7 +77,7 @@ export default {
|
|
|
71
77
|
allEvents: [],
|
|
72
78
|
selectedTab: this.defaultTab,
|
|
73
79
|
didLoadEvents: false,
|
|
74
|
-
extensionTabs: getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.RESOURCE_DETAIL, this.$route),
|
|
80
|
+
extensionTabs: getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.RESOURCE_DETAIL, this.$route, this, this.extensionParams),
|
|
75
81
|
};
|
|
76
82
|
},
|
|
77
83
|
|
|
@@ -140,7 +146,7 @@ export default {
|
|
|
140
146
|
},
|
|
141
147
|
conditionsHaveIssues() {
|
|
142
148
|
if (this.showConditions) {
|
|
143
|
-
return this.value.status?.conditions?.some((cond) => cond.error);
|
|
149
|
+
return this.value.status?.conditions?.filter((cond) => !isConditionReadyAndWaiting(cond)).some((cond) => cond.error);
|
|
144
150
|
}
|
|
145
151
|
|
|
146
152
|
return false;
|
|
@@ -134,11 +134,27 @@ export default {
|
|
|
134
134
|
|
|
135
135
|
return true;
|
|
136
136
|
},
|
|
137
|
+
/**
|
|
138
|
+
* Get a unique value to represent the option
|
|
139
|
+
*/
|
|
137
140
|
getOptionKey(opt) {
|
|
141
|
+
// Use the property from a component level key
|
|
142
|
+
if (opt && this.optionKey) {
|
|
143
|
+
return get(opt, this.optionKey);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Use the property from an option level key
|
|
147
|
+
// This doesn't seem right, think it was meant to represent the actual option key... rather than the key to find the option key
|
|
148
|
+
// This approach also doesn't appear in LabeledSelect
|
|
138
149
|
if (opt?.optionKey) {
|
|
150
|
+
// opt.optionKey should in theory be optionKeyKey
|
|
139
151
|
return get(opt, opt.optionKey);
|
|
140
152
|
}
|
|
141
153
|
|
|
154
|
+
// There's no configuration to help us get a sensible key. Fall back on ..
|
|
155
|
+
// - the label
|
|
156
|
+
// - something random
|
|
157
|
+
|
|
142
158
|
const label = this.getOptionLabel(opt);
|
|
143
159
|
|
|
144
160
|
// label may be type of object
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import NodeAffinity from '@shell/components/form/NodeAffinity.vue';
|
|
3
|
+
import { _CREATE } from '@shell/config/query-params';
|
|
4
|
+
|
|
5
|
+
describe('component: NodeAffinity', () => {
|
|
6
|
+
it('should display the weight input when the priority is preferred', () => {
|
|
7
|
+
const nodeAffinity = {
|
|
8
|
+
preferredDuringSchedulingIgnoredDuringExecution: [{
|
|
9
|
+
preference: { matchExpressions: [] },
|
|
10
|
+
weight: 1
|
|
11
|
+
}],
|
|
12
|
+
requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [{ matchExpressions: [] }] }
|
|
13
|
+
};
|
|
14
|
+
const wrapper = mount(NodeAffinity, { propsData: { mode: _CREATE, value: nodeAffinity } });
|
|
15
|
+
|
|
16
|
+
expect(wrapper.find('[data-testid="node-affinity-weight-index0"]').exists()).toBeTruthy();
|
|
17
|
+
expect(wrapper.find('[data-testid="node-affinity-weight-index1"]').exists()).toBeFalsy();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should display the weight input when the value is cleared', async() => {
|
|
21
|
+
const nodeAffinity = {
|
|
22
|
+
preferredDuringSchedulingIgnoredDuringExecution: [{
|
|
23
|
+
preference: { matchExpressions: [] },
|
|
24
|
+
weight: 1
|
|
25
|
+
}],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const wrapper = mount(NodeAffinity, { propsData: { mode: _CREATE, value: nodeAffinity } });
|
|
29
|
+
|
|
30
|
+
const weightInput = wrapper.find('[data-testid="node-affinity-weight-index0"]');
|
|
31
|
+
|
|
32
|
+
weightInput.setValue('');
|
|
33
|
+
|
|
34
|
+
await wrapper.vm.$nextTick();
|
|
35
|
+
|
|
36
|
+
expect(wrapper.find('[data-testid="node-affinity-weight-index0"]').exists()).toBeTruthy();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import PodAffinity from '@shell/components/form/PodAffinity.vue';
|
|
3
|
+
import { _CREATE } from '@shell/config/query-params';
|
|
4
|
+
|
|
5
|
+
describe('component: PodAffinity', () => {
|
|
6
|
+
it('should display the weight input when the priority is preferred', () => {
|
|
7
|
+
const podAffinity = {
|
|
8
|
+
preferredDuringSchedulingIgnoredDuringExecution: [{
|
|
9
|
+
podAffinityTerm: { topologyKey: 'test topology key 1' },
|
|
10
|
+
weight: 1
|
|
11
|
+
}],
|
|
12
|
+
requiredDuringSchedulingIgnoredDuringExecution: [{ topologyKey: 'test topology key 2' }]
|
|
13
|
+
};
|
|
14
|
+
const wrapper = mount(PodAffinity, {
|
|
15
|
+
propsData: {
|
|
16
|
+
mode: _CREATE, field: 'overrideAffinity', value: { overrideAffinity: { podAffinity } }
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(wrapper.find('[data-testid="pod-affinity-weight-index0"]').exists()).toBeTruthy();
|
|
21
|
+
expect(wrapper.find('[data-testid="pod-affinity-weight-index1"]').exists()).toBeFalsy();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should display the weight input when the value is cleared', async() => {
|
|
25
|
+
const podAffinity = {
|
|
26
|
+
preferredDuringSchedulingIgnoredDuringExecution: [{
|
|
27
|
+
podAffinityTerm: { topologyKey: 'test topology key 1' },
|
|
28
|
+
weight: 1
|
|
29
|
+
}],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const wrapper = mount(PodAffinity, {
|
|
33
|
+
propsData: {
|
|
34
|
+
mode: _CREATE, field: 'overrideAffinity', value: { overrideAffinity: { podAffinity } }
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const weightInput = wrapper.find('[data-testid="pod-affinity-weight-index0"]');
|
|
39
|
+
|
|
40
|
+
weightInput.setValue('');
|
|
41
|
+
|
|
42
|
+
await wrapper.vm.$nextTick();
|
|
43
|
+
|
|
44
|
+
expect(wrapper.find('[data-testid="pod-affinity-weight-index0"]').exists()).toBeTruthy();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { get } from '@shell/utils/object';
|
|
3
|
+
import { isConditionReadyAndWaiting } from '@shell/plugins/dashboard-store/resource-class';
|
|
3
4
|
|
|
4
5
|
export default {
|
|
5
6
|
props: {
|
|
@@ -27,10 +28,10 @@ export default {
|
|
|
27
28
|
|
|
28
29
|
statusErrorConditions() {
|
|
29
30
|
if (this.row.hasError) {
|
|
30
|
-
return this.row?.status.conditions.filter((
|
|
31
|
+
return this.row?.status.conditions.filter((cond) => cond.error === true && !isConditionReadyAndWaiting(cond));
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
return
|
|
34
|
+
return [];
|
|
34
35
|
},
|
|
35
36
|
|
|
36
37
|
formattedConditions() {
|
|
@@ -46,7 +47,7 @@ export default {
|
|
|
46
47
|
return formattedTooltip.toString().replaceAll(',', '');
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
return
|
|
50
|
+
return '';
|
|
50
51
|
},
|
|
51
52
|
},
|
|
52
53
|
|
|
@@ -66,16 +67,19 @@ export default {
|
|
|
66
67
|
v-if="row.unavailableMachines"
|
|
67
68
|
v-clean-tooltip="row.unavailableMachines"
|
|
68
69
|
class="conditions-alert-icon icon-alert icon"
|
|
70
|
+
data-testid="unavailable-machines-alert-icon"
|
|
69
71
|
/>
|
|
70
72
|
<i
|
|
71
73
|
v-if="row.rkeTemplateUpgrade"
|
|
72
74
|
v-clean-tooltip="t('cluster.rkeTemplateUpgrade', { name: row.rkeTemplateUpgrade })"
|
|
73
75
|
class="template-upgrade-icon icon-alert icon"
|
|
76
|
+
data-testid="rke-template-upgrade-alert-icon"
|
|
74
77
|
/>
|
|
75
78
|
<i
|
|
76
|
-
v-if="row.hasError"
|
|
79
|
+
v-if="row.hasError && statusErrorConditions.length > 0"
|
|
77
80
|
v-clean-tooltip="{ content: `<div>${formattedConditions}</div>`, html: true }"
|
|
78
81
|
class="conditions-alert-icon icon-error icon-lg"
|
|
82
|
+
data-testid="conditions-has-error-icon"
|
|
79
83
|
/>
|
|
80
84
|
</span>
|
|
81
85
|
</template>
|