@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.
- package/assets/images/pl/dark/rancher-logo.svg +131 -44
- package/assets/images/pl/rancher-logo.svg +120 -44
- package/assets/styles/base/_basic.scss +2 -2
- package/assets/styles/base/_color-classic.scss +51 -0
- package/assets/styles/base/_color.scss +3 -3
- package/assets/styles/base/_mixins.scss +1 -1
- package/assets/styles/base/_variables-classic.scss +47 -0
- package/assets/styles/global/_button.scss +49 -17
- package/assets/styles/global/_form.scss +1 -1
- package/assets/styles/themes/_dark.scss +4 -0
- package/assets/styles/themes/_light.scss +3 -69
- package/assets/styles/themes/_modern.scss +194 -50
- package/assets/styles/vendor/vue-select.scss +1 -2
- package/assets/translations/en-us.yaml +33 -21
- package/components/ClusterIconMenu.vue +1 -1
- package/components/ClusterProviderIcon.vue +1 -1
- package/components/CodeMirror.vue +1 -1
- package/components/FilterPanel.vue +8 -1
- package/components/IconOrSvg.vue +40 -29
- package/components/PaginatedResourceTable.vue +7 -2
- package/components/PromptRemove.vue +5 -0
- package/components/ResourceDetail/index.vue +1 -0
- package/components/ResourceTable.vue +30 -20
- package/components/SortableTable/sorting.js +3 -1
- package/components/Tabbed/index.vue +5 -5
- package/components/form/ResourceTabs/index.vue +37 -18
- package/components/form/SecretSelector.vue +6 -2
- package/components/nav/Group.vue +29 -9
- package/components/nav/Header.vue +6 -8
- package/components/nav/NamespaceFilter.vue +1 -1
- package/components/nav/TopLevelMenu.helper.ts +47 -20
- package/components/nav/TopLevelMenu.vue +44 -14
- package/components/nav/Type.vue +0 -5
- package/components/nav/__tests__/TopLevelMenu.test.ts +2 -0
- package/config/pagination-table-headers.js +10 -2
- package/config/product/explorer.js +9 -8
- package/config/table-headers.js +9 -0
- package/config/uiplugins.js +1 -1
- package/core/plugin.ts +33 -9
- package/core/types.ts +37 -6
- package/detail/provisioning.cattle.io.cluster.vue +1 -0
- package/dialog/InstallExtensionDialog.vue +71 -45
- package/dialog/UninstallExtensionDialog.vue +2 -1
- package/dialog/__tests__/InstallExtensionDialog.test.ts +111 -0
- package/edit/auth/oidc.vue +86 -16
- package/list/catalog.cattle.io.clusterrepo.vue +2 -2
- package/mixins/__tests__/chart.test.ts +1 -1
- package/mixins/chart.js +1 -1
- package/models/event.js +7 -0
- package/models/provisioning.cattle.io.cluster.js +9 -0
- package/package.json +2 -2
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +6 -0
- package/pages/c/_cluster/apps/charts/StatusLabel.vue +4 -3
- package/pages/c/_cluster/apps/charts/index.vue +12 -11
- package/pages/c/_cluster/explorer/EventsTable.vue +3 -6
- package/pages/c/_cluster/settings/performance.vue +1 -1
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +159 -62
- package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +102 -0
- package/pages/c/_cluster/uiplugins/__tests__/{index.spec.ts → index.test.ts} +121 -55
- package/pages/c/_cluster/uiplugins/index.vue +110 -94
- package/plugins/__tests__/subscribe.events.test.ts +194 -0
- package/plugins/dashboard-store/actions.js +3 -0
- package/plugins/dashboard-store/getters.js +1 -1
- package/plugins/dashboard-store/resource-class.js +15 -4
- package/plugins/steve/__tests__/subscribe.spec.ts +27 -24
- package/plugins/steve/index.js +18 -10
- package/plugins/steve/mutations.js +2 -2
- package/plugins/steve/resourceWatcher.js +2 -2
- package/plugins/steve/steve-pagination-utils.ts +26 -31
- package/plugins/steve/subscribe.js +113 -85
- package/plugins/subscribe-events.ts +211 -0
- package/rancher-components/BadgeState/BadgeState.vue +8 -6
- package/rancher-components/Banner/Banner.vue +2 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +3 -3
- package/rancher-components/Form/Radio/RadioButton.vue +3 -3
- package/scripts/test-plugins-build.sh +4 -5
- package/scripts/typegen.sh +2 -0
- package/store/auth.js +2 -2
- package/store/index.js +12 -22
- package/types/extension-manager.ts +8 -1
- package/types/resources/settings.d.ts +24 -17
- package/types/shell/index.d.ts +534 -336
- package/types/store/subscribe-events.types.ts +70 -0
- package/types/store/subscribe.types.ts +6 -22
- package/types/store/vuex.d.ts +2 -1
- package/types/vue-shim.d.ts +2 -5
- package/utils/pagination-utils.ts +98 -30
- package/utils/pagination-wrapper.ts +6 -8
- package/utils/sort.js +5 -0
- package/utils/unit-tests/pagination-utils.spec.ts +283 -0
- package/utils/validators/formRules/__tests__/index.test.ts +7 -0
- 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 {
|
|
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 =
|
|
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 (!
|
|
464
|
-
console.error(`Unknown event type "${ event }", only ${ Object.keys(
|
|
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
|
-
|
|
470
|
-
|
|
454
|
+
ctx.getters.listenerManager.addEventListenerCallback({
|
|
455
|
+
callback,
|
|
456
|
+
args: {
|
|
457
|
+
event, params, id
|
|
458
|
+
}
|
|
459
|
+
});
|
|
471
460
|
|
|
472
|
-
|
|
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 (!
|
|
481
|
-
|
|
482
|
-
ctx.dispatch('watch',
|
|
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 =
|
|
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 (!
|
|
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
|
-
|
|
490
|
+
ctx.getters.listenerManager.removeEventListenerCallback({
|
|
491
|
+
event, params, id
|
|
492
|
+
});
|
|
505
493
|
|
|
506
|
-
|
|
507
|
-
|
|
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)
|
|
662
|
-
unwatch({ ...obj, stop: true });
|
|
663
|
-
});
|
|
667
|
+
reset.push(...getters['watchesOfType'](type));
|
|
664
668
|
} else if (getters['watchStarted'](obj)) {
|
|
665
|
-
|
|
669
|
+
reset.push(obj);
|
|
666
670
|
}
|
|
667
|
-
}
|
|
668
|
-
},
|
|
669
671
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
+
color: var(--on-info-banner);
|
|
83
|
+
background: var(--info-badge, var(--info-banner));
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
&.bg-error {
|
|
86
|
-
|
|
87
|
+
color: var(--on-error-banner);
|
|
88
|
+
background: var(--error-badge, var(--error-banner));
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
&.bg-warning {
|
|
90
|
-
|
|
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:
|
|
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
|
-
#
|
|
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)
|