@rancher/shell 0.3.9 → 0.3.11

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 (132) hide show
  1. package/assets/translations/en-us.yaml +19 -24
  2. package/assets/translations/zh-hans.yaml +82 -16
  3. package/chart/istio.vue +11 -11
  4. package/chart/rancher-backup/S3.vue +1 -1
  5. package/components/AsyncButton.vue +2 -2
  6. package/components/ButtonGroup.vue +1 -1
  7. package/components/CompoundStatusBadge.vue +1 -1
  8. package/components/CopyCode.vue +1 -1
  9. package/components/DetailText.vue +1 -0
  10. package/components/DetailTop.vue +1 -1
  11. package/components/ExplorerProjectsNamespaces.vue +3 -3
  12. package/components/GlobalRoleBindings.vue +1 -1
  13. package/components/HarvesterServiceAddOnConfig.vue +2 -117
  14. package/components/ResourceDetail/Masthead.vue +1 -1
  15. package/components/ResourceList/Masthead.vue +0 -6
  16. package/components/ResourceList/ResourceLoadingIndicator.vue +1 -9
  17. package/components/ResourceList/index.vue +7 -6
  18. package/components/ResourceTable.vue +13 -3
  19. package/components/SortableTable/THead.vue +4 -3
  20. package/components/SortableTable/index.vue +3 -3
  21. package/components/Tabbed/Tab.vue +1 -1
  22. package/components/Tabbed/index.vue +1 -1
  23. package/components/Wizard.vue +9 -6
  24. package/components/__tests__/NamespaceFilter.test.ts +26 -7
  25. package/components/auth/RoleDetailEdit.vue +1 -1
  26. package/components/auth/SelectPrincipal.vue +1 -1
  27. package/components/fleet/FleetRepos.vue +1 -1
  28. package/components/form/ArrayList.vue +1 -1
  29. package/components/form/ChangePassword.vue +3 -0
  30. package/components/form/KeyValue.vue +3 -2
  31. package/components/form/Labels.vue +34 -14
  32. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  33. package/components/form/NameNsDescription.vue +1 -1
  34. package/components/form/PlusMinus.vue +2 -2
  35. package/components/form/Probe.vue +1 -1
  36. package/components/form/ProjectMemberEditor.vue +8 -4
  37. package/components/form/ResourceQuota/NamespaceRow.vue +1 -1
  38. package/components/form/ServicePorts.vue +2 -2
  39. package/components/form/Tolerations.vue +30 -3
  40. package/components/form/WorkloadPorts.vue +2 -1
  41. package/components/form/__tests__/KeyValue.test.ts +17 -0
  42. package/components/formatter/ClusterLink.vue +3 -3
  43. package/components/formatter/LiveDate.vue +1 -1
  44. package/components/formatter/PodImages.vue +1 -1
  45. package/components/formatter/RKETemplateName.vue +1 -1
  46. package/components/formatter/Shortened.vue +1 -1
  47. package/components/nav/Header.vue +7 -7
  48. package/components/nav/NamespaceFilter.vue +103 -54
  49. package/config/labels-annotations.js +8 -5
  50. package/config/settings.ts +2 -5
  51. package/config/types.js +6 -4
  52. package/core/plugin-routes.ts +26 -7
  53. package/core/plugins-loader.js +2 -0
  54. package/detail/helm.cattle.io.projecthelmchart.vue +2 -2
  55. package/detail/provisioning.cattle.io.cluster.vue +4 -4
  56. package/edit/cis.cattle.io.clusterscan.vue +1 -1
  57. package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +19 -149
  58. package/edit/logging-flow/index.vue +2 -2
  59. package/edit/logging.banzaicloud.io.output/providers/elasticsearch.vue +12 -0
  60. package/edit/logging.banzaicloud.io.output/providers/opensearch.vue +12 -0
  61. package/edit/management.cattle.io.project.vue +7 -0
  62. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +1 -1
  63. package/edit/monitoring.coreos.com.alertmanagerconfig/routeConfig.vue +2 -2
  64. package/edit/monitoring.coreos.com.prometheusrule/GroupRules.vue +11 -8
  65. package/edit/networking.k8s.io.networkpolicy/PolicyRule.vue +2 -2
  66. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +12 -4
  67. package/edit/networking.k8s.io.networkpolicy/__tests__/PolicyRuleTarget.spec.ts +140 -0
  68. package/edit/networking.k8s.io.networkpolicy/__tests__/utils/mock.json +158 -0
  69. package/edit/networking.k8s.io.networkpolicy/__tests__/utils/selectors.ts +45 -0
  70. package/edit/networking.k8s.io.networkpolicy/index.vue +1 -1
  71. package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +1 -1
  72. package/edit/provisioning.cattle.io.cluster/MachinePool.vue +1 -1
  73. package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +1 -1
  74. package/edit/provisioning.cattle.io.cluster/RegistryMirrors.vue +2 -2
  75. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +143 -169
  76. package/edit/provisioning.cattle.io.cluster/rke2.vue +15 -6
  77. package/edit/resources.cattle.io.restore.vue +2 -2
  78. package/edit/service.vue +22 -3
  79. package/edit/storage.k8s.io.storageclass/index.vue +1 -1
  80. package/edit/token.vue +1 -0
  81. package/edit/workload/Job.vue +2 -2
  82. package/edit/workload/index.vue +1 -1
  83. package/edit/workload/mixins/workload.js +7 -1
  84. package/edit/workload/storage/__tests__/Storage.test.ts +84 -5
  85. package/initialize/index.js +1 -0
  86. package/layouts/default.vue +1 -1
  87. package/mixins/resource-fetch-namespaced.js +19 -27
  88. package/mixins/resource-fetch.js +0 -5
  89. package/models/__tests__/namespace.test.ts +125 -0
  90. package/models/management.cattle.io.project.js +6 -1
  91. package/models/persistentvolume.js +1 -1
  92. package/models/workload.service.js +22 -7
  93. package/package.json +17 -5
  94. package/pages/account/index.vue +3 -0
  95. package/pages/auth/login.vue +46 -49
  96. package/pages/c/_cluster/apps/charts/chart.vue +1 -1
  97. package/pages/c/_cluster/apps/charts/install.vue +42 -51
  98. package/pages/c/_cluster/explorer/index.vue +1 -1
  99. package/pages/c/_cluster/monitoring/index.vue +1 -1
  100. package/pages/c/_cluster/settings/performance.vue +53 -18
  101. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  102. package/pages/c/_cluster/uiplugins/index.vue +16 -5
  103. package/pages/home.vue +1 -1
  104. package/pkg/vue.config.js +1 -0
  105. package/plugins/clean-html-directive.js +1 -1
  106. package/plugins/clean-tooltip-directive.js +33 -0
  107. package/plugins/dashboard-store/actions.js +4 -2
  108. package/plugins/dashboard-store/getters.js +6 -0
  109. package/plugins/dashboard-store/mutations.js +2 -2
  110. package/plugins/plugin.js +6 -1
  111. package/plugins/steve/actions.js +1 -1
  112. package/plugins/steve/getters.js +14 -3
  113. package/plugins/steve/resourceWatcher.js +36 -62
  114. package/plugins/steve/subscribe.js +164 -21
  115. package/plugins/steve/worker/index.js +8 -1
  116. package/plugins/steve/worker/web-worker.advanced.js +26 -8
  117. package/plugins/steve/worker/web-worker.basic.js +23 -4
  118. package/rancher-components/components/Form/Checkbox/Checkbox.vue +2 -2
  119. package/rancher-components/components/Form/Radio/RadioGroup.vue +2 -2
  120. package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +1 -1
  121. package/store/index.js +16 -61
  122. package/store/store-types.js +5 -0
  123. package/store/type-map.js +1 -1
  124. package/types/shell/index.d.ts +23 -7
  125. package/utils/__tests__/create-yaml.test.ts +63 -0
  126. package/utils/array.ts +4 -0
  127. package/utils/create-yaml.js +5 -5
  128. package/utils/namespace-filter.js +17 -5
  129. package/utils/projectAndNamespaceFiltering.utils.ts +62 -0
  130. package/utils/selector.js +6 -5
  131. package/utils/settings.ts +5 -7
  132. package/models/k8s.cni.cncf.io.networkattachmentdefinition.js +0 -93
@@ -5,10 +5,9 @@
5
5
  import Socket, {
6
6
  NO_WATCH,
7
7
  NO_SCHEMA,
8
- EVENT_MESSAGE,
9
8
  EVENT_CONNECTED,
9
+ REVISION_TOO_OLD
10
10
  } from '@shell/utils/socket';
11
- import { addParam } from '@shell/utils/url';
12
11
 
13
12
  export const WATCH_STATUSES = {
14
13
  /**
@@ -64,7 +63,7 @@ export const watchKeyFromMessage = (msg) => {
64
63
  };
65
64
 
66
65
  const {
67
- WATCH_PENDING, WATCH_REQUESTED, WATCHING, STOPPED, REMOVE_PENDING, REQUESTED_REMOVE
66
+ WATCH_PENDING, WATCH_REQUESTED, WATCHING, REMOVE_PENDING, REQUESTED_REMOVE
68
67
  } = WATCH_STATUSES;
69
68
 
70
69
  export default class ResourceWatcher extends Socket {
@@ -110,12 +109,13 @@ export default class ResourceWatcher extends Socket {
110
109
  return !!this.watches?.[watchKey];
111
110
  }
112
111
 
113
- async watch(watchKey, providedResourceVersion, providedResourceVersionTime, providedKeyParts = {}, providedSkipResourceVersion) {
112
+ watch(watchKey, providedResourceVersion, providedResourceVersionTime, providedKeyParts = {}, providedSkipResourceVersion) {
114
113
  const {
115
114
  resourceType: providedResourceType,
116
115
  id: providedId,
117
116
  namespace: providedNamespace,
118
- selector: providedSelector
117
+ selector: providedSelector,
118
+ force: providedForce,
119
119
  } = providedKeyParts;
120
120
 
121
121
  this.trace('watch:', 'requested', watchKey);
@@ -126,8 +126,10 @@ export default class ResourceWatcher extends Socket {
126
126
  return;
127
127
  }
128
128
 
129
- if (this.watches?.[watchKey]?.error) {
130
- this.trace('watch:', 'in error, aborting', watchKey);
129
+ if (!providedForce && this.watches?.[watchKey]?.error) {
130
+ if (this.watches?.[watchKey]?.error.reason !== REVISION_TOO_OLD) {
131
+ this.trace('watch:', 'in error, aborting', watchKey);
132
+ }
131
133
 
132
134
  return;
133
135
  }
@@ -136,7 +138,7 @@ export default class ResourceWatcher extends Socket {
136
138
  const id = providedId || this.watches?.[watchKey]?.id;
137
139
  const namespace = providedNamespace || this.watches?.[watchKey]?.namespace;
138
140
  const selector = providedSelector || this.watches?.[watchKey]?.selector;
139
- let skipResourceVersion = this.watches?.[watchKey]?.skipResourceVersion || providedSkipResourceVersion;
141
+ const skipResourceVersion = this.watches?.[watchKey]?.skipResourceVersion || providedSkipResourceVersion;
140
142
 
141
143
  const watchObject = {
142
144
  resourceType,
@@ -145,47 +147,8 @@ export default class ResourceWatcher extends Socket {
145
147
  selector
146
148
  };
147
149
 
148
- let resourceVersionTime = providedResourceVersionTime || this.watches?.[watchKey]?.resourceVersionTime;
149
- let resourceVersion = providedResourceVersion || this.watches?.[watchKey]?.resourceVersion;
150
-
151
- if (!skipResourceVersion && (!resourceVersion || Date.now() - resourceVersionTime > 300000)) { // 300000ms is 5minutes
152
- this.trace('watch:', 'revision update required', watchKey);
153
-
154
- const resourceUrl = this.baseUrl + resourceType;
155
- const limitedResourceUrl = addParam(resourceUrl, 'limit', 1);
156
- const opt = {
157
- method: 'get',
158
- headers: { accept: 'application/json' },
159
- };
160
-
161
- if (this.csrf) {
162
- opt.headers['x-api-csrf'] = this.csrf;
163
- }
164
-
165
- await fetch(limitedResourceUrl, opt)
166
- .then((res) => {
167
- this.watches[watchKey] = { ...watchObject };
168
- if (!res.ok) {
169
- this.watches[watchKey].error = res.json();
170
- console.warn(`Resource error retrieving resourceVersion`, resourceType, ':', res.json()); // eslint-disable-line no-console
171
- } else {
172
- this.watches[watchKey].error = undefined;
173
- }
174
-
175
- return res.json();
176
- })
177
- .then((res) => {
178
- if (res.revision) {
179
- resourceVersionTime = Date.now();
180
- resourceVersion = res.revision;
181
- } else if (!this.watches[watchKey].error) {
182
- // if there wasn't a revision in the response and there wasn't an error we wrote to the watch then the resource doesn't get a revision and we can skip it on subsequent rewatches
183
- skipResourceVersion = true;
184
- }
185
- });
186
- // When this fails we should re-fetch all resources (aka same as resyncWatch, or we actually call it). #7917
187
- // This would match the old approach
188
- }
150
+ const resourceVersionTime = providedResourceVersionTime || this.watches?.[watchKey]?.resourceVersionTime;
151
+ const resourceVersion = providedResourceVersion || this.watches?.[watchKey]?.resourceVersion;
189
152
 
190
153
  const success = this.send(JSON.stringify({
191
154
  ...watchObject,
@@ -240,16 +203,21 @@ export default class ResourceWatcher extends Socket {
240
203
 
241
204
  if (eventName === 'resource.start' && this.watches?.[watchKey]?.status === WATCH_REQUESTED) {
242
205
  this.watches[watchKey].status = WATCHING;
206
+ delete this.watches[watchKey].error;
243
207
  } else if (eventName === 'resource.stop' && this.watches?.[watchKey]) {
244
- if (this.watches?.[watchKey]?.status === REQUESTED_REMOVE) {
245
- delete this.watches[watchKey];
246
- } else {
247
- this.watches[watchKey].status = STOPPED;
248
- delete this.watches[watchKey].resourceVersion;
249
- delete this.watches[watchKey].resourceVersionTime;
250
- this.watch(watchKey);
251
- this.dispatchEvent(new CustomEvent(EVENT_MESSAGE, { detail: event }));
252
- }
208
+ // Find some way to resolve the correct resourceVersion from within the resourceWatcher until then:
209
+ // reset the watch in the resourceWatcher, we'll handle recovery up the chain. For now
210
+ // dispatch the event to the host process which should have a handler for resource.stop
211
+
212
+ // if (this.watches?.[watchKey]?.status === REQUESTED_REMOVE) {
213
+ this.watches[watchKey] = { error: this.watches[watchKey]?.error };
214
+ // } else {
215
+ // this.watches[watchKey].status = STOPPED;
216
+ // delete this.watches[watchKey].resourceVersion;
217
+ // delete this.watches[watchKey].resourceVersionTime;
218
+ // this.watch(watchKey);
219
+ // this.dispatchEvent(new CustomEvent(EVENT_MESSAGE, { detail: event }));
220
+ // }
253
221
  } else if (eventName === 'resource.error') {
254
222
  const err = data?.error?.toLowerCase();
255
223
 
@@ -262,14 +230,20 @@ export default class ResourceWatcher extends Socket {
262
230
 
263
231
  this.watches[watchKey].error = { type: resourceType, reason: NO_SCHEMA };
264
232
  } else if ( err.includes('too old') ) {
265
- // We don't actually know the gap between the requested revision and the oldest available revision.
266
- // For this case we should re-fetch all resources (aka same as resyncWatch, or we actually call it). #7917
267
- // This would match the old approach
268
233
  delete this.watches[watchKey].resourceVersion;
269
234
  delete this.watches[watchKey].resourceVersionTime;
270
235
  delete this.watches[watchKey].skipResourceVersion;
271
- this.watch(watchKey);
236
+ this.watches[watchKey].error = { type: resourceType, reason: REVISION_TOO_OLD };
237
+ // Needs to match sub resyncWatch params
238
+ this.dispatchEvent(new CustomEvent('resync', {
239
+ detail: {
240
+ data: {
241
+ resourceType, id, namespace, selector
242
+ }
243
+ }
244
+ }));
272
245
  }
246
+ this.trace('_onmessage:', 'new error', this.watches[watchKey].error);
273
247
  }
274
248
 
275
249
  super._onmessage(event);
@@ -9,7 +9,8 @@
9
9
 
10
10
  import { addObject, clear, removeObject } from '@shell/utils/array';
11
11
  import { get } from '@shell/utils/object';
12
- import { SCHEMA } from '@shell/config/types';
12
+ import { SCHEMA, MANAGEMENT } from '@shell/config/types';
13
+ import { SETTING } from '@shell/config/settings';
13
14
  import { CSRF } from '@shell/config/cookies';
14
15
  import { getPerformanceSetting } from '@shell/utils/settings';
15
16
  import Socket, {
@@ -29,16 +30,29 @@ import { DATE_FORMAT, TIME_FORMAT } from '@shell/store/prefs';
29
30
  import { escapeHtml } from '@shell/utils/string';
30
31
  import { keyForSubscribe } from '@shell/plugins/steve/resourceWatcher';
31
32
  import { waitFor } from '@shell/utils/async';
33
+ import { WORKER_MODES } from './worker';
34
+ import pAndNFiltering from '@shell/utils/projectAndNamespaceFiltering.utils';
32
35
 
33
36
  import { BLANK_CLUSTER } from '@shell/store/index.js';
37
+ import { STORE } from '@shell/store/store-types';
34
38
 
35
39
  // minimum length of time a disconnect notification is shown
36
40
  const MINIMUM_TIME_NOTIFIED = 3000;
37
41
 
38
- const waitForManagement = (store) => {
39
- const managementReady = () => store.state?.managementReady;
42
+ const workerQueues = {};
40
43
 
41
- return waitFor(managementReady, 'Management');
44
+ const supportedStores = [STORE.CLUSTER, STORE.RANCHER, STORE.MANAGEMENT];
45
+
46
+ const isWaitingForDestroy = (storeName, store) => {
47
+ return store.$workers[storeName]?.waitingForDestroy && store.$workers[storeName].waitingForDestroy();
48
+ };
49
+
50
+ const waitForSettingsSchema = (storeName, store) => {
51
+ return waitFor(() => isWaitingForDestroy(storeName, store) || !!store.getters['management/byId'](SCHEMA, MANAGEMENT.SETTING));
52
+ };
53
+
54
+ const waitForSettings = (storeName, store) => {
55
+ return waitFor(() => isWaitingForDestroy(storeName, store) || !!store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.UI_PERFORMANCE));
42
56
  };
43
57
 
44
58
  const isAdvancedWorker = (ctx) => {
@@ -46,7 +60,7 @@ const isAdvancedWorker = (ctx) => {
46
60
  const storeName = getters.storeName;
47
61
  const clusterId = rootGetters.clusterId;
48
62
 
49
- if (storeName !== 'cluster' || clusterId === BLANK_CLUSTER) {
63
+ if (!supportedStores.includes(storeName) || (clusterId === BLANK_CLUSTER && storeName === STORE.CLUSTER)) {
50
64
  return false;
51
65
  }
52
66
 
@@ -55,19 +69,56 @@ const isAdvancedWorker = (ctx) => {
55
69
  return perfSetting?.advancedWorker.enabled;
56
70
  };
57
71
 
58
- // We only create a worker for the cluster store
59
72
  export async function createWorker(store, ctx) {
60
73
  const { getters, dispatch } = ctx;
61
74
  const storeName = getters.storeName;
62
75
 
63
76
  store.$workers = store.$workers || {};
64
77
 
65
- if (storeName !== 'cluster') {
78
+ if (!supportedStores.includes(storeName)) {
66
79
  return;
67
80
  }
68
81
 
69
- await waitForManagement(store);
70
- // getting perf setting in a separate constant here because it'll provide other values we'll want later.
82
+ if (!store.$workers[storeName]) {
83
+ // we know we need a worker at this point but we don't know which one so we're creating a mock interface
84
+ // it will simply queue up any messages for the real worker to process when it loads up
85
+ store.$workers[storeName] = {
86
+ postMessage: (msg) => {
87
+ if (Object.keys(msg)?.[0] === 'destroyWorker') {
88
+ // The worker has been destroyed before it's been set up. Flag this so we stop waiting for mgmt settings and then can destroy worker.
89
+ // This can occurr when the user is redirected to the log in page
90
+ // - workers created (but waiting)
91
+ // - logout is called
92
+ // - <store>/unsubscribe is dispatched
93
+ // - wait for worker object to be destroyed <-- requires initial wait to be unblocked
94
+ store.$workers[storeName].mode = WORKER_MODES.DESTROY_MOCK;
95
+
96
+ return;
97
+ }
98
+ if (workerQueues[storeName]) {
99
+ workerQueues[storeName].push(msg);
100
+ } else {
101
+ workerQueues[storeName] = [msg];
102
+ }
103
+ },
104
+ mode: WORKER_MODES.WAITING,
105
+ waitingForDestroy: () => {
106
+ return store.$workers[storeName]?.mode === WORKER_MODES.DESTROY_MOCK;
107
+ },
108
+ destroy: () => {
109
+ // Similar to workerActions.destroyWorker
110
+ delete store.$workers[storeName];
111
+ }
112
+ };
113
+ }
114
+
115
+ await waitForSettingsSchema(storeName, store);
116
+ await waitForSettings(storeName, store);
117
+ if (store.$workers[storeName].waitingForDestroy()) {
118
+ store.$workers[storeName].destroy();
119
+
120
+ return;
121
+ }
71
122
  const advancedWorker = isAdvancedWorker(ctx);
72
123
 
73
124
  const workerActions = {
@@ -81,21 +132,30 @@ export async function createWorker(store, ctx) {
81
132
  }
82
133
  },
83
134
  batchChanges: (batch) => {
84
- dispatch('batchChanges', batch);
135
+ dispatch('batchChanges', namespaceHandler.validateBatchChange(ctx, batch));
85
136
  },
86
137
  dispatch: (msg) => {
87
138
  dispatch(`ws.${ msg.name }`, msg);
88
139
  },
140
+ redispatch: (msg) => {
141
+ /**
142
+ * because we had to queue up some messages prior to loading the worker:
143
+ * the basic worker will need to redispatch some of the queued messages back to the UI thread
144
+ */
145
+ Object.entries(msg).forEach(([action, params]) => {
146
+ dispatch(action, params);
147
+ });
148
+ },
89
149
  [EVENT_CONNECT_ERROR]: (e) => {
90
150
  dispatch('error', e );
91
151
  },
92
152
  [EVENT_DISCONNECT_ERROR]: (e) => {
93
153
  dispatch('error', e );
94
- }
154
+ },
95
155
  };
96
156
 
97
- if (!store.$workers[storeName]) {
98
- const workerMode = advancedWorker ? 'advanced' : 'basic';
157
+ if (!store.$workers[storeName] || store.$workers[storeName].mode === WORKER_MODES.WAITING) {
158
+ const workerMode = advancedWorker ? WORKER_MODES.ADVANCED : WORKER_MODES.BASIC;
99
159
  const worker = store.steveCreateWorker(workerMode);
100
160
 
101
161
  store.$workers[storeName] = worker;
@@ -115,6 +175,12 @@ export async function createWorker(store, ctx) {
115
175
  });
116
176
  };
117
177
  }
178
+
179
+ while (workerQueues[storeName]?.length) {
180
+ const message = workerQueues[storeName].shift();
181
+
182
+ store.$workers[storeName].postMessage(message);
183
+ }
118
184
  }
119
185
 
120
186
  export function equivalentWatch(a, b) {
@@ -140,7 +206,67 @@ export function equivalentWatch(a, b) {
140
206
  return true;
141
207
  }
142
208
 
143
- function queueChange({ getters, state }, { data, revision }, load, label) {
209
+ /**
210
+ * Sockets will not be able to subscribe to more than one namespace. If this is requested we pretend to handle it
211
+ * - Changes to all resources are monitored (no namespace provided in sub)
212
+ * - We ignore any events not from a required namespace (we have the conversion of project --> namespaces already)
213
+ */
214
+ const namespaceHandler = {
215
+ /**
216
+ * Note - namespace can be a list of projects or namespaces
217
+ */
218
+ subscribeNamespace: (namespace) => {
219
+ if (pAndNFiltering.isApplicable({ namespaced: namespace }) && namespace.length) {
220
+ return undefined; // AKA sub to everything
221
+ }
222
+
223
+ return namespace;
224
+ },
225
+
226
+ validChange: ({ getters, rootGetters }, type, data) => {
227
+ const haveNamespace = getters.haveNamespace(type);
228
+
229
+ if (haveNamespace?.length) {
230
+ const namespaces = rootGetters.activeNamespaceCache;
231
+
232
+ if (!namespaces[data.metadata.namespace]) {
233
+ return false;
234
+ }
235
+ }
236
+
237
+ return true;
238
+ },
239
+
240
+ validateBatchChange: ({ getters, rootGetters }, batch) => {
241
+ const namespaces = rootGetters.activeNamespaceCache;
242
+
243
+ Object.entries(batch).forEach(([type, entries]) => {
244
+ const haveNamespace = getters.haveNamespace(type);
245
+
246
+ if (!haveNamespace?.length) {
247
+ return;
248
+ }
249
+
250
+ const schema = getters.schemaFor(type);
251
+
252
+ if (!schema?.attributes?.namespaced) {
253
+ return;
254
+ }
255
+
256
+ Object.keys(entries).forEach((id) => {
257
+ const namespace = id.split('/')[0];
258
+
259
+ if (!namespace || !namespaces[namespace]) {
260
+ delete entries[id];
261
+ }
262
+ });
263
+ });
264
+
265
+ return batch;
266
+ }
267
+ };
268
+
269
+ function queueChange({ getters, state, rootGetters }, { data, revision }, load, label) {
144
270
  const type = getters.normalizeType(data.type);
145
271
 
146
272
  const entry = getters.typeEntry(type);
@@ -153,6 +279,10 @@ function queueChange({ getters, state }, { data, revision }, load, label) {
153
279
 
154
280
  // console.log(`${ label } Event [${ state.config.namespace }]`, data.type, data.id); // eslint-disable-line no-console
155
281
 
282
+ if (!namespaceHandler.validChange({ getters, rootGetters }, type, data)) {
283
+ return;
284
+ }
285
+
156
286
  if ( load ) {
157
287
  state.queue.push({
158
288
  action: 'dispatch',
@@ -212,7 +342,7 @@ const sharedActions = {
212
342
 
213
343
  const url = `${ state.config.baseUrl }/subscribe`;
214
344
  const maxTries = growlsDisabled(rootGetters) ? null : 3;
215
- const connectionMetadata = get(opt, 'metadata');
345
+ const metadata = get(opt, 'metadata');
216
346
 
217
347
  if (isAdvancedWorker(ctx)) {
218
348
  if (!this.$workers[getters.storeName]) {
@@ -222,7 +352,7 @@ const sharedActions = {
222
352
  // if the worker is in advanced mode then it'll contain it's own socket which it calls a 'watcher'
223
353
  this.$workers[getters.storeName].postMessage({
224
354
  createWatcher: {
225
- connectionMetadata,
355
+ metadata,
226
356
  url: `${ state.config.baseUrl }/subscribe`,
227
357
  csrf: this.$cookies.get(CSRF, { parseJSON: false }),
228
358
  maxTries
@@ -231,7 +361,7 @@ const sharedActions = {
231
361
  } else if ( socket ) {
232
362
  socket.setAutoReconnect(true);
233
363
  socket.setUrl(url);
234
- socket.connect(connectionMetadata);
364
+ socket.connect(metadata);
235
365
  } else {
236
366
  socket = new Socket(`${ state.config.baseUrl }/subscribe`, true, null, null, maxTries);
237
367
 
@@ -263,7 +393,7 @@ const sharedActions = {
263
393
  }
264
394
  }
265
395
  });
266
- socket.connect(connectionMetadata);
396
+ socket.connect(metadata);
267
397
  }
268
398
  },
269
399
 
@@ -297,6 +427,7 @@ const sharedActions = {
297
427
  type, selector, id, revision, namespace, stop, force
298
428
  } = params;
299
429
 
430
+ namespace = namespaceHandler.subscribeNamespace(namespace);
300
431
  type = getters.normalizeType(type);
301
432
 
302
433
  if (rootGetters['type-map/isSpoofed'](type)) {
@@ -356,7 +487,7 @@ const sharedActions = {
356
487
 
357
488
  const worker = this.$workers?.[getters.storeName] || {};
358
489
 
359
- if (worker.mode === 'advanced') {
490
+ if (worker.mode === WORKER_MODES.ADVANCED || worker.mode === WORKER_MODES.WAITING) {
360
491
  if ( force ) {
361
492
  msg.force = true;
362
493
  }
@@ -375,6 +506,8 @@ const sharedActions = {
375
506
  const { commit, getters, dispatch } = ctx;
376
507
 
377
508
  if (getters['schemaFor'](type)) {
509
+ namespace = namespaceHandler.subscribeNamespace(namespace);
510
+
378
511
  const obj = {
379
512
  type,
380
513
  id,
@@ -760,8 +893,18 @@ const defaultActions = {
760
893
  }
761
894
 
762
895
  // If we're trying to watch this event, attempt to re-watch
763
- if ( getters['schemaFor'](type) && getters['watchStarted'](obj) ) {
764
- commit('setWatchStopped', obj);
896
+ //
897
+ // To make life easier in the advanced worker `resource.stop` --> `watch` is handled here (basically for access to getters.nextResourceVersion)
898
+ // This means the concept of resource sub watch state needs massaging
899
+ const advancedWorker = msg.advancedWorker;
900
+ const localState = !advancedWorker;
901
+ const watchStarted = localState ? getters['watchStarted'](obj) : advancedWorker;
902
+
903
+ if ( getters['schemaFor'](type) && watchStarted) {
904
+ if (localState) {
905
+ commit('setWatchStopped', obj);
906
+ }
907
+
765
908
  dispatch('watch', obj);
766
909
  }
767
910
  },
@@ -3,10 +3,17 @@ import basicWorkerConstructor from '@shell/plugins/steve/worker/web-worker.basic
3
3
  // eslint-disable-next-line no-unused-vars
4
4
  import advancedWorkerConstructor from '@shell/plugins/steve/worker/web-worker.advanced.js';
5
5
 
6
+ export const WORKER_MODES = {
7
+ WAITING: 'waiting',
8
+ DESTROY_MOCK: 'destroy',
9
+ BASIC: 'basic',
10
+ ADVANCED: 'advanced'
11
+ };
12
+
6
13
  export default function storeWorker(mode, options = {}, closures = {}) {
7
14
  let worker;
8
15
 
9
- if (mode === 'advanced') {
16
+ if (mode === WORKER_MODES.ADVANCED) {
10
17
  worker = new advancedWorkerConstructor();
11
18
  } else {
12
19
  worker = new basicWorkerConstructor();
@@ -93,12 +93,12 @@ const workerActions = {
93
93
  }
94
94
  caches[SCHEMA].load(collection);
95
95
  },
96
- createWatcher: (metadata) => {
97
- trace('createWatcher', metadata);
96
+ createWatcher: (opt) => {
97
+ trace('createWatcher', opt);
98
98
 
99
99
  const {
100
- connectionMetadata, maxTries, url, csrf
101
- } = metadata;
100
+ metadata, maxTries, url, csrf
101
+ } = opt;
102
102
 
103
103
  if (!state.watcher) {
104
104
  state.watcher = new ResourceWatcher(url, true, null, null, maxTries, csrf);
@@ -119,6 +119,10 @@ const workerActions = {
119
119
  }
120
120
  });
121
121
 
122
+ state.watcher.addEventListener('resync', (e) => {
123
+ self.postMessage({ redispatch: { resyncWatch: e.detail.data } });
124
+ });
125
+
122
126
  state.watcher.addEventListener(EVENT_CONNECT_ERROR, (e) => {
123
127
  handleConnectionError(EVENT_CONNECT_ERROR, e, state.watcher);
124
128
  });
@@ -129,7 +133,7 @@ const workerActions = {
129
133
 
130
134
  state.watcher.setDebug(state.debugWorker);
131
135
 
132
- state.watcher.connect(connectionMetadata);
136
+ state.watcher.connect(metadata);
133
137
 
134
138
  // Flush the workerQueue
135
139
  while (state.workerQueue.length > 0) {
@@ -183,7 +187,8 @@ const workerActions = {
183
187
  resourceType,
184
188
  id,
185
189
  namespace,
186
- selector
190
+ selector,
191
+ force: msg.force,
187
192
  };
188
193
 
189
194
  state.watcher.watch(watchKey, resourceVersion, resourceVersionTime, watchObject, skipResourceVersion);
@@ -270,10 +275,23 @@ const resourceWatcherActions = {
270
275
  }
271
276
  },
272
277
  'resource.stop': (msg) => {
273
- // State is handled in the resourceWatcher, no need to bubble out to UI thread
278
+ trace('resource.stop', msg);
279
+
280
+ // State is handled in the resourceWatcher....
274
281
  const watchKey = watchKeyFromMessage(msg);
275
282
 
276
283
  removeFromWorkerQueue(watchKey);
284
+
285
+ // ... however we still want to bubble out to UI thread
286
+ // We'll save some hassle and ignore any resource.stop bubble if we're in error. the only thing that will clear that is a resync
287
+ if (!state.watcher?.watches[watchKey]?.error) {
288
+ // See comment in resourceWatcher 'resource.stop' handler, until we can resolve the resourceVersion within the resourceWatcher
289
+ // internally, we'll want to bubble this out to the UI thread. When that's resolved this won't be needed
290
+ resourceWatcherActions.dispatch({
291
+ ...msg,
292
+ advancedWorker: true,
293
+ });
294
+ }
277
295
  },
278
296
  'resource.error': (msg) => {
279
297
  // State is handled in the resourceWatcher, no need to bubble out to UI thread
@@ -287,7 +305,7 @@ const resourceWatcherActions = {
287
305
  /**
288
306
  * Covers message from UI Thread to Worker
289
307
  */
290
- onmessage = (e) => {
308
+ self.onmessage = (e) => {
291
309
  /* on the off chance there's more than key in the message, we handle them in the order that they "keys" method provides which is
292
310
  // good enough for now considering that we never send more than one message action at a time right now */
293
311
  const messageActions = Object.keys(e?.data);
@@ -6,7 +6,7 @@ const SCHEMA_FLUSH_TIMEOUT = 2500;
6
6
 
7
7
  const state = {
8
8
  store: '', // Store name
9
- flushTimer: undefined, // Timer to flush the schema chaneg queue
9
+ flushTimer: undefined, // Timer to flush the schema change queue
10
10
  queue: [], // Schema change queue
11
11
  schemas: {} // Map of schema id to hash to track when a schema actually changes
12
12
  };
@@ -42,6 +42,25 @@ function load(data) {
42
42
  self.postMessage({ load: data });
43
43
  }
44
44
 
45
+ // used for dispatching a function in the worker, primarily for redirecting messages intended for the advanced worker back to the UI thread
46
+ function redispatch(msg) {
47
+ self.postMessage({ redispatch: msg });
48
+ }
49
+
50
+ /**
51
+ * These actions aren't applicable to the basic worker, so bounce back to ui thread
52
+ *
53
+ * These are called when a queue of actions is flushed. Queue is populated from requests made before we know if worker is basic or advanced.
54
+ */
55
+ const advancedWorkerActions = {
56
+ watch: (msg) => {
57
+ redispatch({ send: msg });
58
+ },
59
+ createWatcher: (msg) => {
60
+ redispatch({ subscribe: msg });
61
+ }
62
+ };
63
+
45
64
  const workerActions = {
46
65
  onmessage: (e) => {
47
66
  /* on the off chance there's more than key in the message, we handle them in the order that they "keys" method provides which is
@@ -75,7 +94,6 @@ const workerActions = {
75
94
 
76
95
  state.schemas[schema.id] = hashObj(schema);
77
96
  });
78
- // console.log(JSON.parse(JSON.stringify(state.resources.schemas)));
79
97
  },
80
98
 
81
99
  // Called when schema is updated
@@ -91,7 +109,8 @@ const workerActions = {
91
109
 
92
110
  // Delete the schema from the map, so if it comes back we don't ignore it if the hash is the same
93
111
  delete state.schemas[id];
94
- }
112
+ },
113
+ ...advancedWorkerActions
95
114
  };
96
115
 
97
- onmessage = workerActions.onmessage; // bind everything to the worker's onmessage handler via the workerAction
116
+ self.onmessage = workerActions.onmessage; // bind everything to the worker's onmessage handler via the workerAction
@@ -251,12 +251,12 @@ export default Vue.extend({
251
251
  <template v-else-if="label">{{ label }}</template>
252
252
  <i
253
253
  v-if="tooltipKey"
254
- v-tooltip="t(tooltipKey)"
254
+ v-clean-tooltip="t(tooltipKey)"
255
255
  class="checkbox-info icon icon-info icon-lg"
256
256
  />
257
257
  <i
258
258
  v-else-if="tooltip"
259
- v-tooltip="tooltip"
259
+ v-clean-tooltip="tooltip"
260
260
  class="checkbox-info icon icon-info icon-lg"
261
261
  />
262
262
  </slot>
@@ -184,12 +184,12 @@ export default Vue.extend({
184
184
  </template>
185
185
  <i
186
186
  v-if="tooltipKey"
187
- v-tooltip="t(tooltipKey)"
187
+ v-clean-tooltip="t(tooltipKey)"
188
188
  class="icon icon-info icon-lg"
189
189
  />
190
190
  <i
191
191
  v-else-if="tooltip"
192
- v-tooltip="tooltip"
192
+ v-clean-tooltip="tooltip"
193
193
  class="icon icon-info icon-lg"
194
194
  />
195
195
  </h3>
@@ -44,7 +44,7 @@ export default Vue.extend({
44
44
  >
45
45
  <template v-if="hover">
46
46
  <i
47
- v-tooltip="value.content ? { ...{content: value.content, classes: [`tooltip-${status}`]}, ...value } : value"
47
+ v-clean-tooltip="value.content ? { ...{content: value.content, classes: [`tooltip-${status}`]}, ...value } : value"
48
48
  :class="{'hover':!value, [iconClass]: true}"
49
49
  class="icon status-icon"
50
50
  />