@rancher/shell 0.3.17 → 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 (110) hide show
  1. package/assets/translations/en-us.yaml +8 -4
  2. package/assets/translations/zh-hans.yaml +64 -8
  3. package/components/AsyncButton.vue +1 -1
  4. package/components/Inactivity.vue +10 -0
  5. package/components/LazyImage.vue +2 -2
  6. package/components/PromptRestore.vue +7 -5
  7. package/components/ResourceDetail/Masthead.vue +1 -1
  8. package/components/ResourceDetail/index.vue +4 -2
  9. package/components/__tests__/PromptRestore.test.ts +72 -0
  10. package/components/auth/AzureWarning.vue +1 -1
  11. package/components/fleet/FleetResources.vue +3 -64
  12. package/components/form/FileImageSelector.vue +9 -0
  13. package/components/form/FileSelector.vue +2 -1
  14. package/components/form/MatchExpressions.vue +1 -3
  15. package/components/form/__tests__/FileImageSelector.test.ts +42 -0
  16. package/components/form/__tests__/FileSelector.test.ts +76 -0
  17. package/components/formatter/ClusterProvider.vue +3 -1
  18. package/components/formatter/__tests__/ClusterProvider.test.ts +24 -0
  19. package/components/nav/WindowManager/ContainerShell.vue +60 -36
  20. package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +561 -0
  21. package/config/labels-annotations.js +2 -1
  22. package/config/persistentVolume.ts +108 -0
  23. package/config/product/manager.js +5 -1
  24. package/config/types.js +2 -0
  25. package/core/plugin-helpers.js +19 -3
  26. package/core/types.ts +4 -0
  27. package/detail/fleet.cattle.io.gitrepo.vue +10 -2
  28. package/detail/pod.vue +36 -3
  29. package/detail/workload/index.vue +40 -9
  30. package/edit/__tests__/ui.cattle.io.navlink.test.ts +110 -0
  31. package/edit/fleet.cattle.io.clustergroup.vue +14 -3
  32. package/edit/persistentvolume/__tests__/persistentvolume.test.ts +82 -0
  33. package/edit/persistentvolume/index.vue +2 -1
  34. package/edit/persistentvolume/plugins/csi.vue +3 -1
  35. package/edit/persistentvolume/plugins/longhorn.vue +12 -12
  36. package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +15 -11
  37. package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
  38. package/edit/provisioning.cattle.io.cluster/rke2.vue +5 -1
  39. package/edit/storage.k8s.io.storageclass/index.vue +1 -2
  40. package/edit/ui.cattle.io.navlink.vue +213 -187
  41. package/layouts/default.vue +1 -1
  42. package/list/group.principal.vue +1 -1
  43. package/middleware/authenticated.js +12 -4
  44. package/mixins/create-edit-view/impl.js +2 -2
  45. package/models/chart.js +1 -1
  46. package/models/fleet.cattle.io.cluster.js +33 -4
  47. package/models/fleet.cattle.io.gitrepo.js +112 -38
  48. package/models/management.cattle.io.kontainerdriver.js +14 -0
  49. package/models/persistentvolume.js +2 -111
  50. package/models/pod.js +30 -0
  51. package/models/rke.cattle.io.etcdsnapshot.js +10 -7
  52. package/package.json +1 -1
  53. package/pages/c/_cluster/auth/group.principal/assign-edit.vue +1 -1
  54. package/pages/c/_cluster/auth/roles/index.vue +1 -1
  55. package/pages/c/_cluster/explorer/index.vue +1 -1
  56. package/pages/c/_cluster/manager/cloudCredential/_id.vue +0 -1
  57. package/pages/c/_cluster/manager/cloudCredential/create.vue +0 -1
  58. package/pages/c/_cluster/settings/brand.vue +11 -8
  59. package/pages/c/_cluster/uiplugins/index.vue +9 -4
  60. package/pages/home.vue +1 -1
  61. package/plugins/dashboard-store/__tests__/actions.spec.ts +165 -0
  62. package/plugins/dashboard-store/__tests__/getters.spec.ts +100 -0
  63. package/plugins/dashboard-store/__tests__/{mutations.spec.js → mutations.spec.ts} +2 -2
  64. package/plugins/dashboard-store/actions.js +1 -1
  65. package/plugins/dashboard-store/resource-class.js +4 -0
  66. package/plugins/steve/__tests__/getters.spec.ts +93 -0
  67. package/plugins/steve/getters.js +21 -1
  68. package/plugins/steve/subscribe.js +1 -3
  69. package/rancher-components/components/BadgeState/BadgeState.spec.ts +12 -0
  70. package/rancher-components/components/BadgeState/BadgeState.vue +111 -0
  71. package/rancher-components/components/BadgeState/index.ts +1 -0
  72. package/rancher-components/components/Banner/Banner.test.ts +63 -0
  73. package/rancher-components/components/Banner/Banner.vue +244 -0
  74. package/rancher-components/components/Banner/index.ts +1 -0
  75. package/rancher-components/components/Card/Card.test.ts +37 -0
  76. package/rancher-components/components/Card/Card.vue +167 -0
  77. package/rancher-components/components/Card/index.ts +1 -0
  78. package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +68 -0
  79. package/rancher-components/components/Form/Checkbox/Checkbox.vue +420 -0
  80. package/rancher-components/components/Form/Checkbox/index.ts +1 -0
  81. package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +23 -0
  82. package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +355 -0
  83. package/rancher-components/components/Form/LabeledInput/index.ts +1 -0
  84. package/rancher-components/components/Form/Radio/RadioButton.test.ts +31 -0
  85. package/rancher-components/components/Form/Radio/RadioButton.vue +287 -0
  86. package/rancher-components/components/Form/Radio/RadioGroup.vue +254 -0
  87. package/rancher-components/components/Form/Radio/index.ts +2 -0
  88. package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +170 -0
  89. package/rancher-components/components/Form/TextArea/index.ts +1 -0
  90. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +94 -0
  91. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +149 -0
  92. package/rancher-components/components/Form/ToggleSwitch/index.ts +1 -0
  93. package/rancher-components/components/Form/index.ts +5 -0
  94. package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +151 -0
  95. package/rancher-components/components/LabeledTooltip/index.ts +1 -0
  96. package/rancher-components/components/StringList/StringList.test.ts +484 -0
  97. package/rancher-components/components/StringList/StringList.vue +611 -0
  98. package/rancher-components/components/StringList/index.ts +1 -0
  99. package/scripts/typegen.sh +10 -2
  100. package/store/index.js +1 -3
  101. package/store/store-types.js +2 -0
  102. package/types/api.d.ts +1 -0
  103. package/types/fleet.d.ts +1 -0
  104. package/types/shell/index.d.ts +695 -2
  105. package/types/userPreferences.d.ts +1 -1
  106. package/utils/__mocks__/socket.js +21 -0
  107. package/utils/grafana.js +23 -11
  108. package/utils/selector.js +2 -1
  109. package/utils/validators/formRules/index.ts +3 -3
  110. package/plugins/steve/urloptions.js +0 -47
@@ -7,11 +7,10 @@ import CruResource from '@shell/components/CruResource';
7
7
  import { LabeledInput } from '@components/Form/LabeledInput';
8
8
  import { RadioGroup } from '@components/Form/Radio';
9
9
  import FileImageSelector from '@shell/components/form/FileImageSelector';
10
- import NameNsDescription from '@shell/components/form/NameNsDescription';
11
- import Tab from '@shell/components/Tabbed/Tab';
12
- import Tabbed from '@shell/components/Tabbed';
13
10
  import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
14
- import { normalizeName } from '@shell/utils/kube';
11
+ import NameNsDescription, { normalizeName } from '@shell/components/form/NameNsDescription';
12
+ import { Banner } from '@components/Banner';
13
+ import FormValidation from '@shell/mixins/form-validation';
15
14
 
16
15
  const LINK_TYPE_URL = 'url';
17
16
  const LINK_TYPE_SERVICE = 'service';
@@ -20,16 +19,15 @@ const LINK_TARGET_BLANK = '_blank';
20
19
  const LINK_TARGET_SELF = '_self';
21
20
 
22
21
  export default {
23
- mixins: [CreateEditView],
22
+ mixins: [CreateEditView, FormValidation],
24
23
  components: {
25
24
  CruResource,
26
25
  LabeledInput,
27
26
  RadioGroup,
28
27
  NameNsDescription,
29
- Tabbed,
30
- Tab,
31
28
  FileImageSelector,
32
- LabeledSelect
29
+ LabeledSelect,
30
+ Banner
33
31
  },
34
32
  data() {
35
33
  return {
@@ -57,12 +55,19 @@ export default {
57
55
  label: this.t('navLink.tabs.link.type.service')
58
56
  }
59
57
  ],
60
- currentLinkType: null,
61
- targetName: null,
62
- currentTarget: LINK_TARGET_BLANK,
63
- protocolsOptions: PROTOCOLS,
64
- services: [],
65
- currentService: null,
58
+ currentLinkType: null,
59
+ targetName: null,
60
+ currentTarget: LINK_TARGET_BLANK,
61
+ protocolsOptions: PROTOCOLS,
62
+ services: [],
63
+ currentService: null,
64
+ imageErrorMessage: '',
65
+ fvFormRuleSets: [
66
+ { path: 'metadata.name', rules: ['nameRequired'] },
67
+ { path: 'spec.toURL', rules: ['urlRequired'] },
68
+ { path: 'spec.toService.namespace', rules: ['serviceNamespaceRequired'] },
69
+ { path: 'spec.toService.scheme', rules: ['serviceSchemeRequired'] }],
70
+
66
71
  };
67
72
  },
68
73
  props: {
@@ -113,6 +118,44 @@ export default {
113
118
  label: id, id, name, namespace
114
119
  }) );
115
120
  },
121
+ imageError() {
122
+ return !!this.imageErrorMessage && !this.value.spec.iconSrc;
123
+ },
124
+ fvExtraRules() {
125
+ const isLinkTypeUrl = this.currentLinkType === LINK_TYPE_URL;
126
+ const isServiceTypeUrl = this.currentLinkType === LINK_TYPE_SERVICE;
127
+
128
+ return {
129
+ nameRequired: () => {
130
+ if (!this.value.metadata.name) {
131
+ return this.getError('navLink.name.label');
132
+ }
133
+ },
134
+
135
+ urlRequired: () => {
136
+ const condition = this.value.spec.toURL;
137
+
138
+ if (isLinkTypeUrl && !condition) {
139
+ return this.getError('navLink.tabs.link.toURL.label');
140
+ }
141
+ },
142
+ serviceNamespaceRequired: () => {
143
+ const condition = this.value.spec.toService.name && this.value.spec.toService.namespace;
144
+
145
+ if (isServiceTypeUrl && !condition) {
146
+ return this.getError('navLink.tabs.link.toService.service.label');
147
+ }
148
+ },
149
+ serviceSchemeRequired: () => {
150
+ const condition = this.value.spec.toService.scheme;
151
+
152
+ if (isServiceTypeUrl && !condition) {
153
+ return this.getError('navLink.tabs.link.toService.scheme.label');
154
+ }
155
+ }
156
+
157
+ };
158
+ }
116
159
  },
117
160
  async fetch() {
118
161
  this.services = await this.$store
@@ -215,50 +258,20 @@ export default {
215
258
  getError(label) {
216
259
  return this.$store.getters['i18n/t']('validation.required', { key: this.t(label) });
217
260
  },
218
- /**
219
- * Verify all fields are fulfilled or display footer notification
220
- */
221
- validate() {
222
- const errors = [];
223
-
224
- switch (this.currentLinkType) {
225
- case LINK_TYPE_URL:
226
- if (!this.value.spec.toURL) {
227
- errors.push(this.getError('navLink.tabs.link.toURL.label'));
228
- }
229
- break;
230
-
231
- case LINK_TYPE_SERVICE:
232
- if (!this.value.spec.toService.name || !this.value.spec.toService.namespace) {
233
- errors.push(this.getError(`navLink.tabs.link.toService.service.label`));
234
- }
235
261
 
236
- if (!this.value.spec.toService.scheme) {
237
- errors.push(this.getError(`navLink.tabs.link.toService.scheme.label`));
238
- }
239
- break;
240
-
241
- // no default
242
- }
243
-
244
- if (!this.value.metadata.name) {
245
- errors.push(this.getError('navLink.name.label'));
246
- }
247
-
248
- if (errors.length) {
249
- throw (errors.join('\n'));
250
- }
262
+ setImageError(e) {
263
+ this.imageErrorMessage = e;
251
264
  },
265
+ setIcon(value) {
266
+ this.imageErrorMessage = '';
267
+ this.$emit('update:value.spec.iconSrc ', value);
268
+ }
252
269
  },
253
270
  created() {
254
271
  this.setDefaultValues();
255
272
  this.setUrlType();
256
273
  this.setTargetOption();
257
274
  this.setCurrentService();
258
- // Validate error presence by allowing or resetting submission process
259
- if (this.registerBeforeHook) {
260
- this.registerBeforeHook(this.validate);
261
- }
262
275
  }
263
276
  };
264
277
  </script>
@@ -268,8 +281,10 @@ export default {
268
281
  :can-yaml="!isCreate"
269
282
  :mode="mode"
270
283
  :resource="value"
271
- :errors="errors"
284
+ :errors="fvUnreportedValidationErrors"
272
285
  :cancel-event="true"
286
+ :validation-passed="fvFormIsValid"
287
+ data-testid="Navlink-CRU"
273
288
  @error="e=>errors = e"
274
289
  @finish="save"
275
290
  @cancel="done()"
@@ -285,147 +300,158 @@ export default {
285
300
  name-placeholder="navLink.name.placeholder"
286
301
  description-label="navLink.label.label"
287
302
  description-placeholder="navLink.label.placeholder"
303
+ data-testid="Navlink-name-field"
304
+ :rules="{ name: fvGetAndReportPathRules('metadata.name'), namespace: [], description: [] }"
288
305
  />
289
306
 
290
- <Tabbed
291
- :side-tabs="true"
292
- >
293
- <Tab
294
- name="link"
295
- :label="t('navLink.tabs.link.label')"
296
- >
297
- <div class="row mb-20">
298
- <div class="col span-6">
299
- <RadioGroup
300
- v-model="linkType"
301
- name="type"
302
- :mode="mode"
303
- :options="urlTypeOptions"
304
- />
305
- </div>
306
- </div>
307
-
308
- <template v-if="isURL">
309
- <div class="row mb-20">
310
- <LabeledInput
311
- v-model="value.spec.toURL"
312
- :mode="mode"
313
- :label="t('navLink.tabs.link.toURL.label')"
314
- :required="isURL"
315
- :placeholder="t('navLink.tabs.link.toURL.placeholder')"
316
- />
317
- </div>
318
- </template>
319
- <template v-if="isService">
320
- <div class="row mb-20">
321
- <div class="col span-2">
322
- <LabeledSelect
323
- v-model="value.spec.toService.scheme"
324
- :mode="mode"
325
- :label="t('navLink.tabs.link.toService.scheme.label')"
326
- :required="isService"
327
- :options="protocolsOptions"
328
- :placeholder="t('navLink.tabs.link.toService.scheme.placeholder')"
329
- />
330
- </div>
331
- <div class="col span-5">
332
- <LabeledSelect
333
- v-model="currentService"
334
- :mode="mode"
335
- :label="t('navLink.tabs.link.toService.service.label')"
336
- :options="mappedServices"
337
- :required="isService"
338
- :placeholder="t('navLink.tabs.link.toService.service.placeholder')"
339
- @input="setService"
340
- />
341
- </div>
342
- <div class="col span-2">
343
- <LabeledInput
344
- v-model="value.spec.toService.port"
345
- :mode="mode"
346
- :label="t('navLink.tabs.link.toService.port.label')"
347
- type="number"
348
- :placeholder="t('navLink.tabs.link.toService.port.placeholder')"
349
- />
350
- </div>
351
- <div class="col span-3">
352
- <LabeledInput
353
- v-model="value.spec.toService.path"
354
- :mode="mode"
355
- :label="t('navLink.tabs.link.toService.path.label')"
356
- :placeholder="t('navLink.tabs.link.toService.path.placeholder')"
357
- />
358
- </div>
359
- </div>
360
- </template>
361
- </Tab>
307
+ <div class="spacer" />
308
+ <h2 v-t="'navLink.tabs.link.label'" />
309
+ <div class="row mb-20">
310
+ <div class="col span-6">
311
+ <RadioGroup
312
+ v-model="linkType"
313
+ name="type"
314
+ :mode="mode"
315
+ :options="urlTypeOptions"
316
+ data-testid="Navlink-link-radiogroup"
317
+ />
318
+ </div>
319
+ </div>
362
320
 
363
- <Tab
364
- name="target"
365
- :label="t('navLink.tabs.target.label')"
366
- >
367
- <div class="row mb-20">
368
- <div class="col span-6">
369
- <RadioGroup
370
- v-model="currentTarget"
371
- name="type"
372
- :mode="mode"
373
- :options="targetOptions"
374
- :required="true"
375
- @input="setTargetValue($event)"
376
- />
377
- </div>
378
- <div class="col span-6">
379
- <LabeledInput
380
- v-if="isNamedWindow"
381
- v-model="targetName"
382
- :mode="mode"
383
- :label="t('navLink.tabs.target.namedValue.label')"
384
- @input="setTargetValue($event);"
385
- />
386
- </div>
321
+ <template v-if="isURL">
322
+ <div class="row mb-20">
323
+ <LabeledInput
324
+ v-model="value.spec.toURL"
325
+ :mode="mode"
326
+ :label="t('navLink.tabs.link.toURL.label')"
327
+ :required="isURL"
328
+ :placeholder="t('navLink.tabs.link.toURL.placeholder')"
329
+ :rules="fvGetAndReportPathRules('spec.toURL')"
330
+ data-testid="Navlink-url-field"
331
+ />
332
+ </div>
333
+ </template>
334
+ <template v-if="isService">
335
+ <div class="row mb-20">
336
+ <div class="col span-2">
337
+ <LabeledSelect
338
+ v-model="value.spec.toService.scheme"
339
+ :mode="mode"
340
+ :label="t('navLink.tabs.link.toService.scheme.label')"
341
+ :required="isService"
342
+ :options="protocolsOptions"
343
+ :placeholder="t('navLink.tabs.link.toService.scheme.placeholder')"
344
+ :rules="fvGetAndReportPathRules('spec.toService.scheme')"
345
+ data-testid="Navlink-scheme-field"
346
+ />
387
347
  </div>
388
- </Tab>
389
-
390
- <Tab
391
- name="group"
392
- :label="t('navLink.tabs.group.label')"
393
- >
394
- <div class="row mb-20">
395
- <div class="col span-6">
396
- <LabeledInput
397
- v-model="value.spec.group"
398
- :mode="mode"
399
- :tooltip="t('navLink.tabs.group.group.tooltip')"
400
- :label="t('navLink.tabs.group.group.label')"
401
- />
402
- </div>
403
- <div class="col span-6">
404
- <LabeledInput
405
- v-model="value.spec.sideLabel"
406
- :mode="mode"
407
- :label="t('navLink.tabs.group.sideLabel.label')"
408
- />
409
- </div>
348
+ <div class="col span-5">
349
+ <LabeledSelect
350
+ v-model="currentService"
351
+ :mode="mode"
352
+ :label="t('navLink.tabs.link.toService.service.label')"
353
+ :options="mappedServices"
354
+ :required="isService"
355
+ :placeholder="t('navLink.tabs.link.toService.service.placeholder')"
356
+ :rules="fvGetAndReportPathRules('spec.toService.namespace')"
357
+ data-testid="Navlink-currentService-field"
358
+ @input="setService"
359
+ />
410
360
  </div>
411
-
412
- <div class="row mb-20">
413
- <div class="col span-6">
414
- <LabeledInput
415
- v-model="value.spec.description"
416
- :mode="mode"
417
- :label="t('navLink.tabs.group.description.label')"
418
- />
419
- </div>
420
- <div class="col span-2">
421
- <FileImageSelector
422
- v-model="value.spec.iconSrc"
423
- :mode="mode"
424
- :label="t('navLink.tabs.group.iconSrc.label')"
425
- />
426
- </div>
361
+ <div class="col span-2">
362
+ <LabeledInput
363
+ v-model="value.spec.toService.port"
364
+ :mode="mode"
365
+ :label="t('navLink.tabs.link.toService.port.label')"
366
+ type="number"
367
+ :placeholder="t('navLink.tabs.link.toService.port.placeholder')"
368
+ />
427
369
  </div>
428
- </Tab>
429
- </Tabbed>
370
+ <div class="col span-3">
371
+ <LabeledInput
372
+ v-model="value.spec.toService.path"
373
+ :mode="mode"
374
+ :label="t('navLink.tabs.link.toService.path.label')"
375
+ :placeholder="t('navLink.tabs.link.toService.path.placeholder')"
376
+ />
377
+ </div>
378
+ </div>
379
+ </template>
380
+ <div class="spacer" />
381
+ <h2 v-t="'navLink.tabs.target.label'" />
382
+ <div class="row mb-20">
383
+ <div class="col span-6">
384
+ <RadioGroup
385
+ v-model="currentTarget"
386
+ name="type"
387
+ :mode="mode"
388
+ :options="targetOptions"
389
+ @input="setTargetValue($event)"
390
+ />
391
+ </div>
392
+ <div class="col span-6">
393
+ <LabeledInput
394
+ v-if="isNamedWindow"
395
+ v-model="targetName"
396
+ :mode="mode"
397
+ :label="t('navLink.tabs.target.namedValue.label')"
398
+ @input="setTargetValue($event);"
399
+ />
400
+ </div>
401
+ </div>
402
+ <div class="spacer" />
403
+ <h2 v-t="'navLink.tabs.group.label'" />
404
+
405
+ <div class="row mb-20">
406
+ <div class="col span-6">
407
+ <LabeledInput
408
+ v-model="value.spec.group"
409
+ :mode="mode"
410
+ :tooltip="t('navLink.tabs.group.group.tooltip')"
411
+ :label="t('navLink.tabs.group.group.label')"
412
+ />
413
+ </div>
414
+ <div class="col span-6">
415
+ <LabeledInput
416
+ v-model="value.spec.sideLabel"
417
+ :mode="mode"
418
+ :label="t('navLink.tabs.group.sideLabel.label')"
419
+ />
420
+ </div>
421
+ </div>
422
+
423
+ <div class="row mb-20">
424
+ <div class="col span-6">
425
+ <LabeledInput
426
+ v-model="value.spec.description"
427
+ :mode="mode"
428
+ :label="t('navLink.tabs.group.description.label')"
429
+ />
430
+ </div>
431
+ </div>
432
+
433
+ <h4 v-t="'navLink.tabs.groupImage.label'" />
434
+ <div class="row">
435
+ <label class="text-label">
436
+ {{ t('navLink.tabs.groupImage.iconSrc.tip', {}, true) }}
437
+ </label>
438
+ </div>
439
+ <div class="row">
440
+ <FileImageSelector
441
+ v-model="value.spec.iconSrc"
442
+ :read-as-data-url="true"
443
+ :mode="mode"
444
+ :label="t('navLink.tabs.groupImage.iconSrc.label')"
445
+ accept="image/jpeg,image/png,image/svg+xml"
446
+ @error="setImageError"
447
+ @input="setIcon"
448
+ />
449
+ </div>
450
+ <Banner
451
+ v-if="imageError"
452
+ color="error"
453
+ >
454
+ {{ imageErrorMessage }}
455
+ </Banner>
430
456
  </CruResource>
431
457
  </template>
@@ -38,7 +38,7 @@ import PageHeaderActions from '@shell/mixins/page-actions';
38
38
  import BrowserTabVisibility from '@shell/mixins/browser-tab-visibility';
39
39
  import { getClusterFromRoute, getProductFromRoute } from '@shell/middleware/authenticated';
40
40
  import { BOTTOM } from '@shell/utils/position';
41
- import { BLANK_CLUSTER } from '@shell/store';
41
+ import { BLANK_CLUSTER } from '@shell/store/store-types.js';
42
42
 
43
43
  const SET_LOGIN_ACTION = 'set-as-login';
44
44
 
@@ -8,7 +8,7 @@ import { applyProducts } from '@shell/store/type-map';
8
8
  import { NAME } from '@shell/config/product/auth';
9
9
  import { MODE, _EDIT } from '@shell/config/query-params';
10
10
  import { mapState } from 'vuex';
11
- import { BLANK_CLUSTER } from '@shell/store';
11
+ import { BLANK_CLUSTER } from '@shell/store/store-types.js';
12
12
  import { allHash } from '@shell/utils/promise';
13
13
 
14
14
  export default {
@@ -126,12 +126,20 @@ function setProduct(store, to, redirect) {
126
126
  */
127
127
  function invalidResource(store, to, redirect) {
128
128
  const product = store.getters['currentProduct'];
129
- const inStore = product?.inStore;
130
- const schemaFor = store.getters[`${ inStore }/schemaFor`]; // There's a chance we're in an extension's product who's store could be anything
131
129
  const resource = getResourceFromRoute(to);
132
130
 
133
- // In order to check a resource is valid we need all of these
134
- if (!product || !inStore || !schemaFor || !resource) {
131
+ // In order to check a resource is valid we need these
132
+ if (!product || !resource) {
133
+ return false;
134
+ }
135
+
136
+ // Note - don't use the current products store... because products can override stores for resources with `typeStoreMap`
137
+ const inStore = store.getters['currentStore'](resource);
138
+ // There's a chance we're in an extension's product who's store could be anything, so confirm schemaFor exists
139
+ const schemaFor = store.getters[`${ inStore }/schemaFor`];
140
+
141
+ // In order to check a resource is valid we need these
142
+ if (!inStore || !schemaFor) {
135
143
  return false;
136
144
  }
137
145
 
@@ -65,9 +65,9 @@ export default {
65
65
 
66
66
  let name = this.$route.name;
67
67
 
68
- if ( name.endsWith('-id') ) {
68
+ if ( name?.endsWith('-id') ) {
69
69
  name = name.replace(/(-namespace)?-id$/, '');
70
- } else if ( name.endsWith('-create') ) {
70
+ } else if ( name?.endsWith('-create') ) {
71
71
  name = name.replace(/-create$/, '');
72
72
  }
73
73
 
package/models/chart.js CHANGED
@@ -2,7 +2,7 @@ import { compatibleVersionsFor } from '@shell/store/catalog';
2
2
  import {
3
3
  REPO_TYPE, REPO, CHART, VERSION, _FLAGGED, HIDE_SIDE_NAV
4
4
  } from '@shell/config/query-params';
5
- import { BLANK_CLUSTER } from '@shell/store';
5
+ import { BLANK_CLUSTER } from '@shell/store/store-types.js';
6
6
  import SteveModel from '@shell/plugins/steve/steve-class';
7
7
 
8
8
  export default class Chart extends SteveModel {
@@ -4,6 +4,7 @@ import { _RKE2 } from '@shell/store/prefs';
4
4
  import SteveModel from '@shell/plugins/steve/steve-class';
5
5
  import { escapeHtml } from '@shell/utils/string';
6
6
  import { insertAt } from '@shell/utils/array';
7
+ import jsyaml from 'js-yaml';
7
8
 
8
9
  export default class FleetCluster extends SteveModel {
9
10
  get _availableActions() {
@@ -89,7 +90,7 @@ export default class FleetCluster extends SteveModel {
89
90
  }
90
91
 
91
92
  get state() {
92
- if ( this.spec?.paused === true ) {
93
+ if (this.spec?.paused === true) {
93
94
  return 'paused';
94
95
  }
95
96
 
@@ -124,19 +125,47 @@ export default class FleetCluster extends SteveModel {
124
125
  return mgmt;
125
126
  }
126
127
 
127
- get norman() {
128
- const norman = this.$rootGetters['rancher/byId'](NORMAN.CLUSTER, this.metadata.labels?.[FLEET_LABELS.CLUSTER_NAME]);
128
+ get basicNorman() {
129
+ const norman = this.$rootGetters['rancher/byId'](NORMAN.CLUSTER, this.metadata?.labels?.[FLEET_LABELS.CLUSTER_NAME]);
129
130
 
130
131
  return norman;
131
132
  }
132
133
 
134
+ get norman() {
135
+ if (this.basicNorman) {
136
+ return this.basicNorman;
137
+ }
138
+
139
+ // If navigate to YAML view directly, norman is not loaded yet
140
+ return this.$dispatch('rancher/find', { type: NORMAN.CLUSTER, id: this.metadata.labels[FLEET_LABELS.CLUSTER_NAME] }, { root: true });
141
+ }
142
+
143
+ async normanClone() {
144
+ const norman = await this.norman;
145
+
146
+ return this.$dispatch('rancher/clone', { resource: norman }, { root: true });
147
+ }
148
+
133
149
  get groupByLabel() {
134
150
  const name = this.metadata.namespace;
135
151
 
136
- if ( name ) {
152
+ if (name) {
137
153
  return this.$rootGetters['i18n/t']('resourceTable.groupLabel.workspace', { name: escapeHtml(name) });
138
154
  } else {
139
155
  return this.$rootGetters['i18n/t']('resourceTable.groupLabel.notInAWorkspace');
140
156
  }
141
157
  }
158
+
159
+ async saveYaml(yaml) {
160
+ await this._saveYaml(yaml);
161
+
162
+ const parsed = jsyaml.load(yaml);
163
+
164
+ const norman = await this.normanClone();
165
+
166
+ norman.setLabels(parsed.metadata.labels);
167
+ norman.setAnnotations(parsed.metadata.annotations);
168
+
169
+ await norman.save();
170
+ }
142
171
  }