@rancher/shell 3.0.6 → 3.0.7

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 (78) hide show
  1. package/assets/images/pl/dark/rancher-logo.svg +131 -44
  2. package/assets/images/pl/rancher-logo.svg +120 -44
  3. package/assets/styles/base/_basic.scss +2 -2
  4. package/assets/styles/base/_color-classic.scss +51 -0
  5. package/assets/styles/base/_color.scss +3 -3
  6. package/assets/styles/base/_mixins.scss +1 -1
  7. package/assets/styles/base/_variables-classic.scss +47 -0
  8. package/assets/styles/global/_button.scss +49 -17
  9. package/assets/styles/global/_form.scss +1 -1
  10. package/assets/styles/themes/_dark.scss +4 -0
  11. package/assets/styles/themes/_light.scss +3 -69
  12. package/assets/styles/themes/_modern.scss +194 -50
  13. package/assets/styles/vendor/vue-select.scss +1 -2
  14. package/assets/translations/en-us.yaml +33 -21
  15. package/components/ClusterIconMenu.vue +1 -1
  16. package/components/ClusterProviderIcon.vue +1 -1
  17. package/components/CodeMirror.vue +1 -1
  18. package/components/IconOrSvg.vue +40 -29
  19. package/components/ResourceDetail/index.vue +1 -0
  20. package/components/SortableTable/sorting.js +3 -1
  21. package/components/Tabbed/index.vue +5 -5
  22. package/components/form/ResourceTabs/index.vue +37 -18
  23. package/components/form/SecretSelector.vue +6 -2
  24. package/components/nav/Group.vue +29 -9
  25. package/components/nav/Header.vue +6 -8
  26. package/components/nav/NamespaceFilter.vue +1 -1
  27. package/components/nav/TopLevelMenu.helper.ts +47 -20
  28. package/components/nav/TopLevelMenu.vue +44 -14
  29. package/components/nav/Type.vue +0 -5
  30. package/components/nav/__tests__/TopLevelMenu.test.ts +2 -0
  31. package/config/pagination-table-headers.js +10 -2
  32. package/config/product/explorer.js +4 -3
  33. package/config/table-headers.js +9 -0
  34. package/core/plugin.ts +18 -6
  35. package/core/types.ts +8 -0
  36. package/detail/provisioning.cattle.io.cluster.vue +1 -0
  37. package/dialog/InstallExtensionDialog.vue +71 -45
  38. package/dialog/UninstallExtensionDialog.vue +2 -1
  39. package/dialog/__tests__/InstallExtensionDialog.test.ts +111 -0
  40. package/edit/auth/oidc.vue +86 -16
  41. package/mixins/__tests__/chart.test.ts +1 -1
  42. package/mixins/chart.js +1 -1
  43. package/models/event.js +7 -0
  44. package/models/provisioning.cattle.io.cluster.js +9 -0
  45. package/package.json +1 -1
  46. package/pages/c/_cluster/explorer/EventsTable.vue +3 -6
  47. package/pages/c/_cluster/settings/performance.vue +1 -1
  48. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +159 -62
  49. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +102 -0
  50. package/pages/c/_cluster/uiplugins/__tests__/{index.spec.ts → index.test.ts} +121 -55
  51. package/pages/c/_cluster/uiplugins/index.vue +110 -94
  52. package/plugins/__tests__/subscribe.events.test.ts +194 -0
  53. package/plugins/dashboard-store/actions.js +3 -0
  54. package/plugins/dashboard-store/getters.js +1 -1
  55. package/plugins/dashboard-store/resource-class.js +3 -3
  56. package/plugins/steve/__tests__/subscribe.spec.ts +27 -24
  57. package/plugins/steve/index.js +18 -10
  58. package/plugins/steve/mutations.js +2 -2
  59. package/plugins/steve/resourceWatcher.js +2 -2
  60. package/plugins/steve/steve-pagination-utils.ts +12 -9
  61. package/plugins/steve/subscribe.js +113 -85
  62. package/plugins/subscribe-events.ts +211 -0
  63. package/rancher-components/BadgeState/BadgeState.vue +8 -6
  64. package/rancher-components/Banner/Banner.vue +2 -1
  65. package/rancher-components/Form/Checkbox/Checkbox.vue +3 -3
  66. package/rancher-components/Form/Radio/RadioButton.vue +3 -3
  67. package/store/index.js +12 -22
  68. package/types/extension-manager.ts +8 -1
  69. package/types/resources/settings.d.ts +24 -17
  70. package/types/shell/index.d.ts +352 -335
  71. package/types/store/subscribe-events.types.ts +70 -0
  72. package/types/store/subscribe.types.ts +6 -22
  73. package/utils/pagination-utils.ts +87 -28
  74. package/utils/pagination-wrapper.ts +6 -8
  75. package/utils/sort.js +5 -0
  76. package/utils/unit-tests/pagination-utils.spec.ts +283 -0
  77. package/utils/validators/formRules/__tests__/index.test.ts +7 -0
  78. package/utils/validators/formRules/index.ts +2 -2
@@ -1,17 +1,19 @@
1
1
  import { actions, getters, mutations } from '../subscribe';
2
2
  import { REVISION_TOO_OLD } from '../../../utils/socket';
3
- import { STEVE_WATCH_EVENT } from '../../../types/store/subscribe.types';
3
+ import { STEVE_WATCH_MODE } from '../../../types/store/subscribe.types';
4
4
  import backOff from '../../../utils/back-off';
5
+ import { SteveWatchEventListenerManager } from '../../subscribe-events';
5
6
 
6
7
  describe('steve: subscribe', () => {
7
8
  describe('actions', () => {
8
9
  describe('watch', () => {
9
- const state = {};
10
+ const state = { listenerManager: new SteveWatchEventListenerManager() };
10
11
  const getters = {
11
- normalizeType: (type: string) => type,
12
- schemaFor: () => null,
13
- inError: () => false,
14
- watchStarted: () => false,
12
+ normalizeType: (type: string) => type,
13
+ schemaFor: () => null,
14
+ inError: () => false,
15
+ watchStarted: () => false,
16
+ listenerManager: state.listenerManager
15
17
  };
16
18
  const rootGetters = {
17
19
  'type-map/isSpoofed': () => false,
@@ -211,15 +213,11 @@ describe('steve: subscribe', () => {
211
213
  }, {
212
214
  ...obj,
213
215
  revision,
214
- mode: STEVE_WATCH_EVENT.CHANGES,
216
+ mode: STEVE_WATCH_MODE.RESOURCE_CHANGES,
215
217
  force: true,
216
218
  });
217
219
 
218
- expect(dispatch).toHaveBeenNthCalledWith(1, 'unwatchIncompatible', {
219
- id: undefined, mode: STEVE_WATCH_EVENT.CHANGES, namespace: undefined, selector: undefined, type: obj.type
220
- });
221
-
222
- expect(dispatch).toHaveBeenNthCalledWith(2, 'send', {
220
+ expect(dispatch).toHaveBeenNthCalledWith(1, 'send', {
223
221
  debounceMs: 4000,
224
222
  mode: 'resource.changes',
225
223
  resourceType: obj.type,
@@ -231,7 +229,7 @@ describe('steve: subscribe', () => {
231
229
  state, dispatch, getters, commit
232
230
  }, { ...msg });
233
231
 
234
- expect(dispatch).toHaveBeenCalledTimes(2);
232
+ expect(dispatch).toHaveBeenCalledTimes(1);
235
233
  dispatch.mockClear();
236
234
  };
237
235
 
@@ -252,7 +250,7 @@ describe('steve: subscribe', () => {
252
250
  });
253
251
  expect(state.inError).toStrictEqual(
254
252
  {
255
- 'type=abc,namespace=,id=,selector=': {
253
+ 'type=abc,namespace=,id=,selector=,mode=resource.changes': {
256
254
  obj: {
257
255
  type: msg.resourceType,
258
256
  mode: msg.mode,
@@ -268,7 +266,7 @@ describe('steve: subscribe', () => {
268
266
  }, { ...msg });
269
267
  // stop tries to watch again, however we're in error so will be ignored
270
268
  expect(dispatch).toHaveBeenNthCalledWith(1, 'watch', {
271
- id: undefined, mode: STEVE_WATCH_EVENT.CHANGES, namespace: undefined, selector: undefined, type: obj.type
269
+ id: undefined, mode: STEVE_WATCH_MODE.RESOURCE_CHANGES, namespace: undefined, selector: undefined, standardWatch: true, type: obj.type
272
270
  });
273
271
 
274
272
  dispatch.mockClear();
@@ -349,19 +347,24 @@ describe('steve: subscribe', () => {
349
347
  const obj = { type: 'abc' };
350
348
  const msg = {
351
349
  resourceType: obj.type,
352
- mode: STEVE_WATCH_EVENT.CHANGES,
350
+ mode: STEVE_WATCH_MODE.RESOURCE_CHANGES,
353
351
  };
354
352
 
355
353
  const initStore = () => {
356
- const state = { started: [], inError: {} };
354
+ const state = {
355
+ started: [],
356
+ inError: {},
357
+ listenerManager: new SteveWatchEventListenerManager()
358
+ };
357
359
  const _getters = {
358
- normalizeType: (type: string) => type,
359
- schemaFor: () => ({}),
360
- storeName: 'test',
361
- inError: (...args) => getters.inError(state)(...args),
362
- watchStarted: (...args) => getters.watchStarted(state)(...args),
363
- backOffId: (...args) => getters.backOffId()(...args),
364
- canBackoff: () => true,
360
+ normalizeType: (type: string) => type,
361
+ schemaFor: () => ({}),
362
+ storeName: 'test',
363
+ inError: (...args) => getters.inError(state)(...args),
364
+ watchStarted: (...args) => getters.watchStarted(state)(...args),
365
+ backOffId: (...args) => getters.backOffId()(...args),
366
+ canBackoff: () => true,
367
+ listenerManager: state.listenerManager
365
368
  };
366
369
  const commit = (type, ...args) => mutations[type](state, ...args);
367
370
 
@@ -9,6 +9,8 @@ import {
9
9
  import getters, { STEVE_MODEL_TYPES } from './getters';
10
10
  import mutations from './mutations';
11
11
  import actions from './actions';
12
+ import { SteveWatchEventListenerManager } from '@shell/plugins/subscribe-events';
13
+ import { markRaw } from 'vue';
12
14
 
13
15
  export function SteveFactory(namespace, baseUrl) {
14
16
  return {
@@ -17,16 +19,22 @@ export function SteveFactory(namespace, baseUrl) {
17
19
  state() {
18
20
  return {
19
21
  ...coreStoreState(namespace, baseUrl),
20
- socket: null,
21
- queue: [], // For change event coalescing
22
- wantSocket: false,
23
- debugSocket: false,
24
- allowStreaming: true,
25
- pendingFrames: [],
26
- deferredRequests: {},
27
- started: [],
28
- inError: {},
29
- podsByNamespace: {}, // Cache of pods by namespace
22
+ socket: null,
23
+ queue: [], // For change event coalescing
24
+ wantSocket: false,
25
+ debugSocket: false,
26
+ allowStreaming: true,
27
+ pendingFrames: [],
28
+ deferredRequests: {},
29
+ started: [],
30
+ inError: {},
31
+ /**
32
+ * Socket listener manager for this store
33
+ *
34
+ * Instance of @SteveWatchEventListenerManager . See it's description for more info
35
+ */
36
+ socketListenerManager: markRaw(new SteveWatchEventListenerManager()),
37
+ podsByNamespace: {}, // Cache of pods by namespace
30
38
  };
31
39
  },
32
40
 
@@ -164,10 +164,10 @@ export default {
164
164
  },
165
165
 
166
166
  reset(state) {
167
- // Reset generic store things.... then steve specific things
168
-
167
+ // 1. Reset generic store things
169
168
  resetStore(state, this.commit);
170
169
 
170
+ // 2. Reset steve specific store things
171
171
  this.commit(`${ state.config.namespace }/resetSubscriptions`);
172
172
 
173
173
  // Clear the podsByNamespace cache
@@ -40,10 +40,10 @@ export const WATCH_STATUSES = {
40
40
  * Create a unique key for a specific resource watch's params
41
41
  */
42
42
  export const keyForSubscribe = ({
43
- resourceType, type, namespace, id, selector
43
+ resourceType, type, namespace, id, selector, mode
44
44
  } = {}) => {
45
45
  const keyMap = {
46
- type: resourceType || type, namespace, id, selector
46
+ type: resourceType || type, namespace, id, selector, mode
47
47
  };
48
48
 
49
49
  return Object.entries(keyMap)
@@ -17,9 +17,10 @@ import {
17
17
  } from '@shell/config/types';
18
18
  import { CAPI as CAPI_LAB_AND_ANO, CATTLE_PUBLIC_ENDPOINTS, STORAGE, UI_PROJECT_SECRET_COPY } from '@shell/config/labels-annotations';
19
19
  import { Schema } from '@shell/plugins/steve/schema';
20
- import { PaginationSettingsStore } from '@shell/types/resources/settings';
20
+ import { PaginationSettingsStores } from '@shell/types/resources/settings';
21
21
  import paginationUtils from '@shell/utils/pagination-utils';
22
22
  import { KubeLabelSelector, KubeLabelSelectorExpression } from '@shell/types/kube/kube-api';
23
+ import { parseField } from '@shell/utils/sort';
23
24
 
24
25
  /**
25
26
  * This is a workaround for a ts build issue found in check-plugins-build.
@@ -401,9 +402,13 @@ class StevePaginationUtils extends NamespaceProjectFilters {
401
402
 
402
403
  const joined = opt.pagination.sort
403
404
  .map((s) => {
404
- this.validateField(validateFields, schema, s.field);
405
+ // Use the same mechanism as local sorting to flip logic for asc/des
406
+ const { field, reverse } = parseField(s.field);
407
+ const asc = reverse ? !s.asc : s.asc;
405
408
 
406
- return `${ s.asc ? '' : '-' }${ this.convertArrayPath(s.field) }`;
409
+ this.validateField(validateFields, schema, field);
410
+
411
+ return `${ asc ? '' : '-' }${ this.convertArrayPath(field) }`;
407
412
  })
408
413
  .join(',');
409
414
 
@@ -615,7 +620,6 @@ class StevePaginationUtils extends NamespaceProjectFilters {
615
620
  res.push(`filter=!${ labelKey }`);
616
621
  break;
617
622
  case 'Gt':
618
- // Currently broken - see https://github.com/rancher/rancher/issues/50057
619
623
  // Only applicable to node affinity (atm) - https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#operators
620
624
 
621
625
  if (typeof exp.values !== 'string') {
@@ -628,7 +632,6 @@ class StevePaginationUtils extends NamespaceProjectFilters {
628
632
  res.push(`filter=${ labelKey } > (${ exp.values })`);
629
633
  break;
630
634
  case 'Lt':
631
- // Currently broken - see https://github.com/rancher/rancher/issues/50057
632
635
  // Only applicable to node affinity (atm) - https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#operators
633
636
  if (typeof exp.values !== 'string') {
634
637
  console.error(`Skipping labelSelector to API filter param conversion for ${ exp.key }(Lt) as no value was supplied`); // eslint-disable-line no-console
@@ -649,7 +652,7 @@ class StevePaginationUtils extends NamespaceProjectFilters {
649
652
  }
650
653
  }
651
654
 
652
- export const PAGINATION_SETTINGS_STORE_DEFAULTS: PaginationSettingsStore = {
655
+ export const PAGINATION_SETTINGS_STORE_DEFAULTS: PaginationSettingsStores = {
653
656
  cluster: {
654
657
  resources: {
655
658
  enableAll: false,
@@ -662,7 +665,7 @@ export const PAGINATION_SETTINGS_STORE_DEFAULTS: PaginationSettingsStore = {
662
665
  CATALOG.APP, CATALOG.OPERATION,
663
666
  HPA, INGRESS, SERVICE,
664
667
  PV, CONFIG_MAP, STORAGE_CLASS, PVC, SECRET,
665
- WORKLOAD_TYPES.REPLICA_SET, WORKLOAD_TYPES.REPLICATION_CONTROLLER
668
+ WORKLOAD_TYPES.REPLICA_SET, WORKLOAD_TYPES.REPLICATION_CONTROLLER,
666
669
  ],
667
670
  generic: true,
668
671
  }
@@ -673,8 +676,8 @@ export const PAGINATION_SETTINGS_STORE_DEFAULTS: PaginationSettingsStore = {
673
676
  enableAll: false,
674
677
  enableSome: {
675
678
  enabled: [
676
- // { resource: CAPI.RANCHER_CLUSTER, context: ['home', 'side-bar'] }, // Disabled due to https://github.com/rancher/dashboard/issues/14493
677
- // { resource: MANAGEMENT.CLUSTER, context: ['side-bar'] }, // Disabled due to https://github.com/rancher/dashboard/issues/14493
679
+ // { resource: CAPI.RANCHER_CLUSTER, context: ['home', 'side-bar'] },
680
+ // { resource: MANAGEMENT.CLUSTER, context: ['side-bar'] },
678
681
  { resource: CATALOG.APP, context: ['branding'] },
679
682
  SECRET
680
683
  ],
@@ -23,11 +23,13 @@
23
23
  * Successfully flow - watch
24
24
  * 1. UI --> Rancher: _watch_ request
25
25
  * 2. Rancher --> UI: `resource.start`. UI sets watch as started
26
+ * ...
26
27
  * 3. Rancher --> UI: `resource.change` (contains data). UI caches data
27
28
  *
28
29
  * Successful flow - watch - new mode
29
30
  * 1. UI --> Rancher: _watch_ request
30
31
  * 2. Rancher --> UI: `resource.start`. UI sets watch as started
32
+ * ...
31
33
  * 3. Rancher --> UI: `resource.changes` (contains no data). UI makes a HTTP request to fetch data
32
34
  *
33
35
  * Successful flow - unwatch
@@ -84,9 +86,10 @@ import { WORKER_MODES } from './worker';
84
86
  import acceptOrRejectSocketMessage from './accept-or-reject-socket-message';
85
87
  import { BLANK_CLUSTER, STORE } from '@shell/store/store-types.js';
86
88
  import { _MERGE } from '@shell/plugins/dashboard-store/actions';
87
- import { STEVE_WATCH_EVENT, STEVE_WATCH_MODE } from '@shell/types/store/subscribe.types';
89
+ import { STEVE_WATCH_EVENT_TYPES, STEVE_WATCH_MODE } from '@shell/types/store/subscribe.types';
88
90
  import paginationUtils from '@shell/utils/pagination-utils';
89
91
  import backOff from '@shell/utils/back-off';
92
+ import { SteveWatchEventListenerManager } from '@shell/plugins/subscribe-events';
90
93
 
91
94
  // minimum length of time a disconnect notification is shown
92
95
  const MINIMUM_TIME_NOTIFIED = 3000;
@@ -312,24 +315,6 @@ function growlsDisabled(rootGetters) {
312
315
  return getPerformanceSetting(rootGetters)?.disableWebsocketNotification;
313
316
  }
314
317
 
315
- /**
316
- * Supported events are listed
317
- *
318
- * of type { [key: STEVE_WATCH_EVENT]: STEVE_WATCH_EVENT_LISTENER[]}
319
- */
320
- const listeners = { [STEVE_WATCH_EVENT.CHANGES]: [] };
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
318
  /**
334
319
  * clear the provided error, but also ensure any backoff request associated with it is cleared as well
335
320
  */
@@ -452,7 +437,7 @@ const sharedActions = {
452
437
  * @param {STEVE_WATCH_EVENT_PARAMS} event
453
438
  */
454
439
  watchEvent(ctx, {
455
- event = STEVE_WATCH_EVENT.CHANGES,
440
+ event = STEVE_WATCH_EVENT_TYPES.CHANGES,
456
441
  id,
457
442
  callback,
458
443
  /**
@@ -460,26 +445,27 @@ const sharedActions = {
460
445
  */
461
446
  params
462
447
  }) {
463
- if (!listeners[event]) {
464
- console.error(`Unknown event type "${ event }", only ${ Object.keys(listeners).join(',') } are supported`); // eslint-disable-line no-console
448
+ if (!ctx.getters.listenerManager.isSupportedEventType(event)) {
449
+ console.error(`Unknown event type "${ event }", only ${ Object.keys(ctx.getters.listenerManager.supportedEventTypes).join(',') } are supported`); // eslint-disable-line no-console
465
450
 
466
451
  return;
467
452
  }
468
453
 
469
- // STEVE_WATCH_EVENT_LISTENER | undefined
470
- let listener = listeners[event].find((l) => equivalentWatch(l.params, params));
454
+ ctx.getters.listenerManager.addEventListenerCallback({
455
+ callback,
456
+ args: {
457
+ event, params, id
458
+ }
459
+ });
471
460
 
472
- if (!listener) {
473
- listener = {
474
- params,
475
- callbacks: { }
476
- };
477
- listeners[event].push(listener);
478
- }
461
+ const hasStandardWatch = ctx.getters.listenerManager.hasStandardWatch({ params });
479
462
 
480
- if (!listener.callbacks[id]) {
481
- listener.callbacks[id] = callback;
482
- ctx.dispatch('watch', params);
463
+ if (!hasStandardWatch) {
464
+ // If there's nothing to piggy back on... start a watch to do so.
465
+ ctx.dispatch('watch', {
466
+ ...params,
467
+ standardWatch: false // Ensure that we don't treat this as a standard watch
468
+ });
483
469
  }
484
470
  },
485
471
 
@@ -488,24 +474,26 @@ const sharedActions = {
488
474
  * @param {STEVE_UNWATCH_EVENT_PARAMS} event
489
475
  */
490
476
  unwatchEvent(ctx, {
491
- event = STEVE_WATCH_EVENT.CHANGES,
477
+ event = STEVE_WATCH_EVENT_TYPES.CHANGES,
492
478
  id,
493
479
  /**
494
480
  * of type @STEVE_WATCH_PARAMS
495
481
  */
496
482
  params
497
483
  }) {
498
- if (!listeners[event]) {
484
+ if (!ctx.getters.listenerManager.isSupportedEventType(event)) {
499
485
  console.info(`Attempted to unwatch for an event "${ event }" but it had no watchers`); // eslint-disable-line no-console
500
486
 
501
487
  return;
502
488
  }
503
489
 
504
- const existing = listeners[event].find((l) => equivalentWatch(l.params, params));
490
+ ctx.getters.listenerManager.removeEventListenerCallback({
491
+ event, params, id
492
+ });
505
493
 
506
- if (existing) {
507
- delete existing.callbacks[id];
508
- }
494
+ // Unwatch the underlying standard watch
495
+ // Note - If we were piggybacking on a watch that previously existed we won't unwatch it
496
+ ctx.dispatch('unwatch', params);
509
497
  },
510
498
 
511
499
  /**
@@ -517,7 +505,7 @@ const sharedActions = {
517
505
  state.debugSocket && console.info(`Watch Request [${ getters.storeName }]`, JSON.stringify(params)); // eslint-disable-line no-console
518
506
  let {
519
507
  // eslint-disable-next-line prefer-const
520
- type, selector, id, revision, namespace, stop, force, mode
508
+ type, selector, id, revision, namespace, stop, force, mode, standardWatch = true
521
509
  } = params;
522
510
 
523
511
  namespace = acceptOrRejectSocketMessage.subscribeNamespace(namespace);
@@ -562,10 +550,6 @@ const sharedActions = {
562
550
  return;
563
551
  }
564
552
 
565
- if (!stop) {
566
- dispatch('unwatchIncompatible', messageMeta);
567
- }
568
-
569
553
  // Watch errors mean we make a http request to get latest revision (which is still missing) and try to re-watch with it...
570
554
  // etc
571
555
  if (typeof revision === 'undefined') {
@@ -618,6 +602,12 @@ const sharedActions = {
618
602
  return;
619
603
  }
620
604
 
605
+ if (!stop && standardWatch) {
606
+ // Track that this watch is just a normal one, not one kicked off by listeners
607
+ // This helps us keep the watch going (for listeners) instead of in unwatch just stopping it
608
+ getters.listenerManager.setStandardWatch({ standardWatch: true, args: { event: msg.mode, params: msg } });
609
+ }
610
+
621
611
  return dispatch('send', msg);
622
612
  },
623
613
 
@@ -642,6 +632,21 @@ const sharedActions = {
642
632
  };
643
633
 
644
634
  const unwatch = (obj) => {
635
+ // Has this normal watch got listeners? If so
636
+ const hasStandardWatch = ctx.getters.listenerManager.hasStandardWatch({ params: obj });
637
+ const watchHasListeners = ctx.getters.listenerManager.hasEventListeners({ params: obj });
638
+
639
+ if (hasStandardWatch) {
640
+ // If we have listeners for this watch... make sure it knows there's now no root standard watch
641
+ ctx.getters.listenerManager.setStandardWatch({ standardWatch: false, args: { params: obj } });
642
+ }
643
+
644
+ if (watchHasListeners) {
645
+ // Does this watch have listeners? if so we shouldn't stop it (they still need it)
646
+
647
+ return;
648
+ }
649
+
645
650
  if (getters['watchStarted'](obj)) {
646
651
  // Set that we don't want to watch this type
647
652
  // Otherwise, the dispatch to unwatch below will just cause a re-watch when we
@@ -651,45 +656,28 @@ const sharedActions = {
651
656
  // Make sure anything in the pending queue for the type is removed, since we've now removed the type
652
657
  commit('clearFromQueue', type);
653
658
  }
654
- // Ensure anything pinging in the background is stopped
655
- backOff.resetPrefix(getters.backOffId(obj));
656
659
  };
657
660
 
661
+ const objKey = keyForSubscribe(obj);
662
+ const reset = [];
663
+
658
664
  if (isAdvancedWorker(ctx)) {
659
665
  dispatch('watch', obj); // Ask the backend to stop watching the type
660
666
  } else if (all) {
661
- getters['watchesOfType'](type).forEach((obj) => {
662
- unwatch({ ...obj, stop: true });
663
- });
667
+ reset.push(...getters['watchesOfType'](type));
664
668
  } else if (getters['watchStarted'](obj)) {
665
- unwatch(obj);
669
+ reset.push(obj);
666
670
  }
667
- }
668
- },
669
671
 
670
- /**
671
- * Unwatch watches that are incompatible with the new type
672
- */
673
- unwatchIncompatible({
674
- state, dispatch, getters, commit
675
- }, messageMeta) {
676
- // Step 1 - Clear incompatible watches that have STARTED
677
- const watchesOfType = getters.watchesOfType(messageMeta.type);
678
-
679
- watchesOfType
680
- .filter((entry) => shouldUnwatchIncompatible(messageMeta, entry.mode))
681
- .forEach((entry) => {
682
- dispatch('unwatch', entry);
672
+ reset.forEach((obj) => {
673
+ unwatch(obj);
674
+ // Ensure anything pinging in the background is stopped
675
+ dispatch('resetWatchBackOff', {
676
+ type,
677
+ compareWatches: (entry) => objKey === keyForSubscribe(entry)
678
+ });
683
679
  });
684
-
685
- // Step 2 - Clear inError state for incompatible watches (these won't appear in watchesOfType / state.started)
686
- // (important for the backoff case... for example backoff request to find would overwrite findPage res if executed after nav from detail to list)
687
- const inErrorOfType = Object.values(state.inError || {})
688
- .filter((error) => error.obj.type === messageMeta.type);
689
-
690
- inErrorOfType
691
- .filter((error) => shouldUnwatchIncompatible(messageMeta, error.obj.mode))
692
- .forEach((error) => clearInError({ getters, commit }, error));
680
+ }
693
681
  },
694
682
 
695
683
  /**
@@ -704,7 +692,7 @@ const sharedActions = {
704
692
  if (resetStarted && state.started?.length) {
705
693
  let entries = state.started;
706
694
 
707
- if (type) { // Filter out ones for types we're no interested in
695
+ if (type || compareWatches) { // Filter out ones for types we're no interested in
708
696
  entries = entries
709
697
  .filter((obj) => compareWatches ? compareWatches(obj) : obj.type === type);
710
698
  }
@@ -718,7 +706,7 @@ const sharedActions = {
718
706
  // however resource.stop clears `started` and we need the settings to persist over start-->error-->stop-->start cycles
719
707
  let entries = Object.values(state.inError || {});
720
708
 
721
- if (type) { // Filter out ones for types we're no interested in
709
+ if (type || compareWatches) { // Filter out ones for types we're no interested in
722
710
  entries = entries
723
711
  .filter((error) => compareWatches ? compareWatches(error.obj) : error.obj.type === type);
724
712
  }
@@ -817,6 +805,7 @@ const defaultActions = {
817
805
 
818
806
  if ( getters.schemaFor(entry.type) ) {
819
807
  commit('setWatchStopped', entry);
808
+ // Delete the cached socket revision, forcing the watch to get latest revision from cached resources instead
820
809
  delete entry.revision;
821
810
  promises.push(dispatch('watch', entry));
822
811
  }
@@ -901,11 +890,7 @@ const defaultActions = {
901
890
  }
902
891
 
903
892
  // Should any listeners be notified of this request for them to kick off their own event handling?
904
- const listener = listeners[STEVE_WATCH_MODE.RESOURCE_CHANGES].find((sl) => equivalentWatch(sl.params, params));
905
-
906
- if (listener) {
907
- Object.values(listener.callbacks).forEach((cb) => cb());
908
- }
893
+ getters.listenerManager.triggerEventListener({ event: STEVE_WATCH_MODE.RESOURCE_CHANGES, params });
909
894
  } else {
910
895
  have = getters['all'](resourceType).slice();
911
896
 
@@ -1086,10 +1071,16 @@ const defaultActions = {
1086
1071
  mode: msg.mode,
1087
1072
  };
1088
1073
 
1074
+ // Unwatch watches that are incompatible with the new type
1075
+ // This is mainly to prevent the cache being polluted with resources that aren't compatible with it's aim
1076
+ // For instance if the store/cache for pods contains a namespace X and we watch another namespace Y... we don't want ns X resources added to cache
1077
+
1078
+ // Unwatch incompatible watches
1089
1079
  state.started.filter((entry) => {
1090
1080
  if (
1091
- entry.type === newWatch.type &&
1092
- entry.namespace !== newWatch.namespace
1081
+ (entry.type === newWatch.type) &&
1082
+ (entry.namespace !== newWatch.namespace) &&
1083
+ (!entry.mode && !newWatch.mode) // mode watches will be handled when they become an issue
1093
1084
  ) {
1094
1085
  return true;
1095
1086
  }
@@ -1178,7 +1169,25 @@ const defaultActions = {
1178
1169
  commit('setWatchStopped', obj);
1179
1170
  }
1180
1171
 
1181
- dispatch('watch', obj);
1172
+ // Now re-watch
1173
+ const hasEventListeners = getters.listenerManager.hasEventListeners({ params: obj });
1174
+ const hasStandardWatch = getters.listenerManager.hasStandardWatch({ params: obj });
1175
+
1176
+ dispatch('watch', {
1177
+ ...obj,
1178
+ // hasEventListeners && !hasStandardWatch ? false : true
1179
+ // if this watch isn't associated with a normal watch... (there are no listeners, or there are listeners but also a normal watch)
1180
+ standardWatch: !(hasEventListeners && !hasStandardWatch)
1181
+ });
1182
+
1183
+ if (hasEventListeners) {
1184
+ // If there's event listeners always kick them off
1185
+ // - The re-watch associated with normal watches will watch from a revision from it's own cache
1186
+ // - The revision in that cache might be ahead of the state the listeners have, so the watch won't ping something for the listeners to trigger on
1187
+ // - so to work around this whenever we start the watches again trigger off the changes for it
1188
+ // Improvement - we only do one event here (currently the only one supported), could expand to others
1189
+ getters.listenerManager.triggerEventListener({ event: STEVE_WATCH_EVENT_TYPES.CHANGES, params: obj });
1190
+ }
1182
1191
  }
1183
1192
  },
1184
1193
 
@@ -1210,6 +1219,15 @@ const defaultActions = {
1210
1219
  }
1211
1220
  }
1212
1221
 
1222
+ const havePage = ctx.getters['havePage'](type);
1223
+
1224
+ if (havePage) {
1225
+ console.warn(`Prevented watch \`resource.change\` data from polluting the cache for type "${ type }" (currently represents a page). To prevent any further issues the watch has been stopped.`, data); // eslint-disable-line no-console
1226
+ ctx.dispatch('unwatch', data);
1227
+
1228
+ return;
1229
+ }
1230
+
1213
1231
  queueChange(ctx, msg, true, 'Change');
1214
1232
 
1215
1233
  const typeOption = ctx.rootGetters['type-map/optionsFor'](type);
@@ -1344,6 +1362,7 @@ const defaultMutations = {
1344
1362
  clearTimeout(state.queueTimer);
1345
1363
  state.deferredRequests = {};
1346
1364
  state.queueTimer = null;
1365
+ state.socketListenerManager = new SteveWatchEventListenerManager(state.config.namespace);
1347
1366
  },
1348
1367
 
1349
1368
  clearFromQueue(state, type) {
@@ -1444,6 +1463,15 @@ const defaultGetters = {
1444
1463
 
1445
1464
  return revision || null;
1446
1465
  },
1466
+
1467
+ /**
1468
+ * Get the watch listener manager for this store
1469
+ *
1470
+ * Instance of @SteveWatchEventListenerManager . See it's description for more info
1471
+ */
1472
+ listenerManager: (state) => {
1473
+ return state.socketListenerManager;
1474
+ },
1447
1475
  };
1448
1476
 
1449
1477
  export const actions = {