@rancher/shell 3.0.5 → 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 (92) 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/FilterPanel.vue +8 -1
  19. package/components/IconOrSvg.vue +40 -29
  20. package/components/PaginatedResourceTable.vue +7 -2
  21. package/components/PromptRemove.vue +5 -0
  22. package/components/ResourceDetail/index.vue +1 -0
  23. package/components/ResourceTable.vue +30 -20
  24. package/components/SortableTable/sorting.js +3 -1
  25. package/components/Tabbed/index.vue +5 -5
  26. package/components/form/ResourceTabs/index.vue +37 -18
  27. package/components/form/SecretSelector.vue +6 -2
  28. package/components/nav/Group.vue +29 -9
  29. package/components/nav/Header.vue +6 -8
  30. package/components/nav/NamespaceFilter.vue +1 -1
  31. package/components/nav/TopLevelMenu.helper.ts +47 -20
  32. package/components/nav/TopLevelMenu.vue +44 -14
  33. package/components/nav/Type.vue +0 -5
  34. package/components/nav/__tests__/TopLevelMenu.test.ts +2 -0
  35. package/config/pagination-table-headers.js +10 -2
  36. package/config/product/explorer.js +9 -8
  37. package/config/table-headers.js +9 -0
  38. package/config/uiplugins.js +1 -1
  39. package/core/plugin.ts +33 -9
  40. package/core/types.ts +37 -6
  41. package/detail/provisioning.cattle.io.cluster.vue +1 -0
  42. package/dialog/InstallExtensionDialog.vue +71 -45
  43. package/dialog/UninstallExtensionDialog.vue +2 -1
  44. package/dialog/__tests__/InstallExtensionDialog.test.ts +111 -0
  45. package/edit/auth/oidc.vue +86 -16
  46. package/list/catalog.cattle.io.clusterrepo.vue +2 -2
  47. package/mixins/__tests__/chart.test.ts +1 -1
  48. package/mixins/chart.js +1 -1
  49. package/models/event.js +7 -0
  50. package/models/provisioning.cattle.io.cluster.js +9 -0
  51. package/package.json +2 -2
  52. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +6 -0
  53. package/pages/c/_cluster/apps/charts/StatusLabel.vue +4 -3
  54. package/pages/c/_cluster/apps/charts/index.vue +12 -11
  55. package/pages/c/_cluster/explorer/EventsTable.vue +3 -6
  56. package/pages/c/_cluster/settings/performance.vue +1 -1
  57. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +159 -62
  58. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +102 -0
  59. package/pages/c/_cluster/uiplugins/__tests__/{index.spec.ts → index.test.ts} +121 -55
  60. package/pages/c/_cluster/uiplugins/index.vue +110 -94
  61. package/plugins/__tests__/subscribe.events.test.ts +194 -0
  62. package/plugins/dashboard-store/actions.js +3 -0
  63. package/plugins/dashboard-store/getters.js +1 -1
  64. package/plugins/dashboard-store/resource-class.js +15 -4
  65. package/plugins/steve/__tests__/subscribe.spec.ts +27 -24
  66. package/plugins/steve/index.js +18 -10
  67. package/plugins/steve/mutations.js +2 -2
  68. package/plugins/steve/resourceWatcher.js +2 -2
  69. package/plugins/steve/steve-pagination-utils.ts +26 -31
  70. package/plugins/steve/subscribe.js +113 -85
  71. package/plugins/subscribe-events.ts +211 -0
  72. package/rancher-components/BadgeState/BadgeState.vue +8 -6
  73. package/rancher-components/Banner/Banner.vue +2 -1
  74. package/rancher-components/Form/Checkbox/Checkbox.vue +3 -3
  75. package/rancher-components/Form/Radio/RadioButton.vue +3 -3
  76. package/scripts/test-plugins-build.sh +4 -5
  77. package/scripts/typegen.sh +2 -0
  78. package/store/auth.js +2 -2
  79. package/store/index.js +12 -22
  80. package/types/extension-manager.ts +8 -1
  81. package/types/resources/settings.d.ts +24 -17
  82. package/types/shell/index.d.ts +534 -336
  83. package/types/store/subscribe-events.types.ts +70 -0
  84. package/types/store/subscribe.types.ts +6 -22
  85. package/types/store/vuex.d.ts +2 -1
  86. package/types/vue-shim.d.ts +2 -5
  87. package/utils/pagination-utils.ts +98 -30
  88. package/utils/pagination-wrapper.ts +6 -8
  89. package/utils/sort.js +5 -0
  90. package/utils/unit-tests/pagination-utils.spec.ts +283 -0
  91. package/utils/validators/formRules/__tests__/index.test.ts +7 -0
  92. package/utils/validators/formRules/index.ts +2 -2
@@ -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 = {
@@ -0,0 +1,211 @@
1
+ import { keyForSubscribe } from '@shell/plugins/steve/resourceWatcher';
2
+ import {
3
+ SubscribeEventListener, SubscribeEventCallbackArgs, SubscribeEventListenerArgs, SubscribeEventWatch, SubscribeEventWatchArgs,
4
+ STEVE_WATCH_EVENT_LISTENER_CALLBACK
5
+ } from '@shell/types/store/subscribe-events.types';
6
+ import { STEVE_WATCH_EVENT_TYPES, STEVE_WATCH_PARAMS } from '@shell/types/store/subscribe.types';
7
+
8
+ type SubscribeEventWatches = { [socketId: string]: SubscribeEventWatch};
9
+
10
+ /**
11
+ * For a specific resource watch, listen for a specific event type and trigger callback when received
12
+ *
13
+ * For example, listen for provisioning.cattle.io clusters messages of type resource.changes and trigger callback when received
14
+ *
15
+ * Watch - UI is watching a resource type restricted by nothing/id/namespace/selector. For example
16
+ * - watch all pods
17
+ * - watch specific pod
18
+ * - watch pods with specific labels
19
+ * Event - Rancher socket messages TO the ui. For example
20
+ * - resource.started
21
+ * - resource.change
22
+ * - resource.changes
23
+ * Listener - listen to events, trigger when received. For example
24
+ * - listen for resource.changes messages for the all pods watch
25
+ * Callback - triggered when a listener has heard something
26
+ * - watch for all pods receives a resource.changes message, it has a listener, listener executes it's callback
27
+ *
28
+ * Watch 0:M Events 0:M Listeners 0:M Callbacks
29
+ */
30
+ export class SteveWatchEventListenerManager {
31
+ private keyForSubscribe({ params }: {params: STEVE_WATCH_PARAMS}): string {
32
+ return keyForSubscribe(params);
33
+ }
34
+
35
+ /**
36
+ * collection of ui --> rancher watches. we keep state specific to this class here
37
+ */
38
+ private watches: SubscribeEventWatches = {};
39
+
40
+ /**
41
+ * Not all event types can be listened to are supported, only these
42
+ */
43
+ public readonly supportedEventTypes: STEVE_WATCH_EVENT_TYPES[] = [STEVE_WATCH_EVENT_TYPES.CHANGES];
44
+
45
+ /**
46
+ * Not all event types can be listened to are supported, check if one is
47
+ */
48
+ public isSupportedEventType(type: STEVE_WATCH_EVENT_TYPES): boolean {
49
+ return !!this.supportedEventTypes.includes(type);
50
+ }
51
+
52
+ /** **** Watches ***********************/
53
+
54
+ public getWatch({ params } : SubscribeEventWatchArgs): SubscribeEventWatch {
55
+ const socketId = this.keyForSubscribe({ params });
56
+
57
+ return this.watches[socketId];
58
+ }
59
+
60
+ private initialiseWatch({ params }: SubscribeEventWatchArgs): SubscribeEventWatch {
61
+ const socketId = this.keyForSubscribe({ params });
62
+
63
+ this.watches[socketId] = {
64
+ hasStandardWatch: false,
65
+ listeners: []
66
+ };
67
+
68
+ return this.watches[socketId];
69
+ }
70
+
71
+ /**
72
+ * This is just tidying the entry
73
+ *
74
+ * All watches associated with this type should be unwatched
75
+ */
76
+ private deleteWatch({ params } : SubscribeEventWatchArgs) {
77
+ const socketId = this.keyForSubscribe({ params });
78
+
79
+ delete this.watches[socketId];
80
+ }
81
+
82
+ /**
83
+ * Is there a standard non-listener watch for this this type
84
+ */
85
+ public hasStandardWatch({ params } : SubscribeEventWatchArgs): boolean {
86
+ const socketId = this.keyForSubscribe({ params });
87
+
88
+ return this.watches[socketId]?.hasStandardWatch;
89
+ }
90
+
91
+ /**
92
+ * Set if this type has a standard non-listener watch associated with it
93
+ */
94
+ public setStandardWatch({ standardWatch, args }: { standardWatch: boolean, args: SubscribeEventWatchArgs}) {
95
+ const { params } = args;
96
+
97
+ let watch = this.getWatch({ params });
98
+
99
+ if (!watch) {
100
+ if (!standardWatch) {
101
+ // no point setting a non-existent watch as not started
102
+ return;
103
+ }
104
+ watch = this.initialiseWatch({ params });
105
+ }
106
+
107
+ watch.hasStandardWatch = standardWatch;
108
+
109
+ // if we've just set this to false and there's no listeners, tidy up the entry
110
+ if (!watch.hasStandardWatch && watch.listeners.length === 0) {
111
+ this.deleteWatch({ params });
112
+ }
113
+ }
114
+
115
+ /** **** Listeners ***********************/
116
+
117
+ public hasEventListeners({ params }: SubscribeEventWatchArgs): boolean {
118
+ const socketId = this.keyForSubscribe({ params });
119
+ const watch = this.watches[socketId];
120
+ const listener = watch?.listeners.find((l) => Object.values(l.callbacks).length > 0);
121
+
122
+ return !!listener;
123
+ }
124
+
125
+ public getEventListener({ entryOnly, args }: { entryOnly?: boolean, args: SubscribeEventListenerArgs}): SubscribeEventListener | null {
126
+ const { params, event } = args;
127
+ const socketId = this.keyForSubscribe({ params });
128
+ const watch = this.watches[socketId];
129
+
130
+ if (watch) {
131
+ const listener = watch.listeners.find((w) => w.event === event);
132
+
133
+ if (listener && (entryOnly || !!Object.keys(listener?.callbacks || {}).length)) {
134
+ return listener;
135
+ }
136
+ }
137
+
138
+ return null;
139
+ }
140
+
141
+ public addEventListener({ event, params }: SubscribeEventListenerArgs): SubscribeEventListener {
142
+ if (!event) {
143
+ throw new Error(`Cannot add a socket watch event listener if there's no event to listen to`);
144
+ }
145
+
146
+ let watch = this.getWatch({ params });
147
+
148
+ if (!watch) {
149
+ watch = this.initialiseWatch({ params });
150
+ }
151
+
152
+ let listener = this.getEventListener({ entryOnly: true, args: { event, params } });
153
+
154
+ if (!listener) {
155
+ listener = {
156
+ event,
157
+ callbacks: { },
158
+ };
159
+ watch.listeners.push(listener);
160
+ }
161
+
162
+ return listener;
163
+ }
164
+
165
+ public triggerEventListener({ event, params }: SubscribeEventListenerArgs) {
166
+ const eventWatcher = this.getEventListener({ entryOnly: false, args: { event, params } });
167
+
168
+ if (eventWatcher) {
169
+ Object.values(eventWatcher.callbacks).forEach((cb) => {
170
+ cb();
171
+ });
172
+ }
173
+ }
174
+
175
+ public triggerAllEventListeners({ params }: SubscribeEventWatchArgs) {
176
+ const watch = this.getWatch({ params });
177
+
178
+ watch.listeners.forEach((l) => {
179
+ Object.values(l.callbacks || {}).forEach((cb) => cb());
180
+ });
181
+ }
182
+
183
+ /** **** Callbacks ***********************/
184
+
185
+ public addEventListenerCallback({ callback, args }: {
186
+ callback: STEVE_WATCH_EVENT_LISTENER_CALLBACK,
187
+ args: SubscribeEventCallbackArgs
188
+ }): SubscribeEventListener {
189
+ const { params, event, id } = args;
190
+ const eventWatcher = this.addEventListener({ event, params });
191
+
192
+ if (!eventWatcher.callbacks[id]) {
193
+ eventWatcher.callbacks[id] = callback;
194
+ }
195
+
196
+ return eventWatcher;
197
+ }
198
+
199
+ /**
200
+ * This is just tidying the entry
201
+ *
202
+ * All watches associated with this type should be unwatched
203
+ */
204
+ public removeEventListenerCallback({ event, params, id }: SubscribeEventCallbackArgs) {
205
+ const existing = this.getEventListener({ args: { event, params } });
206
+
207
+ if (existing) {
208
+ delete existing.callbacks[id];
209
+ }
210
+ }
211
+ }
@@ -79,22 +79,24 @@ export default defineComponent({
79
79
  border-radius: 20px;
80
80
 
81
81
  &.bg-info {
82
- border-color: var(--info);
82
+ color: var(--on-info-banner);
83
+ background: var(--info-badge, var(--info-banner));
83
84
  }
84
85
 
85
86
  &.bg-error {
86
- border-color: var(--error);
87
+ color: var(--on-error-banner);
88
+ background: var(--error-badge, var(--error-banner));
87
89
  }
88
90
 
89
91
  &.bg-warning {
90
- border-color: var(--warning);
92
+ color: var(--on-warning-banner);
93
+ background: var(--warning-badge, var(--warning-banner));
91
94
  }
92
95
 
93
96
  // Successful states are de-emphasized by using [text-]color instead of background-color
94
97
  &.bg-success {
95
- color: var(--success);
96
- background: transparent;
97
- border-color: var(--success);
98
+ color: var(--on-success-banner, var(--success-text));
99
+ background: var(--success-badge, var(--success));
98
100
  }
99
101
 
100
102
  // Added badge-disabled instead of bg-disabled since bg-disabled is used in other places with !important styling, an investigation is needed to make the naming consistent
@@ -231,12 +231,13 @@ $icon-size: 24px;
231
231
  .warning & {
232
232
  background: var(--warning-banner-bg);
233
233
  border-color: var(--warning);
234
+ color: var(--warning-banner-text, var(--body-text));
234
235
  }
235
236
 
236
237
  .error & {
237
238
  background: var(--error-banner-bg);
238
239
  border-color: var(--error);
239
- color: var(--error);
240
+ color: var(--error-banner-text, var(--error));
240
241
  }
241
242
 
242
243
  &.stacked {
@@ -416,7 +416,7 @@ $fontColor: var(--input-label);
416
416
  width: 14px;
417
417
  background-color: var(--body-bg);
418
418
  border-radius: var(--border-radius);
419
- border: 1px solid var(--border);
419
+ border: 1px solid var(--input-border);
420
420
  flex-shrink: 0;
421
421
 
422
422
  &:focus-visible {
@@ -440,12 +440,12 @@ $fontColor: var(--input-label);
440
440
  }
441
441
 
442
442
  input:checked ~ .checkbox-custom {
443
- background-color:var(--primary);
443
+ background-color: var(--active, var(--primary));
444
444
  -webkit-transform: rotate(0deg) scale(1);
445
445
  -ms-transform: rotate(0deg) scale(1);
446
446
  transform: rotate(0deg) scale(1);
447
447
  opacity:1;
448
- border: 1px solid var(--primary);
448
+ border: 1px solid var(--active, var(--primary));
449
449
  }
450
450
 
451
451
  // Custom Checkbox tick
@@ -274,7 +274,7 @@ $fontColor: var(--input-label);
274
274
  min-width: 14px;
275
275
  background-color: var(--input-bg);
276
276
  border-radius: 50%;
277
- border: 1.5px solid var(--border);
277
+ border: 1.5px solid var(--input-border);
278
278
  margin-top: 5px;
279
279
  }
280
280
 
@@ -284,12 +284,12 @@ $fontColor: var(--input-label);
284
284
 
285
285
  .radio-custom {
286
286
  &[aria-checked="true"] {
287
- background-color: var(--primary);
287
+ background-color: var(--active, var(--primary));
288
288
  -webkit-transform: rotate(0deg) scale(1);
289
289
  -ms-transform: rotate(0deg) scale(1);
290
290
  transform: rotate(0deg) scale(1);
291
291
  opacity:1;
292
- border: 1.5px solid var(--primary);
292
+ border: 1.5px solid var(--active, var(--primary));
293
293
 
294
294
  // Ensure that checked radio buttons are muted but still visibly selected when muted
295
295
  &.text-muted {
@@ -98,12 +98,11 @@ createTestComponent() {
98
98
  # Add test list component to the test package
99
99
  # Validates rancher-components imports
100
100
 
101
- # NOTE - This fails if importing some components with TS imports...
102
- # cp ${SHELL_DIR}/list/catalog.cattle.io.clusterrepo.vue pkg/test-pkg/list
103
- # See https://github.com/rancher/dashboard/issues/12918
104
-
105
- # Use a basic list instead
101
+ # Basic list instead
106
102
  cp ${SHELL_DIR}/list/namespace.vue pkg/test-pkg/list
103
+
104
+ # More complex list
105
+ cp ${SHELL_DIR}/list/catalog.cattle.io.clusterrepo.vue pkg/test-pkg/list
107
106
  }
108
107
 
109
108
  # Publish shell pkg (tag is needed as publish-shell is optimized to work with release-shell-pkg workflow)