@rancher/shell 0.3.17 → 0.3.19

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 (118) 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 +8 -6
  7. package/components/ResourceDetail/Masthead.vue +1 -1
  8. package/components/ResourceDetail/index.vue +4 -2
  9. package/components/__tests__/PromptRestore.test.ts +142 -0
  10. package/components/auth/AzureWarning.vue +1 -1
  11. package/components/auth/RoleDetailEdit.vue +2 -0
  12. package/components/fleet/FleetResources.vue +3 -64
  13. package/components/form/FileImageSelector.vue +9 -0
  14. package/components/form/FileSelector.vue +2 -1
  15. package/components/form/MatchExpressions.vue +1 -3
  16. package/components/form/__tests__/FileImageSelector.test.ts +42 -0
  17. package/components/form/__tests__/FileSelector.test.ts +76 -0
  18. package/components/formatter/ClusterProvider.vue +3 -1
  19. package/components/formatter/__tests__/ClusterProvider.test.ts +24 -0
  20. package/components/nav/WindowManager/ContainerShell.vue +60 -36
  21. package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +561 -0
  22. package/config/labels-annotations.js +2 -1
  23. package/config/persistentVolume.ts +108 -0
  24. package/config/product/manager.js +5 -1
  25. package/config/types.js +2 -0
  26. package/core/plugin-helpers.js +19 -3
  27. package/core/types.ts +4 -0
  28. package/detail/fleet.cattle.io.gitrepo.vue +10 -2
  29. package/detail/pod.vue +36 -3
  30. package/detail/workload/index.vue +40 -9
  31. package/dialog/DiagnosticTimingsDialog.vue +1 -0
  32. package/edit/__tests__/ui.cattle.io.navlink.test.ts +110 -0
  33. package/edit/fleet.cattle.io.clustergroup.vue +14 -3
  34. package/edit/persistentvolume/__tests__/persistentvolume.test.ts +82 -0
  35. package/edit/persistentvolume/index.vue +2 -1
  36. package/edit/persistentvolume/plugins/csi.vue +3 -1
  37. package/edit/persistentvolume/plugins/longhorn.vue +12 -12
  38. package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +15 -11
  39. package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
  40. package/edit/provisioning.cattle.io.cluster/rke2.vue +5 -1
  41. package/edit/storage.k8s.io.storageclass/index.vue +1 -2
  42. package/edit/ui.cattle.io.navlink.vue +213 -186
  43. package/layouts/default.vue +1 -1
  44. package/list/group.principal.vue +1 -1
  45. package/middleware/authenticated.js +12 -4
  46. package/mixins/create-edit-view/impl.js +2 -2
  47. package/models/chart.js +1 -1
  48. package/models/etcdbackup.js +2 -1
  49. package/models/fleet.cattle.io.cluster.js +33 -4
  50. package/models/fleet.cattle.io.gitrepo.js +112 -38
  51. package/models/management.cattle.io.cluster.js +13 -3
  52. package/models/management.cattle.io.kontainerdriver.js +14 -0
  53. package/models/persistentvolume.js +2 -111
  54. package/models/pod.js +30 -0
  55. package/models/rke.cattle.io.etcdsnapshot.js +10 -7
  56. package/package.json +1 -1
  57. package/pages/c/_cluster/apps/charts/install.vue +74 -25
  58. package/pages/c/_cluster/auth/group.principal/assign-edit.vue +1 -1
  59. package/pages/c/_cluster/auth/roles/index.vue +1 -1
  60. package/pages/c/_cluster/explorer/index.vue +1 -1
  61. package/pages/c/_cluster/manager/cloudCredential/_id.vue +0 -1
  62. package/pages/c/_cluster/manager/cloudCredential/create.vue +0 -1
  63. package/pages/c/_cluster/settings/brand.vue +11 -8
  64. package/pages/c/_cluster/uiplugins/index.vue +9 -4
  65. package/pages/diagnostic.vue +5 -3
  66. package/pages/home.vue +1 -1
  67. package/plugins/dashboard-store/__tests__/actions.spec.ts +165 -0
  68. package/plugins/dashboard-store/__tests__/getters.spec.ts +100 -0
  69. package/plugins/dashboard-store/__tests__/{mutations.spec.js → mutations.spec.ts} +2 -2
  70. package/plugins/dashboard-store/actions.js +1 -1
  71. package/plugins/dashboard-store/resource-class.js +4 -0
  72. package/plugins/steve/__tests__/getters.spec.ts +93 -0
  73. package/plugins/steve/getters.js +21 -1
  74. package/plugins/steve/subscribe.js +1 -3
  75. package/rancher-components/components/BadgeState/BadgeState.spec.ts +12 -0
  76. package/rancher-components/components/BadgeState/BadgeState.vue +111 -0
  77. package/rancher-components/components/BadgeState/index.ts +1 -0
  78. package/rancher-components/components/Banner/Banner.test.ts +63 -0
  79. package/rancher-components/components/Banner/Banner.vue +244 -0
  80. package/rancher-components/components/Banner/index.ts +1 -0
  81. package/rancher-components/components/Card/Card.test.ts +37 -0
  82. package/rancher-components/components/Card/Card.vue +167 -0
  83. package/rancher-components/components/Card/index.ts +1 -0
  84. package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +68 -0
  85. package/rancher-components/components/Form/Checkbox/Checkbox.vue +420 -0
  86. package/rancher-components/components/Form/Checkbox/index.ts +1 -0
  87. package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +23 -0
  88. package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +355 -0
  89. package/rancher-components/components/Form/LabeledInput/index.ts +1 -0
  90. package/rancher-components/components/Form/Radio/RadioButton.test.ts +31 -0
  91. package/rancher-components/components/Form/Radio/RadioButton.vue +287 -0
  92. package/rancher-components/components/Form/Radio/RadioGroup.vue +254 -0
  93. package/rancher-components/components/Form/Radio/index.ts +2 -0
  94. package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +170 -0
  95. package/rancher-components/components/Form/TextArea/index.ts +1 -0
  96. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +94 -0
  97. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +149 -0
  98. package/rancher-components/components/Form/ToggleSwitch/index.ts +1 -0
  99. package/rancher-components/components/Form/index.ts +5 -0
  100. package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +151 -0
  101. package/rancher-components/components/LabeledTooltip/index.ts +1 -0
  102. package/rancher-components/components/StringList/StringList.test.ts +484 -0
  103. package/rancher-components/components/StringList/StringList.vue +611 -0
  104. package/rancher-components/components/StringList/index.ts +1 -0
  105. package/scripts/extension/publish +54 -14
  106. package/scripts/typegen.sh +10 -2
  107. package/store/index.js +1 -3
  108. package/store/store-types.js +2 -0
  109. package/types/api.d.ts +1 -0
  110. package/types/fleet.d.ts +1 -0
  111. package/types/shell/index.d.ts +696 -2
  112. package/types/userPreferences.d.ts +1 -1
  113. package/utils/__mocks__/socket.js +21 -0
  114. package/utils/grafana.js +23 -11
  115. package/utils/selector.js +2 -1
  116. package/utils/socket.js +1 -0
  117. package/utils/validators/formRules/index.ts +3 -3
  118. package/plugins/steve/urloptions.js +0 -47
@@ -7,10 +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';
11
+ import NameNsDescription from '@shell/components/form/NameNsDescription';
12
+ import { Banner } from '@components/Banner';
13
+ import FormValidation from '@shell/mixins/form-validation';
14
14
  import { normalizeName } from '@shell/utils/kube';
15
15
 
16
16
  const LINK_TYPE_URL = 'url';
@@ -20,16 +20,15 @@ const LINK_TARGET_BLANK = '_blank';
20
20
  const LINK_TARGET_SELF = '_self';
21
21
 
22
22
  export default {
23
- mixins: [CreateEditView],
23
+ mixins: [CreateEditView, FormValidation],
24
24
  components: {
25
25
  CruResource,
26
26
  LabeledInput,
27
27
  RadioGroup,
28
28
  NameNsDescription,
29
- Tabbed,
30
- Tab,
31
29
  FileImageSelector,
32
- LabeledSelect
30
+ LabeledSelect,
31
+ Banner
33
32
  },
34
33
  data() {
35
34
  return {
@@ -57,12 +56,19 @@ export default {
57
56
  label: this.t('navLink.tabs.link.type.service')
58
57
  }
59
58
  ],
60
- currentLinkType: null,
61
- targetName: null,
62
- currentTarget: LINK_TARGET_BLANK,
63
- protocolsOptions: PROTOCOLS,
64
- services: [],
65
- currentService: null,
59
+ currentLinkType: null,
60
+ targetName: null,
61
+ currentTarget: LINK_TARGET_BLANK,
62
+ protocolsOptions: PROTOCOLS,
63
+ services: [],
64
+ currentService: null,
65
+ imageErrorMessage: '',
66
+ fvFormRuleSets: [
67
+ { path: 'metadata.name', rules: ['nameRequired'] },
68
+ { path: 'spec.toURL', rules: ['urlRequired'] },
69
+ { path: 'spec.toService.namespace', rules: ['serviceNamespaceRequired'] },
70
+ { path: 'spec.toService.scheme', rules: ['serviceSchemeRequired'] }],
71
+
66
72
  };
67
73
  },
68
74
  props: {
@@ -113,6 +119,44 @@ export default {
113
119
  label: id, id, name, namespace
114
120
  }) );
115
121
  },
122
+ imageError() {
123
+ return !!this.imageErrorMessage && !this.value.spec.iconSrc;
124
+ },
125
+ fvExtraRules() {
126
+ const isLinkTypeUrl = this.currentLinkType === LINK_TYPE_URL;
127
+ const isServiceTypeUrl = this.currentLinkType === LINK_TYPE_SERVICE;
128
+
129
+ return {
130
+ nameRequired: () => {
131
+ if (!this.value.metadata.name) {
132
+ return this.getError('navLink.name.label');
133
+ }
134
+ },
135
+
136
+ urlRequired: () => {
137
+ const condition = this.value.spec.toURL;
138
+
139
+ if (isLinkTypeUrl && !condition) {
140
+ return this.getError('navLink.tabs.link.toURL.label');
141
+ }
142
+ },
143
+ serviceNamespaceRequired: () => {
144
+ const condition = this.value.spec.toService.name && this.value.spec.toService.namespace;
145
+
146
+ if (isServiceTypeUrl && !condition) {
147
+ return this.getError('navLink.tabs.link.toService.service.label');
148
+ }
149
+ },
150
+ serviceSchemeRequired: () => {
151
+ const condition = this.value.spec.toService.scheme;
152
+
153
+ if (isServiceTypeUrl && !condition) {
154
+ return this.getError('navLink.tabs.link.toService.scheme.label');
155
+ }
156
+ }
157
+
158
+ };
159
+ }
116
160
  },
117
161
  async fetch() {
118
162
  this.services = await this.$store
@@ -215,50 +259,20 @@ export default {
215
259
  getError(label) {
216
260
  return this.$store.getters['i18n/t']('validation.required', { key: this.t(label) });
217
261
  },
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
-
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
262
 
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
- }
263
+ setImageError(e) {
264
+ this.imageErrorMessage = e;
251
265
  },
266
+ setIcon(value) {
267
+ this.imageErrorMessage = '';
268
+ this.$emit('update:value.spec.iconSrc ', value);
269
+ }
252
270
  },
253
271
  created() {
254
272
  this.setDefaultValues();
255
273
  this.setUrlType();
256
274
  this.setTargetOption();
257
275
  this.setCurrentService();
258
- // Validate error presence by allowing or resetting submission process
259
- if (this.registerBeforeHook) {
260
- this.registerBeforeHook(this.validate);
261
- }
262
276
  }
263
277
  };
264
278
  </script>
@@ -268,8 +282,10 @@ export default {
268
282
  :can-yaml="!isCreate"
269
283
  :mode="mode"
270
284
  :resource="value"
271
- :errors="errors"
285
+ :errors="fvUnreportedValidationErrors"
272
286
  :cancel-event="true"
287
+ :validation-passed="fvFormIsValid"
288
+ data-testid="Navlink-CRU"
273
289
  @error="e=>errors = e"
274
290
  @finish="save"
275
291
  @cancel="done()"
@@ -285,147 +301,158 @@ export default {
285
301
  name-placeholder="navLink.name.placeholder"
286
302
  description-label="navLink.label.label"
287
303
  description-placeholder="navLink.label.placeholder"
304
+ data-testid="Navlink-name-field"
305
+ :rules="{ name: fvGetAndReportPathRules('metadata.name'), namespace: [], description: [] }"
288
306
  />
289
307
 
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>
308
+ <div class="spacer" />
309
+ <h2 v-t="'navLink.tabs.link.label'" />
310
+ <div class="row mb-20">
311
+ <div class="col span-6">
312
+ <RadioGroup
313
+ v-model="linkType"
314
+ name="type"
315
+ :mode="mode"
316
+ :options="urlTypeOptions"
317
+ data-testid="Navlink-link-radiogroup"
318
+ />
319
+ </div>
320
+ </div>
362
321
 
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>
322
+ <template v-if="isURL">
323
+ <div class="row mb-20">
324
+ <LabeledInput
325
+ v-model="value.spec.toURL"
326
+ :mode="mode"
327
+ :label="t('navLink.tabs.link.toURL.label')"
328
+ :required="isURL"
329
+ :placeholder="t('navLink.tabs.link.toURL.placeholder')"
330
+ :rules="fvGetAndReportPathRules('spec.toURL')"
331
+ data-testid="Navlink-url-field"
332
+ />
333
+ </div>
334
+ </template>
335
+ <template v-if="isService">
336
+ <div class="row mb-20">
337
+ <div class="col span-2">
338
+ <LabeledSelect
339
+ v-model="value.spec.toService.scheme"
340
+ :mode="mode"
341
+ :label="t('navLink.tabs.link.toService.scheme.label')"
342
+ :required="isService"
343
+ :options="protocolsOptions"
344
+ :placeholder="t('navLink.tabs.link.toService.scheme.placeholder')"
345
+ :rules="fvGetAndReportPathRules('spec.toService.scheme')"
346
+ data-testid="Navlink-scheme-field"
347
+ />
387
348
  </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>
349
+ <div class="col span-5">
350
+ <LabeledSelect
351
+ v-model="currentService"
352
+ :mode="mode"
353
+ :label="t('navLink.tabs.link.toService.service.label')"
354
+ :options="mappedServices"
355
+ :required="isService"
356
+ :placeholder="t('navLink.tabs.link.toService.service.placeholder')"
357
+ :rules="fvGetAndReportPathRules('spec.toService.namespace')"
358
+ data-testid="Navlink-currentService-field"
359
+ @input="setService"
360
+ />
410
361
  </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>
362
+ <div class="col span-2">
363
+ <LabeledInput
364
+ v-model="value.spec.toService.port"
365
+ :mode="mode"
366
+ :label="t('navLink.tabs.link.toService.port.label')"
367
+ type="number"
368
+ :placeholder="t('navLink.tabs.link.toService.port.placeholder')"
369
+ />
427
370
  </div>
428
- </Tab>
429
- </Tabbed>
371
+ <div class="col span-3">
372
+ <LabeledInput
373
+ v-model="value.spec.toService.path"
374
+ :mode="mode"
375
+ :label="t('navLink.tabs.link.toService.path.label')"
376
+ :placeholder="t('navLink.tabs.link.toService.path.placeholder')"
377
+ />
378
+ </div>
379
+ </div>
380
+ </template>
381
+ <div class="spacer" />
382
+ <h2 v-t="'navLink.tabs.target.label'" />
383
+ <div class="row mb-20">
384
+ <div class="col span-6">
385
+ <RadioGroup
386
+ v-model="currentTarget"
387
+ name="type"
388
+ :mode="mode"
389
+ :options="targetOptions"
390
+ @input="setTargetValue($event)"
391
+ />
392
+ </div>
393
+ <div class="col span-6">
394
+ <LabeledInput
395
+ v-if="isNamedWindow"
396
+ v-model="targetName"
397
+ :mode="mode"
398
+ :label="t('navLink.tabs.target.namedValue.label')"
399
+ @input="setTargetValue($event);"
400
+ />
401
+ </div>
402
+ </div>
403
+ <div class="spacer" />
404
+ <h2 v-t="'navLink.tabs.group.label'" />
405
+
406
+ <div class="row mb-20">
407
+ <div class="col span-6">
408
+ <LabeledInput
409
+ v-model="value.spec.group"
410
+ :mode="mode"
411
+ :tooltip="t('navLink.tabs.group.group.tooltip')"
412
+ :label="t('navLink.tabs.group.group.label')"
413
+ />
414
+ </div>
415
+ <div class="col span-6">
416
+ <LabeledInput
417
+ v-model="value.spec.sideLabel"
418
+ :mode="mode"
419
+ :label="t('navLink.tabs.group.sideLabel.label')"
420
+ />
421
+ </div>
422
+ </div>
423
+
424
+ <div class="row mb-20">
425
+ <div class="col span-6">
426
+ <LabeledInput
427
+ v-model="value.spec.description"
428
+ :mode="mode"
429
+ :label="t('navLink.tabs.group.description.label')"
430
+ />
431
+ </div>
432
+ </div>
433
+
434
+ <h4 v-t="'navLink.tabs.groupImage.label'" />
435
+ <div class="row">
436
+ <label class="text-label">
437
+ {{ t('navLink.tabs.groupImage.iconSrc.tip', {}, true) }}
438
+ </label>
439
+ </div>
440
+ <div class="row">
441
+ <FileImageSelector
442
+ v-model="value.spec.iconSrc"
443
+ :read-as-data-url="true"
444
+ :mode="mode"
445
+ :label="t('navLink.tabs.groupImage.iconSrc.label')"
446
+ accept="image/jpeg,image/png,image/svg+xml"
447
+ @error="setImageError"
448
+ @input="setIcon"
449
+ />
450
+ </div>
451
+ <Banner
452
+ v-if="imageError"
453
+ color="error"
454
+ >
455
+ {{ imageErrorMessage }}
456
+ </Banner>
430
457
  </CruResource>
431
458
  </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 {
@@ -1,10 +1,11 @@
1
1
  import NormanModel from '@shell/plugins/steve/norman-class';
2
+ import { STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
2
3
 
3
4
  export default class Rke1EtcdBackup extends NormanModel {
4
5
  get _availableActions() {
5
6
  const restore = {
6
7
  action: 'promptRestore',
7
- enabled: true,
8
+ enabled: this.state === STATES_ENUM.ACTIVE,
8
9
  icon: 'icon icon-fw icon-backup-restore',
9
10
  label: 'Restore'
10
11
  };
@@ -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
  }