@rancher/shell 0.3.11 → 0.3.13

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 (95) hide show
  1. package/assets/translations/en-us.yaml +51 -5
  2. package/chart/monitoring/StorageClassSelector.vue +1 -0
  3. package/chart/monitoring/index.vue +4 -0
  4. package/chart/monitoring/prometheus/index.vue +6 -3
  5. package/components/ActionMenu.vue +1 -1
  6. package/components/DetailTop.vue +0 -2
  7. package/components/ExplorerMembers.vue +22 -10
  8. package/components/ExplorerProjectsNamespaces.vue +1 -0
  9. package/components/GrafanaDashboard.vue +2 -2
  10. package/components/Inactivity.vue +1 -0
  11. package/components/ModalWithCard.vue +1 -0
  12. package/components/Tabbed/index.vue +2 -0
  13. package/components/Wizard.vue +4 -3
  14. package/components/form/KeyValue.vue +12 -7
  15. package/components/form/NodeAffinity.vue +29 -7
  16. package/components/form/PodAffinity.vue +27 -7
  17. package/components/form/Taints.vue +6 -0
  18. package/components/formatter/ExtensionCache.vue +74 -0
  19. package/components/nav/Header.vue +1 -0
  20. package/components/nav/WindowManager/ContainerShell.vue +10 -0
  21. package/components/nav/WindowManager/index.vue +1 -0
  22. package/config/product/explorer.js +1 -10
  23. package/config/product/monitoring.js +2 -1
  24. package/config/router.js +3 -3
  25. package/config/table-headers.js +32 -24
  26. package/config/uiplugins.js +11 -0
  27. package/config/workload.ts +1 -0
  28. package/core/types.ts +25 -7
  29. package/creators/pkg/files/.github/workflows/build-container.yml +64 -0
  30. package/creators/pkg/init +13 -6
  31. package/detail/node.vue +2 -2
  32. package/detail/workload/index.vue +1 -1
  33. package/edit/__tests__/management.cattle.io.setting.test.ts +1 -1
  34. package/edit/__tests__/namespace.test.ts +46 -0
  35. package/edit/autoscaling.horizontalpodautoscaler/metric-target.vue +0 -2
  36. package/edit/logging.banzaicloud.io.output/__tests__/logging.banzaicloud.io.output.test.ts +43 -0
  37. package/edit/logging.banzaicloud.io.output/index.vue +8 -5
  38. package/edit/logging.banzaicloud.io.output/providers/__tests__/loki.test.ts +13 -0
  39. package/edit/logging.banzaicloud.io.output/providers/loki.vue +1 -0
  40. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +0 -2
  41. package/edit/monitoring.coreos.com.receiver/index.vue +32 -1
  42. package/edit/monitoring.coreos.com.receiver/types/email.vue +12 -4
  43. package/edit/namespace.vue +2 -1
  44. package/edit/provisioning.cattle.io.cluster/MachinePool.vue +36 -6
  45. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +2 -2
  46. package/edit/provisioning.cattle.io.cluster/rke2.vue +58 -13
  47. package/middleware/authenticated.js +1 -0
  48. package/models/__tests__/batch.cronjob.test.ts +88 -0
  49. package/models/cluster/node.js +8 -0
  50. package/models/management.cattle.io.clusterroletemplatebinding.js +5 -1
  51. package/models/projectroletemplatebinding.js +9 -1
  52. package/models/workload.js +1 -1
  53. package/package.json +1 -1
  54. package/pages/__tests__/prefs.test.ts +96 -0
  55. package/pages/auth/setup.vue +13 -13
  56. package/pages/c/_cluster/apps/charts/chart.vue +1 -1
  57. package/pages/c/_cluster/apps/charts/install.vue +5 -2
  58. package/pages/c/_cluster/monitoring/index.vue +10 -5
  59. package/pages/c/_cluster/settings/performance.vue +2 -0
  60. package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +601 -0
  61. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +183 -0
  62. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +50 -9
  63. package/pages/c/_cluster/uiplugins/index.vue +329 -224
  64. package/pages/fail-whale.vue +1 -1
  65. package/pages/home.vue +11 -0
  66. package/pages/prefs.vue +20 -1
  67. package/plugins/plugin.js +1 -1
  68. package/public/index.html +6 -1
  69. package/rancher-components/components/Card/Card.vue +1 -0
  70. package/rancher-components/components/Form/Radio/RadioGroup.vue +1 -0
  71. package/scripts/extension/bundle +20 -4
  72. package/scripts/extension/helm/charts/ui-plugin-server/.helmignore +23 -0
  73. package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +20 -0
  74. package/scripts/extension/helm/charts/ui-plugin-server/templates/_helpers.tpl +52 -0
  75. package/scripts/extension/helm/charts/ui-plugin-server/templates/cr.yaml +12 -0
  76. package/scripts/extension/helm/charts/ui-plugin-server/values.yaml +6 -0
  77. package/scripts/extension/helm/package/Dockerfile +27 -0
  78. package/scripts/extension/helm/package/nginx.conf +17 -0
  79. package/scripts/extension/helm/scripts/package +23 -0
  80. package/scripts/extension/helm/scripts/patch +101 -0
  81. package/scripts/extension/helm/scripts/version +31 -0
  82. package/scripts/extension/helmpatch +3 -25
  83. package/scripts/extension/publish +50 -33
  84. package/types/shell/index.d.ts +39 -33
  85. package/utils/__tests__/grafana.test.ts +2 -2
  86. package/utils/error.js +11 -0
  87. package/utils/grafana.js +5 -4
  88. package/vue.config.js +3 -17
  89. package/components/.DS_Store +0 -0
  90. package/components/__tests__/.DS_Store +0 -0
  91. package/creators/pkg/package-lock.json +0 -37
  92. package/rancher-components/StringList/StringList.test.ts +0 -80
  93. package/rancher-components/StringList/StringList.vue +0 -593
  94. package/rancher-components/StringList/index.ts +0 -1
  95. package/yarn-error.log +0 -196
@@ -57,7 +57,7 @@ export default {
57
57
  project: null,
58
58
  projects: null,
59
59
  viewMode: _VIEW,
60
- containerResourceLimits: this.value.annotations[CONTAINER_DEFAULT_RESOURCE_LIMIT] || this.getDefaultContainerResourceLimits(projectName),
60
+ containerResourceLimits: this.value.annotations?.[CONTAINER_DEFAULT_RESOURCE_LIMIT] || this.getDefaultContainerResourceLimits(projectName),
61
61
  projectName,
62
62
  HARVESTER_TYPES,
63
63
  RANCHER_TYPES,
@@ -181,6 +181,7 @@ export default {
181
181
  >
182
182
  <LabeledSelect
183
183
  v-model="projectName"
184
+ data-testid="name-ns-description-project"
184
185
  :label="t('namespace.project.label')"
185
186
  :options="projectOpts"
186
187
  />
@@ -59,6 +59,12 @@ export default {
59
59
  type: Array,
60
60
  default: () => []
61
61
  },
62
+
63
+ // Is the UI busy (e.g. during save)
64
+ busy: {
65
+ type: Boolean,
66
+ default: false,
67
+ }
62
68
  },
63
69
 
64
70
  data() {
@@ -117,8 +123,7 @@ export default {
117
123
 
118
124
  isWindows() {
119
125
  return this.value?.config?.os === 'windows';
120
- }
121
-
126
+ },
122
127
  },
123
128
 
124
129
  watch: {
@@ -133,6 +138,18 @@ export default {
133
138
  }
134
139
  },
135
140
 
141
+ /**
142
+ * On creation, ensure that the pool is marked valid - custom machine pools can emit further validation events
143
+ */
144
+ created() {
145
+ this.$emit('validationChanged', true);
146
+ },
147
+
148
+ beforeDestroy() {
149
+ // Ensure we emit validation event so parent can forget any validation for this Machine Pool when it is removed
150
+ this.$emit('validationChanged', undefined);
151
+ },
152
+
136
153
  methods: {
137
154
  async test() {
138
155
  if ( typeof this.$refs.configComponent?.test === 'function' ) {
@@ -166,6 +183,11 @@ export default {
166
183
  if (advancedComponent && !advancedComponent.show) {
167
184
  advancedComponent.toggle();
168
185
  }
186
+ },
187
+
188
+ // Propagate up validation status for this Machine Pool
189
+ validationChanged(val) {
190
+ this.$emit('validationChanged', val);
169
191
  }
170
192
  }
171
193
  };
@@ -188,7 +210,7 @@ export default {
188
210
  :mode="mode"
189
211
  :label="t('cluster.machinePool.name.label')"
190
212
  :required="true"
191
- :disabled="!value.config || !!value.config.id"
213
+ :disabled="!value.config || !!value.config.id || busy"
192
214
  />
193
215
  </div>
194
216
  <div class="col span-4">
@@ -196,6 +218,7 @@ export default {
196
218
  v-model.number="value.pool.quantity"
197
219
  :mode="mode"
198
220
  :label="t('cluster.machinePool.quantity.label')"
221
+ :disabled="busy"
199
222
  type="number"
200
223
  min="0"
201
224
  :required="true"
@@ -209,18 +232,19 @@ export default {
209
232
  v-model="value.pool.etcdRole"
210
233
  :mode="mode"
211
234
  :label="t('cluster.machinePool.role.etcd')"
212
- :disabled="isWindows"
235
+ :disabled="isWindows || busy"
213
236
  />
214
237
  <Checkbox
215
238
  v-model="value.pool.controlPlaneRole"
216
239
  :mode="mode"
217
240
  :label="t('cluster.machinePool.role.controlPlane')"
218
- :disabled="isWindows"
241
+ :disabled="isWindows || busy"
219
242
  />
220
243
  <Checkbox
221
244
  v-model="value.pool.workerRole"
222
245
  :mode="mode"
223
246
  :label="t('cluster.machinePool.role.worker')"
247
+ :disabled="busy"
224
248
  />
225
249
  </div>
226
250
  </div>
@@ -237,9 +261,11 @@ export default {
237
261
  :credential-id="credentialId"
238
262
  :pool-index="idx"
239
263
  :machine-pools="machinePools"
264
+ :busy="busy"
240
265
  @error="e=>errors = e"
241
266
  @updateMachineCount="updateMachineCount"
242
267
  @expandAdvanced="expandAdvanced"
268
+ @validationChanged="validationChanged"
243
269
  />
244
270
  <Banner
245
271
  v-else-if="value.configMissing"
@@ -279,6 +305,7 @@ export default {
279
305
  :mode="mode"
280
306
  :output-modifier="true"
281
307
  :base-unit="t('cluster.machinePool.autoReplace.unit')"
308
+ :disabled="busy"
282
309
  @input="value.pool.unhealthyNodeTimeout = `${unhealthyNodeTimeoutInteger}s`"
283
310
  />
284
311
  </div>
@@ -290,6 +317,7 @@ export default {
290
317
  v-model="value.pool.drainBeforeDelete"
291
318
  :mode="mode"
292
319
  :label="t('cluster.machinePool.drain.label')"
320
+ :disabled="busy"
293
321
  />
294
322
  </div>
295
323
  </div>
@@ -297,16 +325,18 @@ export default {
297
325
  <KeyValue
298
326
  v-model="value.pool.labels"
299
327
  :add-label="t('labels.addLabel')"
300
- :mode="mode"
328
+ :disabled="busy"
301
329
  :title="t('cluster.machinePool.labels.label')"
302
330
  :read-allowed="false"
303
331
  :value-can-be-empty="true"
304
332
  />
333
+
305
334
  <div class="spacer" />
306
335
 
307
336
  <Taints
308
337
  v-model="value.pool.taints"
309
338
  :mode="mode"
339
+ :disabled="busy"
310
340
  />
311
341
 
312
342
  <portal-target
@@ -240,7 +240,7 @@ describe('component: rke2', () => {
240
240
  stubs: defaultStubs
241
241
  });
242
242
 
243
- expect(wrapper.vm.validationPassed()).toBe(result);
243
+ expect((wrapper.vm as any).validationPassed).toBe(result);
244
244
  });
245
245
 
246
246
  it('should allow creation of K3 clusters if pool machines are missing', () => {
@@ -265,6 +265,6 @@ describe('component: rke2', () => {
265
265
  stubs: defaultStubs
266
266
  });
267
267
 
268
- expect(wrapper.vm.validationPassed()).toBe(true);
268
+ expect((wrapper.vm as any).validationPassed).toBe(true);
269
269
  });
270
270
  });
@@ -26,6 +26,7 @@ import {
26
26
  } from '@shell/utils/object';
27
27
  import { allHash } from '@shell/utils/promise';
28
28
  import { sortBy } from '@shell/utils/sort';
29
+
29
30
  import { camelToTitle, nlToBr } from '@shell/utils/string';
30
31
  import { compare, sortable } from '@shell/utils/version';
31
32
  import { isHarvesterSatisfiesVersion } from '@shell/utils/cluster';
@@ -330,7 +331,7 @@ export default {
330
331
  allPSPs: null,
331
332
  allPSAs: [],
332
333
  nodeComponent: null,
333
- credentialId: null,
334
+ credentialId: '',
334
335
  credential: null,
335
336
  machinePools: null,
336
337
  rke2Versions: null,
@@ -361,6 +362,8 @@ export default {
361
362
  psps: null, // List of policies if any
362
363
  truncateHostnames: truncateLimit === NETBIOS_TRUNCATION_LENGTH,
363
364
  truncateLimit,
365
+ busy: false,
366
+ machinePoolValidation: {} // map of validation states for each machine pool
364
367
  };
365
368
  },
366
369
 
@@ -706,9 +709,20 @@ export default {
706
709
  return false;
707
710
  }
708
711
 
712
+ if (this.customCredentialComponentRequired === false) {
713
+ return false;
714
+ }
715
+
709
716
  return true;
710
717
  },
711
718
 
719
+ /**
720
+ * Only for extensions - extension can register a 'false' cloud credential to indicate that a cloud credential is not needed
721
+ */
722
+ customCredentialComponentRequired() {
723
+ return this.$plugin.getDynamic('cloud-credential', this.provider);
724
+ },
725
+
712
726
  hasMachinePools() {
713
727
  if ( this.provider === 'custom' || this.provider === 'import' ) {
714
728
  return false;
@@ -1016,6 +1030,17 @@ export default {
1016
1030
  return false;
1017
1031
  }
1018
1032
  },
1033
+
1034
+ validationPassed() {
1035
+ const validRequiredPools = this.hasMachinePools ? this.hasRequiredNodes() : true;
1036
+
1037
+ let base = (this.provider === 'custom' || this.isElementalCluster || !!this.credentialId);
1038
+
1039
+ // and in all of the validation statuses for each machine pool
1040
+ Object.values(this.machinePoolValidation).forEach(v => (base = base && v));
1041
+
1042
+ return validRequiredPools && base;
1043
+ },
1019
1044
  },
1020
1045
 
1021
1046
  watch: {
@@ -1260,11 +1285,10 @@ export default {
1260
1285
 
1261
1286
  async syncMachineConfigWithLatest(machinePool) {
1262
1287
  if (machinePool?.config?.id) {
1263
- const latestConfig = await this.$store.dispatch('management/find', {
1264
- type: machinePool.config.type,
1265
- id: machinePool.config.id,
1266
- opt: { force: true },
1267
- });
1288
+ // Use management/request instead of management/find to avoid overwriting the current machine pool in the store
1289
+ const _latestConfig = await this.$store.dispatch('management/request', { url: `/v1/${ machinePool.config.type }s/${ machinePool.config.id }` });
1290
+ const latestConfig = await this.$store.dispatch('management/create', _latestConfig);
1291
+
1268
1292
  const clonedCurrentConfig = await this.$store.dispatch('management/clone', { resource: machinePool.config });
1269
1293
  const clonedLatestConfig = await this.$store.dispatch('management/clone', { resource: latestConfig });
1270
1294
 
@@ -1284,6 +1308,7 @@ export default {
1284
1308
  }
1285
1309
 
1286
1310
  await this.syncMachineConfigWithLatest(entry);
1311
+
1287
1312
  // Capitals and such aren't allowed;
1288
1313
  set(entry.pool, 'name', normalizeName(entry.pool.name) || 'pool');
1289
1314
 
@@ -1340,12 +1365,6 @@ export default {
1340
1365
  return this.nodeTotals?.color && Object.values(this.nodeTotals.color).every(color => color !== NODE_TOTAL.error.color);
1341
1366
  },
1342
1367
 
1343
- validationPassed() {
1344
- const validMachinePools = this.hasMachinePools ? this.hasRequiredNodes() : true;
1345
-
1346
- return (this.provider === 'custom' || this.isElementalCluster || !!this.credentialId) && validMachinePools;
1347
- },
1348
-
1349
1368
  cancelCredential() {
1350
1369
  if ( this.$refs.cruresource ) {
1351
1370
  this.$refs.cruresource.emitOrRoute();
@@ -1398,7 +1417,18 @@ export default {
1398
1417
  });
1399
1418
  },
1400
1419
 
1420
+ // Set busy before save and clear after save
1401
1421
  async saveOverride(btnCb) {
1422
+ this.$set(this, 'busy', true);
1423
+
1424
+ return await this._doSaveOverride((done) => {
1425
+ this.$set(this, 'busy', false);
1426
+
1427
+ return btnCb(done);
1428
+ });
1429
+ },
1430
+
1431
+ async _doSaveOverride(btnCb) {
1402
1432
  if ( this.errors ) {
1403
1433
  clear(this.errors);
1404
1434
  }
@@ -2048,6 +2078,16 @@ export default {
2048
2078
  this.lastDefaultPodSecurityPolicyTemplateName = value;
2049
2079
  },
2050
2080
 
2081
+ /**
2082
+ * Track Machine Pool validation status
2083
+ */
2084
+ machinePoolValidationChanged(id, value) {
2085
+ if (value === undefined) {
2086
+ this.$delete(this.machinePoolValidation, id);
2087
+ } else {
2088
+ this.$set(this.machinePoolValidation, id, value);
2089
+ }
2090
+ }
2051
2091
  },
2052
2092
  };
2053
2093
  </script>
@@ -2063,7 +2103,7 @@ export default {
2063
2103
  v-else
2064
2104
  ref="cruresource"
2065
2105
  :mode="mode"
2066
- :validation-passed="validationPassed() && fvFormIsValid"
2106
+ :validation-passed="validationPassed && fvFormIsValid"
2067
2107
  :resource="value"
2068
2108
  :errors="errors"
2069
2109
  :cancel-event="true"
@@ -2092,6 +2132,7 @@ export default {
2092
2132
  :provider="provider"
2093
2133
  :cancel="cancelCredential"
2094
2134
  :showing-form="showForm"
2135
+ class="mt-20"
2095
2136
  />
2096
2137
 
2097
2138
  <div
@@ -2166,6 +2207,7 @@ export default {
2166
2207
  :name="obj.id"
2167
2208
  :label="obj.pool.name || '(Not Named)'"
2168
2209
  :show-header="false"
2210
+ :error="!machinePoolValidation[obj.id]"
2169
2211
  >
2170
2212
  <MachinePool
2171
2213
  ref="pool"
@@ -2176,7 +2218,9 @@ export default {
2176
2218
  :credential-id="credentialId"
2177
2219
  :idx="idx"
2178
2220
  :machine-pools="machinePools"
2221
+ :busy="busy"
2179
2222
  @error="e=>errors = e"
2223
+ @validationChanged="v=>machinePoolValidationChanged(obj.id, v)"
2180
2224
  />
2181
2225
  </Tab>
2182
2226
  </template>
@@ -2653,6 +2697,7 @@ export default {
2653
2697
  </Banner>
2654
2698
  </div>
2655
2699
  </div>
2700
+
2656
2701
  <div
2657
2702
  v-if="serverArgs['tls-san']"
2658
2703
  class="row mb-20"
@@ -420,6 +420,7 @@ export default async function({
420
420
  if ( e instanceof ClusterNotFoundError ) {
421
421
  return redirect(302, '/home');
422
422
  } else {
423
+ // Sets error 500 if lost connection to API
423
424
  store.commit('setError', { error: e, locationError: new Error('Auth Middleware') });
424
425
 
425
426
  return redirect(302, '/fail-whale');
@@ -0,0 +1,88 @@
1
+ import Cronjob from '@shell/models/batch.cronjob';
2
+ describe('class Cronjob', () => {
3
+ it('should have no ownerReferences by default', () => {
4
+ const cronJobData = {
5
+ id: 'any-id',
6
+ type: 'batch.job',
7
+ apiVersion: 'batch/v1',
8
+ kind: 'Job',
9
+ metadata: {
10
+ name: 'any-name',
11
+ namespace: 'any-namespace',
12
+ uid: 'any-uid'
13
+ },
14
+ spec: { jobTemplate: {} }
15
+ };
16
+ const expectation = {
17
+ name: 'any-name', namespace: 'any-namespace', uid: 'any-uid'
18
+ };
19
+ const cronjob = new Cronjob(cronJobData);
20
+
21
+ expect(cronjob.metadata).toStrictEqual(expectation);
22
+ });
23
+
24
+ describe('method runNow', () => {
25
+ it('should populate job metadata', async() => {
26
+ const jobData = {
27
+ id: 'any-id',
28
+ type: 'batch.job',
29
+ apiVersion: 'batch/v1',
30
+ kind: 'Job',
31
+ metadata: {
32
+ name: 'any-name',
33
+ namespace: 'any-namespace',
34
+ uid: 'any-uid'
35
+ },
36
+ spec: { jobTemplate: {} }
37
+ };
38
+ const date = Date.now();
39
+ const expected = {
40
+ name: `${ jobData.metadata.name }-${ date }`,
41
+ namespace: jobData.metadata.namespace,
42
+ ownerReferences: [{
43
+ apiVersion: 'batch/v1',
44
+ controller: true,
45
+ kind: 'Job',
46
+ name: jobData.metadata.name,
47
+ uid: jobData.metadata.uid
48
+ }],
49
+ uid: jobData.metadata.uid
50
+ };
51
+ const dispatcher = () => ({
52
+ ...jobData,
53
+ save: jest.fn(),
54
+ goToDetail: jest.fn()
55
+ });
56
+ const cronjob = new Cronjob(jobData, { dispatch: dispatcher });
57
+
58
+ jest
59
+ .useFakeTimers()
60
+ .setSystemTime(date);
61
+ jest.spyOn(cronjob, '$dispatch').mockImplementation(dispatcher);
62
+
63
+ await cronjob.runNow();
64
+
65
+ expect(cronjob.metadata).toStrictEqual(expected);
66
+ });
67
+
68
+ it('should redirect to another page', async() => {
69
+ const jobData = {
70
+ metadata: { name: 'any-name' },
71
+ spec: { jobTemplate: {} }
72
+ };
73
+ const callback = jest.fn();
74
+ const dispatcher = () => ({
75
+ ...jobData,
76
+ save: jest.fn(),
77
+ goToDetail: callback
78
+ });
79
+ const cronjob = new Cronjob(jobData, { dispatch: dispatcher });
80
+
81
+ jest.spyOn(cronjob, '$dispatch').mockImplementation(dispatcher);
82
+
83
+ await cronjob.runNow();
84
+
85
+ expect(callback).toHaveBeenCalledWith();
86
+ });
87
+ });
88
+ });
@@ -209,6 +209,14 @@ export default class ClusterNode extends SteveModel {
209
209
  return ((this.ramUsage * 100) / this.ramCapacity).toString();
210
210
  }
211
211
 
212
+ get ramReserved() {
213
+ return parseSi(this.status?.allocatable?.memory);
214
+ }
215
+
216
+ get ramReservedPercentage() {
217
+ return ((this.ramUsage * 100) / this.ramReserved).toString();
218
+ }
219
+
212
220
  get podUsage() {
213
221
  return calculatePercentage(this.status.allocatable.pods, this.status.capacity.pods);
214
222
  }
@@ -37,13 +37,17 @@ export default class CRTB extends HybridModel {
37
37
  }, { root: true });
38
38
  }
39
39
 
40
+ get syncPrincipal() {
41
+ return this.$rootGetters['rancher/byId'](NORMAN.PRINCIPAL, this.principalId);
42
+ }
43
+
40
44
  get principalId() {
41
45
  // We've either set it ourselves or it's comes from native properties
42
46
  return this.principalName || this.userPrincipalName || this.groupPrincipalName;
43
47
  }
44
48
 
45
49
  get nameDisplay() {
46
- return this.user?.nameDisplay || this.userName || this.principalId;
50
+ return this.user?.nameDisplay || this.userName || this.syncPrincipal?.nameDisplay || this.principalId;
47
51
  }
48
52
 
49
53
  get roleDisplay() {
@@ -1,4 +1,4 @@
1
- import { MANAGEMENT } from '@shell/config/types';
1
+ import { MANAGEMENT, NORMAN } from '@shell/config/types';
2
2
  import NormanModel from '@shell/plugins/steve/norman-class';
3
3
 
4
4
  export default class PRTB extends NormanModel {
@@ -24,4 +24,12 @@ export default class PRTB extends NormanModel {
24
24
  id: this.id?.replace(':', '/')
25
25
  }, { root: true });
26
26
  }
27
+
28
+ get syncPrincipal() {
29
+ return this.$rootGetters['rancher/byId'](NORMAN.PRINCIPAL, this.principalId);
30
+ }
31
+
32
+ get nameDisplay() {
33
+ return this.syncPrincipal?.nameDisplay || super.nameDisplay;
34
+ }
27
35
  }
@@ -4,7 +4,7 @@ import { WORKLOAD_TYPES, SERVICE, POD } from '@shell/config/types';
4
4
  import { get, set } from '@shell/utils/object';
5
5
  import day from 'dayjs';
6
6
  import { convertSelectorObj, matching, matches } from '@shell/utils/selector';
7
- import { SEPARATOR } from '@shell/components/DetailTop';
7
+ import { SEPARATOR } from '@shell/config/workload';
8
8
  import WorkloadService from '@shell/models/workload.service';
9
9
 
10
10
  export const defaultContainer = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -0,0 +1,96 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import Preferences from '@shell/pages/prefs.vue';
3
+
4
+ describe('page: prefs should', () => {
5
+ it.each([
6
+ ['Fri, May 5 2023', 0],
7
+ ['Fri, 5 May 2023', 1],
8
+ ['5/5/2023 (D/M/YYYY)', 2],
9
+ ['5/5/2023 (M/D/YYYY)', 3],
10
+ ['2023-05-05 (YYYY-MM-DD)', 4],
11
+ ])('format the date optionally with a cue as %p for date 05/05/2023', (expected, index) => {
12
+ const date = '05/05/2023';
13
+ const format = ['ddd, MMM D YYYY', 'ddd, D MMM YYYY', 'D/M/YYYY', 'M/D/YYYY', 'YYYY-MM-DD'];
14
+
15
+ jest
16
+ .useFakeTimers()
17
+ .setSystemTime(new Date(date));
18
+
19
+ const wrapper = shallowMount(Preferences, {
20
+ propsData: { value: 'asd' },
21
+ computed: {
22
+ pm: jest.fn(),
23
+ am: jest.fn(),
24
+ },
25
+ mocks: {
26
+ $store: {
27
+ getters: {
28
+ 'prefs/options': () => format,
29
+ 'prefs/get': jest.fn(),
30
+ 'management/schemaFor': jest.fn(),
31
+ isSingleProduct: jest.fn(),
32
+ 'i18n/t': jest.fn(),
33
+ }
34
+ }
35
+ },
36
+ stubs: {
37
+ BackLink: { template: '<div />' },
38
+ ButtonGroup: { template: '<div />' },
39
+ LabeledSelect: { template: '<div />' },
40
+ Checkbox: { template: '<div />' },
41
+ LandingPagePreference: { template: '<div />' },
42
+ LocaleSelector: { template: '<div />' },
43
+ }
44
+ });
45
+
46
+ const options = (wrapper.vm as unknown as any).dateOptions;
47
+
48
+ expect(options[index].label).toBe(expected);
49
+ });
50
+
51
+ it.each([
52
+ ['Mon, May 1 2023', 0],
53
+ ['Mon, 1 May 2023', 1],
54
+ ['1/5/2023', 2],
55
+ ['5/1/2023', 3],
56
+ ['2023-05-01', 4],
57
+ ])('format the date %p without cue for date 01/05/2023', (expected, index) => {
58
+ const date = '05/01/2023';
59
+ const format = ['ddd, MMM D YYYY', 'ddd, D MMM YYYY', 'D/M/YYYY', 'M/D/YYYY', 'YYYY-MM-DD'];
60
+
61
+ jest
62
+ .useFakeTimers()
63
+ .setSystemTime(new Date(date));
64
+
65
+ const wrapper = shallowMount(Preferences, {
66
+ propsData: { value: 'asd' },
67
+ computed: {
68
+ pm: jest.fn(),
69
+ am: jest.fn(),
70
+ },
71
+ mocks: {
72
+ $store: {
73
+ getters: {
74
+ 'prefs/options': () => format,
75
+ 'prefs/get': jest.fn(),
76
+ 'management/schemaFor': jest.fn(),
77
+ isSingleProduct: jest.fn(),
78
+ 'i18n/t': jest.fn(),
79
+ }
80
+ }
81
+ },
82
+ stubs: {
83
+ BackLink: { template: '<div />' },
84
+ ButtonGroup: { template: '<div />' },
85
+ LabeledSelect: { template: '<div />' },
86
+ Checkbox: { template: '<div />' },
87
+ LandingPagePreference: { template: '<div />' },
88
+ LocaleSelector: { template: '<div />' },
89
+ }
90
+ });
91
+
92
+ const options = (wrapper.vm as unknown as any).dateOptions;
93
+
94
+ expect(options[index].label).toBe(expected);
95
+ });
96
+ });
@@ -16,6 +16,7 @@ import { isDevBuild } from '@shell/utils/version';
16
16
  import { exceptionToErrorsArray } from '@shell/utils/error';
17
17
  import Password from '@shell/components/form/Password';
18
18
  import { applyProducts } from '@shell/store/type-map';
19
+ import BrandImage from '@shell/components/BrandImage';
19
20
 
20
21
  const calcIsFirstLogin = (store) => {
21
22
  const firstLoginSetting = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.FIRST_LOGIN);
@@ -76,7 +77,7 @@ export default {
76
77
  },
77
78
 
78
79
  components: {
79
- AsyncButton, LabeledInput, CopyToClipboard, Checkbox, RadioGroup, Password
80
+ AsyncButton, LabeledInput, CopyToClipboard, Checkbox, RadioGroup, Password, BrandImage
80
81
  },
81
82
 
82
83
  async asyncData({ route, req, store }) {
@@ -284,7 +285,6 @@ export default {
284
285
  v-clean-html="t(isFirstLogin ? 'setup.setPassword' : 'setup.newUserSetPassword', { username }, true)"
285
286
  class="text-center mb-20 mt-20 setup-title"
286
287
  />
287
-
288
288
  <Password
289
289
  v-if="!haveCurrent"
290
290
  v-model.trim="current"
@@ -431,8 +431,10 @@ export default {
431
431
  </div>
432
432
  </div>
433
433
  </div>
434
-
435
- <div class="col span-6 landscape" />
434
+ <BrandImage
435
+ class="col span-6 landscape"
436
+ file-name="login-landscape.svg"
437
+ />
436
438
  </div>
437
439
  </form>
438
440
  </template>
@@ -464,6 +466,13 @@ export default {
464
466
  .span-6 {
465
467
  padding: 0 60px;
466
468
  }
469
+
470
+ .landscape {
471
+ height: 100vh;
472
+ margin: 0;
473
+ object-fit: cover;
474
+ padding: 0;
475
+ }
467
476
  }
468
477
 
469
478
  .form-col {
@@ -491,14 +500,5 @@ export default {
491
500
  p {
492
501
  line-height: 20px;
493
502
  }
494
-
495
- }
496
-
497
- .landscape {
498
- background-image: url('~@shell/assets/images/pl/login-landscape.svg');
499
- background-repeat: no-repeat;
500
- background-size: cover;
501
- background-position: center center;
502
- height: 100vh;
503
503
  }
504
504
  </style>