@rancher/shell 3.0.2-rc.4 → 3.0.2-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/images/providers/nutanix.svg +12 -1
- package/assets/styles/base/_basic.scss +2 -1
- package/assets/styles/base/_helpers.scss +4 -0
- package/assets/styles/base/_variables.scss +2 -0
- package/assets/styles/global/_labeled-input.scss +5 -13
- package/assets/styles/global/_layout.scss +1 -0
- package/assets/styles/global/_select.scss +5 -0
- package/assets/styles/themes/_dark.scss +1 -3
- package/assets/styles/themes/_light.scss +5 -1
- package/assets/translations/en-us.yaml +142 -22
- package/assets/translations/zh-hans.yaml +0 -3
- package/cloud-credential/azure.vue +1 -1
- package/components/ActionMenuShell.vue +105 -0
- package/components/AppModal.vue +2 -2
- package/components/AsyncButton.vue +2 -0
- package/components/ButtonGroup.vue +15 -1
- package/components/ButtonMultiAction.vue +5 -1
- package/components/ClusterBadge.vue +1 -0
- package/components/ClusterIconMenu.vue +3 -0
- package/components/ClusterProviderIcon.vue +14 -1
- package/components/CodeMirror.vue +96 -5
- package/components/Collapse.vue +16 -3
- package/components/CopyToClipboardText.vue +3 -1
- package/components/CruResource.vue +9 -0
- package/components/CruResourceFooter.vue +1 -1
- package/components/ExplorerMembers.vue +2 -1
- package/components/ExplorerProjectsNamespaces.vue +7 -0
- package/components/Import.vue +14 -1
- package/components/LandingPagePreference.vue +4 -2
- package/components/PodSecurityAdmission.vue +8 -6
- package/components/PromptChangePassword.vue +1 -0
- package/components/PromptRemove.vue +23 -21
- package/components/ResourceDetail/Masthead.vue +30 -11
- package/components/ResourceDetail/__tests__/Masthead.test.ts +61 -0
- package/components/ResourceDetail/index.vue +6 -0
- package/components/ResourceTable.vue +6 -14
- package/components/ResourceYaml.vue +1 -0
- package/components/SelectIconGrid.vue +2 -0
- package/components/Setting.vue +115 -0
- package/components/SortableTable/THead.vue +2 -0
- package/components/SortableTable/index.vue +38 -14
- package/components/Tabbed/index.vue +16 -15
- package/components/Wizard.vue +108 -104
- package/components/YamlEditor.vue +12 -2
- package/components/__tests__/Collapse.test.ts +2 -2
- package/components/auth/Principal.vue +29 -17
- package/components/auth/__tests__/Principal.test.ts +40 -0
- package/components/auth/login/ldap.vue +7 -0
- package/components/fleet/FleetBundles.vue +1 -1
- package/components/fleet/FleetRepos.vue +1 -1
- package/components/fleet/FleetResources.vue +0 -2
- package/components/fleet/FleetSummary.vue +60 -65
- package/components/fleet/ForceDirectedTreeChart/index.vue +5 -1
- package/components/fleet/__tests__/FleetSummary.test.ts +49 -9
- package/components/form/ArrayList.vue +6 -2
- package/components/form/ColorInput.vue +1 -0
- package/components/form/KeyValue.vue +11 -12
- package/components/form/LabeledSelect.vue +16 -3
- package/components/form/Labels.vue +8 -1
- package/components/form/Members/MembershipEditor.vue +230 -222
- package/components/form/Members/__tests__/MembershipEditor.test.ts +62 -0
- package/components/form/Password.vue +3 -0
- package/components/form/ProjectMemberEditor.vue +6 -3
- package/components/form/ResourceTabs/index.vue +15 -13
- package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +5 -4
- package/components/form/SchedulingCustomization.vue +85 -0
- package/components/form/Select.vue +4 -2
- package/components/form/SelectOrCreateAuthSecret.vue +2 -1
- package/components/form/UnitInput.vue +1 -2
- package/components/form/__tests__/ArrayList.test.ts +9 -6
- package/components/form/__tests__/LabeledSelect.test.ts +37 -0
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +34 -0
- package/components/formatter/LiveDate.vue +3 -1
- package/components/formatter/ServiceType.vue +12 -4
- package/components/formatter/WorkloadHealthScale.vue +2 -1
- package/components/nav/Header.vue +35 -2
- package/components/nav/HeaderPageActionMenu.vue +11 -40
- package/components/nav/Jump.vue +8 -2
- package/components/nav/NamespaceFilter.vue +5 -4
- package/components/nav/Pinned.vue +1 -1
- package/components/nav/TopLevelMenu.helper.ts +5 -5
- package/components/nav/WindowManager/ContainerLogs.vue +97 -49
- package/components/nav/WindowManager/ContainerShell.vue +99 -18
- package/components/nav/WindowManager/index.vue +85 -6
- package/components/templates/default.vue +2 -47
- package/config/features.js +1 -0
- package/config/home-links.js +1 -1
- package/config/labels-annotations.js +11 -1
- package/config/router/navigation-guards/index.js +2 -1
- package/config/router/navigation-guards/record-last-route.js +24 -0
- package/config/settings.ts +66 -98
- package/config/version.js +1 -1
- package/core/types-provisioning.ts +7 -0
- package/detail/fleet.cattle.io.bundle.vue +7 -0
- package/detail/fleet.cattle.io.cluster.vue +0 -3
- package/detail/fleet.cattle.io.gitrepo.vue +8 -15
- package/detail/provisioning.cattle.io.cluster.vue +8 -2
- package/dialog/DeactivateDriverDialog.vue +5 -5
- package/dialog/GitRepoForceUpdateDialog.vue +132 -0
- package/directives/strip-html-aria-label.js +19 -0
- package/edit/__tests__/cis.cattle.io.clusterscan.test.ts +87 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +217 -37
- package/edit/auth/__tests__/oidc.test.ts +26 -9
- package/edit/auth/ldap/__tests__/config.test.ts +40 -0
- package/edit/auth/ldap/config.vue +67 -89
- package/edit/auth/oidc.vue +15 -1
- package/edit/catalog.cattle.io.clusterrepo.vue +12 -8
- package/edit/cis.cattle.io.clusterscan.vue +13 -1
- package/edit/fleet.cattle.io.gitrepo.vue +198 -72
- package/edit/logging-flow/Match.vue +0 -21
- package/edit/management.cattle.io.project.vue +1 -1
- package/edit/monitoring.coreos.com.prometheusrule/AlertingRule.vue +10 -3
- package/edit/monitoring.coreos.com.prometheusrule/RecordingRule.vue +5 -1
- package/edit/monitoring.coreos.com.prometheusrule/index.vue +5 -2
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +8 -1
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +2 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.test.ts +55 -15
- package/edit/provisioning.cattle.io.cluster/index.vue +39 -39
- package/edit/provisioning.cattle.io.cluster/rke2.vue +63 -12
- package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +37 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +3 -2
- package/edit/resources.cattle.io.backup.vue +150 -15
- package/edit/secret/__tests__/ssh.test.ts +79 -0
- package/edit/secret/ssh.vue +7 -1
- package/edit/workload/Job.vue +2 -2
- package/edit/workload/index.vue +3 -1
- package/initialize/install-directives.js +2 -0
- package/initialize/install-plugins.js +6 -1
- package/list/catalog.cattle.io.app.vue +21 -4
- package/list/fleet.cattle.io.bundle.vue +1 -1
- package/list/management.cattle.io.setting.vue +34 -129
- package/list/provisioning.cattle.io.cluster.vue +11 -3
- package/machine-config/vmwarevsphere.vue +15 -8
- package/mixins/__tests__/auth-config.test.ts +74 -0
- package/mixins/__tests__/chart.test.ts +5 -4
- package/mixins/__tests__/create-edit-view.test.ts +38 -0
- package/mixins/auth-config.js +9 -1
- package/mixins/chart.js +2 -2
- package/mixins/create-edit-view/impl.js +4 -1
- package/mixins/vue-select-overrides.js +10 -0
- package/models/__tests__/catalog.cattle.io.app.test.ts +148 -0
- package/models/__tests__/fleet.cattle.io.gitrepo.test.ts +157 -0
- package/models/__tests__/secret.test.ts +56 -13
- package/models/catalog.cattle.io.app.js +112 -37
- package/models/cluster.js +11 -0
- package/models/fleet.cattle.io.bundle.js +40 -2
- package/models/fleet.cattle.io.gitrepo.js +169 -109
- package/models/management.cattle.io.fleetworkspace.js +4 -0
- package/models/management.cattle.io.kontainerdriver.js +7 -0
- package/models/nodedriver.js +4 -1
- package/models/provisioning.cattle.io.cluster.js +24 -0
- package/models/secret.js +1 -1
- package/package.json +4 -4
- package/pages/auth/login.vue +4 -2
- package/pages/auth/verify.vue +11 -1
- package/pages/c/_cluster/apps/charts/chart.vue +1 -0
- package/pages/c/_cluster/apps/charts/index.vue +6 -4
- package/pages/c/_cluster/apps/charts/install.vue +1 -1
- package/pages/c/_cluster/explorer/ConfigBadge.vue +3 -5
- package/pages/c/_cluster/explorer/EventsTable.vue +3 -2
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +9 -9
- package/pages/c/_cluster/explorer/index.vue +33 -35
- package/pages/c/_cluster/explorer/tools/index.vue +17 -4
- package/pages/c/_cluster/fleet/index.vue +0 -5
- package/pages/c/_cluster/legacy/project/index.vue +1 -1
- package/pages/c/_cluster/settings/performance.vue +52 -53
- package/pages/c/_cluster/uiplugins/index.vue +21 -22
- package/pages/home.vue +17 -12
- package/pages/prefs.vue +5 -1
- package/plugins/shortkey.js +10 -1
- package/plugins/steve/steve-pagination-utils.ts +58 -8
- package/promptRemove/management.cattle.io.fleetworkspace.vue +98 -0
- package/promptRemove/management.cattle.io.globalrole.vue +1 -1
- package/promptRemove/management.cattle.io.project.vue +2 -8
- package/promptRemove/management.cattle.io.roletemplate.vue +1 -1
- package/promptRemove/mixin/roleDeletionCheck.js +1 -7
- package/promptRemove/pod.vue +7 -28
- package/rancher-components/Card/Card.vue +9 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +42 -6
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +30 -3
- package/rancher-components/Form/Radio/RadioButton.vue +18 -3
- package/rancher-components/Form/Radio/RadioGroup.vue +39 -5
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +13 -1
- package/rancher-components/RcButton/RcButton.test.ts +97 -0
- package/rancher-components/RcButton/RcButton.vue +14 -9
- package/rancher-components/RcDropdown/RcDropdown.vue +3 -1
- package/rancher-components/RcDropdown/RcDropdownItem.vue +8 -14
- package/rancher-components/RcDropdown/RcDropdownMenu.vue +66 -0
- package/rancher-components/RcDropdown/index.ts +1 -0
- package/rancher-components/RcDropdown/types.ts +27 -0
- package/rancher-components/RcDropdown/useDropdownContext.ts +5 -2
- package/scripts/typegen.sh +1 -0
- package/store/__tests__/auth.test.ts +120 -0
- package/store/action-menu.js +13 -3
- package/store/auth.js +14 -9
- package/store/catalog.js +14 -7
- package/store/features.js +1 -0
- package/store/prefs.js +9 -28
- package/store/type-map.utils.ts +4 -0
- package/types/resources/settings.d.ts +27 -20
- package/types/shell/index.d.ts +18 -2
- package/utils/__tests__/array.test.ts +13 -1
- package/utils/__tests__/string.test.ts +80 -1
- package/utils/array.ts +13 -0
- package/utils/auth.js +4 -0
- package/utils/cluster.js +1 -1
- package/{edit/monitoring.coreos.com.prometheusrule → utils}/duration.js +5 -3
- package/utils/pagination-utils.ts +15 -2
- package/utils/string.js +31 -7
- package/utils/validators/formRules/__tests__/index.test.ts +27 -0
- package/utils/validators/formRules/index.ts +16 -0
- package/edit/provisioning.cattle.io.cluster/import.vue +0 -198
package/store/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GITHUB_NONCE, GITHUB_REDIRECT, GITHUB_SCOPE } from '@shell/config/query-params';
|
|
2
2
|
import { NORMAN } from '@shell/config/types';
|
|
3
3
|
import { _MULTI } from '@shell/plugins/dashboard-store/actions';
|
|
4
|
-
import { addObjects, findBy } from '@shell/utils/array';
|
|
4
|
+
import { addObjects, findBy, joinStringList } from '@shell/utils/array';
|
|
5
5
|
import { openAuthPopup, returnTo } from '@shell/utils/auth';
|
|
6
6
|
import { base64Encode } from '@shell/utils/crypto';
|
|
7
7
|
import { removeEmberPage } from '@shell/utils/ember-page';
|
|
@@ -18,12 +18,12 @@ export const BASE_SCOPES = {
|
|
|
18
18
|
|
|
19
19
|
const KEY = 'rc_nonce';
|
|
20
20
|
|
|
21
|
-
const ERR_NONCE = 'nonce';
|
|
22
|
-
|
|
23
21
|
export const LOGIN_ERRORS = {
|
|
24
22
|
CLIENT: 'client',
|
|
25
23
|
CLIENT_UNAUTHORIZED: 'client_unauthorized',
|
|
26
|
-
SERVER: 'server'
|
|
24
|
+
SERVER: 'server',
|
|
25
|
+
NONCE: 'nonce',
|
|
26
|
+
USER_UNAUTHORIZED: 'user_unauthorized',
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
export const state = function() {
|
|
@@ -230,18 +230,22 @@ export const actions = {
|
|
|
230
230
|
const encodedNonce = await dispatch('encodeNonce', baseNonce);
|
|
231
231
|
|
|
232
232
|
const fromQuery = unescape(parseUrl(redirectUrl).query?.[GITHUB_SCOPE] || '');
|
|
233
|
-
|
|
233
|
+
let scopes = fromQuery.split(/[, ]+/).filter((x) => !!x);
|
|
234
234
|
|
|
235
235
|
if (BASE_SCOPES[provider]) {
|
|
236
236
|
addObjects(scopes, BASE_SCOPES[provider]);
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
// Need to merge these 2 formats preventing duplicates between code and UI, e.g.
|
|
240
|
+
// [ 'openid profile email' ] from BASE_SCOPES
|
|
241
|
+
// 'openid profile email customScope' from the UI
|
|
242
|
+
if (opt.scopes) {
|
|
243
|
+
scopes = [joinStringList(scopes[0], opt.scopes)];
|
|
241
244
|
}
|
|
242
245
|
|
|
243
246
|
let url = removeParam(redirectUrl, GITHUB_SCOPE);
|
|
244
247
|
|
|
248
|
+
// TODO: #13457 - Verify use case of scopesJoinChar anywhere outside this repository
|
|
245
249
|
const params = {
|
|
246
250
|
[GITHUB_SCOPE]: scopes.join(opt.scopesJoinChar || ','), // Some providers won't accept comma separated scopes
|
|
247
251
|
[GITHUB_NONCE]: encodedNonce
|
|
@@ -267,13 +271,13 @@ export const actions = {
|
|
|
267
271
|
try {
|
|
268
272
|
parsed = JSON.parse(expectJSON);
|
|
269
273
|
} catch {
|
|
270
|
-
return
|
|
274
|
+
return LOGIN_ERRORS.NONCE;
|
|
271
275
|
}
|
|
272
276
|
|
|
273
277
|
const expect = parsed.nonce;
|
|
274
278
|
|
|
275
279
|
if ( !expect || expect !== nonce ) {
|
|
276
|
-
return
|
|
280
|
+
return LOGIN_ERRORS.NONCE;
|
|
277
281
|
}
|
|
278
282
|
|
|
279
283
|
const body = { code };
|
|
@@ -310,6 +314,7 @@ export const actions = {
|
|
|
310
314
|
const url = await dispatch('redirectTo', {
|
|
311
315
|
provider,
|
|
312
316
|
redirectUrl,
|
|
317
|
+
scopes: body.scope,
|
|
313
318
|
test: true,
|
|
314
319
|
redirect: false
|
|
315
320
|
});
|
package/store/catalog.js
CHANGED
|
@@ -31,6 +31,13 @@ const CERTIFIED_SORTS = {
|
|
|
31
31
|
other: 3,
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
export const APP_UPGRADE_STATUS = {
|
|
35
|
+
NOT_APPLICABLE: 'not_applicable', // managed by fleet
|
|
36
|
+
NO_UPGRADE: 'no_upgrade', // no upgrade found
|
|
37
|
+
SINGLE_UPGRADE: 'single_upgrade', // a version available to upgrade to
|
|
38
|
+
MULTIPLE_UPGRADES: 'multiple_upgrades' // more than one match found
|
|
39
|
+
};
|
|
40
|
+
|
|
34
41
|
export const WINDOWS = 'windows';
|
|
35
42
|
export const LINUX = 'linux';
|
|
36
43
|
|
|
@@ -101,7 +108,7 @@ export const getters = {
|
|
|
101
108
|
|
|
102
109
|
chart(state, getters) {
|
|
103
110
|
return ({
|
|
104
|
-
key, repoType, repoName, chartName,
|
|
111
|
+
key, repoType, repoName, chartName, includeHidden, showDeprecated, multiple
|
|
105
112
|
}) => {
|
|
106
113
|
if ( key && !repoType && !repoName && !chartName) {
|
|
107
114
|
const parsed = parseKey(key);
|
|
@@ -111,7 +118,7 @@ export const getters = {
|
|
|
111
118
|
chartName = parsed.chartName;
|
|
112
119
|
}
|
|
113
120
|
|
|
114
|
-
let
|
|
121
|
+
let matchingCharts = filterBy(getters.charts, {
|
|
115
122
|
repoType,
|
|
116
123
|
repoName,
|
|
117
124
|
chartName,
|
|
@@ -119,18 +126,18 @@ export const getters = {
|
|
|
119
126
|
});
|
|
120
127
|
|
|
121
128
|
if ( includeHidden === false ) {
|
|
122
|
-
|
|
129
|
+
matchingCharts = matchingCharts.filter((x) => !x.hidden);
|
|
123
130
|
}
|
|
124
131
|
|
|
125
|
-
if ( !
|
|
132
|
+
if ( !matchingCharts.length ) {
|
|
126
133
|
return;
|
|
127
134
|
}
|
|
128
135
|
|
|
129
|
-
if (
|
|
130
|
-
|
|
136
|
+
if (multiple) {
|
|
137
|
+
return matchingCharts;
|
|
131
138
|
}
|
|
132
139
|
|
|
133
|
-
return
|
|
140
|
+
return matchingCharts[0];
|
|
134
141
|
};
|
|
135
142
|
},
|
|
136
143
|
|
package/store/features.js
CHANGED
|
@@ -36,6 +36,7 @@ export const FLEET_WORKSPACE_BACK = create('provisioningv2-fleet-workspace-back-
|
|
|
36
36
|
export const STEVE_CACHE = create('ui-sql-cache', false);
|
|
37
37
|
export const UIEXTENSION = create('uiextension', true);
|
|
38
38
|
export const PROVISIONING_PRE_BOOTSTRAP = create('provisioningprebootstrap', false);
|
|
39
|
+
export const SCHEDULING_CUSTOMIZATION = create('cluster-agent-scheduling-customization', false);
|
|
39
40
|
|
|
40
41
|
// Not currently used.. no point defining ones we don't use
|
|
41
42
|
// export const EMBEDDED_CLUSTER_API = create('embedded-cluster-api', true);
|
package/store/prefs.js
CHANGED
|
@@ -130,6 +130,7 @@ export const state = function() {
|
|
|
130
130
|
cookiesLoaded: false,
|
|
131
131
|
data: {},
|
|
132
132
|
definitions,
|
|
133
|
+
authRedirect: null
|
|
133
134
|
};
|
|
134
135
|
};
|
|
135
136
|
|
|
@@ -215,6 +216,9 @@ export const getters = {
|
|
|
215
216
|
case (afterLoginRoutePref === 'home'):
|
|
216
217
|
return { name: 'home' };
|
|
217
218
|
case (afterLoginRoutePref === 'last-visited'): {
|
|
219
|
+
if (state.authRedirect) {
|
|
220
|
+
return state.authRedirect;
|
|
221
|
+
}
|
|
218
222
|
const lastVisitedPref = getters['get'](LAST_VISITED);
|
|
219
223
|
|
|
220
224
|
if (lastVisitedPref) {
|
|
@@ -265,6 +269,10 @@ export const mutations = {
|
|
|
265
269
|
setDefinition(state, { name, definition = {} }) {
|
|
266
270
|
state.definitions[name] = definition;
|
|
267
271
|
},
|
|
272
|
+
|
|
273
|
+
setAuthRedirect(state, route) {
|
|
274
|
+
state.authRedirect = route;
|
|
275
|
+
}
|
|
268
276
|
};
|
|
269
277
|
|
|
270
278
|
export const actions = {
|
|
@@ -491,9 +499,7 @@ export const actions = {
|
|
|
491
499
|
return;
|
|
492
500
|
}
|
|
493
501
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
return dispatch('set', { key: LAST_VISITED, value: toSave });
|
|
502
|
+
return dispatch('set', { key: LAST_VISITED, value: route });
|
|
497
503
|
},
|
|
498
504
|
|
|
499
505
|
toggleTheme({ getters, dispatch }) {
|
|
@@ -523,28 +529,3 @@ export const actions = {
|
|
|
523
529
|
}
|
|
524
530
|
}
|
|
525
531
|
};
|
|
526
|
-
|
|
527
|
-
function getLoginRoute(route) {
|
|
528
|
-
let parts = route.name?.split('-') || [];
|
|
529
|
-
const params = {};
|
|
530
|
-
const routeParams = route.params || {};
|
|
531
|
-
|
|
532
|
-
// Find the 'resource' part of the route, if it is there
|
|
533
|
-
const index = parts.findIndex((p) => p === 'resource');
|
|
534
|
-
|
|
535
|
-
if (index >= 0) {
|
|
536
|
-
parts = parts.slice(0, index);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// Just keep the params that are needed
|
|
540
|
-
parts.forEach((param) => {
|
|
541
|
-
if (routeParams[param]) {
|
|
542
|
-
params[param] = routeParams[param];
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
return {
|
|
547
|
-
name: parts.join('-'),
|
|
548
|
-
params
|
|
549
|
-
};
|
|
550
|
-
}
|
package/store/type-map.utils.ts
CHANGED
|
@@ -123,6 +123,10 @@ export function createHeaders(
|
|
|
123
123
|
* Given a schema's attribute.column value create a header
|
|
124
124
|
*/
|
|
125
125
|
export function headerFromSchemaColString(colName: string, schema: Schema, rootGetters: VuexStoreGetters, pagination: boolean, ageColumn: TableColumn): TableColumn {
|
|
126
|
+
if (!schema) {
|
|
127
|
+
throw new Error(`Unable to create header for column '${ colName }' from schema: schema is missing`);
|
|
128
|
+
}
|
|
129
|
+
|
|
126
130
|
const col = schema.attributes.columns.find((c) => c.name === colName);
|
|
127
131
|
|
|
128
132
|
if (!col) {
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
export interface PaginationSettingsStore {
|
|
3
|
+
[name: string]: {
|
|
4
|
+
resources: {
|
|
5
|
+
/**
|
|
6
|
+
* Enable for all resources in this store
|
|
7
|
+
*/
|
|
8
|
+
enableAll: boolean,
|
|
9
|
+
enableSome: {
|
|
10
|
+
/**
|
|
11
|
+
* Specific resource type to enable
|
|
12
|
+
*/
|
|
13
|
+
enabled: (string | { resource: string, context: string[]})[],
|
|
14
|
+
/**
|
|
15
|
+
* There's no hardcoded headers or custom list for the resource type, headers will be generated from schema attributes.columns
|
|
16
|
+
*/
|
|
17
|
+
generic: boolean,
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
1
23
|
/**
|
|
2
24
|
* Settings to handle server side pagination
|
|
3
25
|
*/
|
|
@@ -6,29 +28,14 @@ export interface PaginationSettings {
|
|
|
6
28
|
* Global setting to enable or disable
|
|
7
29
|
*/
|
|
8
30
|
enabled: boolean,
|
|
31
|
+
/**
|
|
32
|
+
* Override `stores` and apply pagination to a set of default resource types that can change between versions
|
|
33
|
+
*/
|
|
34
|
+
useDefaultStores: boolean,
|
|
9
35
|
/**
|
|
10
36
|
* Should pagination be enabled for resources in a store
|
|
11
37
|
*/
|
|
12
|
-
stores:
|
|
13
|
-
[name: string]: {
|
|
14
|
-
resources: {
|
|
15
|
-
/**
|
|
16
|
-
* Enable for all resources in this store
|
|
17
|
-
*/
|
|
18
|
-
enableAll: boolean,
|
|
19
|
-
enableSome: {
|
|
20
|
-
/**
|
|
21
|
-
* Specific resource type to enable
|
|
22
|
-
*/
|
|
23
|
-
enabled: (string | { resource: string, context: string[]})[],
|
|
24
|
-
/**
|
|
25
|
-
* There's no hardcoded headers or custom list for the resource type, headers will be generated from schema attributes.columns
|
|
26
|
-
*/
|
|
27
|
-
generic: boolean,
|
|
28
|
-
},
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
38
|
+
stores: PaginationSettingsStore | undefined
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
type Links = {
|
package/types/shell/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export const RESOURCE_QUOTA: "field.cattle.io/resourceQuota";
|
|
|
42
42
|
export const AZURE_MIGRATED: "auth.cattle.io/azuread-endpoint-migrated";
|
|
43
43
|
export const WORKSPACE_ANNOTATION: "objectset.rio.cattle.io/id";
|
|
44
44
|
export const NODE_ARCHITECTURE: "kubernetes.io/arch";
|
|
45
|
+
export const IMPORTED_CLUSTER_VERSION_MANAGEMENT: "rancher.io/imported-cluster-version-management";
|
|
45
46
|
export namespace KUBERNETES {
|
|
46
47
|
let SERVICE_ACCOUNT_UID: string;
|
|
47
48
|
let SERVICE_ACCOUNT_NAME: string;
|
|
@@ -81,6 +82,7 @@ export namespace CAPI {
|
|
|
81
82
|
let MACHINE_NAME: string;
|
|
82
83
|
let DELETE_MACHINE: string;
|
|
83
84
|
let PROVIDER: string;
|
|
85
|
+
let HUMAN_NAME: string;
|
|
84
86
|
let SECRET_AUTH: string;
|
|
85
87
|
let SECRET_WILL_DELETE: string;
|
|
86
88
|
let UI_CUSTOM_PROVIDER: string;
|
|
@@ -122,6 +124,7 @@ export namespace CATALOG {
|
|
|
122
124
|
let HIDDEN_REPO: string;
|
|
123
125
|
}
|
|
124
126
|
export namespace FLEET {
|
|
127
|
+
export let REPO_NAME: string;
|
|
125
128
|
export let CLUSTER_DISPLAY_NAME: string;
|
|
126
129
|
export let CLUSTER_NAME: string;
|
|
127
130
|
export let BUNDLE_ID: string;
|
|
@@ -132,6 +135,8 @@ export namespace FLEET {
|
|
|
132
135
|
let CLUSTER_NAMESPACE_1: string;
|
|
133
136
|
export { CLUSTER_NAMESPACE_1 as CLUSTER_NAMESPACE };
|
|
134
137
|
export let CLUSTER: string;
|
|
138
|
+
export let CREATED_BY_USER_ID: string;
|
|
139
|
+
export let CREATED_BY_USER_NAME: string;
|
|
135
140
|
}
|
|
136
141
|
export namespace RBAC {
|
|
137
142
|
let PRODUCT: string;
|
|
@@ -2220,7 +2225,7 @@ export function getVersionData(): {
|
|
|
2220
2225
|
export function setVersionData(v: any): void;
|
|
2221
2226
|
export function getKubeVersionData(): {};
|
|
2222
2227
|
export function setKubeVersionData(v: any): void;
|
|
2223
|
-
export const CURRENT_RANCHER_VERSION: "2.
|
|
2228
|
+
export const CURRENT_RANCHER_VERSION: "2.11";
|
|
2224
2229
|
}
|
|
2225
2230
|
|
|
2226
2231
|
// @shell/mixins/create-edit-view/impl
|
|
@@ -3172,6 +3177,7 @@ export const FLEET_WORKSPACE_BACK: any;
|
|
|
3172
3177
|
export const STEVE_CACHE: any;
|
|
3173
3178
|
export const UIEXTENSION: any;
|
|
3174
3179
|
export const PROVISIONING_PRE_BOOTSTRAP: any;
|
|
3180
|
+
export const SCHEDULING_CUSTOMIZATION: any;
|
|
3175
3181
|
export namespace getters {
|
|
3176
3182
|
function get(state: any, getters: any, rootState: any, rootGetters: any): (name: any) => any;
|
|
3177
3183
|
}
|
|
@@ -3260,6 +3266,7 @@ export function state(): {
|
|
|
3260
3266
|
cookiesLoaded: boolean;
|
|
3261
3267
|
data: {};
|
|
3262
3268
|
definitions: {};
|
|
3269
|
+
authRedirect: any;
|
|
3263
3270
|
};
|
|
3264
3271
|
export namespace getters {
|
|
3265
3272
|
function get(state: any): (key: any) => any;
|
|
@@ -3280,6 +3287,7 @@ export namespace mutations {
|
|
|
3280
3287
|
name: any;
|
|
3281
3288
|
definition?: {};
|
|
3282
3289
|
}): void;
|
|
3290
|
+
function setAuthRedirect(state: any, route: any): void;
|
|
3283
3291
|
}
|
|
3284
3292
|
export namespace actions {
|
|
3285
3293
|
function set({ dispatch, commit, rootGetters, state }: {
|
|
@@ -3785,6 +3793,13 @@ export function generateZip(files: any): any;
|
|
|
3785
3793
|
export function downloadUrl(url: any, id?: string): void;
|
|
3786
3794
|
}
|
|
3787
3795
|
|
|
3796
|
+
// @shell/utils/duration
|
|
3797
|
+
|
|
3798
|
+
declare module '@shell/utils/duration' {
|
|
3799
|
+
export function toMilliseconds(input: any): number;
|
|
3800
|
+
export function toSeconds(input: any): number;
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3788
3803
|
// @shell/utils/dynamic-importer
|
|
3789
3804
|
|
|
3790
3805
|
declare module '@shell/utils/dynamic-importer' {
|
|
@@ -4356,7 +4371,7 @@ export function random32(count: any): number | number[];
|
|
|
4356
4371
|
export function randomStr(length?: number, chars?: string): any;
|
|
4357
4372
|
export function formatPercent(value: any, maxPrecision?: number): string;
|
|
4358
4373
|
export function pluralize(str: any): any;
|
|
4359
|
-
export function resourceNames(names: any,
|
|
4374
|
+
export function resourceNames(names: any, t: any, options?: {}): any;
|
|
4360
4375
|
export function indent(lines: any, count?: number, token?: string, afterRegex?: any): any;
|
|
4361
4376
|
export function decamelize(str: any): any;
|
|
4362
4377
|
export function dasherize(str: any): any;
|
|
@@ -4379,6 +4394,7 @@ export function sanitizeIP(v: any): any;
|
|
|
4379
4394
|
*/
|
|
4380
4395
|
export function xOfy(x: any, y: any): string;
|
|
4381
4396
|
export function isBase64(value: any): boolean;
|
|
4397
|
+
export function generateRandomAlphaString(length: any): string;
|
|
4382
4398
|
export namespace CHARSET {
|
|
4383
4399
|
export { num as NUMERIC };
|
|
4384
4400
|
export let NO_VOWELS: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
addObject, addObjects, clear, filterBy, findBy, getUniqueLabelKeys, insertAt, isArray, removeAt, removeObject, removeObjects, replaceWith, sameContents, uniq
|
|
2
|
+
addObject, addObjects, clear, filterBy, findBy, getUniqueLabelKeys, insertAt, isArray, joinStringList, removeAt, removeObject, removeObjects, replaceWith, sameContents, uniq
|
|
3
3
|
} from '@shell/utils/array';
|
|
4
4
|
|
|
5
5
|
interface Obj {
|
|
@@ -500,3 +500,15 @@ describe('fx: getUniqueLabelKeys', () => {
|
|
|
500
500
|
expect(result).toStrictEqual(expected);
|
|
501
501
|
});
|
|
502
502
|
});
|
|
503
|
+
|
|
504
|
+
describe('fx: joinStringList', () => {
|
|
505
|
+
it('should join two lists of strings', () => {
|
|
506
|
+
const a = 'a b c';
|
|
507
|
+
const b = 'b c d';
|
|
508
|
+
const separator = ' ';
|
|
509
|
+
|
|
510
|
+
const result = joinStringList(a, b, separator);
|
|
511
|
+
|
|
512
|
+
expect(result).toBe('a b c d');
|
|
513
|
+
});
|
|
514
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decodeHtml } from '@shell/utils/string';
|
|
1
|
+
import { decodeHtml, resourceNames } from '@shell/utils/string';
|
|
2
2
|
|
|
3
3
|
describe('fx: decodeHtml', () => {
|
|
4
4
|
it('should decode HTML values from escaped string into valid markup', () => {
|
|
@@ -10,3 +10,82 @@ describe('fx: decodeHtml', () => {
|
|
|
10
10
|
expect(result).toStrictEqual(expectation);
|
|
11
11
|
});
|
|
12
12
|
});
|
|
13
|
+
|
|
14
|
+
describe('fx: resourceNames', () => {
|
|
15
|
+
// default plusMore function
|
|
16
|
+
const t = (key: string, options: { count: number }) => {
|
|
17
|
+
switch (key) {
|
|
18
|
+
case 'generic.and':
|
|
19
|
+
return ' and ';
|
|
20
|
+
case 'generic.comma':
|
|
21
|
+
return ', ';
|
|
22
|
+
case 'promptRemove.andOthers': {
|
|
23
|
+
if (options.count === 0) {
|
|
24
|
+
return '.';
|
|
25
|
+
}
|
|
26
|
+
if (options.count === 1) {
|
|
27
|
+
return 'and <b>one other</b>.';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return `and <b>${ options.count } others</b>.`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
describe.each([
|
|
36
|
+
['no options',
|
|
37
|
+
[
|
|
38
|
+
[[], '', {}],
|
|
39
|
+
[['item1'], '<b>item1</b>.', {}],
|
|
40
|
+
[['item1', 'item2'], '<b>item1</b> and <b>item2</b>.', {}],
|
|
41
|
+
[['item1', 'item2', 'item3'], '<b>item1</b>, <b>item2</b> and <b>item3</b>.', {}],
|
|
42
|
+
[['item1', 'item2', 'item3', 'item4', 'item5'], '<b>item1</b>, <b>item2</b>, <b>item3</b>, <b>item4</b> and <b>item5</b>.', {}],
|
|
43
|
+
[['item1', 'item2', 'item3', 'item4', 'item5', 'item6'], '<b>item1</b>, <b>item2</b>, <b>item3</b>, <b>item4</b>, <b>item5</b>and <b>one other</b>.', {}],
|
|
44
|
+
[['item1', 'item2', 'item3', 'item4', 'item5', 'item6', 'item7'], '<b>item1</b>, <b>item2</b>, <b>item3</b>, <b>item4</b>, <b>item5</b>and <b>2 others</b>.', {}],
|
|
45
|
+
]
|
|
46
|
+
],
|
|
47
|
+
['plusMore option',
|
|
48
|
+
[
|
|
49
|
+
[[], '', { plusMore: 'foo' }],
|
|
50
|
+
[['item1'], '<b>item1</b>.', { plusMore: 'foo' }],
|
|
51
|
+
[['item1', 'item2'], '<b>item1</b> and <b>item2</b>.', { plusMore: 'foo' }],
|
|
52
|
+
[['item1', 'item2', 'item3'], '<b>item1</b>, <b>item2</b> and <b>item3</b>.', { plusMore: 'foo' }],
|
|
53
|
+
[['item1', 'item2', 'item3', 'item4', 'item5'], '<b>item1</b>, <b>item2</b>, <b>item3</b>, <b>item4</b> and <b>item5</b>.', { plusMore: 'foo' }],
|
|
54
|
+
[['item1', 'item2', 'item3', 'item4', 'item5', 'item6'], '<b>item1</b>, <b>item2</b>, <b>item3</b>, <b>item4</b>, <b>item5</b>foo', { plusMore: 'foo' }],
|
|
55
|
+
[['item1', 'item2', 'item3', 'item4', 'item5', 'item6', 'item7'], '<b>item1</b>, <b>item2</b>, <b>item3</b>, <b>item4</b>, <b>item5</b>foo', { plusMore: 'foo' }],
|
|
56
|
+
]
|
|
57
|
+
],
|
|
58
|
+
['endString option',
|
|
59
|
+
[
|
|
60
|
+
[[], '', { endString: false }],
|
|
61
|
+
[['item1'], '<b>item1</b> ', { endString: false }],
|
|
62
|
+
[['item1', 'item2'], '<b>item1</b> and <b>item2</b> ', { endString: false }],
|
|
63
|
+
[['item1', 'item2'], '<b>item1</b> and <b>item2</b>.', { endString: '' }],
|
|
64
|
+
[['item1', 'item2'], '<b>item1</b> and <b>item2</b>foo', { endString: 'foo' }],
|
|
65
|
+
]
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
'plusMore & endString options', [
|
|
69
|
+
[[], '', { plusMore: '', endString: false }],
|
|
70
|
+
[[], '', { plusMore: 'foo', endString: 'foo' }],
|
|
71
|
+
[['item1'], '<b>item1</b>foo', { plusMore: 'foo', endString: 'foo' }],
|
|
72
|
+
[['item1', 'item2'], '<b>item1</b> and <b>item2</b>foo', { plusMore: 'foo', endString: 'foo' }],
|
|
73
|
+
[['item1', 'item2'], '<b>item1</b> and <b>item2</b> ', { plusMore: 'foo', endString: false }],
|
|
74
|
+
[['item1', 'item2', 'item3', 'item4', 'item5', 'item6'], '<b>item1</b>, <b>item2</b>, <b>item3</b>, <b>item4</b>, <b>item5</b>foo', { plusMore: 'foo', endString: 'foo' }],
|
|
75
|
+
[['item1', 'item2', 'item3', 'item4', 'item5', 'item6', 'item7'], '<b>item1</b>, <b>item2</b>, <b>item3</b>, <b>item4</b>, <b>item5</b>foo', { plusMore: 'foo', endString: 'foo' }],
|
|
76
|
+
[['item1', 'item2', 'item3', 'item4', 'item5', 'item6'], '<b>item1</b>, <b>item2</b>, <b>item3</b>, <b>item4</b>, <b>item5</b>foo', { plusMore: 'foo', endString: false }],
|
|
77
|
+
[['item1', 'item2', 'item3', 'item4', 'item5', 'item6', 'item7'], '<b>item1</b>, <b>item2</b>, <b>item3</b>, <b>item4</b>, <b>item5</b>foo', { plusMore: 'foo', endString: false }],
|
|
78
|
+
]
|
|
79
|
+
]
|
|
80
|
+
])('should build a single message from a list of names, with: %p', (_, args) => {
|
|
81
|
+
it.each(args)(`having: %p`, (
|
|
82
|
+
names,
|
|
83
|
+
expectation,
|
|
84
|
+
options,
|
|
85
|
+
) => {
|
|
86
|
+
const result = resourceNames(names, t, options);
|
|
87
|
+
|
|
88
|
+
expect(result).toStrictEqual(expectation);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
package/utils/array.ts
CHANGED
|
@@ -241,3 +241,16 @@ export function getUniqueLabelKeys<T extends KubeResource>(aryResources: T[]): s
|
|
|
241
241
|
|
|
242
242
|
return Object.keys(uniqueObj).sort();
|
|
243
243
|
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Join list as string into a new string without duplicates
|
|
247
|
+
* @param {string} a 'a b c'
|
|
248
|
+
* @param {string} b 'b c d'
|
|
249
|
+
* @param {string} [separator=' ']
|
|
250
|
+
* @return {string} 'a b c d'
|
|
251
|
+
*/
|
|
252
|
+
export const joinStringList = (a: string, b: string, separator = ' '): string => {
|
|
253
|
+
const all = a.split(separator).concat(b.split(separator));
|
|
254
|
+
|
|
255
|
+
return [...new Set(all)].join(separator);
|
|
256
|
+
};
|
package/utils/auth.js
CHANGED
|
@@ -315,6 +315,10 @@ export function isLoggedIn(store, me) {
|
|
|
315
315
|
export function notLoggedIn(store, redirect, route) {
|
|
316
316
|
store.commit('auth/hasAuth', true);
|
|
317
317
|
|
|
318
|
+
if (!route.name.includes('auth')) {
|
|
319
|
+
store.commit('prefs/setAuthRedirect', route);
|
|
320
|
+
}
|
|
321
|
+
|
|
318
322
|
if ( route.name === 'index' ) {
|
|
319
323
|
return redirect('/auth/login');
|
|
320
324
|
} else {
|
package/utils/cluster.js
CHANGED
|
@@ -81,7 +81,7 @@ export function paginationFilterOnlyKubernetesClusters(store) {
|
|
|
81
81
|
|
|
82
82
|
return PaginationParamFilter.createMultipleFields([
|
|
83
83
|
new PaginationFilterField({
|
|
84
|
-
field: `metadata.labels
|
|
84
|
+
field: `metadata.labels[${ CAPI.PROVIDER }]`,
|
|
85
85
|
equals: false,
|
|
86
86
|
value: VIRTUAL_HARVESTER_PROVIDER,
|
|
87
87
|
exact: true
|
|
@@ -8,9 +8,7 @@ const UNIT_TO_MS =
|
|
|
8
8
|
w: 7 * 24 * 60 * 60 * 1000,
|
|
9
9
|
y: 365 * 24 * 60 * 60 * 1000
|
|
10
10
|
};
|
|
11
|
-
|
|
12
|
-
// https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/#rule
|
|
13
|
-
// https://prometheus.io/docs/prometheus/latest/configuration/configuration/#duration
|
|
11
|
+
|
|
14
12
|
const DURATION_REGEX = /^(?:([0-9]+)y)?(?:([0-9]+)w)?(?:([0-9]+)d)?(?:([0-9]+)h)?(?:([0-9]+)m)?(?:([0-9]+)s)?(?:([0-9]+)ms)?$/;
|
|
15
13
|
|
|
16
14
|
export function toMilliseconds(input) {
|
|
@@ -39,3 +37,7 @@ export function toMilliseconds(input) {
|
|
|
39
37
|
|
|
40
38
|
return 0;
|
|
41
39
|
}
|
|
40
|
+
|
|
41
|
+
export function toSeconds(input) {
|
|
42
|
+
return Math.floor(toMilliseconds(input) / 1000);
|
|
43
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PaginationSettings } from '@shell/types/resources/settings';
|
|
1
|
+
import { PaginationSettings, PaginationSettingsStore } from '@shell/types/resources/settings';
|
|
2
2
|
import {
|
|
3
3
|
NAMESPACE_FILTER_ALL_USER as ALL_USER,
|
|
4
4
|
NAMESPACE_FILTER_ALL as ALL,
|
|
@@ -14,6 +14,7 @@ import { sameArrayObjects } from '@shell/utils/array';
|
|
|
14
14
|
import { isEqual } from '@shell/utils/object';
|
|
15
15
|
import { STEVE_CACHE } from '@shell/store/features';
|
|
16
16
|
import { getPerformanceSetting } from '@shell/utils/settings';
|
|
17
|
+
import { PAGINATION_SETTINGS_STORE_DEFAULTS } from '@shell/plugins/steve/steve-pagination-utils';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Helper functions for server side pagination
|
|
@@ -32,6 +33,18 @@ class PaginationUtils {
|
|
|
32
33
|
return perf.serverPagination;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
public getStoreSettings(ctx: any): PaginationSettingsStore
|
|
37
|
+
public getStoreSettings(serverPagination: PaginationSettings): PaginationSettingsStore
|
|
38
|
+
public getStoreSettings(arg: any | PaginationSettings): PaginationSettingsStore {
|
|
39
|
+
const serverPagination: PaginationSettings = arg?.rootGetters !== undefined ? this.getSettings(arg) : arg;
|
|
40
|
+
|
|
41
|
+
return serverPagination?.useDefaultStores ? this.getStoreDefault() : serverPagination?.stores || this.getStoreDefault();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public getStoreDefault(): PaginationSettingsStore {
|
|
45
|
+
return PAGINATION_SETTINGS_STORE_DEFAULTS;
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
isSteveCacheEnabled({ rootGetters }: any): boolean {
|
|
36
49
|
// We always get Feature flags as part of start up (see `dispatch('features/loadServer')` in loadManagement)
|
|
37
50
|
return rootGetters['features/get']?.(STEVE_CACHE);
|
|
@@ -58,7 +71,7 @@ class PaginationUtils {
|
|
|
58
71
|
return false;
|
|
59
72
|
}
|
|
60
73
|
|
|
61
|
-
const storeSettings = settings
|
|
74
|
+
const storeSettings = this.getStoreSettings(settings)?.[enabledFor.store];
|
|
62
75
|
|
|
63
76
|
// No pagination setting for target store, not enabled
|
|
64
77
|
if (!storeSettings) {
|
package/utils/string.js
CHANGED
|
@@ -151,16 +151,36 @@ export function pluralize(str) {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
export function resourceNames(names,
|
|
154
|
+
export function resourceNames(names, t, options = {}) {
|
|
155
|
+
const MAX_NAMES_COUNT = 5;
|
|
156
|
+
|
|
157
|
+
let { plusMore, endString } = options;
|
|
158
|
+
|
|
159
|
+
// plusMore default value
|
|
160
|
+
if (!plusMore) {
|
|
161
|
+
plusMore = t('promptRemove.andOthers', { count: names.length > MAX_NAMES_COUNT ? names.length - MAX_NAMES_COUNT : 0 });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// endString default value
|
|
165
|
+
if (!endString) {
|
|
166
|
+
endString = endString === false ? ' ' : '.';
|
|
167
|
+
}
|
|
168
|
+
|
|
155
169
|
return names.reduce((res, name, i) => {
|
|
156
|
-
if (i
|
|
157
|
-
|
|
170
|
+
if (i < MAX_NAMES_COUNT) {
|
|
171
|
+
res += `<b>${ escapeHtml( name ) }</b>`;
|
|
172
|
+
|
|
173
|
+
if (i === names.length - 1) {
|
|
174
|
+
res += endString;
|
|
175
|
+
} else if (i === names.length - 2) {
|
|
176
|
+
res += names.length <= 5 ? t('generic.and') : '';
|
|
177
|
+
} else {
|
|
178
|
+
res += i < MAX_NAMES_COUNT - 1 ? t('generic.comma') : '';
|
|
179
|
+
}
|
|
158
180
|
}
|
|
159
|
-
|
|
160
|
-
if (i ===
|
|
181
|
+
|
|
182
|
+
if (i === MAX_NAMES_COUNT) {
|
|
161
183
|
res += plusMore;
|
|
162
|
-
} else {
|
|
163
|
-
res += i === names.length - 2 ? t('generic.and') : t('generic.comma');
|
|
164
184
|
}
|
|
165
185
|
|
|
166
186
|
return res;
|
|
@@ -336,3 +356,7 @@ export function isBase64(value) {
|
|
|
336
356
|
|
|
337
357
|
return base64regex.test(value);
|
|
338
358
|
}
|
|
359
|
+
|
|
360
|
+
export function generateRandomAlphaString(length) {
|
|
361
|
+
return Array.from({ length }, () => String.fromCharCode(97 + Math.random() * 26 | 0)).join('');
|
|
362
|
+
}
|
|
@@ -244,6 +244,33 @@ describe('formRules', () => {
|
|
|
244
244
|
expect(formRuleResult).toStrictEqual(expectedResult);
|
|
245
245
|
});
|
|
246
246
|
|
|
247
|
+
describe('"registryUrl": has the expected output for each input', () => {
|
|
248
|
+
const expectedTranslation = JSON.stringify({ message: 'cluster.privateRegistry.privateRegistryUrlError' });
|
|
249
|
+
const testCases = [
|
|
250
|
+
// Empty
|
|
251
|
+
[undefined, undefined],
|
|
252
|
+
|
|
253
|
+
// Word
|
|
254
|
+
['registry', expectedTranslation],
|
|
255
|
+
|
|
256
|
+
// Without schema
|
|
257
|
+
['registry.io', undefined],
|
|
258
|
+
|
|
259
|
+
// With schemas
|
|
260
|
+
['http://registry.io', undefined],
|
|
261
|
+
['https://registry.io', undefined],
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
it.each(testCases)(
|
|
265
|
+
'should return undefined or correct message based on the provided url',
|
|
266
|
+
(url, expected) => {
|
|
267
|
+
const formRuleResult = formRules.registryUrl(url);
|
|
268
|
+
|
|
269
|
+
expect(formRuleResult).toStrictEqual(expected);
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
247
274
|
it('"ruleGroups" : returns undefined when rulegroups are supplied', () => {
|
|
248
275
|
const testValue = { groups: ['group1'] };
|
|
249
276
|
const formRuleResult = formRules.ruleGroups(testValue);
|