@rancher/shell 0.3.9 → 0.3.10

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 (127) 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/DetailTop.vue +1 -1
  10. package/components/ExplorerProjectsNamespaces.vue +3 -3
  11. package/components/GlobalRoleBindings.vue +1 -1
  12. package/components/HarvesterServiceAddOnConfig.vue +2 -117
  13. package/components/ResourceDetail/Masthead.vue +1 -1
  14. package/components/ResourceList/Masthead.vue +0 -6
  15. package/components/ResourceList/ResourceLoadingIndicator.vue +1 -9
  16. package/components/ResourceList/index.vue +7 -6
  17. package/components/ResourceTable.vue +13 -3
  18. package/components/SortableTable/THead.vue +3 -3
  19. package/components/SortableTable/index.vue +3 -3
  20. package/components/Tabbed/Tab.vue +1 -1
  21. package/components/Tabbed/index.vue +1 -1
  22. package/components/Wizard.vue +9 -6
  23. package/components/__tests__/NamespaceFilter.test.ts +26 -7
  24. package/components/auth/RoleDetailEdit.vue +1 -1
  25. package/components/auth/SelectPrincipal.vue +1 -1
  26. package/components/fleet/FleetRepos.vue +1 -1
  27. package/components/form/ArrayList.vue +1 -1
  28. package/components/form/KeyValue.vue +3 -2
  29. package/components/form/Labels.vue +34 -14
  30. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  31. package/components/form/NameNsDescription.vue +1 -1
  32. package/components/form/PlusMinus.vue +2 -2
  33. package/components/form/Probe.vue +1 -1
  34. package/components/form/ProjectMemberEditor.vue +8 -4
  35. package/components/form/ResourceQuota/NamespaceRow.vue +1 -1
  36. package/components/form/ServicePorts.vue +2 -2
  37. package/components/form/Tolerations.vue +30 -3
  38. package/components/form/WorkloadPorts.vue +2 -1
  39. package/components/form/__tests__/KeyValue.test.ts +17 -0
  40. package/components/formatter/ClusterLink.vue +3 -3
  41. package/components/formatter/LiveDate.vue +1 -1
  42. package/components/formatter/PodImages.vue +1 -1
  43. package/components/formatter/RKETemplateName.vue +1 -1
  44. package/components/formatter/Shortened.vue +1 -1
  45. package/components/nav/Header.vue +7 -7
  46. package/components/nav/NamespaceFilter.vue +103 -54
  47. package/config/labels-annotations.js +8 -5
  48. package/config/settings.ts +2 -5
  49. package/config/types.js +6 -4
  50. package/core/plugin-routes.ts +26 -7
  51. package/core/plugins-loader.js +2 -0
  52. package/detail/provisioning.cattle.io.cluster.vue +4 -4
  53. package/edit/cis.cattle.io.clusterscan.vue +1 -1
  54. package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +19 -149
  55. package/edit/logging-flow/index.vue +2 -2
  56. package/edit/logging.banzaicloud.io.output/providers/elasticsearch.vue +12 -0
  57. package/edit/logging.banzaicloud.io.output/providers/opensearch.vue +12 -0
  58. package/edit/management.cattle.io.project.vue +7 -0
  59. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +1 -1
  60. package/edit/monitoring.coreos.com.alertmanagerconfig/routeConfig.vue +2 -2
  61. package/edit/monitoring.coreos.com.prometheusrule/GroupRules.vue +11 -8
  62. package/edit/networking.k8s.io.networkpolicy/PolicyRule.vue +2 -2
  63. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +12 -4
  64. package/edit/networking.k8s.io.networkpolicy/__tests__/PolicyRuleTarget.spec.ts +140 -0
  65. package/edit/networking.k8s.io.networkpolicy/__tests__/utils/mock.json +158 -0
  66. package/edit/networking.k8s.io.networkpolicy/__tests__/utils/selectors.ts +45 -0
  67. package/edit/networking.k8s.io.networkpolicy/index.vue +1 -1
  68. package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +1 -1
  69. package/edit/provisioning.cattle.io.cluster/MachinePool.vue +1 -1
  70. package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +1 -1
  71. package/edit/provisioning.cattle.io.cluster/RegistryMirrors.vue +2 -2
  72. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +143 -169
  73. package/edit/provisioning.cattle.io.cluster/rke2.vue +15 -6
  74. package/edit/resources.cattle.io.restore.vue +2 -2
  75. package/edit/service.vue +22 -3
  76. package/edit/storage.k8s.io.storageclass/index.vue +1 -1
  77. package/edit/workload/Job.vue +2 -2
  78. package/edit/workload/index.vue +1 -1
  79. package/edit/workload/mixins/workload.js +7 -1
  80. package/edit/workload/storage/__tests__/Storage.test.ts +84 -5
  81. package/initialize/index.js +1 -0
  82. package/layouts/default.vue +1 -1
  83. package/mixins/resource-fetch-namespaced.js +19 -27
  84. package/mixins/resource-fetch.js +0 -5
  85. package/models/__tests__/namespace.test.ts +125 -0
  86. package/models/management.cattle.io.project.js +6 -1
  87. package/models/persistentvolume.js +1 -1
  88. package/models/workload.service.js +22 -7
  89. package/package.json +17 -5
  90. package/pages/auth/login.vue +46 -49
  91. package/pages/c/_cluster/apps/charts/chart.vue +1 -1
  92. package/pages/c/_cluster/apps/charts/install.vue +42 -51
  93. package/pages/c/_cluster/explorer/index.vue +1 -1
  94. package/pages/c/_cluster/monitoring/index.vue +1 -1
  95. package/pages/c/_cluster/settings/performance.vue +53 -18
  96. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  97. package/pages/c/_cluster/uiplugins/index.vue +16 -5
  98. package/pages/home.vue +1 -1
  99. package/pkg/vue.config.js +1 -0
  100. package/plugins/clean-html-directive.js +1 -1
  101. package/plugins/clean-tooltip-directive.js +33 -0
  102. package/plugins/dashboard-store/actions.js +4 -2
  103. package/plugins/dashboard-store/getters.js +6 -0
  104. package/plugins/dashboard-store/mutations.js +2 -2
  105. package/plugins/plugin.js +6 -1
  106. package/plugins/steve/actions.js +1 -1
  107. package/plugins/steve/getters.js +14 -3
  108. package/plugins/steve/resourceWatcher.js +36 -62
  109. package/plugins/steve/subscribe.js +137 -21
  110. package/plugins/steve/worker/index.js +7 -1
  111. package/plugins/steve/worker/web-worker.advanced.js +26 -8
  112. package/plugins/steve/worker/web-worker.basic.js +23 -4
  113. package/rancher-components/components/Form/Checkbox/Checkbox.vue +2 -2
  114. package/rancher-components/components/Form/Radio/RadioGroup.vue +2 -2
  115. package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +1 -1
  116. package/store/index.js +16 -61
  117. package/store/store-types.js +5 -0
  118. package/store/type-map.js +1 -1
  119. package/types/shell/index.d.ts +23 -7
  120. package/utils/__tests__/create-yaml.test.ts +63 -0
  121. package/utils/array.ts +4 -0
  122. package/utils/create-yaml.js +5 -5
  123. package/utils/namespace-filter.js +17 -5
  124. package/utils/projectAndNamespaceFiltering.utils.ts +62 -0
  125. package/utils/selector.js +6 -5
  126. package/utils/settings.ts +5 -7
  127. 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,25 @@ 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 waitForSettingsSchema = (store) => {
47
+ return waitFor(() => !!store.getters['management/byId'](SCHEMA, MANAGEMENT.SETTING));
48
+ };
49
+
50
+ const waitForSettings = (store) => {
51
+ return waitFor(() => !!store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.UI_PERFORMANCE));
42
52
  };
43
53
 
44
54
  const isAdvancedWorker = (ctx) => {
@@ -46,7 +56,7 @@ const isAdvancedWorker = (ctx) => {
46
56
  const storeName = getters.storeName;
47
57
  const clusterId = rootGetters.clusterId;
48
58
 
49
- if (storeName !== 'cluster' || clusterId === BLANK_CLUSTER) {
59
+ if (!supportedStores.includes(storeName) || (clusterId === BLANK_CLUSTER && storeName === STORE.CLUSTER)) {
50
60
  return false;
51
61
  }
52
62
 
@@ -55,19 +65,33 @@ const isAdvancedWorker = (ctx) => {
55
65
  return perfSetting?.advancedWorker.enabled;
56
66
  };
57
67
 
58
- // We only create a worker for the cluster store
59
68
  export async function createWorker(store, ctx) {
60
69
  const { getters, dispatch } = ctx;
61
70
  const storeName = getters.storeName;
62
71
 
63
72
  store.$workers = store.$workers || {};
64
73
 
65
- if (storeName !== 'cluster') {
74
+ if (!supportedStores.includes(storeName)) {
66
75
  return;
67
76
  }
68
77
 
69
- await waitForManagement(store);
70
- // getting perf setting in a separate constant here because it'll provide other values we'll want later.
78
+ if (!store.$workers[storeName]) {
79
+ // we know we need a worker at this point but we don't know which one so we're creating a mock interface
80
+ // it will simply queue up any messages for the real worker to process when it loads up
81
+ store.$workers[storeName] = {
82
+ postMessage: (msg) => {
83
+ if (workerQueues[storeName]) {
84
+ workerQueues[storeName].push(msg);
85
+ } else {
86
+ workerQueues[storeName] = [msg];
87
+ }
88
+ },
89
+ mode: WORKER_MODES.WAITING
90
+ };
91
+ }
92
+
93
+ await waitForSettingsSchema(store);
94
+ await waitForSettings(store);
71
95
  const advancedWorker = isAdvancedWorker(ctx);
72
96
 
73
97
  const workerActions = {
@@ -81,21 +105,30 @@ export async function createWorker(store, ctx) {
81
105
  }
82
106
  },
83
107
  batchChanges: (batch) => {
84
- dispatch('batchChanges', batch);
108
+ dispatch('batchChanges', namespaceHandler.validateBatchChange(ctx, batch));
85
109
  },
86
110
  dispatch: (msg) => {
87
111
  dispatch(`ws.${ msg.name }`, msg);
88
112
  },
113
+ redispatch: (msg) => {
114
+ /**
115
+ * because we had to queue up some messages prior to loading the worker:
116
+ * the basic worker will need to redispatch some of the queued messages back to the UI thread
117
+ */
118
+ Object.entries(msg).forEach(([action, params]) => {
119
+ dispatch(action, params);
120
+ });
121
+ },
89
122
  [EVENT_CONNECT_ERROR]: (e) => {
90
123
  dispatch('error', e );
91
124
  },
92
125
  [EVENT_DISCONNECT_ERROR]: (e) => {
93
126
  dispatch('error', e );
94
- }
127
+ },
95
128
  };
96
129
 
97
- if (!store.$workers[storeName]) {
98
- const workerMode = advancedWorker ? 'advanced' : 'basic';
130
+ if (!store.$workers[storeName] || store.$workers[storeName].mode === WORKER_MODES.WAITING) {
131
+ const workerMode = advancedWorker ? WORKER_MODES.ADVANCED : WORKER_MODES.BASIC;
99
132
  const worker = store.steveCreateWorker(workerMode);
100
133
 
101
134
  store.$workers[storeName] = worker;
@@ -115,6 +148,12 @@ export async function createWorker(store, ctx) {
115
148
  });
116
149
  };
117
150
  }
151
+
152
+ while (workerQueues[storeName]?.length) {
153
+ const message = workerQueues[storeName].shift();
154
+
155
+ store.$workers[storeName].postMessage(message);
156
+ }
118
157
  }
119
158
 
120
159
  export function equivalentWatch(a, b) {
@@ -140,7 +179,67 @@ export function equivalentWatch(a, b) {
140
179
  return true;
141
180
  }
142
181
 
143
- function queueChange({ getters, state }, { data, revision }, load, label) {
182
+ /**
183
+ * Sockets will not be able to subscribe to more than one namespace. If this is requested we pretend to handle it
184
+ * - Changes to all resources are monitored (no namespace provided in sub)
185
+ * - We ignore any events not from a required namespace (we have the conversion of project --> namespaces already)
186
+ */
187
+ const namespaceHandler = {
188
+ /**
189
+ * Note - namespace can be a list of projects or namespaces
190
+ */
191
+ subscribeNamespace: (namespace) => {
192
+ if (pAndNFiltering.isApplicable({ namespaced: namespace }) && namespace.length) {
193
+ return undefined; // AKA sub to everything
194
+ }
195
+
196
+ return namespace;
197
+ },
198
+
199
+ validChange: ({ getters, rootGetters }, type, data) => {
200
+ const haveNamespace = getters.haveNamespace(type);
201
+
202
+ if (haveNamespace?.length) {
203
+ const namespaces = rootGetters.activeNamespaceCache;
204
+
205
+ if (!namespaces[data.metadata.namespace]) {
206
+ return false;
207
+ }
208
+ }
209
+
210
+ return true;
211
+ },
212
+
213
+ validateBatchChange: ({ getters, rootGetters }, batch) => {
214
+ const namespaces = rootGetters.activeNamespaceCache;
215
+
216
+ Object.entries(batch).forEach(([type, entries]) => {
217
+ const haveNamespace = getters.haveNamespace(type);
218
+
219
+ if (!haveNamespace?.length) {
220
+ return;
221
+ }
222
+
223
+ const schema = getters.schemaFor(type);
224
+
225
+ if (!schema?.attributes?.namespaced) {
226
+ return;
227
+ }
228
+
229
+ Object.keys(entries).forEach((id) => {
230
+ const namespace = id.split('/')[0];
231
+
232
+ if (!namespace || !namespaces[namespace]) {
233
+ delete entries[id];
234
+ }
235
+ });
236
+ });
237
+
238
+ return batch;
239
+ }
240
+ };
241
+
242
+ function queueChange({ getters, state, rootGetters }, { data, revision }, load, label) {
144
243
  const type = getters.normalizeType(data.type);
145
244
 
146
245
  const entry = getters.typeEntry(type);
@@ -153,6 +252,10 @@ function queueChange({ getters, state }, { data, revision }, load, label) {
153
252
 
154
253
  // console.log(`${ label } Event [${ state.config.namespace }]`, data.type, data.id); // eslint-disable-line no-console
155
254
 
255
+ if (!namespaceHandler.validChange({ getters, rootGetters }, type, data)) {
256
+ return;
257
+ }
258
+
156
259
  if ( load ) {
157
260
  state.queue.push({
158
261
  action: 'dispatch',
@@ -212,7 +315,7 @@ const sharedActions = {
212
315
 
213
316
  const url = `${ state.config.baseUrl }/subscribe`;
214
317
  const maxTries = growlsDisabled(rootGetters) ? null : 3;
215
- const connectionMetadata = get(opt, 'metadata');
318
+ const metadata = get(opt, 'metadata');
216
319
 
217
320
  if (isAdvancedWorker(ctx)) {
218
321
  if (!this.$workers[getters.storeName]) {
@@ -222,7 +325,7 @@ const sharedActions = {
222
325
  // if the worker is in advanced mode then it'll contain it's own socket which it calls a 'watcher'
223
326
  this.$workers[getters.storeName].postMessage({
224
327
  createWatcher: {
225
- connectionMetadata,
328
+ metadata,
226
329
  url: `${ state.config.baseUrl }/subscribe`,
227
330
  csrf: this.$cookies.get(CSRF, { parseJSON: false }),
228
331
  maxTries
@@ -231,7 +334,7 @@ const sharedActions = {
231
334
  } else if ( socket ) {
232
335
  socket.setAutoReconnect(true);
233
336
  socket.setUrl(url);
234
- socket.connect(connectionMetadata);
337
+ socket.connect(metadata);
235
338
  } else {
236
339
  socket = new Socket(`${ state.config.baseUrl }/subscribe`, true, null, null, maxTries);
237
340
 
@@ -263,7 +366,7 @@ const sharedActions = {
263
366
  }
264
367
  }
265
368
  });
266
- socket.connect(connectionMetadata);
369
+ socket.connect(metadata);
267
370
  }
268
371
  },
269
372
 
@@ -297,6 +400,7 @@ const sharedActions = {
297
400
  type, selector, id, revision, namespace, stop, force
298
401
  } = params;
299
402
 
403
+ namespace = namespaceHandler.subscribeNamespace(namespace);
300
404
  type = getters.normalizeType(type);
301
405
 
302
406
  if (rootGetters['type-map/isSpoofed'](type)) {
@@ -356,7 +460,7 @@ const sharedActions = {
356
460
 
357
461
  const worker = this.$workers?.[getters.storeName] || {};
358
462
 
359
- if (worker.mode === 'advanced') {
463
+ if (worker.mode === WORKER_MODES.ADVANCED || worker.mode === WORKER_MODES.WAITING) {
360
464
  if ( force ) {
361
465
  msg.force = true;
362
466
  }
@@ -375,6 +479,8 @@ const sharedActions = {
375
479
  const { commit, getters, dispatch } = ctx;
376
480
 
377
481
  if (getters['schemaFor'](type)) {
482
+ namespace = namespaceHandler.subscribeNamespace(namespace);
483
+
378
484
  const obj = {
379
485
  type,
380
486
  id,
@@ -760,8 +866,18 @@ const defaultActions = {
760
866
  }
761
867
 
762
868
  // If we're trying to watch this event, attempt to re-watch
763
- if ( getters['schemaFor'](type) && getters['watchStarted'](obj) ) {
764
- commit('setWatchStopped', obj);
869
+ //
870
+ // To make life easier in the advanced worker `resource.stop` --> `watch` is handled here (basically for access to getters.nextResourceVersion)
871
+ // This means the concept of resource sub watch state needs massaging
872
+ const advancedWorker = msg.advancedWorker;
873
+ const localState = !advancedWorker;
874
+ const watchStarted = localState ? getters['watchStarted'](obj) : advancedWorker;
875
+
876
+ if ( getters['schemaFor'](type) && watchStarted) {
877
+ if (localState) {
878
+ commit('setWatchStopped', obj);
879
+ }
880
+
765
881
  dispatch('watch', obj);
766
882
  }
767
883
  },
@@ -3,10 +3,16 @@ 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
+ BASIC: 'basic',
9
+ ADVANCED: 'advanced'
10
+ };
11
+
6
12
  export default function storeWorker(mode, options = {}, closures = {}) {
7
13
  let worker;
8
14
 
9
- if (mode === 'advanced') {
15
+ if (mode === WORKER_MODES.ADVANCED) {
10
16
  worker = new advancedWorkerConstructor();
11
17
  } else {
12
18
  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
  />