@rancher/shell 3.0.5-rc.6 → 3.0.5-rc.8

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 (243) hide show
  1. package/assets/brand/classic/metadata.json +3 -0
  2. package/assets/styles/app.scss +1 -0
  3. package/assets/styles/base/_color.scss +16 -0
  4. package/assets/styles/base/_helpers.scss +10 -0
  5. package/assets/styles/base/_variables.scss +18 -12
  6. package/assets/styles/fonts/_icons.scss +1 -32
  7. package/assets/styles/global/_layout.scss +1 -1
  8. package/assets/styles/themes/_dark.scss +262 -258
  9. package/assets/styles/themes/_light.scss +538 -509
  10. package/assets/styles/themes/_modern.scss +914 -0
  11. package/assets/translations/en-us.yaml +110 -29
  12. package/chart/__tests__/S3.test.ts +2 -1
  13. package/cloud-credential/generic.vue +18 -10
  14. package/cloud-credential/harvester.vue +1 -9
  15. package/components/AdvancedSection.vue +8 -0
  16. package/components/ChartReadme.vue +17 -7
  17. package/components/CodeMirror.vue +1 -1
  18. package/components/Drawer/Chrome.vue +0 -1
  19. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +27 -28
  20. package/components/Drawer/ResourceDetailDrawer/composables.ts +4 -24
  21. package/components/Drawer/ResourceDetailDrawer/index.vue +18 -4
  22. package/components/InstallHelmCharts.vue +656 -0
  23. package/components/LazyImage.vue +60 -4
  24. package/components/Loading.vue +1 -1
  25. package/components/LocaleSelector.vue +7 -2
  26. package/components/Markdown.vue +4 -0
  27. package/components/PaginatedResourceTable.vue +46 -1
  28. package/components/PromptRestore.vue +22 -44
  29. package/components/Resource/Detail/Masthead/composable.ts +16 -0
  30. package/components/Resource/Detail/Masthead/index.vue +37 -0
  31. package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +10 -2
  32. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +26 -7
  33. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +8 -1
  34. package/components/Resource/Detail/Metadata/KeyValue.vue +12 -10
  35. package/components/Resource/Detail/Metadata/Rectangle.vue +3 -1
  36. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +10 -17
  37. package/components/Resource/Detail/Metadata/composables.ts +9 -7
  38. package/components/Resource/Detail/Metadata/index.vue +17 -2
  39. package/components/Resource/Detail/Page.vue +35 -21
  40. package/components/Resource/Detail/SpacedRow.vue +1 -1
  41. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +8 -9
  42. package/components/Resource/Detail/TitleBar/composables.ts +5 -5
  43. package/components/Resource/Detail/TitleBar/index.vue +12 -3
  44. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  45. package/components/ResourceDetail/index.vue +569 -72
  46. package/components/ResourceList/index.vue +1 -0
  47. package/components/ResourceTable.vue +6 -1
  48. package/components/ResourceYaml.vue +1 -1
  49. package/components/RichTranslation.vue +106 -0
  50. package/components/SlideInPanelManager.vue +13 -10
  51. package/components/SortableTable/index.vue +5 -5
  52. package/components/SortableTable/selection.js +0 -1
  53. package/components/Tabbed/index.vue +35 -4
  54. package/components/__tests__/LazyImage.spec.ts +121 -0
  55. package/components/__tests__/PromptRestore.test.ts +1 -65
  56. package/components/__tests__/RichTranslation.test.ts +115 -0
  57. package/components/fleet/FleetStatus.vue +4 -0
  58. package/components/fleet/dashboard/ResourcePanel.vue +2 -1
  59. package/components/form/ClusterAppearance.vue +5 -0
  60. package/components/form/FileImageSelector.vue +1 -1
  61. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  62. package/components/form/NameNsDescription.vue +1 -0
  63. package/components/form/Networking.vue +24 -19
  64. package/components/form/ProjectMemberEditor.vue +1 -1
  65. package/components/form/ResourceLabeledSelect.vue +22 -8
  66. package/components/form/ResourceTabs/index.vue +20 -0
  67. package/components/form/SecretSelector.vue +9 -0
  68. package/components/form/SelectOrCreateAuthSecret.vue +6 -3
  69. package/components/form/__tests__/Networking.test.ts +116 -0
  70. package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
  71. package/components/formatter/FleetApplicationSource.vue +25 -17
  72. package/components/formatter/PodImages.vue +1 -1
  73. package/components/formatter/__tests__/LiveDate.test.ts +10 -2
  74. package/components/google/AccountAccess.vue +44 -46
  75. package/components/nav/Favorite.vue +4 -0
  76. package/components/nav/Group.vue +4 -1
  77. package/components/nav/NotificationCenter/Notification.vue +1 -27
  78. package/components/nav/WindowManager/index.vue +3 -3
  79. package/composables/resources.ts +2 -2
  80. package/config/labels-annotations.js +3 -2
  81. package/config/pagination-table-headers.js +8 -1
  82. package/config/product/explorer.js +27 -2
  83. package/config/product/manager.js +0 -1
  84. package/config/query-params.js +10 -0
  85. package/config/router/routes.js +21 -1
  86. package/config/system-namespaces.js +1 -1
  87. package/config/table-headers.js +30 -1
  88. package/config/types.js +1 -1
  89. package/config/version.js +1 -1
  90. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +11 -0
  91. package/detail/__tests__/workload.test.ts +164 -0
  92. package/detail/configmap.vue +33 -75
  93. package/detail/projectsecret.vue +11 -0
  94. package/detail/provisioning.cattle.io.cluster.vue +351 -369
  95. package/detail/secret.vue +49 -308
  96. package/detail/workload/index.vue +38 -21
  97. package/dialog/InstallExtensionDialog.vue +8 -5
  98. package/dialog/RotateEncryptionKeyDialog.vue +10 -30
  99. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  100. package/edit/auth/ldap/__tests__/config.test.ts +14 -0
  101. package/edit/auth/ldap/config.vue +24 -0
  102. package/edit/compliance.cattle.io.clusterscan.vue +1 -1
  103. package/edit/configmap.vue +4 -1
  104. package/edit/fleet.cattle.io.gitrepo.vue +5 -6
  105. package/edit/fleet.cattle.io.helmop.vue +78 -56
  106. package/edit/logging.banzaicloud.io.output/index.vue +1 -1
  107. package/edit/logging.banzaicloud.io.output/providers/awsElasticsearch.vue +5 -6
  108. package/edit/networking.k8s.io.ingress/Certificate.vue +20 -22
  109. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +8 -3
  110. package/edit/networking.k8s.io.ingress/Rule.vue +2 -5
  111. package/edit/networking.k8s.io.ingress/RulePath.vue +17 -11
  112. package/edit/networking.k8s.io.ingress/__tests__/Certificate.test.ts +165 -0
  113. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +11 -10
  114. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -3
  115. package/edit/networking.k8s.io.networkpolicy/index.vue +17 -17
  116. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +3 -2
  117. package/edit/provisioning.cattle.io.cluster/rke2.vue +123 -61
  118. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
  119. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +22 -13
  120. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +10 -12
  121. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +39 -38
  122. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +41 -19
  123. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +16 -3
  124. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +32 -33
  125. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +9 -10
  126. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +1 -3
  127. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +16 -9
  128. package/edit/secret/basic.vue +1 -0
  129. package/edit/secret/index.vue +126 -15
  130. package/edit/workload/index.vue +5 -14
  131. package/list/projectsecret.vue +345 -0
  132. package/list/provisioning.cattle.io.cluster.vue +1 -69
  133. package/list/secret.vue +109 -0
  134. package/machine-config/__tests__/vmwarevsphere.test.ts +5 -7
  135. package/machine-config/google.vue +9 -1
  136. package/machine-config/vmwarevsphere.vue +7 -17
  137. package/mixins/__tests__/brand.spec.ts +2 -2
  138. package/mixins/chart.js +0 -2
  139. package/mixins/create-edit-view/impl.js +10 -1
  140. package/mixins/resource-fetch-api-pagination.js +11 -12
  141. package/mixins/resource-fetch.js +3 -1
  142. package/models/__tests__/chart.test.ts +111 -80
  143. package/models/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  144. package/models/__tests__/node.test.ts +7 -63
  145. package/models/catalog.cattle.io.app.js +1 -1
  146. package/models/catalog.cattle.io.operation.js +1 -1
  147. package/models/chart.js +36 -20
  148. package/models/cloudcredential.js +2 -163
  149. package/models/cluster/node.js +7 -7
  150. package/models/cluster.x-k8s.io.machine.js +3 -3
  151. package/models/cluster.x-k8s.io.machinedeployment.js +11 -2
  152. package/models/compliance.cattle.io.clusterscan.js +2 -2
  153. package/models/configmap.js +4 -0
  154. package/models/constraints.gatekeeper.sh.constraint.js +1 -1
  155. package/models/fleet-application.js +0 -17
  156. package/models/fleet.cattle.io.cluster.js +2 -2
  157. package/models/fleet.cattle.io.gitrepo.js +15 -1
  158. package/models/fleet.cattle.io.helmop.js +26 -22
  159. package/models/management.cattle.io.setting.js +4 -0
  160. package/models/persistentvolumeclaim.js +1 -1
  161. package/models/pod.js +2 -2
  162. package/models/provisioning.cattle.io.cluster.js +39 -67
  163. package/models/rke.cattle.io.etcdsnapshot.js +1 -1
  164. package/models/secret.js +161 -2
  165. package/models/storage.k8s.io.storageclass.js +2 -2
  166. package/models/workload.js +3 -3
  167. package/package.json +11 -10
  168. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +1 -0
  169. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +4 -1
  170. package/pages/c/_cluster/apps/charts/__tests__/AppChartCardFooter.spec.js +41 -0
  171. package/pages/c/_cluster/apps/charts/chart.vue +422 -174
  172. package/pages/c/_cluster/apps/charts/index.vue +46 -35
  173. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  174. package/pages/c/_cluster/explorer/projectsecret.vue +24 -0
  175. package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
  176. package/pages/c/_cluster/fleet/index.vue +103 -45
  177. package/pages/c/_cluster/manager/cloudCredential/index.vue +2 -59
  178. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +10 -3
  179. package/pages/c/_cluster/uiplugins/index.vue +36 -25
  180. package/plugins/dashboard-store/__tests__/normalize.test.ts +223 -0
  181. package/plugins/dashboard-store/__tests__/resource-class.test.ts +191 -0
  182. package/plugins/dashboard-store/__tests__/utils/normalize-usecases.ts +1526 -0
  183. package/plugins/dashboard-store/actions.js +42 -22
  184. package/plugins/dashboard-store/normalize.js +29 -17
  185. package/plugins/dashboard-store/resource-class.js +83 -17
  186. package/plugins/steve/__tests__/getters.test.ts +1 -1
  187. package/plugins/steve/__tests__/subscribe.spec.ts +259 -1
  188. package/plugins/steve/getters.js +8 -2
  189. package/plugins/steve/resourceWatcher.js +10 -3
  190. package/plugins/steve/steve-pagination-utils.ts +14 -3
  191. package/plugins/steve/subscribe.js +192 -19
  192. package/plugins/steve/worker/web-worker.advanced.js +2 -0
  193. package/rancher-components/Card/Card.vue +0 -18
  194. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.test.ts +15 -0
  195. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +65 -0
  196. package/rancher-components/Pill/RcStatusBadge/index.ts +2 -0
  197. package/rancher-components/Pill/RcStatusBadge/types.ts +5 -0
  198. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.test.ts +33 -0
  199. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +75 -0
  200. package/rancher-components/Pill/RcStatusIndicator/index.ts +2 -0
  201. package/rancher-components/Pill/RcStatusIndicator/types.ts +7 -0
  202. package/rancher-components/Pill/types.ts +2 -0
  203. package/rancher-components/RcButton/RcButton.vue +1 -1
  204. package/rancher-components/RcDropdown/RcDropdown.test.ts +98 -0
  205. package/rancher-components/RcDropdown/RcDropdown.vue +5 -0
  206. package/rancher-components/RcDropdown/RcDropdownItem.vue +7 -1
  207. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +2 -1
  208. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +2 -1
  209. package/rancher-components/RcDropdown/useDropdownContext.ts +21 -0
  210. package/rancher-components/RcDropdown/useDropdownItem.ts +30 -1
  211. package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -0
  212. package/rancher-components/RcItemCard/RcItemCard.vue +40 -6
  213. package/store/__tests__/catalog.test.ts +93 -1
  214. package/store/aws.js +19 -8
  215. package/store/catalog.js +8 -3
  216. package/types/kube/kube-api.ts +12 -0
  217. package/types/resources/settings.d.ts +1 -1
  218. package/types/shell/index.d.ts +643 -585
  219. package/types/store/pagination.types.ts +16 -6
  220. package/types/uiplugins.ts +73 -0
  221. package/utils/__tests__/back-off.test.ts +354 -0
  222. package/utils/__tests__/create-yaml.test.ts +235 -0
  223. package/utils/__tests__/kontainer.test.ts +19 -0
  224. package/utils/__tests__/uiplugins.test.ts +84 -0
  225. package/utils/back-off.ts +176 -0
  226. package/utils/create-yaml.js +103 -9
  227. package/utils/dynamic-importer.js +8 -0
  228. package/utils/kontainer.ts +3 -5
  229. package/utils/pagination-utils.ts +18 -0
  230. package/utils/style.ts +3 -0
  231. package/utils/uiplugins.ts +29 -2
  232. package/utils/validators/__tests__/setting.test.js +92 -0
  233. package/utils/validators/formRules/__tests__/index.test.ts +88 -7
  234. package/utils/validators/formRules/index.ts +83 -8
  235. package/utils/validators/setting.js +17 -0
  236. package/cloud-credential/__tests__/harvester.test.ts +0 -18
  237. package/components/ResourceDetail/__tests__/index.test.ts +0 -135
  238. package/components/ResourceDetail/legacy.vue +0 -562
  239. package/components/formatter/CloudCredExpired.vue +0 -69
  240. package/models/etcdbackup.js +0 -45
  241. package/pages/explorer/resource/detail/configmap.vue +0 -42
  242. package/pages/explorer/resource/detail/secret.vue +0 -50
  243. package/utils/aws.js +0 -0
@@ -3,8 +3,57 @@
3
3
  *
4
4
  * Covers three use cases
5
5
  * 1) Handles subscription within this file
6
- * 2) Handles `cluster` subscriptions for some basic types in a web worker (SETTING.UI_PERFORMANCE advancedWorker = false)
6
+ * 2) Handles `cluster` subscriptions for some basic types in a web worker (SETTING.UI_PERFORMANCE advancedWorker = false) (is this true??)
7
7
  * 2) Handles `cluster` subscriptions and optimisations in an advanced worker (SETTING.UI_PERFORMANCE advancedWorker = true)
8
+ *
9
+ * Very roughly this does...
10
+ *
11
+ * 1. _Subscribes_ to a web socket (v1, v3, v1 cluster)
12
+ * 2. UI --> Rancher: Sends a _watch_ message for a specific resource type (which can have qualifying filters)
13
+ * 3. Rancher --> UI: Rancher can send a number of messages back
14
+ * - `resource.start` - watch has started
15
+ * - `resource.error` - watch has errored, usually a result of bad data in the resource.start message
16
+ * - `resource.change` - a resource has changed, this is it's new value
17
+ * - `resource.changes` - if in this mode, no resource.change events are sent, instead one debounced message is sent without any resource data
18
+ * - `resource.stop` - either we have requested the watch stops, or there has been a resource.error
19
+ * 4. UI --> Rancher: Sends an _unwatch_ request for a matching _watch_ request
20
+ *
21
+ * Below are some VERY brief steps for common flows. Some will link together
22
+ *
23
+ * Successfully flow - watch
24
+ * 1. UI --> Rancher: _watch_ request
25
+ * 2. Rancher --> UI: `resource.start`. UI sets watch as started
26
+ * 3. Rancher --> UI: `resource.change` (contains data). UI caches data
27
+ *
28
+ * Successful flow - watch - new mode
29
+ * 1. UI --> Rancher: _watch_ request
30
+ * 2. Rancher --> UI: `resource.start`. UI sets watch as started
31
+ * 3. Rancher --> UI: `resource.changes` (contains no data). UI makes a HTTP request to fetch data
32
+ *
33
+ * Successful flow - unwatch
34
+ * 1. UI --> Rancher: _unwatch_ request
35
+ * 2. Rancher --> UI: `resource.stop`. UI sets watch as stopped
36
+ *
37
+ * Successful flow - resource.stop received
38
+ * 1. Rancher --> UI: `resource.stop`. UI sets watch as stopped
39
+ * 2. UI --> Rancher: _watch_ request
40
+ *
41
+ * Successful flow - socket disconnected
42
+ * 1. Socket closes|disconnects (not sure which)
43
+ * 2. UI: reopens socket
44
+ * 3. UI --> Rancher: _watch_ request (for every started watch)
45
+ *
46
+ * Error Flow
47
+ * 1. UI --> Rancher: _watch_ request
48
+ * 2. Rancher --> UI: `resource.start`. UI sets watch as started
49
+ * 3. Rancher --> UI: `resource.error`. UI sets watch as errored.
50
+ * a) UI: in the event of 'too old' the UI will make a http request to fetch a new revision and re-watch with it. This process is delayed on each call
51
+ * 4. Rancher --> UI: `resource.stop`. UI sets watch as stop (note the resource.stop flow above is avoided given error state)
52
+ *
53
+ * Additionally
54
+ * - if we receive resource.stop, unless the watch is in error, we immediately send back a watch event
55
+ * - if the web socket is disconnected (for steve based sockets it happens every 30 mins, or when there are permission changes)
56
+ * the ui will re-connect it and re-watch all previous watches using a best effort revision
8
57
  */
9
58
 
10
59
  import { addObject, clear, removeObject } from '@shell/utils/array';
@@ -37,6 +86,7 @@ import { BLANK_CLUSTER, STORE } from '@shell/store/store-types.js';
37
86
  import { _MERGE } from '@shell/plugins/dashboard-store/actions';
38
87
  import { STEVE_WATCH_EVENT, STEVE_WATCH_MODE } from '@shell/types/store/subscribe.types';
39
88
  import paginationUtils from '@shell/utils/pagination-utils';
89
+ import backOff from '@shell/utils/back-off';
40
90
 
41
91
  // minimum length of time a disconnect notification is shown
42
92
  const MINIMUM_TIME_NOTIFIED = 3000;
@@ -88,7 +138,7 @@ export async function createWorker(store, ctx) {
88
138
  postMessage: (msg) => {
89
139
  if (Object.keys(msg)?.[0] === 'destroyWorker') {
90
140
  // The worker has been destroyed before it's been set up. Flag this so we stop waiting for mgmt settings and then can destroy worker.
91
- // This can occurr when the user is redirected to the log in page
141
+ // This can occur when the user is redirected to the log in page
92
142
  // - workers created (but waiting)
93
143
  // - logout is called
94
144
  // - <store>/unsubscribe is dispatched
@@ -269,6 +319,27 @@ function growlsDisabled(rootGetters) {
269
319
  */
270
320
  const listeners = { [STEVE_WATCH_EVENT.CHANGES]: [] };
271
321
 
322
+ /**
323
+ * Given a started or error entry, is it compatible with the given change in mode?
324
+ */
325
+ const shouldUnwatchIncompatible = (messageMeta, mode) => {
326
+ if (messageMeta.mode === STEVE_WATCH_EVENT.CHANGES) {
327
+ return mode !== STEVE_WATCH_EVENT.CHANGES;
328
+ }
329
+
330
+ return mode === STEVE_WATCH_EVENT.CHANGES;
331
+ };
332
+
333
+ /**
334
+ * clear the provided error, but also ensure any backoff request associated with it is cleared as well
335
+ */
336
+ const clearInError = ({ getters, commit }, error) => {
337
+ // for this watch ... get the specific prefix we care about ... reset back-offs related to it
338
+ backOff.resetPrefix(getters.backOffId(error.obj, ''));
339
+ // Clear out stale error state (next time around we can try again with a new revision that was just fetched)
340
+ commit('clearInError', error.obj);
341
+ };
342
+
272
343
  /**
273
344
  * Actions that cover all cases (see file description)
274
345
  */
@@ -347,7 +418,9 @@ const sharedActions = {
347
418
  }
348
419
  },
349
420
 
350
- unsubscribe({ commit, getters, state }) {
421
+ async unsubscribe({
422
+ commit, getters, state, dispatch
423
+ }) {
351
424
  const socket = state.socket;
352
425
 
353
426
  commit('setWantSocket', false);
@@ -364,6 +437,8 @@ const sharedActions = {
364
437
  cleanupTasks.push(socket.disconnect());
365
438
  }
366
439
 
440
+ await dispatch('resetWatchBackOff');
441
+
367
442
  return Promise.all(cleanupTasks);
368
443
  },
369
444
 
@@ -574,6 +649,8 @@ const sharedActions = {
574
649
  // Make sure anything in the pending queue for the type is removed, since we've now removed the type
575
650
  commit('clearFromQueue', type);
576
651
  }
652
+ // Ensure anything pinging in the background is stopped
653
+ backOff.resetPrefix(getters.backOffId(obj));
577
654
  };
578
655
 
579
656
  if (isAdvancedWorker(ctx)) {
@@ -591,19 +668,63 @@ const sharedActions = {
591
668
  /**
592
669
  * Unwatch watches that are incompatible with the new type
593
670
  */
594
- unwatchIncompatible({ state, dispatch, getters }, messageMeta) {
671
+ unwatchIncompatible({
672
+ state, dispatch, getters, commit
673
+ }, messageMeta) {
674
+ // Step 1 - Clear incompatible watches that have STARTED
595
675
  const watchesOfType = getters.watchesOfType(messageMeta.type);
596
- let unwatch = [];
597
676
 
598
- if (messageMeta.mode === STEVE_WATCH_EVENT.CHANGES) {
599
- // resource.changes should not be running when other types are, so unwatch
600
- unwatch = watchesOfType.filter((entry) => entry.mode !== STEVE_WATCH_EVENT.CHANGES);
601
- } else {
602
- // all other modes of watches should not be running when resource.changes is, so unwatch
603
- unwatch = watchesOfType.filter((entry) => entry.mode === STEVE_WATCH_EVENT.CHANGES);
677
+ watchesOfType
678
+ .filter((entry) => shouldUnwatchIncompatible(messageMeta, entry.mode))
679
+ .forEach((entry) => {
680
+ dispatch('unwatch', entry);
681
+ });
682
+
683
+ // Step 2 - Clear inError state for incompatible watches (these won't appear in watchesOfType / state.started)
684
+ // (important for the backoff case... for example backoff request to find would overwrite findPage res if executed after nav from detail to list)
685
+ const inErrorOfType = Object.values(state.inError || {})
686
+ .filter((error) => error.obj.type === messageMeta.type);
687
+
688
+ inErrorOfType
689
+ .filter((error) => shouldUnwatchIncompatible(messageMeta, error.obj.mode))
690
+ .forEach((error) => clearInError({ getters, commit }, error));
691
+ },
692
+
693
+ /**
694
+ * Ensure there's no back-off process waiting to run for
695
+ * - resource.changes fetchResources
696
+ * - resource.error resyncWatches
697
+ */
698
+ resetWatchBackOff({ state, getters, commit }, {
699
+ type, compareWatches, resetInError = true, resetStarted = true
700
+ } = { resetInError: true, resetStarted: true }) {
701
+ // Step 1 - Reset back-offs related to watches that have STARTED
702
+ if (resetStarted && state.started?.length) {
703
+ let entries = state.started;
704
+
705
+ if (type) { // Filter out ones for types we're no interested in
706
+ entries = entries
707
+ .filter((obj) => compareWatches ? compareWatches(obj) : obj.type === type);
708
+ }
709
+
710
+ entries.forEach((obj) => backOff.resetPrefix(getters.backOffId(obj, '')));
604
711
  }
605
712
 
606
- unwatch.forEach((entry) => dispatch('unwatch', entry));
713
+ // Step 2 - Reset back-offs related to watches that are in error (and may not be started)
714
+ if (resetInError && state.inError) {
715
+ // (it would be nicer if we could store backOff state in `state.started`,
716
+ // however resource.stop clears `started` and we need the settings to persist over start-->error-->stop-->start cycles
717
+ let entries = Object.values(state.inError || {});
718
+
719
+ if (type) { // Filter out ones for types we're no interested in
720
+ entries = entries
721
+ .filter((error) => compareWatches ? compareWatches(error.obj) : error.obj.type === type);
722
+ }
723
+
724
+ entries
725
+ .filter((error) => error.reason === REVISION_TOO_OLD) // Filter out ones for reasons we're not interested in
726
+ .forEach((error) => clearInError({ getters, commit }, error));
727
+ }
607
728
  },
608
729
 
609
730
  'ws.ping'({ getters, dispatch }, msg) {
@@ -863,15 +984,20 @@ const defaultActions = {
863
984
  }
864
985
  },
865
986
 
866
- closed({ state, getters }) {
987
+ async closed({ state, getters, dispatch }) {
867
988
  state.debugSocket && console.info(`WebSocket Closed [${ getters.storeName }]`); // eslint-disable-line no-console
989
+
990
+ await dispatch('resetWatchBackOff');
868
991
  clearTimeout(state.queueTimer);
869
992
  state.queueTimer = null;
870
993
  },
871
994
 
872
- error({
995
+ async error({
873
996
  getters, state, dispatch, rootGetters
874
997
  }, e) {
998
+ state.debugSocket && console.info(`WebSocket Error [${ getters.storeName }]`); // eslint-disable-line no-console
999
+
1000
+ await dispatch('resetWatchBackOff');
875
1001
  clearTimeout(state.queueTimer);
876
1002
  state.queueTimer = null;
877
1003
 
@@ -986,7 +1112,23 @@ const defaultActions = {
986
1112
  // 1) blocks attempts by resource.stop to resub (as type is in error)
987
1113
  // 2) will be cleared when resyncWatch --> watch (with force) --> resource.start completes
988
1114
  commit('setInError', { msg, reason: REVISION_TOO_OLD });
989
- dispatch('resyncWatch', msg);
1115
+
1116
+ // See Scenario 1 from https://github.com/rancher/dashboard/issues/14974
1117
+ // The watch that results from resyncWatch will fail and end up here if the revision isn't (yet) known
1118
+ // So re-retry resyncWatch until it does OR
1119
+ // - we're already re-retrying
1120
+ // - early exist from `execute`
1121
+ // - we give up (exceed max retries)
1122
+ // - early exist from `execute`
1123
+ // - we need to stop (socket is disconnected or closed, type is 'forgotten', watch is unwatched)
1124
+ // - `reset` called asynchronously
1125
+ // - Note - we won't need to clear the id outside of the above scenarios because `too old` only occurs on fresh watches (covered by above scenarios)
1126
+ backOff.execute({
1127
+ id: getters.backOffId(msg, REVISION_TOO_OLD),
1128
+ description: `Invalid watch revision, re-syncing`,
1129
+ canFn: () => getters.canBackoff(this.$socket),
1130
+ delayedFn: () => dispatch('resyncWatch', msg),
1131
+ });
990
1132
  } else if ( err.includes('the server does not allow this method on the requested resource')) {
991
1133
  commit('setInError', { msg, reason: NO_PERMS });
992
1134
  }
@@ -1170,21 +1312,33 @@ const defaultMutations = {
1170
1312
  setInError(state, { msg, reason }) {
1171
1313
  const key = keyForSubscribe(msg);
1172
1314
 
1173
- state.inError[key] = reason;
1315
+ const { data, resourceType, ...obj } = msg;
1316
+
1317
+ obj.type = msg.resourceType || msg.type;
1318
+
1319
+ state.inError[key] = { obj, reason };
1174
1320
  },
1175
1321
 
1176
1322
  clearInError(state, msg) {
1323
+ // Callers of this should consider using local clearInError instead
1324
+
1177
1325
  const key = keyForSubscribe(msg);
1178
1326
 
1179
1327
  delete state.inError[key];
1180
1328
  },
1181
1329
 
1330
+ /**
1331
+ * Clear out socket state
1332
+ */
1182
1333
  resetSubscriptions(state) {
1183
- // Clear out socket state. This is only ever called from reset... which is always called after we `disconnect` above.
1184
- // This could probably be folded in to there
1185
1334
  clear(state.started);
1186
1335
  clear(state.pendingFrames);
1187
1336
  clear(state.queue);
1337
+ // Note - we clear async operations here (like queueTimer) and we should also do so for backoff requests via
1338
+ // resetWatchBackOff, however can't because this is a mutation and it's an action
1339
+ // We shouldn't need to though given resetSubscription is called from store reset, which includes forgetType
1340
+ // on everything in the store, which resets backoff requests.
1341
+ // Additionally this is probably called on a cluster store, so we also call resetWatchBackOff when the socket disconnects
1188
1342
  clearTimeout(state.queueTimer);
1189
1343
  state.deferredRequests = {};
1190
1344
  state.queueTimer = null;
@@ -1202,8 +1356,27 @@ const defaultMutations = {
1202
1356
  * Getters that cover cases 1 & 2 (see file description)
1203
1357
  */
1204
1358
  const defaultGetters = {
1359
+ /**
1360
+ * Get a unique id that can be used to track a process that can be backed-off
1361
+ *
1362
+ * @param obj - the usual id/namespace/selector, etc,
1363
+ * @param postFix - something else to uniquely id this back-off
1364
+ */
1365
+ backOffId: () => (obj, postFix) => {
1366
+ return `${ keyForSubscribe(obj) }${ postFix ? `:${ postFix }` : '' }`;
1367
+ },
1368
+
1369
+ /**
1370
+ * Can the back off process run?
1371
+ *
1372
+ * If we're not connected no.
1373
+ */
1374
+ canBackoff: () => ($socket) => {
1375
+ return $socket.state === EVENT_CONNECTED;
1376
+ },
1377
+
1205
1378
  inError: (state) => (obj) => {
1206
- return state.inError[keyForSubscribe(obj)];
1379
+ return state.inError[keyForSubscribe(obj)]?.reason;
1207
1380
  },
1208
1381
 
1209
1382
  watchesOfType: (state) => (type) => {
@@ -3,6 +3,8 @@
3
3
  * relocates cluster resource sockets off the UI thread and into a webworker
4
4
  */
5
5
 
6
+ // Status of this is TBD - https://github.com/rancher/dashboard/issues/15111
7
+
6
8
  import { SCHEMA, COUNT } from '@shell/config/types';
7
9
  import ResourceWatcher, { watchKeyFromMessage } from '@shell/plugins/steve/resourceWatcher';
8
10
  import ResourceCache from '@shell/plugins/steve/caches/resourceCache';
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import { defineComponent, PropType } from 'vue';
3
- import { useBasicSetupFocusTrap } from '@shell/composables/focusTrap';
4
3
 
5
4
  export default defineComponent({
6
5
 
@@ -51,23 +50,6 @@ export default defineComponent({
51
50
  sticky: {
52
51
  type: Boolean,
53
52
  default: false,
54
- },
55
- triggerFocusTrap: {
56
- type: Boolean,
57
- default: false,
58
- },
59
- },
60
- setup(props) {
61
- if (props.triggerFocusTrap) {
62
- useBasicSetupFocusTrap('#focus-trap-card-container-element', {
63
- // needs to be false because of import YAML modal from header
64
- // where the YAML editor itself is a focus trap
65
- // and we can't have it superseed the "escape key" to blur that UI element
66
- // In this case the focus trap moves the focus out of the modal
67
- // correctly once it closes because of the "onBeforeUnmount" trigger
68
- escapeDeactivates: false,
69
- allowOutsideClick: true,
70
- });
71
53
  }
72
54
  }
73
55
  });
@@ -0,0 +1,15 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import RcStatusBadge from './index';
3
+ import { Status } from '@components/Pill/types';
4
+
5
+ describe('component: RcStatusBadge', () => {
6
+ const statuses: Status[] = ['info', 'success', 'warning', 'error', 'unknown', 'none'];
7
+
8
+ it.each(statuses)('should apply correct classes for shape and status "%s"', (status) => {
9
+ const wrapper = mount(RcStatusBadge, { props: { status } });
10
+
11
+ const shapeEl = wrapper.find('.rc-status-badge');
12
+
13
+ expect(shapeEl.classes()).toContain(status);
14
+ });
15
+ });
@@ -0,0 +1,65 @@
1
+ <script setup lang="ts">
2
+ import { RcStatusBadgeProps } from '@components/Pill/RcStatusBadge/types';
3
+
4
+ const props = defineProps<RcStatusBadgeProps>();
5
+ </script>
6
+
7
+ <template>
8
+ <div
9
+ class="rc-status-badge"
10
+ :class="{[props.status]: true}"
11
+ >
12
+ <slot name="default" />
13
+ </div>
14
+ </template>
15
+
16
+ <style lang="scss" scoped>
17
+ .rc-status-badge {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ padding: 1px 7px;
22
+
23
+ border: 1px solid transparent;
24
+ border-radius: 30px;
25
+
26
+ font-family: Lato;
27
+ font-size: 12px;
28
+ line-height: 19px;
29
+
30
+ &.info {
31
+ background-color: var(--rc-info-secondary);
32
+ border-color: var(--rc-info-secondary);
33
+ color: var(--rc-info);
34
+ }
35
+
36
+ &.success {
37
+ background-color: var(--rc-success-secondary);
38
+ border-color: var(--rc-success-secondary);
39
+ color: var(--rc-success);
40
+ }
41
+
42
+ &.warning {
43
+ background-color: var(--rc-warning);
44
+ border-color: var(--rc-warning);
45
+ color: var(--rc-warning-secondary);
46
+ }
47
+
48
+ &.error {
49
+ background-color: var(--rc-error);
50
+ border-color: var(--rc-error);
51
+ color: var(--rc-error-secondary);
52
+ }
53
+
54
+ &.unknown {
55
+ background-color: var(--rc-unknown);
56
+ border-color: var(--rc-unknown);
57
+ color: var(--rc-unknown-secondary);
58
+ }
59
+
60
+ &.none {
61
+ border-color: var(--rc-none);
62
+ color: var(--rc-none-secondary);
63
+ }
64
+ }
65
+ </style>
@@ -0,0 +1,2 @@
1
+ export { default } from './RcStatusBadge.vue';
2
+ export type { Status } from '@components/Pill/types';
@@ -0,0 +1,5 @@
1
+ import { Status } from '@components/Pill/types';
2
+
3
+ export interface RcStatusBadgeProps {
4
+ status: Status;
5
+ }
@@ -0,0 +1,33 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import RcStatusIndicator, { Shape } from './index';
3
+ import { Status } from '@components/Pill/types';
4
+
5
+ describe('component: RcStatusIndicator', () => {
6
+ const shapes: Shape[] = ['disc', 'horizontal-bar', 'vertical-bar'];
7
+ const statuses: Status[] = ['info', 'success', 'warning', 'error', 'unknown', 'none'];
8
+
9
+ const combinations: {shape: Shape, status: Status}[] = [];
10
+
11
+ shapes.forEach((shape) => {
12
+ statuses.forEach((status) => {
13
+ combinations.push({
14
+ shape,
15
+ status
16
+ });
17
+ });
18
+ });
19
+
20
+ it.each(combinations)('should apply correct classes for shape "$shape" and status "$status"', ({ shape, status }) => {
21
+ const wrapper = mount(RcStatusIndicator, {
22
+ props: {
23
+ shape,
24
+ status,
25
+ }
26
+ });
27
+
28
+ const shapeEl = wrapper.find('.shape');
29
+
30
+ expect(shapeEl.classes()).toContain(shape);
31
+ expect(shapeEl.classes()).toContain(status);
32
+ });
33
+ });
@@ -0,0 +1,75 @@
1
+ <script setup lang="ts">
2
+ import { RcStatusIndicatorProps } from '@components/Pill/RcStatusIndicator/types';
3
+
4
+ const props = defineProps<RcStatusIndicatorProps>();
5
+ </script>
6
+
7
+ <template>
8
+ <div class="rc-status-indicator">
9
+ <div
10
+ class="shape"
11
+ :class="{[props.shape]: true, [props.status]: true}"
12
+ />
13
+ </div>
14
+ </template>
15
+
16
+ <style lang="scss" scoped>
17
+ .rc-status-indicator {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ height: 21px;
22
+
23
+ .shape {
24
+ display: inline-block;
25
+ border: 1px solid transparent;
26
+
27
+ &.disc {
28
+ width: 6px;
29
+ height: 6px;
30
+ border-radius: 50%;
31
+ }
32
+
33
+ &.horizontal-bar {
34
+ width: 16px;
35
+ height: 4px;
36
+ border-radius: 2px;
37
+ }
38
+
39
+ &.vertical-bar {
40
+ width: 4px;
41
+ height: 16px;
42
+ border-radius: 2px;
43
+ }
44
+
45
+ &.info {
46
+ background-color: var(--rc-info);
47
+ border-color: var(--rc-info);
48
+ }
49
+
50
+ &.success {
51
+ background-color: var(--rc-success);
52
+ border-color: var(--rc-success);
53
+ }
54
+
55
+ &.warning {
56
+ background-color: var(--rc-warning);
57
+ border-color: var(--rc-warning);
58
+ }
59
+
60
+ &.error {
61
+ background-color: var(--rc-error);
62
+ border-color: var(--rc-error);
63
+ }
64
+
65
+ &.unknown {
66
+ background-color: var(--rc-unknown);
67
+ border-color: var(--rc-unknown);
68
+ }
69
+
70
+ &.none {
71
+ border-color: var(--rc-none);
72
+ }
73
+ }
74
+ }
75
+ </style>
@@ -0,0 +1,2 @@
1
+ export { default } from './RcStatusIndicator.vue';
2
+ export type { Shape } from './types';
@@ -0,0 +1,7 @@
1
+ import { Status } from '@components/Pill/types';
2
+ export type Shape = 'disc' | 'horizontal-bar' | 'vertical-bar';
3
+
4
+ export interface RcStatusIndicatorProps {
5
+ shape: Shape;
6
+ status: Status;
7
+ }
@@ -0,0 +1,2 @@
1
+ export type Shape = 'disc' | 'horizontal-bar' | 'vertical-bar';
2
+ export type Status = 'info' | 'success' | 'warning' | 'error' | 'unknown' | 'none';
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * <rc-button primary @click="doAction">Perform an Action</rc-button>
9
9
  */
10
- import { computed, ref, defineExpose } from 'vue';
10
+ import { computed, ref } from 'vue';
11
11
  import { ButtonRoleProps, ButtonSizeProps } from './types';
12
12
 
13
13
  const buttonRoles: { role: keyof ButtonRoleProps, className: string }[] = [
@@ -0,0 +1,98 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { defineComponent } from 'vue';
3
+ import { RcDropdown } from '@components/RcDropdown';
4
+
5
+ const vDropdownMock = defineComponent({
6
+ template: `
7
+ <div class="popper">
8
+ <slot name="popper" />
9
+ </div>
10
+ `,
11
+ });
12
+
13
+ describe('component: RcDropdown.vue', () => {
14
+ it('should not change the height if the dropdown fits within the screen', async() => {
15
+ Object.defineProperty(window, 'innerHeight', { value: 800 });
16
+
17
+ const wrapper = mount(RcDropdown, { global: { components: { 'v-dropdown': vDropdownMock } } });
18
+
19
+ const dropdownTarget = wrapper.find('[dropdown-menu-collection]').element as HTMLElement;
20
+
21
+ Object.defineProperty(dropdownTarget, 'getBoundingClientRect', {
22
+ value: () => ({
23
+ top: 200,
24
+ bottom: 600,
25
+ height: 400,
26
+ }),
27
+ writable: true,
28
+ });
29
+
30
+ await wrapper.findComponent(vDropdownMock).vm.$emit('apply-show');
31
+ await wrapper.vm.$nextTick();
32
+
33
+ expect(dropdownTarget.style.height).toBe('');
34
+ });
35
+
36
+ it('should apply correct height if dropdown exceeds the top edge', async() => {
37
+ Object.defineProperty(window, 'innerHeight', { value: 800 });
38
+
39
+ const wrapper = mount(RcDropdown, { global: { components: { 'v-dropdown': vDropdownMock } } });
40
+
41
+ const dropdownTarget = wrapper.find('[dropdown-menu-collection]').element as HTMLElement;
42
+
43
+ Object.defineProperty(dropdownTarget, 'getBoundingClientRect', {
44
+ value: () => ({
45
+ top: 2, // Exceeds (top - padding)
46
+ bottom: 300,
47
+ height: 298,
48
+ }),
49
+ });
50
+
51
+ await wrapper.findComponent(vDropdownMock).vm.$emit('apply-show');
52
+ await wrapper.vm.$nextTick();
53
+
54
+ expect(dropdownTarget.style.height).toBe('268px');
55
+ });
56
+
57
+ it('should apply correct height if dropdown exceeds the bottom edge', async() => {
58
+ Object.defineProperty(window, 'innerHeight', { value: 925 });
59
+
60
+ const wrapper = mount(RcDropdown, { global: { components: { 'v-dropdown': vDropdownMock } } });
61
+
62
+ const dropdownTarget = wrapper.find('[dropdown-menu-collection]').element as HTMLElement;
63
+
64
+ Object.defineProperty(dropdownTarget, 'getBoundingClientRect', {
65
+ value: () => ({
66
+ top: 200,
67
+ bottom: 920, // Exceeds (bottom + padding)
68
+ height: 720,
69
+ }),
70
+ });
71
+
72
+ await wrapper.findComponent(vDropdownMock).vm.$emit('apply-show');
73
+ await wrapper.vm.$nextTick();
74
+
75
+ expect(dropdownTarget.style.height).toBe('693px');
76
+ });
77
+
78
+ it('should apply correct height if dropdown exceeds both top and bottom edges', async() => {
79
+ Object.defineProperty(window, 'innerHeight', { value: 400 });
80
+
81
+ const wrapper = mount(RcDropdown, { global: { components: { 'v-dropdown': vDropdownMock } } });
82
+
83
+ const dropdownTarget = wrapper.find('[dropdown-menu-collection]').element as HTMLElement;
84
+
85
+ Object.defineProperty(dropdownTarget, 'getBoundingClientRect', {
86
+ value: () => ({
87
+ top: -800, // Exceeds top
88
+ bottom: 800, // Exceeds bottom
89
+ height: 1600,
90
+ }),
91
+ });
92
+
93
+ await wrapper.findComponent(vDropdownMock).vm.$emit('apply-show');
94
+ await wrapper.vm.$nextTick();
95
+
96
+ expect(dropdownTarget.style.height).toBe('368px');
97
+ });
98
+ });