@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.
- package/assets/translations/en-us.yaml +19 -24
- package/assets/translations/zh-hans.yaml +82 -16
- package/chart/istio.vue +11 -11
- package/chart/rancher-backup/S3.vue +1 -1
- package/components/AsyncButton.vue +2 -2
- package/components/ButtonGroup.vue +1 -1
- package/components/CompoundStatusBadge.vue +1 -1
- package/components/CopyCode.vue +1 -1
- package/components/DetailText.vue +1 -0
- package/components/DetailTop.vue +1 -1
- package/components/ExplorerProjectsNamespaces.vue +3 -3
- package/components/GlobalRoleBindings.vue +1 -1
- package/components/HarvesterServiceAddOnConfig.vue +2 -117
- package/components/ResourceDetail/Masthead.vue +1 -1
- package/components/ResourceList/Masthead.vue +0 -6
- package/components/ResourceList/ResourceLoadingIndicator.vue +1 -9
- package/components/ResourceList/index.vue +7 -6
- package/components/ResourceTable.vue +13 -3
- package/components/SortableTable/THead.vue +4 -3
- package/components/SortableTable/index.vue +3 -3
- package/components/Tabbed/Tab.vue +1 -1
- package/components/Tabbed/index.vue +1 -1
- package/components/Wizard.vue +9 -6
- package/components/__tests__/NamespaceFilter.test.ts +26 -7
- package/components/auth/RoleDetailEdit.vue +1 -1
- package/components/auth/SelectPrincipal.vue +1 -1
- package/components/fleet/FleetRepos.vue +1 -1
- package/components/form/ArrayList.vue +1 -1
- package/components/form/ChangePassword.vue +3 -0
- package/components/form/KeyValue.vue +3 -2
- package/components/form/Labels.vue +34 -14
- package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
- package/components/form/NameNsDescription.vue +1 -1
- package/components/form/PlusMinus.vue +2 -2
- package/components/form/Probe.vue +1 -1
- package/components/form/ProjectMemberEditor.vue +8 -4
- package/components/form/ResourceQuota/NamespaceRow.vue +1 -1
- package/components/form/ServicePorts.vue +2 -2
- package/components/form/Tolerations.vue +30 -3
- package/components/form/WorkloadPorts.vue +2 -1
- package/components/form/__tests__/KeyValue.test.ts +17 -0
- package/components/formatter/ClusterLink.vue +3 -3
- package/components/formatter/LiveDate.vue +1 -1
- package/components/formatter/PodImages.vue +1 -1
- package/components/formatter/RKETemplateName.vue +1 -1
- package/components/formatter/Shortened.vue +1 -1
- package/components/nav/Header.vue +7 -7
- package/components/nav/NamespaceFilter.vue +103 -54
- package/config/labels-annotations.js +8 -5
- package/config/settings.ts +2 -5
- package/config/types.js +6 -4
- package/core/plugin-routes.ts +26 -7
- package/core/plugins-loader.js +2 -0
- package/detail/helm.cattle.io.projecthelmchart.vue +2 -2
- package/detail/provisioning.cattle.io.cluster.vue +4 -4
- package/edit/cis.cattle.io.clusterscan.vue +1 -1
- package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +19 -149
- package/edit/logging-flow/index.vue +2 -2
- package/edit/logging.banzaicloud.io.output/providers/elasticsearch.vue +12 -0
- package/edit/logging.banzaicloud.io.output/providers/opensearch.vue +12 -0
- package/edit/management.cattle.io.project.vue +7 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +1 -1
- package/edit/monitoring.coreos.com.alertmanagerconfig/routeConfig.vue +2 -2
- package/edit/monitoring.coreos.com.prometheusrule/GroupRules.vue +11 -8
- package/edit/networking.k8s.io.networkpolicy/PolicyRule.vue +2 -2
- package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +12 -4
- package/edit/networking.k8s.io.networkpolicy/__tests__/PolicyRuleTarget.spec.ts +140 -0
- package/edit/networking.k8s.io.networkpolicy/__tests__/utils/mock.json +158 -0
- package/edit/networking.k8s.io.networkpolicy/__tests__/utils/selectors.ts +45 -0
- package/edit/networking.k8s.io.networkpolicy/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/MachinePool.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/RegistryMirrors.vue +2 -2
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +143 -169
- package/edit/provisioning.cattle.io.cluster/rke2.vue +15 -6
- package/edit/resources.cattle.io.restore.vue +2 -2
- package/edit/service.vue +22 -3
- package/edit/storage.k8s.io.storageclass/index.vue +1 -1
- package/edit/token.vue +1 -0
- package/edit/workload/Job.vue +2 -2
- package/edit/workload/index.vue +1 -1
- package/edit/workload/mixins/workload.js +7 -1
- package/edit/workload/storage/__tests__/Storage.test.ts +84 -5
- package/initialize/index.js +1 -0
- package/layouts/default.vue +1 -1
- package/mixins/resource-fetch-namespaced.js +19 -27
- package/mixins/resource-fetch.js +0 -5
- package/models/__tests__/namespace.test.ts +125 -0
- package/models/management.cattle.io.project.js +6 -1
- package/models/persistentvolume.js +1 -1
- package/models/workload.service.js +22 -7
- package/package.json +17 -5
- package/pages/account/index.vue +3 -0
- package/pages/auth/login.vue +46 -49
- package/pages/c/_cluster/apps/charts/chart.vue +1 -1
- package/pages/c/_cluster/apps/charts/install.vue +42 -51
- package/pages/c/_cluster/explorer/index.vue +1 -1
- package/pages/c/_cluster/monitoring/index.vue +1 -1
- package/pages/c/_cluster/settings/performance.vue +53 -18
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
- package/pages/c/_cluster/uiplugins/index.vue +16 -5
- package/pages/home.vue +1 -1
- package/pkg/vue.config.js +1 -0
- package/plugins/clean-html-directive.js +1 -1
- package/plugins/clean-tooltip-directive.js +33 -0
- package/plugins/dashboard-store/actions.js +4 -2
- package/plugins/dashboard-store/getters.js +6 -0
- package/plugins/dashboard-store/mutations.js +2 -2
- package/plugins/plugin.js +6 -1
- package/plugins/steve/actions.js +1 -1
- package/plugins/steve/getters.js +14 -3
- package/plugins/steve/resourceWatcher.js +36 -62
- package/plugins/steve/subscribe.js +164 -21
- package/plugins/steve/worker/index.js +8 -1
- package/plugins/steve/worker/web-worker.advanced.js +26 -8
- package/plugins/steve/worker/web-worker.basic.js +23 -4
- package/rancher-components/components/Form/Checkbox/Checkbox.vue +2 -2
- package/rancher-components/components/Form/Radio/RadioGroup.vue +2 -2
- package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +1 -1
- package/store/index.js +16 -61
- package/store/store-types.js +5 -0
- package/store/type-map.js +1 -1
- package/types/shell/index.d.ts +23 -7
- package/utils/__tests__/create-yaml.test.ts +63 -0
- package/utils/array.ts +4 -0
- package/utils/create-yaml.js +5 -5
- package/utils/namespace-filter.js +17 -5
- package/utils/projectAndNamespaceFiltering.utils.ts +62 -0
- package/utils/selector.js +6 -5
- package/utils/settings.ts +5 -7
- 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,
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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.
|
|
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
|
|
39
|
-
const managementReady = () => store.state?.managementReady;
|
|
42
|
+
const workerQueues = {};
|
|
40
43
|
|
|
41
|
-
|
|
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
|
|
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
|
|
78
|
+
if (!supportedStores.includes(storeName)) {
|
|
66
79
|
return;
|
|
67
80
|
}
|
|
68
81
|
|
|
69
|
-
|
|
70
|
-
|
|
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 ?
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 ===
|
|
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
|
-
|
|
764
|
-
|
|
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 ===
|
|
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: (
|
|
97
|
-
trace('createWatcher',
|
|
96
|
+
createWatcher: (opt) => {
|
|
97
|
+
trace('createWatcher', opt);
|
|
98
98
|
|
|
99
99
|
const {
|
|
100
|
-
|
|
101
|
-
} =
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
/>
|