@rancher/shell 3.0.5-rc.6 → 3.0.5-rc.8
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/brand/classic/metadata.json +3 -0
- package/assets/styles/app.scss +1 -0
- package/assets/styles/base/_color.scss +16 -0
- package/assets/styles/base/_helpers.scss +10 -0
- package/assets/styles/base/_variables.scss +18 -12
- package/assets/styles/fonts/_icons.scss +1 -32
- package/assets/styles/global/_layout.scss +1 -1
- package/assets/styles/themes/_dark.scss +262 -258
- package/assets/styles/themes/_light.scss +538 -509
- package/assets/styles/themes/_modern.scss +914 -0
- package/assets/translations/en-us.yaml +110 -29
- package/chart/__tests__/S3.test.ts +2 -1
- package/cloud-credential/generic.vue +18 -10
- package/cloud-credential/harvester.vue +1 -9
- package/components/AdvancedSection.vue +8 -0
- package/components/ChartReadme.vue +17 -7
- package/components/CodeMirror.vue +1 -1
- package/components/Drawer/Chrome.vue +0 -1
- package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +27 -28
- package/components/Drawer/ResourceDetailDrawer/composables.ts +4 -24
- package/components/Drawer/ResourceDetailDrawer/index.vue +18 -4
- package/components/InstallHelmCharts.vue +656 -0
- package/components/LazyImage.vue +60 -4
- package/components/Loading.vue +1 -1
- package/components/LocaleSelector.vue +7 -2
- package/components/Markdown.vue +4 -0
- package/components/PaginatedResourceTable.vue +46 -1
- package/components/PromptRestore.vue +22 -44
- package/components/Resource/Detail/Masthead/composable.ts +16 -0
- package/components/Resource/Detail/Masthead/index.vue +37 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +10 -2
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +26 -7
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +8 -1
- package/components/Resource/Detail/Metadata/KeyValue.vue +12 -10
- package/components/Resource/Detail/Metadata/Rectangle.vue +3 -1
- package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +10 -17
- package/components/Resource/Detail/Metadata/composables.ts +9 -7
- package/components/Resource/Detail/Metadata/index.vue +17 -2
- package/components/Resource/Detail/Page.vue +35 -21
- package/components/Resource/Detail/SpacedRow.vue +1 -1
- package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +8 -9
- package/components/Resource/Detail/TitleBar/composables.ts +5 -5
- package/components/Resource/Detail/TitleBar/index.vue +12 -3
- package/components/ResourceDetail/Masthead/legacy.vue +1 -1
- package/components/ResourceDetail/index.vue +569 -72
- package/components/ResourceList/index.vue +1 -0
- package/components/ResourceTable.vue +6 -1
- package/components/ResourceYaml.vue +1 -1
- package/components/RichTranslation.vue +106 -0
- package/components/SlideInPanelManager.vue +13 -10
- package/components/SortableTable/index.vue +5 -5
- package/components/SortableTable/selection.js +0 -1
- package/components/Tabbed/index.vue +35 -4
- package/components/__tests__/LazyImage.spec.ts +121 -0
- package/components/__tests__/PromptRestore.test.ts +1 -65
- package/components/__tests__/RichTranslation.test.ts +115 -0
- package/components/fleet/FleetStatus.vue +4 -0
- package/components/fleet/dashboard/ResourcePanel.vue +2 -1
- package/components/form/ClusterAppearance.vue +5 -0
- package/components/form/FileImageSelector.vue +1 -1
- package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
- package/components/form/NameNsDescription.vue +1 -0
- package/components/form/Networking.vue +24 -19
- package/components/form/ProjectMemberEditor.vue +1 -1
- package/components/form/ResourceLabeledSelect.vue +22 -8
- package/components/form/ResourceTabs/index.vue +20 -0
- package/components/form/SecretSelector.vue +9 -0
- package/components/form/SelectOrCreateAuthSecret.vue +6 -3
- package/components/form/__tests__/Networking.test.ts +116 -0
- package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
- package/components/formatter/FleetApplicationSource.vue +25 -17
- package/components/formatter/PodImages.vue +1 -1
- package/components/formatter/__tests__/LiveDate.test.ts +10 -2
- package/components/google/AccountAccess.vue +44 -46
- package/components/nav/Favorite.vue +4 -0
- package/components/nav/Group.vue +4 -1
- package/components/nav/NotificationCenter/Notification.vue +1 -27
- package/components/nav/WindowManager/index.vue +3 -3
- package/composables/resources.ts +2 -2
- package/config/labels-annotations.js +3 -2
- package/config/pagination-table-headers.js +8 -1
- package/config/product/explorer.js +27 -2
- package/config/product/manager.js +0 -1
- package/config/query-params.js +10 -0
- package/config/router/routes.js +21 -1
- package/config/system-namespaces.js +1 -1
- package/config/table-headers.js +30 -1
- package/config/types.js +1 -1
- package/config/version.js +1 -1
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +11 -0
- package/detail/__tests__/workload.test.ts +164 -0
- package/detail/configmap.vue +33 -75
- package/detail/projectsecret.vue +11 -0
- package/detail/provisioning.cattle.io.cluster.vue +351 -369
- package/detail/secret.vue +49 -308
- package/detail/workload/index.vue +38 -21
- package/dialog/InstallExtensionDialog.vue +8 -5
- package/dialog/RotateEncryptionKeyDialog.vue +10 -30
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
- package/edit/auth/ldap/__tests__/config.test.ts +14 -0
- package/edit/auth/ldap/config.vue +24 -0
- package/edit/compliance.cattle.io.clusterscan.vue +1 -1
- package/edit/configmap.vue +4 -1
- package/edit/fleet.cattle.io.gitrepo.vue +5 -6
- package/edit/fleet.cattle.io.helmop.vue +78 -56
- package/edit/logging.banzaicloud.io.output/index.vue +1 -1
- package/edit/logging.banzaicloud.io.output/providers/awsElasticsearch.vue +5 -6
- package/edit/networking.k8s.io.ingress/Certificate.vue +20 -22
- package/edit/networking.k8s.io.ingress/DefaultBackend.vue +8 -3
- package/edit/networking.k8s.io.ingress/Rule.vue +2 -5
- package/edit/networking.k8s.io.ingress/RulePath.vue +17 -11
- package/edit/networking.k8s.io.ingress/__tests__/Certificate.test.ts +165 -0
- package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +11 -10
- package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -3
- package/edit/networking.k8s.io.networkpolicy/index.vue +17 -17
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +3 -2
- package/edit/provisioning.cattle.io.cluster/rke2.vue +123 -61
- package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +22 -13
- package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +10 -12
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +39 -38
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +41 -19
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +16 -3
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +32 -33
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +9 -10
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +1 -3
- package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +16 -9
- package/edit/secret/basic.vue +1 -0
- package/edit/secret/index.vue +126 -15
- package/edit/workload/index.vue +5 -14
- package/list/projectsecret.vue +345 -0
- package/list/provisioning.cattle.io.cluster.vue +1 -69
- package/list/secret.vue +109 -0
- package/machine-config/__tests__/vmwarevsphere.test.ts +5 -7
- package/machine-config/google.vue +9 -1
- package/machine-config/vmwarevsphere.vue +7 -17
- package/mixins/__tests__/brand.spec.ts +2 -2
- package/mixins/chart.js +0 -2
- package/mixins/create-edit-view/impl.js +10 -1
- package/mixins/resource-fetch-api-pagination.js +11 -12
- package/mixins/resource-fetch.js +3 -1
- package/models/__tests__/chart.test.ts +111 -80
- package/models/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
- package/models/__tests__/node.test.ts +7 -63
- package/models/catalog.cattle.io.app.js +1 -1
- package/models/catalog.cattle.io.operation.js +1 -1
- package/models/chart.js +36 -20
- package/models/cloudcredential.js +2 -163
- package/models/cluster/node.js +7 -7
- package/models/cluster.x-k8s.io.machine.js +3 -3
- package/models/cluster.x-k8s.io.machinedeployment.js +11 -2
- package/models/compliance.cattle.io.clusterscan.js +2 -2
- package/models/configmap.js +4 -0
- package/models/constraints.gatekeeper.sh.constraint.js +1 -1
- package/models/fleet-application.js +0 -17
- package/models/fleet.cattle.io.cluster.js +2 -2
- package/models/fleet.cattle.io.gitrepo.js +15 -1
- package/models/fleet.cattle.io.helmop.js +26 -22
- package/models/management.cattle.io.setting.js +4 -0
- package/models/persistentvolumeclaim.js +1 -1
- package/models/pod.js +2 -2
- package/models/provisioning.cattle.io.cluster.js +39 -67
- package/models/rke.cattle.io.etcdsnapshot.js +1 -1
- package/models/secret.js +161 -2
- package/models/storage.k8s.io.storageclass.js +2 -2
- package/models/workload.js +3 -3
- package/package.json +11 -10
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +1 -0
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +4 -1
- package/pages/c/_cluster/apps/charts/__tests__/AppChartCardFooter.spec.js +41 -0
- package/pages/c/_cluster/apps/charts/chart.vue +422 -174
- package/pages/c/_cluster/apps/charts/index.vue +46 -35
- package/pages/c/_cluster/apps/charts/install.vue +1 -1
- package/pages/c/_cluster/explorer/projectsecret.vue +24 -0
- package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
- package/pages/c/_cluster/fleet/index.vue +103 -45
- package/pages/c/_cluster/manager/cloudCredential/index.vue +2 -59
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +10 -3
- package/pages/c/_cluster/uiplugins/index.vue +36 -25
- package/plugins/dashboard-store/__tests__/normalize.test.ts +223 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +191 -0
- package/plugins/dashboard-store/__tests__/utils/normalize-usecases.ts +1526 -0
- package/plugins/dashboard-store/actions.js +42 -22
- package/plugins/dashboard-store/normalize.js +29 -17
- package/plugins/dashboard-store/resource-class.js +83 -17
- package/plugins/steve/__tests__/getters.test.ts +1 -1
- package/plugins/steve/__tests__/subscribe.spec.ts +259 -1
- package/plugins/steve/getters.js +8 -2
- package/plugins/steve/resourceWatcher.js +10 -3
- package/plugins/steve/steve-pagination-utils.ts +14 -3
- package/plugins/steve/subscribe.js +192 -19
- package/plugins/steve/worker/web-worker.advanced.js +2 -0
- package/rancher-components/Card/Card.vue +0 -18
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.test.ts +15 -0
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +65 -0
- package/rancher-components/Pill/RcStatusBadge/index.ts +2 -0
- package/rancher-components/Pill/RcStatusBadge/types.ts +5 -0
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.test.ts +33 -0
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +75 -0
- package/rancher-components/Pill/RcStatusIndicator/index.ts +2 -0
- package/rancher-components/Pill/RcStatusIndicator/types.ts +7 -0
- package/rancher-components/Pill/types.ts +2 -0
- package/rancher-components/RcButton/RcButton.vue +1 -1
- package/rancher-components/RcDropdown/RcDropdown.test.ts +98 -0
- package/rancher-components/RcDropdown/RcDropdown.vue +5 -0
- package/rancher-components/RcDropdown/RcDropdownItem.vue +7 -1
- package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +2 -1
- package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +2 -1
- package/rancher-components/RcDropdown/useDropdownContext.ts +21 -0
- package/rancher-components/RcDropdown/useDropdownItem.ts +30 -1
- package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +40 -6
- package/store/__tests__/catalog.test.ts +93 -1
- package/store/aws.js +19 -8
- package/store/catalog.js +8 -3
- package/types/kube/kube-api.ts +12 -0
- package/types/resources/settings.d.ts +1 -1
- package/types/shell/index.d.ts +643 -585
- package/types/store/pagination.types.ts +16 -6
- package/types/uiplugins.ts +73 -0
- package/utils/__tests__/back-off.test.ts +354 -0
- package/utils/__tests__/create-yaml.test.ts +235 -0
- package/utils/__tests__/kontainer.test.ts +19 -0
- package/utils/__tests__/uiplugins.test.ts +84 -0
- package/utils/back-off.ts +176 -0
- package/utils/create-yaml.js +103 -9
- package/utils/dynamic-importer.js +8 -0
- package/utils/kontainer.ts +3 -5
- package/utils/pagination-utils.ts +18 -0
- package/utils/style.ts +3 -0
- package/utils/uiplugins.ts +29 -2
- package/utils/validators/__tests__/setting.test.js +92 -0
- package/utils/validators/formRules/__tests__/index.test.ts +88 -7
- package/utils/validators/formRules/index.ts +83 -8
- package/utils/validators/setting.js +17 -0
- package/cloud-credential/__tests__/harvester.test.ts +0 -18
- package/components/ResourceDetail/__tests__/index.test.ts +0 -135
- package/components/ResourceDetail/legacy.vue +0 -562
- package/components/formatter/CloudCredExpired.vue +0 -69
- package/models/etcdbackup.js +0 -45
- package/pages/explorer/resource/detail/configmap.vue +0 -42
- package/pages/explorer/resource/detail/secret.vue +0 -50
- package/utils/aws.js +0 -0
|
@@ -79,6 +79,29 @@ const findAllGetter = (getters, type, opt) => {
|
|
|
79
79
|
return opt.namespaced ? getters.matching(type, null, opt.namespaced, { skipSelector: true }) : getters.all(type);
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
const createFindWatchArg = ({
|
|
83
|
+
type, id, opt, res
|
|
84
|
+
}) => {
|
|
85
|
+
const revision = typeof opt.revision !== 'undefined' ? opt.revision : res?.metadata?.resourceVersion;
|
|
86
|
+
const watchMsg = {
|
|
87
|
+
type,
|
|
88
|
+
id,
|
|
89
|
+
// Although not used by sockets, we need this for when resyncWatch calls find... which needs namespace to construct the url
|
|
90
|
+
namespace: opt.namespaced,
|
|
91
|
+
revision: revision || '',
|
|
92
|
+
force: opt.forceWatch === true,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const idx = id.indexOf('/');
|
|
96
|
+
|
|
97
|
+
if ( idx > 0 ) {
|
|
98
|
+
watchMsg.namespace = id.substr(0, idx);
|
|
99
|
+
watchMsg.id = id.substr(idx + 1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return watchMsg;
|
|
103
|
+
};
|
|
104
|
+
|
|
82
105
|
export default {
|
|
83
106
|
request() {
|
|
84
107
|
throw new Error('Not Implemented');
|
|
@@ -657,6 +680,12 @@ export default {
|
|
|
657
680
|
out = getters.byId(type, id);
|
|
658
681
|
|
|
659
682
|
if ( out ) {
|
|
683
|
+
if ( opt.watch !== false ) {
|
|
684
|
+
dispatch('watch', createFindWatchArg({
|
|
685
|
+
type, id, opt, res: undefined
|
|
686
|
+
}));
|
|
687
|
+
}
|
|
688
|
+
|
|
660
689
|
return out;
|
|
661
690
|
}
|
|
662
691
|
}
|
|
@@ -669,26 +698,9 @@ export default {
|
|
|
669
698
|
await dispatch('load', { data: res });
|
|
670
699
|
|
|
671
700
|
if ( opt.watch !== false ) {
|
|
672
|
-
|
|
673
|
-
type,
|
|
674
|
-
|
|
675
|
-
// Although not used by sockets, we need this for when resyncWatch calls find... which needs namespace to construct the url
|
|
676
|
-
namespace: opt.namespaced,
|
|
677
|
-
// Override the revision. Used in cases where we need to avoid using the resource's own revision which would be `too old`.
|
|
678
|
-
// For the above case opt.revision will be `null`. If left as `undefined` the subscribe mechanism will try to determine a revision
|
|
679
|
-
// from resources in store (which would be this one, with the too old revision)
|
|
680
|
-
revision: typeof opt.revision !== 'undefined' ? opt.revision : res?.metadata?.resourceVersion,
|
|
681
|
-
force: opt.forceWatch === true,
|
|
682
|
-
};
|
|
683
|
-
|
|
684
|
-
const idx = id.indexOf('/');
|
|
685
|
-
|
|
686
|
-
if ( idx > 0 ) {
|
|
687
|
-
watchMsg.namespace = id.substr(0, idx);
|
|
688
|
-
watchMsg.id = id.substr(idx + 1);
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
dispatch('watch', watchMsg);
|
|
701
|
+
dispatch('watch', createFindWatchArg({
|
|
702
|
+
type, id, opt, res
|
|
703
|
+
}));
|
|
692
704
|
}
|
|
693
705
|
|
|
694
706
|
out = getters.byId(type, id);
|
|
@@ -808,13 +820,21 @@ export default {
|
|
|
808
820
|
return classify(ctx, resource.toJSON(), true);
|
|
809
821
|
},
|
|
810
822
|
|
|
811
|
-
|
|
812
|
-
|
|
823
|
+
/**
|
|
824
|
+
* Remove all cached entries for a resource and stop watches
|
|
825
|
+
*/
|
|
813
826
|
forgetType({ commit, dispatch, state }, type, compareWatches) {
|
|
827
|
+
// Stop all known watches
|
|
814
828
|
state.started
|
|
815
829
|
.filter((entry) => compareWatches ? compareWatches(entry) : entry.type === type)
|
|
816
830
|
.forEach((entry) => dispatch('unwatch', entry));
|
|
817
831
|
|
|
832
|
+
// Stop all known back-off watch processes for this type
|
|
833
|
+
dispatch('resetWatchBackOff', {
|
|
834
|
+
type, compareWatches, resetStarted: false
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
// Remove entries from store
|
|
818
838
|
commit('forgetType', type);
|
|
819
839
|
},
|
|
820
840
|
|
|
@@ -17,28 +17,40 @@ export function normalizeType(type) {
|
|
|
17
17
|
return type;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Detect and resolve conflicts from a 409 response.
|
|
22
|
+
*
|
|
23
|
+
* @param {*} initialValue the initial value before changes
|
|
24
|
+
* @param {*} userValue the value containing the local changes. this function will intentionally mutate this to contain changes made from the server
|
|
25
|
+
* @param {*} serverValue the very latest value from the server
|
|
26
|
+
* @returns If `value` has been successfully updated return a false-y value. Else they can't be resolved, return an array of errors to show the user.
|
|
27
|
+
*/
|
|
28
|
+
export async function handleConflict(initialValue, userValue, serverValue, store, storeNamespace, toJSON = (x) => x.toJSON()) {
|
|
29
|
+
// initial value
|
|
30
|
+
const initial = await store.dispatch(`${ storeNamespace }/cleanForDiff`, toJSON(initialValue), { root: true });
|
|
31
|
+
// changed value (user edits)
|
|
32
|
+
const user = await store.dispatch(`${ storeNamespace }/cleanForDiff`, toJSON(userValue), { root: true });
|
|
33
|
+
// server value
|
|
34
|
+
const server = await store.dispatch(`${ storeNamespace }/cleanForDiff`, toJSON(serverValue), { root: true });
|
|
35
|
+
|
|
36
|
+
// changes made to the server value
|
|
37
|
+
const serverChanges = changeset(initial, server);
|
|
38
|
+
// changes made locally
|
|
39
|
+
const userChanges = changeset(initial, user);
|
|
40
|
+
// Any incompatibilities between changes made locally and the server?
|
|
41
|
+
const actualConflicts = changesetConflicts(serverChanges, userChanges);
|
|
42
|
+
|
|
43
|
+
console.log('Background Change', serverChanges); // eslint-disable-line no-console
|
|
44
|
+
console.log('User Change', userChanges); // eslint-disable-line no-console
|
|
34
45
|
console.log('Conflicts', actualConflicts); // eslint-disable-line no-console
|
|
35
46
|
|
|
36
|
-
|
|
37
|
-
|
|
47
|
+
userValue.metadata.resourceVersion = serverValue.metadata.resourceVersion;
|
|
48
|
+
// Apply changes made on the server to the changed (user) value
|
|
49
|
+
applyChangeset(userValue, serverChanges);
|
|
38
50
|
|
|
39
51
|
if ( actualConflicts.length ) {
|
|
40
52
|
// Stop the save and let the user inspect and continue editing
|
|
41
|
-
const out = [
|
|
53
|
+
const out = [store.getters['i18n/t']('validation.conflict', { fields: actualConflicts.join(', '), fieldCount: actualConflicts.length })];
|
|
42
54
|
|
|
43
55
|
return out;
|
|
44
56
|
} else {
|
|
@@ -32,9 +32,11 @@ import isFunction from 'lodash/isFunction';
|
|
|
32
32
|
import isString from 'lodash/isString';
|
|
33
33
|
import { markRaw } from 'vue';
|
|
34
34
|
|
|
35
|
+
import { handleConflict } from '@shell/plugins/dashboard-store/normalize';
|
|
35
36
|
import { ExtensionPoint, ActionLocation } from '@shell/core/types';
|
|
36
37
|
import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
|
|
37
38
|
import { parse } from '@shell/utils/selector';
|
|
39
|
+
import { importDrawer } from '@shell/utils/dynamic-importer';
|
|
38
40
|
|
|
39
41
|
export const DNS_LIKE_TYPES = ['dnsLabel', 'dnsLabelRestricted', 'hostname'];
|
|
40
42
|
|
|
@@ -858,6 +860,10 @@ export default class Resource {
|
|
|
858
860
|
|
|
859
861
|
// ------------------------------------------------------------------
|
|
860
862
|
|
|
863
|
+
get canEdit() {
|
|
864
|
+
return this.canUpdate && this.canCustomEdit;
|
|
865
|
+
}
|
|
866
|
+
|
|
861
867
|
get availableActions() {
|
|
862
868
|
const all = this._availableActions;
|
|
863
869
|
|
|
@@ -897,6 +903,26 @@ export default class Resource {
|
|
|
897
903
|
return out;
|
|
898
904
|
}
|
|
899
905
|
|
|
906
|
+
showConfiguration(returnFocusSelector) {
|
|
907
|
+
const onClose = () => this.$ctx.commit('slideInPanel/close', undefined, { root: true });
|
|
908
|
+
|
|
909
|
+
this.$ctx.commit('slideInPanel/open', {
|
|
910
|
+
component: importDrawer('ResourceDetailDrawer'),
|
|
911
|
+
componentProps: {
|
|
912
|
+
resource: this,
|
|
913
|
+
onClose,
|
|
914
|
+
width: '73%',
|
|
915
|
+
// We want this to be full viewport height top to bottom
|
|
916
|
+
height: '100vh',
|
|
917
|
+
top: '0',
|
|
918
|
+
'z-index': 101, // We want this to be above the main side menu
|
|
919
|
+
closeOnRouteChange: ['name', 'params', 'query'], // We want to ignore hash changes, tables in extensions can trigger the drawer to close while opening
|
|
920
|
+
triggerFocusTrap: true,
|
|
921
|
+
returnFocusSelector
|
|
922
|
+
}
|
|
923
|
+
}, { root: true });
|
|
924
|
+
}
|
|
925
|
+
|
|
900
926
|
// You can add custom actions by overriding your own availableActions (and probably reading super._availableActions)
|
|
901
927
|
get _availableActions() {
|
|
902
928
|
// get menu actions available by plugins configuration
|
|
@@ -904,6 +930,12 @@ export default class Resource {
|
|
|
904
930
|
const extensionMenuActions = getApplicableExtensionEnhancements(this.$rootState, ExtensionPoint.ACTION, ActionLocation.TABLE, currentRoute, this);
|
|
905
931
|
|
|
906
932
|
const all = [
|
|
933
|
+
{
|
|
934
|
+
action: 'showConfiguration',
|
|
935
|
+
label: this.t('action.showConfiguration'),
|
|
936
|
+
icon: 'icon icon-document',
|
|
937
|
+
enabled: this.disableResourceDetailDrawer !== true && (this.canCustomEdit || this.canYaml), // If the resource can't show an edit or a yaml we don't want to show the configuration drawer
|
|
938
|
+
},
|
|
907
939
|
{ divider: true },
|
|
908
940
|
{
|
|
909
941
|
action: this.canUpdate ? 'goToEdit' : 'goToViewConfig',
|
|
@@ -1343,9 +1375,7 @@ export default class Resource {
|
|
|
1343
1375
|
this.currentRouter().push(location);
|
|
1344
1376
|
}
|
|
1345
1377
|
|
|
1346
|
-
goToEdit(moreQuery = {}) {
|
|
1347
|
-
const location = this.detailLocation;
|
|
1348
|
-
|
|
1378
|
+
goToEdit(moreQuery = {}, location = this.detailLocation) {
|
|
1349
1379
|
location.query = {
|
|
1350
1380
|
...location.query,
|
|
1351
1381
|
[MODE]: _EDIT,
|
|
@@ -1528,11 +1558,11 @@ export default class Resource {
|
|
|
1528
1558
|
}
|
|
1529
1559
|
}
|
|
1530
1560
|
|
|
1531
|
-
async saveYaml(yaml) {
|
|
1532
|
-
await this._saveYaml(yaml);
|
|
1561
|
+
async saveYaml(yaml, initialYaml) {
|
|
1562
|
+
await this._saveYaml(yaml, initialYaml);
|
|
1533
1563
|
}
|
|
1534
1564
|
|
|
1535
|
-
async _saveYaml(yaml) {
|
|
1565
|
+
async _saveYaml(yaml, initialYaml, depth = 0) {
|
|
1536
1566
|
/* Multipart support, but need to know the right cluster and work for management store
|
|
1537
1567
|
and "apply" seems to only work for create, not update.
|
|
1538
1568
|
|
|
@@ -1570,20 +1600,56 @@ export default class Resource {
|
|
|
1570
1600
|
data: yaml
|
|
1571
1601
|
});
|
|
1572
1602
|
} else {
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1603
|
+
try {
|
|
1604
|
+
res = await this.followLink('update', {
|
|
1605
|
+
method: 'PUT',
|
|
1606
|
+
headers,
|
|
1607
|
+
data: yaml
|
|
1608
|
+
});
|
|
1609
|
+
} catch (err) {
|
|
1610
|
+
const IS_ERR_409 = err.status === 409 || err._status === 409;
|
|
1611
|
+
|
|
1612
|
+
// Conflict, the resource being edited has changed since starting editing
|
|
1613
|
+
if (IS_ERR_409 && depth === 0 && initialYaml) {
|
|
1614
|
+
const inStore = this.$rootGetters['currentStore'](this.type);
|
|
1615
|
+
|
|
1616
|
+
const initialValue = jsyaml.load(initialYaml);
|
|
1617
|
+
const value = jsyaml.load(yaml);
|
|
1618
|
+
const liveValue = this.$rootGetters[`${ inStore }/byId`](this.type, this.id);
|
|
1619
|
+
|
|
1620
|
+
const handledConflictErr = await handleConflict(
|
|
1621
|
+
initialValue,
|
|
1622
|
+
value,
|
|
1623
|
+
liveValue,
|
|
1624
|
+
{
|
|
1625
|
+
dispatch: this.$dispatch,
|
|
1626
|
+
getters: this.$rootGetters
|
|
1627
|
+
},
|
|
1628
|
+
this.$rootGetters['currentStore'](this.type),
|
|
1629
|
+
(v) => v.toJSON ? v.toJSON() : v
|
|
1630
|
+
);
|
|
1631
|
+
|
|
1632
|
+
if (handledConflictErr === false) {
|
|
1633
|
+
// It was automatically figured out, save again
|
|
1634
|
+
await this._saveYaml(jsyaml.dump(value), null, depth + 1);
|
|
1635
|
+
} else {
|
|
1636
|
+
throw handledConflictErr;
|
|
1637
|
+
}
|
|
1638
|
+
} else {
|
|
1639
|
+
throw err;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1578
1642
|
}
|
|
1579
1643
|
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1644
|
+
if (res) {
|
|
1645
|
+
await this.$dispatch(`load`, {
|
|
1646
|
+
data: res,
|
|
1647
|
+
existing: (isCreate ? this : undefined)
|
|
1648
|
+
});
|
|
1584
1649
|
|
|
1585
|
-
|
|
1586
|
-
|
|
1650
|
+
if (this.isSpoofed) {
|
|
1651
|
+
await this.$dispatch('cluster/findAll', { type: this.type, opt: { force: true } }, { root: true });
|
|
1652
|
+
}
|
|
1587
1653
|
}
|
|
1588
1654
|
}
|
|
1589
1655
|
|
|
@@ -120,7 +120,7 @@ describe('steve: getters:', () => {
|
|
|
120
120
|
expect(urlOptionsGetter('/v1/foo', { excludeFields: ['bar'] })).toBe('/v1/foo?exclude=bar');
|
|
121
121
|
});
|
|
122
122
|
it('returns a string without an exclude statement for "managedFields" if omitExcludeFields includes it and the url starts with "/v1/"', () => {
|
|
123
|
-
expect(urlOptionsGetter('/v1/foo', { omitExcludeFields: ['metadata.managedFields'] })).toBe('/v1/foo
|
|
123
|
+
expect(urlOptionsGetter('/v1/foo', { omitExcludeFields: ['metadata.managedFields'] })).toBe('/v1/foo');
|
|
124
124
|
});
|
|
125
125
|
it('returns a string without an exclude statement if excludeFields is set but the url does not start with "/v1/"', () => {
|
|
126
126
|
expect(urlOptionsGetter('foo', { excludeFields: ['bar'] })).toBe('foo');
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { actions, getters } from '../subscribe';
|
|
1
|
+
import { actions, getters, mutations } from '../subscribe';
|
|
2
|
+
import { REVISION_TOO_OLD } from '../../../utils/socket';
|
|
3
|
+
import { STEVE_WATCH_EVENT } from '../../../types/store/subscribe.types';
|
|
4
|
+
import backOff from '../../../utils/back-off';
|
|
2
5
|
|
|
3
6
|
describe('steve: subscribe', () => {
|
|
4
7
|
describe('actions', () => {
|
|
@@ -171,4 +174,259 @@ describe('steve: subscribe', () => {
|
|
|
171
174
|
});
|
|
172
175
|
});
|
|
173
176
|
});
|
|
177
|
+
|
|
178
|
+
describe('backoff', () => {
|
|
179
|
+
const waitForBackOff = async(advanceTimersByTime = 20000) => {
|
|
180
|
+
jest.advanceTimersByTime(advanceTimersByTime);
|
|
181
|
+
// jest.advanceTimersByTime(advanceTimersByTime);
|
|
182
|
+
await Promise.resolve();
|
|
183
|
+
await Promise.resolve();
|
|
184
|
+
await Promise.resolve();
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
describe('stale cache in replicate that handles watch', () => {
|
|
188
|
+
/**
|
|
189
|
+
1. ui makes http request.
|
|
190
|
+
- it's handled by up-to-date replica A
|
|
191
|
+
- response contains an up-to-date revision X
|
|
192
|
+
2. ui makes watch request referencing up-to-date revision X from A
|
|
193
|
+
- it's received by replica B with a stale cache which does not contain revision X.
|
|
194
|
+
- replicate B rejects watch with unknown revision message (i.e. 'too old')
|
|
195
|
+
3. ui receives unknown revision and makes a new request
|
|
196
|
+
- this should backoff until eventually succeeding
|
|
197
|
+
*/
|
|
198
|
+
|
|
199
|
+
const startWatch = ({
|
|
200
|
+
ctx,
|
|
201
|
+
obj, msg,
|
|
202
|
+
revision
|
|
203
|
+
}) => {
|
|
204
|
+
const {
|
|
205
|
+
state, dispatch, getters, rootGetters, commit
|
|
206
|
+
} = ctx;
|
|
207
|
+
|
|
208
|
+
// call watch
|
|
209
|
+
actions.watch({
|
|
210
|
+
state, dispatch, getters, rootGetters
|
|
211
|
+
}, {
|
|
212
|
+
...obj,
|
|
213
|
+
revision,
|
|
214
|
+
mode: STEVE_WATCH_EVENT.CHANGES,
|
|
215
|
+
force: true,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
expect(dispatch).toHaveBeenNthCalledWith(1, 'unwatchIncompatible', {
|
|
219
|
+
id: undefined, mode: STEVE_WATCH_EVENT.CHANGES, namespace: undefined, selector: undefined, type: obj.type
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(dispatch).toHaveBeenNthCalledWith(2, 'send', {
|
|
223
|
+
debounceMs: 4000,
|
|
224
|
+
mode: 'resource.changes',
|
|
225
|
+
resourceType: obj.type,
|
|
226
|
+
resourceVersion: revision.toString(),
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Receive start from BE
|
|
230
|
+
actions['ws.resource.start']({
|
|
231
|
+
state, dispatch, getters, commit
|
|
232
|
+
}, { ...msg });
|
|
233
|
+
|
|
234
|
+
expect(dispatch).toHaveBeenCalledTimes(2);
|
|
235
|
+
dispatch.mockClear();
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const errorWatch = ({
|
|
239
|
+
ctx,
|
|
240
|
+
obj, msg,
|
|
241
|
+
}) => {
|
|
242
|
+
const {
|
|
243
|
+
state, dispatch, getters, commit
|
|
244
|
+
} = ctx;
|
|
245
|
+
|
|
246
|
+
// Receive error from BE
|
|
247
|
+
actions['ws.resource.error']({
|
|
248
|
+
dispatch, getters, commit
|
|
249
|
+
}, {
|
|
250
|
+
...msg,
|
|
251
|
+
data: { error: 'too old' }
|
|
252
|
+
});
|
|
253
|
+
expect(state.inError).toStrictEqual(
|
|
254
|
+
{
|
|
255
|
+
'type=abc,namespace=,id=,selector=': {
|
|
256
|
+
obj: {
|
|
257
|
+
type: msg.resourceType,
|
|
258
|
+
mode: msg.mode,
|
|
259
|
+
},
|
|
260
|
+
reason: REVISION_TOO_OLD
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
// Receive stop from BE
|
|
266
|
+
actions['ws.resource.stop']({
|
|
267
|
+
state, dispatch, getters, commit
|
|
268
|
+
}, { ...msg });
|
|
269
|
+
// stop tries to watch again, however we're in error so will be ignored
|
|
270
|
+
expect(dispatch).toHaveBeenNthCalledWith(1, 'watch', {
|
|
271
|
+
id: undefined, mode: STEVE_WATCH_EVENT.CHANGES, namespace: undefined, selector: undefined, type: obj.type
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
dispatch.mockClear();
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const cycleFail = async({
|
|
278
|
+
ctx,
|
|
279
|
+
obj, msg,
|
|
280
|
+
revision,
|
|
281
|
+
tooManyTries = false,
|
|
282
|
+
}) => {
|
|
283
|
+
const { dispatch } = ctx;
|
|
284
|
+
|
|
285
|
+
startWatch({
|
|
286
|
+
ctx, obj, msg, revision
|
|
287
|
+
});
|
|
288
|
+
errorWatch({
|
|
289
|
+
ctx, obj, msg
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
await waitForBackOff(50000);
|
|
293
|
+
await waitForBackOff(50000);
|
|
294
|
+
|
|
295
|
+
if (tooManyTries) {
|
|
296
|
+
expect(dispatch).toHaveBeenCalledTimes(0);
|
|
297
|
+
} else {
|
|
298
|
+
expect(dispatch).toHaveBeenCalledTimes(1);
|
|
299
|
+
expect(dispatch).toHaveBeenCalledWith('resyncWatch', {
|
|
300
|
+
...msg,
|
|
301
|
+
data: { error: 'too old' }
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
await waitForBackOff();
|
|
306
|
+
|
|
307
|
+
if (tooManyTries) {
|
|
308
|
+
expect(dispatch).toHaveBeenCalledTimes(0);
|
|
309
|
+
} else {
|
|
310
|
+
expect(dispatch).toHaveBeenCalledTimes(1);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
dispatch.mockClear();
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const cycleSucceed = async({
|
|
317
|
+
ctx,
|
|
318
|
+
obj, msg,
|
|
319
|
+
revision
|
|
320
|
+
}) => {
|
|
321
|
+
const { dispatch } = ctx;
|
|
322
|
+
|
|
323
|
+
dispatch.mockImplementation(async(type: string) => {
|
|
324
|
+
if (type === 'resyncWatch') {
|
|
325
|
+
return Promise.resolve();
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
startWatch({
|
|
330
|
+
ctx, obj, msg, revision
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
await waitForBackOff();
|
|
334
|
+
|
|
335
|
+
expect(dispatch).toHaveBeenCalledTimes(0);
|
|
336
|
+
|
|
337
|
+
await waitForBackOff();
|
|
338
|
+
|
|
339
|
+
expect(dispatch).toHaveBeenCalledTimes(0);
|
|
340
|
+
|
|
341
|
+
dispatch.mockClear();
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const dispatch = jest.fn();
|
|
345
|
+
const rootGetters = {
|
|
346
|
+
'type-map/isSpoofed': () => false,
|
|
347
|
+
'management/byId': () => ({ value: true })
|
|
348
|
+
};
|
|
349
|
+
const obj = { type: 'abc' };
|
|
350
|
+
const msg = {
|
|
351
|
+
resourceType: obj.type,
|
|
352
|
+
mode: STEVE_WATCH_EVENT.CHANGES,
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const initStore = () => {
|
|
356
|
+
const state = { started: [], inError: {} };
|
|
357
|
+
const _getters = {
|
|
358
|
+
normalizeType: (type: string) => type,
|
|
359
|
+
schemaFor: () => ({}),
|
|
360
|
+
storeName: 'test',
|
|
361
|
+
inError: (...args) => getters.inError(state)(...args),
|
|
362
|
+
watchStarted: (...args) => getters.watchStarted(state)(...args),
|
|
363
|
+
backOffId: (...args) => getters.backOffId()(...args),
|
|
364
|
+
canBackoff: () => true,
|
|
365
|
+
};
|
|
366
|
+
const commit = (type, ...args) => mutations[type](state, ...args);
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
state, dispatch, getters: _getters, rootGetters, commit
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
beforeAll(() => {
|
|
374
|
+
jest.useFakeTimers();
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
afterEach(() => {
|
|
378
|
+
backOff.resetAll();
|
|
379
|
+
dispatch.mockClear();
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// eslint-disable-next-line jest/expect-expect
|
|
383
|
+
it('succeeds', async() => {
|
|
384
|
+
jest.useFakeTimers();
|
|
385
|
+
|
|
386
|
+
const ctx = initStore();
|
|
387
|
+
|
|
388
|
+
await cycleSucceed({
|
|
389
|
+
ctx, msg, obj, revision: 1
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// eslint-disable-next-line jest/expect-expect
|
|
394
|
+
it('succeeds after a few failures', async() => {
|
|
395
|
+
jest.useFakeTimers();
|
|
396
|
+
|
|
397
|
+
const ctx = initStore();
|
|
398
|
+
|
|
399
|
+
await cycleFail({
|
|
400
|
+
ctx, msg, obj, revision: 1
|
|
401
|
+
});
|
|
402
|
+
await cycleFail({
|
|
403
|
+
ctx, msg, obj, revision: 1
|
|
404
|
+
});
|
|
405
|
+
await cycleFail({
|
|
406
|
+
ctx, msg, obj, revision: 1
|
|
407
|
+
});
|
|
408
|
+
await cycleFail({
|
|
409
|
+
ctx, msg, obj, revision: 1
|
|
410
|
+
});
|
|
411
|
+
await cycleSucceed({
|
|
412
|
+
ctx, msg, obj, revision: 1
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// eslint-disable-next-line jest/expect-expect
|
|
417
|
+
it('never succeeds', async() => {
|
|
418
|
+
const ctx = initStore();
|
|
419
|
+
|
|
420
|
+
for (let i = 0; i < 10; i++) {
|
|
421
|
+
await cycleFail({
|
|
422
|
+
ctx, msg, obj, revision: 1
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
await cycleFail({
|
|
427
|
+
ctx, msg, obj, revision: 1, tooManyTries: true
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
});
|
|
174
432
|
});
|
package/plugins/steve/getters.js
CHANGED
|
@@ -154,9 +154,15 @@ export default {
|
|
|
154
154
|
opt.excludeFields = Array.isArray(opt?.omitExcludeFields) ? excludeFields.filter((f) => !f.includes(opt.omitExcludeFields)) : excludeFields;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
if (opt.excludeFields.length) {
|
|
158
|
+
const excludeParamsString = opt.excludeFields.map((field) => `exclude=${ field }`).join('&');
|
|
158
159
|
|
|
159
|
-
|
|
160
|
+
url += `${ url.includes('?') ? '&' : '?' }${ excludeParamsString }`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (opt.revision) {
|
|
164
|
+
url += `${ url.includes('?') ? '&' : '?' }${ `revision=${ opt.revision }` }`;
|
|
165
|
+
}
|
|
160
166
|
}
|
|
161
167
|
// End: Exclude
|
|
162
168
|
|
|
@@ -36,12 +36,19 @@ export const WATCH_STATUSES = {
|
|
|
36
36
|
REMOVE_REQUESTED: 'removed_requested'
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Create a unique key for a specific resource watch's params
|
|
41
|
+
*/
|
|
39
42
|
export const keyForSubscribe = ({
|
|
40
43
|
resourceType, type, namespace, id, selector
|
|
41
44
|
} = {}) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
const keyMap = {
|
|
46
|
+
type: resourceType || type, namespace, id, selector
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return Object.entries(keyMap)
|
|
50
|
+
.map(([prop, value]) => `${ prop }=${ value || '' }`)
|
|
51
|
+
.join(',');
|
|
45
52
|
};
|
|
46
53
|
|
|
47
54
|
export const watchKeyFromMessage = (msg) => {
|
|
@@ -15,7 +15,9 @@ import {
|
|
|
15
15
|
HPA,
|
|
16
16
|
SECRET
|
|
17
17
|
} from '@shell/config/types';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
CAPI as CAPI_LAB_AND_ANO, CATTLE_PUBLIC_ENDPOINTS, STORAGE, UI_PROJECT_SECRET, UI_PROJECT_SECRET_COPY
|
|
20
|
+
} from '@shell/config/labels-annotations';
|
|
19
21
|
import { Schema } from '@shell/plugins/steve/schema';
|
|
20
22
|
import { PaginationSettingsStore } from '@shell/types/resources/settings';
|
|
21
23
|
import paginationUtils from '@shell/utils/pagination-utils';
|
|
@@ -184,6 +186,10 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
184
186
|
[CONFIG_MAP]: [
|
|
185
187
|
{ field: 'metadata.labels[harvesterhci.io/cloud-init-template]' }
|
|
186
188
|
],
|
|
189
|
+
[SECRET]: [
|
|
190
|
+
{ field: `metadata.labels[${ UI_PROJECT_SECRET }]` },
|
|
191
|
+
{ field: `metadata.annotations[${ UI_PROJECT_SECRET_COPY }]` },
|
|
192
|
+
],
|
|
187
193
|
[NAMESPACE]: [
|
|
188
194
|
{ field: 'metadata.labels[field.cattle.io/projectId]' }
|
|
189
195
|
],
|
|
@@ -499,7 +505,11 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
499
505
|
// Check if the API supports filtering by this field
|
|
500
506
|
this.validateField(validateFields, schema, field.field);
|
|
501
507
|
|
|
502
|
-
|
|
508
|
+
// we're just checking that the field exists, so there's no value
|
|
509
|
+
if (field.exists) {
|
|
510
|
+
return field.field;
|
|
511
|
+
}
|
|
512
|
+
const encodedValue = encodeURIComponent(field.value || '');
|
|
503
513
|
|
|
504
514
|
// = exact match (equals + exact)
|
|
505
515
|
// ~ partial match (equals + !exact)
|
|
@@ -508,7 +518,7 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
508
518
|
const operator = `${ field.equals ? '' : '!' }${ field.exact ? '=' : '~' }`;
|
|
509
519
|
let safeValue;
|
|
510
520
|
|
|
511
|
-
if (StevePaginationUtils.VALID_FIELD_VALUE_REGEX.test(field.value)) {
|
|
521
|
+
if (StevePaginationUtils.VALID_FIELD_VALUE_REGEX.test(field.value || '')) {
|
|
512
522
|
// Does not contain any protected characters, send as is
|
|
513
523
|
safeValue = encodedValue;
|
|
514
524
|
} else {
|
|
@@ -674,6 +684,7 @@ export const PAGINATION_SETTINGS_STORE_DEFAULTS: PaginationSettingsStore = {
|
|
|
674
684
|
// { resource: CAPI.RANCHER_CLUSTER, context: ['home', 'side-bar'] }, // Disabled due to https://github.com/rancher/dashboard/issues/14493
|
|
675
685
|
// { resource: MANAGEMENT.CLUSTER, context: ['side-bar'] }, // Disabled due to https://github.com/rancher/dashboard/issues/14493
|
|
676
686
|
{ resource: CATALOG.APP, context: ['branding'] },
|
|
687
|
+
SECRET
|
|
677
688
|
],
|
|
678
689
|
generic: false,
|
|
679
690
|
}
|