@rancher/shell 0.3.16 → 0.3.18

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.
Files changed (174) hide show
  1. package/assets/images/wechat-qr-code.jpg +0 -0
  2. package/assets/translations/en-us.yaml +75 -16
  3. package/assets/translations/zh-hans.yaml +151 -15
  4. package/chart/__tests__/S3.test.ts +50 -0
  5. package/chart/rancher-backup/S3.vue +21 -0
  6. package/chart/rancher-backup/index.vue +4 -0
  7. package/components/AsyncButton.vue +1 -1
  8. package/components/CommunityLinks.vue +1 -0
  9. package/components/FileDiff.vue +92 -85
  10. package/components/Inactivity.vue +10 -0
  11. package/components/LazyImage.vue +2 -2
  12. package/components/PromptRestore.vue +7 -5
  13. package/components/ResourceDetail/Masthead.vue +1 -1
  14. package/components/ResourceDetail/index.vue +8 -14
  15. package/components/ResourceList/index.vue +1 -1
  16. package/components/ResourceTable.vue +50 -2
  17. package/components/YamlEditor.vue +1 -0
  18. package/components/__tests__/PromptRestore.test.ts +72 -0
  19. package/components/auth/AzureWarning.vue +1 -1
  20. package/components/auth/RoleDetailEdit.vue +1 -0
  21. package/components/fleet/FleetResources.vue +3 -64
  22. package/components/form/FileImageSelector.vue +9 -0
  23. package/components/form/FileSelector.vue +2 -1
  24. package/components/form/MatchExpressions.vue +1 -3
  25. package/components/form/NameNsDescription.vue +28 -12
  26. package/components/form/NodeAffinity.vue +2 -2
  27. package/components/form/PodAffinity.vue +2 -2
  28. package/components/form/ResourceTabs/index.vue +8 -2
  29. package/components/form/Select.vue +16 -0
  30. package/components/form/__tests__/FileImageSelector.test.ts +42 -0
  31. package/components/form/__tests__/FileSelector.test.ts +76 -0
  32. package/components/form/__tests__/NodeAffinity.test.ts +38 -0
  33. package/components/form/__tests__/PodAffinity.test.ts +46 -0
  34. package/components/formatter/ClusterLink.vue +8 -4
  35. package/components/formatter/ClusterProvider.vue +3 -1
  36. package/components/formatter/ImageName.vue +23 -0
  37. package/components/formatter/PodImages.vue +7 -1
  38. package/components/formatter/__tests__/ClusterLink.test.ts +101 -0
  39. package/components/formatter/__tests__/ClusterProvider.test.ts +24 -0
  40. package/components/nav/Header.vue +2 -2
  41. package/components/nav/WindowManager/ContainerShell.vue +60 -36
  42. package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +561 -0
  43. package/config/__test__/home-links.test.ts +62 -0
  44. package/config/home-links.js +15 -3
  45. package/config/labels-annotations.js +7 -2
  46. package/config/persistentVolume.ts +108 -0
  47. package/config/product/manager.js +5 -1
  48. package/config/router.js +0 -4
  49. package/config/settings.ts +4 -0
  50. package/config/table-headers.js +6 -5
  51. package/config/types.js +2 -0
  52. package/config/uiplugins.js +50 -5
  53. package/core/plugin-helpers.js +39 -15
  54. package/core/plugin.ts +9 -0
  55. package/core/plugins.js +1 -1
  56. package/core/types-provisioning.ts +253 -0
  57. package/core/types.ts +21 -3
  58. package/detail/autoscaling.horizontalpodautoscaler/index.vue +50 -1
  59. package/detail/fleet.cattle.io.gitrepo.vue +10 -2
  60. package/detail/node.vue +6 -6
  61. package/detail/pod.vue +38 -9
  62. package/detail/provisioning.cattle.io.cluster.vue +46 -7
  63. package/detail/workload/index.vue +49 -18
  64. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +62 -0
  65. package/edit/__tests__/ui.cattle.io.navlink.test.ts +110 -0
  66. package/edit/auth/github.vue +1 -0
  67. package/edit/autoscaling.horizontalpodautoscaler/hpa-scaling-rule.vue +130 -0
  68. package/edit/autoscaling.horizontalpodautoscaler/index.vue +79 -0
  69. package/edit/fleet.cattle.io.clustergroup.vue +14 -3
  70. package/edit/fleet.cattle.io.gitrepo.vue +18 -1
  71. package/edit/namespace.vue +9 -1
  72. package/edit/networking.k8s.io.ingress/RulePath.vue +0 -2
  73. package/edit/persistentvolume/__tests__/persistentvolume.test.ts +82 -0
  74. package/edit/persistentvolume/index.vue +2 -1
  75. package/edit/persistentvolume/plugins/csi.vue +3 -1
  76. package/edit/persistentvolume/plugins/longhorn.vue +12 -12
  77. package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +1 -30
  78. package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +15 -11
  79. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +79 -1
  80. package/edit/provisioning.cattle.io.cluster/index.vue +53 -1
  81. package/edit/provisioning.cattle.io.cluster/rke2.vue +335 -151
  82. package/edit/storage.k8s.io.storageclass/index.vue +1 -2
  83. package/edit/ui.cattle.io.navlink.vue +213 -186
  84. package/initialize/App.js +3 -13
  85. package/initialize/layouts.ts +26 -0
  86. package/layouts/default.vue +1 -1
  87. package/list/group.principal.vue +1 -1
  88. package/list/provisioning.cattle.io.cluster.vue +8 -1
  89. package/middleware/authenticated.js +101 -5
  90. package/mixins/brand.js +39 -3
  91. package/mixins/child-hook.js +2 -2
  92. package/mixins/create-edit-view/impl.js +4 -4
  93. package/models/chart.js +1 -1
  94. package/models/fleet.cattle.io.cluster.js +33 -4
  95. package/models/fleet.cattle.io.gitrepo.js +113 -38
  96. package/models/management.cattle.io.kontainerdriver.js +14 -0
  97. package/models/persistentvolume.js +2 -111
  98. package/models/pod.js +30 -0
  99. package/models/provisioning.cattle.io.cluster.js +9 -1
  100. package/models/rke.cattle.io.etcdsnapshot.js +10 -7
  101. package/package.json +2 -2
  102. package/pages/about.vue +8 -2
  103. package/pages/auth/login.vue +1 -1
  104. package/pages/auth/logout.vue +11 -3
  105. package/pages/c/_cluster/apps/charts/index.vue +5 -2
  106. package/pages/c/_cluster/apps/charts/install.vue +5 -0
  107. package/pages/c/_cluster/auth/group.principal/assign-edit.vue +1 -1
  108. package/pages/c/_cluster/auth/roles/index.vue +1 -1
  109. package/pages/c/_cluster/explorer/index.vue +2 -11
  110. package/pages/c/_cluster/manager/cloudCredential/_id.vue +0 -1
  111. package/pages/c/_cluster/manager/cloudCredential/create.vue +0 -1
  112. package/pages/c/_cluster/settings/brand.vue +11 -8
  113. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +177 -0
  114. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +19 -3
  115. package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +90 -21
  116. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +107 -37
  117. package/pages/c/_cluster/uiplugins/index.vue +160 -44
  118. package/pages/docs/_doc.vue +9 -3
  119. package/pages/home.vue +6 -6
  120. package/pages/support/index.vue +10 -4
  121. package/pkg/auto-import.js +1 -1
  122. package/plugins/clean-tooltip-directive.js +1 -1
  123. package/plugins/dashboard-store/__tests__/actions.spec.ts +165 -0
  124. package/plugins/dashboard-store/__tests__/getters.spec.ts +100 -0
  125. package/plugins/dashboard-store/__tests__/{mutations.spec.js → mutations.spec.ts} +2 -2
  126. package/plugins/dashboard-store/actions.js +1 -1
  127. package/plugins/dashboard-store/resource-class.js +39 -2
  128. package/plugins/plugin.js +9 -1
  129. package/plugins/steve/__tests__/getters.spec.ts +93 -0
  130. package/plugins/steve/getters.js +21 -1
  131. package/plugins/steve/subscribe.js +1 -3
  132. package/rancher-components/BadgeState/BadgeState.vue +5 -1
  133. package/rancher-components/Banner/Banner.test.ts +51 -1
  134. package/rancher-components/Banner/Banner.vue +134 -53
  135. package/rancher-components/Card/Card.test.ts +37 -0
  136. package/rancher-components/Card/Card.vue +24 -7
  137. package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
  138. package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
  139. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
  140. package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
  141. package/rancher-components/Form/Radio/RadioButton.test.ts +31 -0
  142. package/rancher-components/Form/Radio/RadioButton.vue +30 -13
  143. package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
  144. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
  145. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
  146. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
  147. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
  148. package/rancher-components/StringList/StringList.test.ts +453 -49
  149. package/rancher-components/StringList/StringList.vue +44 -26
  150. package/scripts/extension/publish +2 -2
  151. package/scripts/typegen.sh +11 -2
  152. package/server/server-middleware.js +4 -12
  153. package/store/index.js +14 -3
  154. package/store/prefs.js +0 -3
  155. package/store/store-types.js +2 -0
  156. package/store/type-map.js +17 -29
  157. package/types/api.d.ts +1 -0
  158. package/types/fleet.d.ts +1 -0
  159. package/types/shell/index.d.ts +931 -85
  160. package/types/userPreferences.d.ts +1 -1
  161. package/utils/__mocks__/socket.js +21 -0
  162. package/utils/grafana.js +23 -11
  163. package/utils/kube.js +9 -0
  164. package/utils/object.js +27 -0
  165. package/utils/selector.js +2 -1
  166. package/utils/settings.ts +2 -2
  167. package/utils/validators/formRules/index.ts +3 -3
  168. package/vue.config.js +3 -2
  169. package/components/.DS_Store +0 -0
  170. package/components/__tests__/.DS_Store +0 -0
  171. package/creators/pkg/package-lock.json +0 -37
  172. package/pages/safeMode.vue +0 -17
  173. package/plugins/steve/urloptions.js +0 -47
  174. package/yarn-error.log +0 -196
@@ -1,5 +1,6 @@
1
1
  <script>
2
- import { Diff2Html } from 'diff2html';
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
- computed: {
42
- html() {
43
- const outputFormat = this.sideBySide ? 'side-by-side' : 'line-by-line';
44
- const showFiles = false;
45
- const matching = 'words';
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
- const json = Diff2Html.getJsonFromDiff(patch, {
54
- inputFormat: 'diff',
55
- outputFormat,
56
- showFiles,
57
- matching
58
- });
59
-
60
- return Diff2Html.getPrettyHtml(json, {
61
- inputFormat: 'json',
62
- outputFormat,
63
- showFiles,
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/dist/diff2html.min.css';
117
-
118
- .d2h-file-header {
119
- display: none;
120
- }
121
-
122
- .d2h-file-wrapper {
123
- border-color: var(--diff-border);
124
- }
125
-
126
- .d2h-diff-table {
127
- font-family: Menlo,Consolas,monospace;
128
- font-size: 13px;
129
- }
130
-
131
- .d2h-emptyplaceholder, .d2h-code-side-emptyplaceholder {
132
- border-color: var(--diff-linenum-border);
133
- background-color: var(--diff-empty-placeholder);
134
- }
135
-
136
- .d2h-code-linenumber,
137
- .d2h-code-side-linenumber {
138
- background-color: var(--diff-linenum-bg);
139
- color: var(--diff-linenum);
140
- border-color: var(--diff-linenum-border);
141
- border-left: 0;
142
- }
143
-
144
- .d2h-code-line del,.d2h-code-side-line del {
145
- background-color: var(--diff-line-del-bg);
146
- }
147
-
148
- .d2h-code-line ins,.d2h-code-side-line ins {
149
- background-color: var(--diff-line-ins-bg);
150
- }
151
-
152
- .d2h-del {
153
- background-color: var(--diff-del-bg);
154
- border-color: var(--diff-del-border);
155
- color: var(--body-text);
156
- }
157
-
158
- .d2h-ins {
159
- background-color: var(--diff-ins-bg);
160
- border-color: var(--diff-ins-border);
161
- color: var(--body-text);
162
- }
163
-
164
- .d2h-info {
165
- background-color: var(--diff-header-bg);
166
- color: var(--diff-header);
167
- border-color: var(--diff-header-border);
168
- }
169
-
170
- .d2h-file-diff .d2h-del.d2h-change {
171
- background-color: var(--diff-chg-del);
172
- }
173
-
174
- .d2h-file-diff .d2h-ins.d2h-change {
175
- background-color: var(--diff-chg-ins);
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>
@@ -6,6 +6,8 @@ import throttle from 'lodash/throttle';
6
6
  import { MANAGEMENT } from '@shell/config/types';
7
7
  import { DEFAULT_PERF_SETTING, SETTING } from '@shell/config/settings';
8
8
 
9
+ let globalId;
10
+
9
11
  export default {
10
12
  name: 'Inactivity',
11
13
  components: {
@@ -22,6 +24,7 @@ export default {
22
24
  courtesyTimerId: null,
23
25
  courtesyCountdown: null,
24
26
  trackInactivity: throttle(this._trackInactivity, 1000),
27
+ id: null,
25
28
  };
26
29
  },
27
30
  async mounted() {
@@ -72,9 +75,16 @@ export default {
72
75
  this.clearAllTimeouts();
73
76
  const endTime = Date.now() + this.showModalAfter * 1000;
74
77
 
78
+ this.id = endTime;
79
+ globalId = endTime;
80
+
75
81
  const checkInactivityTimer = () => {
76
82
  const now = Date.now();
77
83
 
84
+ if (this.id !== globalId) {
85
+ return;
86
+ }
87
+
78
88
  if (now >= endTime) {
79
89
  this.isOpen = true;
80
90
  this.startCountdown();
@@ -3,12 +3,12 @@ export default {
3
3
  props: {
4
4
  initialSrc: {
5
5
  type: String,
6
- default: require('~shell/assets/images/generic-catalog.svg'),
6
+ default: require('@shell/assets/images/generic-catalog.svg'),
7
7
  },
8
8
 
9
9
  errorSrc: {
10
10
  type: String,
11
- default: require('~shell/assets/images/generic-catalog.svg'),
11
+ default: require('@shell/assets/images/generic-catalog.svg'),
12
12
  },
13
13
 
14
14
  src: {
@@ -14,6 +14,7 @@ import { DATE_FORMAT, TIME_FORMAT } from '@shell/store/prefs';
14
14
  import { escapeHtml } from '@shell/utils/string';
15
15
  import day from 'dayjs';
16
16
  import { sortBy } from '@shell/utils/sort';
17
+ import { STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
17
18
 
18
19
  export default {
19
20
  components: {
@@ -51,7 +52,7 @@ export default {
51
52
  // Was the dialog opened to restore a specific snapshot, or opened on a cluster to choose
52
53
  isCluster() {
53
54
  const isSnapshot = this.toRestore[0]?.type.toLowerCase() === NORMAN.ETCD_BACKUP ||
54
- this.toRestore[0]?.type.toLowerCase() === SNAPSHOT;
55
+ this.toRestore[0]?.type.toLowerCase() === SNAPSHOT;
55
56
 
56
57
  return !isSnapshot;
57
58
  },
@@ -113,7 +114,7 @@ export default {
113
114
  const cluster = this.toRestore?.[0];
114
115
  let promise;
115
116
 
116
- if (!cluster.isRke2) {
117
+ if (!cluster?.isRke2) {
117
118
  promise = this.$store.dispatch('rancher/findAll', { type: NORMAN.ETCD_BACKUP }).then((snapshots) => {
118
119
  return snapshots.filter((s) => s.clusterId === cluster.metadata.name);
119
120
  });
@@ -121,12 +122,13 @@ export default {
121
122
  promise = this.$store.dispatch('management/findAll', { type: SNAPSHOT }).then((snapshots) => {
122
123
  const toRestoreClusterName = cluster?.clusterName || cluster?.metadata?.name;
123
124
 
124
- return snapshots.filter((s) => s.clusterName === toRestoreClusterName);
125
+ return snapshots.filter((s) => s?.snapshotFile?.status === STATES_ENUM.SUCCESSFUL && s.clusterName === toRestoreClusterName
126
+ );
125
127
  });
126
128
  }
127
129
 
128
130
  // Map of snapshots by name
129
- const allSnapshosts = await promise.then((snapshots) => {
131
+ const allSnapshots = await promise.then((snapshots) => {
130
132
  return snapshots.reduce((v, s) => {
131
133
  v[s.name] = s;
132
134
 
@@ -136,7 +138,7 @@ export default {
136
138
  this.errors = exceptionToErrorsArray(err);
137
139
  });
138
140
 
139
- this.allSnapshots = allSnapshosts;
141
+ this.allSnapshots = allSnapshots;
140
142
  this.sortedSnapshots = sortBy(Object.values(this.allSnapshots), ['snapshotFile.createdAt', 'created', 'metadata.creationTimestamp'], true);
141
143
  },
142
144
 
@@ -216,7 +216,7 @@ export default {
216
216
  },
217
217
 
218
218
  parent() {
219
- const displayName = this.value.parentNameOverride || this.$store.getters['type-map/labelFor'](this.schema);
219
+ const displayName = this.value?.parentNameOverride || this.$store.getters['type-map/labelFor'](this.schema);
220
220
  const product = this.$store.getters['currentProduct'].name;
221
221
 
222
222
  const defaultLocation = {
@@ -83,16 +83,18 @@ export default {
83
83
  default: 'resource-details'
84
84
  }
85
85
  },
86
+
86
87
  async fetch() {
87
88
  const store = this.$store;
88
89
  const route = this.$route;
89
90
  const params = route.params;
90
- const inStore = this.storeOverride || store.getters['currentStore'](params.resource);
91
+ let resource = this.resourceOverride || params.resource;
92
+
93
+ const inStore = this.storeOverride || store.getters['currentStore'](resource);
91
94
  const realMode = this.realMode;
92
95
 
93
96
  // eslint-disable-next-line prefer-const
94
97
  let { namespace, id } = params;
95
- let resource = this.resourceOverride || params.resource;
96
98
 
97
99
  // There are 6 "real" modes that can be put into the query string
98
100
  // These are mapped down to the 3 regular page "mode"s that create-edit-view components
@@ -193,6 +195,9 @@ export default {
193
195
  opt: { watch: true }
194
196
  });
195
197
  } catch (e) {
198
+ if (e.status === 404 || e.status === 403) {
199
+ store.dispatch('loadingError', new Error(this.t('nav.failWhale.resourceIdNotFound', { resource, fqid }, true)));
200
+ }
196
201
  liveModel = {};
197
202
  notFound = fqid;
198
203
  }
@@ -367,18 +372,7 @@ export default {
367
372
  </script>
368
373
 
369
374
  <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>
375
+ <Loading v-if="$fetchState.pending || notFound" />
382
376
  <div v-else>
383
377
  <Masthead
384
378
  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(`Type ${ resource } not found, unable to display list`));
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="_key"
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"
@@ -241,6 +241,7 @@ export default {
241
241
  :side-by-side="diffMode === 'split'"
242
242
  :orig="original"
243
243
  :neu="curValue"
244
+ :footer-space="80"
244
245
  />
245
246
  </div>
246
247
  </template>
@@ -0,0 +1,72 @@
1
+ import { shallowMount, createLocalVue } from '@vue/test-utils';
2
+ import PromptRestore from '@shell/components/PromptRestore.vue';
3
+ import Vuex from 'vuex';
4
+ import { ExtendedVue, Vue } from 'vue/types/vue';
5
+ import { DefaultProps } from 'vue/types/options';
6
+ import { CAPI } from '@shell/config/types';
7
+ import { STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
8
+
9
+ const SUCCESSFUL_SNAPSHOT_1 = {
10
+ isRke2: true,
11
+ type: CAPI.RANCHER_CLUSTER,
12
+ created: 'Thu Jul 20 2023 11:11:39',
13
+ snapshotFile: { status: STATES_ENUM.SUCCESSFUL },
14
+ id: 'id',
15
+ name: 'name'
16
+ };
17
+ const SUCCESSFUL_SNAPSHOT_2 = {
18
+ isRke2: true,
19
+ type: CAPI.RANCHER_CLUSTER,
20
+ created: 'Thu Jul 20 2022 11:11:39',
21
+ snapshotFile: { status: STATES_ENUM.SUCCESSFUL },
22
+ id: 'id2',
23
+ name: 'name2'
24
+ };
25
+ const FAILED_SNAPSHOT = {
26
+ isRke2: true,
27
+ type: CAPI.RANCHER_CLUSTER,
28
+ created: 'Thu Jul 20 2021 11:11:39',
29
+ snapshotFile: { status: STATES_ENUM.FAILED },
30
+ id: 'id_3',
31
+ name: 'name_3'
32
+ };
33
+
34
+ describe('component: GrowlManager', () => {
35
+ const localVue = createLocalVue();
36
+
37
+ localVue.use(Vuex);
38
+
39
+ const testCases = [
40
+ [[], 0],
41
+ [[FAILED_SNAPSHOT], 0],
42
+ [[SUCCESSFUL_SNAPSHOT_1], 1],
43
+ [[SUCCESSFUL_SNAPSHOT_1, SUCCESSFUL_SNAPSHOT_2], 2],
44
+ [[FAILED_SNAPSHOT, SUCCESSFUL_SNAPSHOT_1, SUCCESSFUL_SNAPSHOT_2], 2]
45
+ ];
46
+
47
+ it.each(testCases)('should list RKE2 snapshots properly', async(snapShots, expected) => {
48
+ const store = new Vuex.Store({
49
+ modules: {
50
+ 'action-menu': {
51
+ namespaced: true,
52
+ state: {
53
+ showPromptRestore: true,
54
+ toRestore: snapShots
55
+ },
56
+ },
57
+ },
58
+ getters: { 'i18n/t': () => jest.fn(), 'prefs/get': () => jest.fn() },
59
+ actions: { 'management/findAll': jest.fn().mockResolvedValue(snapShots), 'rancher/findAll': jest.fn().mockResolvedValue([]) }
60
+ });
61
+
62
+ const wrapper = shallowMount(PromptRestore as unknown as ExtendedVue<Vue, {}, {}, {}, DefaultProps>, {
63
+ store,
64
+ localVue
65
+ });
66
+
67
+ await wrapper.vm.fetchSnapshots();
68
+ await wrapper.vm.$nextTick();
69
+
70
+ expect(wrapper.vm.clusterSnapshots).toHaveLength(expected);
71
+ });
72
+ });
@@ -3,7 +3,7 @@
3
3
  import { NORMAN, MANAGEMENT } from '@shell/config/types';
4
4
  import { get } from '@shell/utils/object';
5
5
  import { AZURE_MIGRATED } from '@shell/config/labels-annotations';
6
- import { BLANK_CLUSTER } from '@shell/store';
6
+ import { BLANK_CLUSTER } from '@shell/store/store-types.js';
7
7
 
8
8
  export default {
9
9
  async fetch() {
@@ -668,6 +668,7 @@ export default {
668
668
  :value="getRule('resources', props.row.value)"
669
669
  :disabled="isBuiltin"
670
670
  :options="resourceOptions"
671
+ option-key="optionKey"
671
672
  :searchable="true"
672
673
  :taggable="true"
673
674
  :mode="mode"
@@ -1,9 +1,6 @@
1
1
  <script>
2
- import { colorForState, stateDisplay, stateSort, STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
3
2
  import SortableTable from '@shell/components/SortableTable';
4
- import { NAME as EXPLORER } from '@shell/config/product/explorer';
5
- import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
6
- import { randomStr } from '@shell/utils/string';
3
+ import { AGE } from '@shell/config/table-headers';
7
4
 
8
5
  export default {
9
6
  name: 'FleetResources',
@@ -19,66 +16,7 @@ export default {
19
16
 
20
17
  computed: {
21
18
  computedResources() {
22
- const clusters = this.value.targetClusters || [];
23
- const resources = this.value.status?.resources || [];
24
- const out = [];
25
-
26
- for ( const r of resources ) {
27
- let namespacedName = r.name;
28
-
29
- if ( r.namespace ) {
30
- namespacedName = `${ r.namespace }:${ r.name }`;
31
- }
32
-
33
- for ( const c of clusters ) {
34
- let state = r.state;
35
- const perEntry = r.perClusterState?.find((x) => x.clusterId === c.id );
36
- const tooMany = r.perClusterState?.length >= 10 || false;
37
-
38
- if ( perEntry ) {
39
- state = perEntry.state;
40
- } else if ( tooMany ) {
41
- state = STATES_ENUM.UNKNOWN;
42
- } else {
43
- state = STATES_ENUM.READY;
44
- }
45
-
46
- const color = colorForState(state).replace('text-', 'bg-');
47
- const display = stateDisplay(state);
48
-
49
- const detailLocation = {
50
- name: `c-cluster-product-resource${ r.namespace ? '-namespace' : '' }-id`,
51
- params: {
52
- product: EXPLORER,
53
- cluster: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
54
- resource: r.type,
55
- namespace: r.namespace,
56
- id: r.name,
57
- }
58
- };
59
-
60
- out.push({
61
- key: `${ r.id }-${ c.id }-${ r.type }-${ r.namespace }-${ r.name }`,
62
- tableKey: `${ r.id }-${ c.id }-${ r.type }-${ r.namespace }-${ r.name }-${ randomStr(8) }`,
63
- kind: r.kind,
64
- apiVersion: r.apiVersion,
65
- type: r.type,
66
- id: r.id,
67
- namespace: r.namespace,
68
- name: r.name,
69
- clusterId: c.id,
70
- clusterName: c.nameDisplay,
71
- state,
72
- stateBackground: color,
73
- stateDisplay: display,
74
- stateSort: stateSort(color, display),
75
- namespacedName,
76
- detailLocation,
77
- });
78
- }
79
- }
80
-
81
- return out;
19
+ return this.value.resourcesStatuses;
82
20
  },
83
21
 
84
22
  resourceHeaders() {
@@ -122,6 +60,7 @@ export default {
122
60
  sort: 'namespace',
123
61
  label: 'Namespace',
124
62
  },
63
+ { ...AGE }
125
64
  ];
126
65
  },
127
66
  }