@rancher/shell 3.0.12-rc.2 → 3.0.12-rc.3
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/apis/impl/apis.ts +6 -0
- package/apis/index.ts +26 -0
- package/apis/intf/resources-api/cluster-api.ts +18 -0
- package/apis/intf/resources-api/mgmt-api.ts +15 -0
- package/apis/intf/resources-api/resource-base.ts +107 -0
- package/apis/intf/resources-api/resource-constants.ts +147 -0
- package/apis/intf/resources-api/resources-api.ts +143 -0
- package/apis/intf/resources.ts +49 -0
- package/apis/intf/{modal.ts → shell-api/modal.ts} +21 -26
- package/apis/intf/shell-api/proxy.ts +216 -0
- package/apis/intf/{slide-in.ts → shell-api/slide-in.ts} +4 -3
- package/apis/intf/{system.ts → shell-api/system.ts} +4 -1
- package/apis/intf/shell.ts +12 -6
- package/apis/resources/__tests__/resources-api-class.test.ts +550 -0
- package/apis/resources/index.ts +22 -0
- package/apis/resources/resources-api-class.ts +187 -0
- package/apis/shell/__tests__/proxy.test.ts +369 -0
- package/apis/shell/index.ts +8 -1
- package/apis/shell/modal.ts +4 -1
- package/apis/shell/notifications.ts +9 -6
- package/apis/shell/proxy.ts +256 -0
- package/apis/shell/slide-in.ts +4 -1
- package/apis/vue-shim.d.ts +2 -1
- package/assets/data/aws-regions.json +4 -0
- package/assets/fonts/lato/LatoLatin-Black.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Black.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-BlackItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-BlackItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Bold.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Bold.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-BoldItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-BoldItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Heavy.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Heavy.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-HeavyItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-HeavyItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Italic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Italic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Light.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Light.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-LightItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-LightItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Medium.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Medium.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-MediumItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-MediumItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Regular.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Regular.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Semibold.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Semibold.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff2 +0 -0
- package/assets/styles/base/_variables.scss +2 -0
- package/assets/styles/fonts/_fontstack.scss +132 -8
- package/assets/translations/en-us.yaml +22 -5
- package/chart/monitoring/index.vue +10 -1
- package/components/ActionDropdownShell.vue +2 -1
- package/components/CruResourceFooter.vue +9 -5
- package/components/ExplorerProjectsNamespaces.vue +1 -1
- package/components/InstallHelmCharts.vue +2 -2
- package/components/LandingPagePreference.vue +14 -5
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +15 -1
- package/components/Resource/Detail/Metadata/index.vue +6 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +12 -1
- package/components/Resource/Detail/SpacedRow.vue +3 -1
- package/components/Resource/Detail/TitleBar/index.vue +10 -11
- package/components/ResourceList/Masthead.vue +12 -8
- package/components/SelectIconGrid.vue +0 -10
- package/components/SingleClusterInfo.vue +1 -0
- package/components/SortableTable/__tests__/sorting.test.ts +126 -0
- package/components/SortableTable/index.vue +6 -9
- package/components/SortableTable/selection.js +23 -5
- package/components/SortableTable/sorting.js +6 -3
- package/components/Wizard.vue +14 -13
- package/components/fleet/FleetBundles.vue +100 -12
- package/components/fleet/FleetClusterTargets/index.vue +37 -15
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +149 -115
- package/components/fleet/__tests__/FleetClusters.test.ts +12 -12
- package/components/form/LabeledSelect.vue +20 -3
- package/components/form/NameNsDescription.vue +11 -0
- package/components/form/Security.vue +6 -2
- package/components/form/WorkloadPorts.vue +2 -7
- package/components/form/__tests__/Security.test.ts +76 -0
- package/components/formatter/Autoscaler.vue +4 -4
- package/components/formatter/ClusterKubeVersion.vue +27 -0
- package/components/formatter/ClusterLink.vue +1 -7
- package/components/formatter/ClusterProvider.vue +6 -10
- package/components/formatter/FleetSummaryGraph.vue +0 -3
- package/components/formatter/MachineSummaryGraph.vue +1 -1
- package/components/formatter/PodsUsage.vue +2 -2
- package/components/formatter/__tests__/Autoscaler.test.ts +19 -22
- package/components/formatter/__tests__/FleetSummaryGraph.test.ts +216 -0
- package/components/formatter/__tests__/PodsUsage.test.ts +6 -10
- package/components/nav/NamespaceFilter.vue +2 -2
- package/components/nav/TopLevelMenu.helper.ts +15 -3
- package/components/nav/TopLevelMenu.vue +16 -5
- package/components/nav/__tests__/TopLevelMenu.test.ts +145 -21
- package/components/templates/home.vue +18 -0
- package/components/templates/plain.vue +18 -0
- package/components/templates/standalone.vue +17 -0
- package/composables/useFormValidation.ts +93 -0
- package/composables/useVeeValidateField.test.ts +159 -0
- package/composables/useVeeValidateField.ts +67 -0
- package/config/pagination-table-headers.js +18 -1
- package/config/product/manager.js +82 -21
- package/config/router/routes.js +6 -0
- package/config/table-headers.js +20 -1
- package/config/types.js +2 -1
- package/core/__tests__/plugin-products.test.ts +904 -20
- package/core/plugin-products-base.ts +107 -7
- package/core/plugin-products.ts +4 -0
- package/core/plugin-types.ts +111 -1
- package/core/plugin.ts +15 -7
- package/core/productDebugger.js +9 -4
- package/core/types-provisioning.ts +43 -30
- package/core/types.ts +57 -20
- package/detail/__tests__/pod.test.ts +41 -0
- package/detail/harvesterhci.io.management.cluster.vue +6 -2
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +4 -10
- package/edit/auth/__tests__/azuread.test.ts +217 -34
- package/edit/auth/azuread.vue +122 -14
- package/edit/auth/oidc.vue +2 -2
- package/edit/networking.k8s.io.ingress/DefaultBackend.vue +13 -4
- package/edit/networking.k8s.io.ingress/RulePath.vue +8 -4
- package/edit/networking.k8s.io.ingress/index.vue +75 -20
- package/edit/provisioning.cattle.io.cluster/__tests__/MachinePool.test.ts +104 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +11 -7
- package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -4
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +37 -4
- package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +132 -7
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -1
- package/edit/secret/__tests__/ssh.test.ts +5 -6
- package/edit/secret/basic.vue +31 -0
- package/edit/secret/index.vue +68 -17
- package/edit/secret/registry.vue +38 -0
- package/edit/secret/ssh.vue +29 -0
- package/edit/secret/tls.vue +30 -0
- package/edit/service.vue +4 -4
- package/edit/workload/Upgrading.vue +3 -3
- package/edit/workload/__tests__/Upgrading.test.ts +6 -9
- package/edit/workload/mixins/workload.js +2 -1
- package/list/fleet.cattle.io.bundle.vue +7 -104
- package/list/fleet.cattle.io.clusterregistrationtoken.vue +20 -0
- package/list/provisioning.cattle.io.cluster.vue +262 -180
- package/list/utils/management.cattle.io.cluster.utils.ts +128 -0
- package/mixins/__tests__/chart.test.ts +112 -0
- package/mixins/brand.js +2 -1
- package/mixins/chart.js +12 -8
- package/mixins/resource-fetch-api-pagination.js +41 -5
- package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +67 -67
- package/models/__tests__/management.cattle.io.cluster.test.ts +1 -1
- package/models/__tests__/management.cattle.io.node.ts +6 -5
- package/models/__tests__/management.cattle.io.nodepool.ts +5 -4
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +32 -11
- package/models/base-cluster.x-k8s.io.js +26 -0
- package/models/cluster.js +1 -1
- package/models/cluster.x-k8s.io.machine.js +4 -22
- package/models/cluster.x-k8s.io.machinedeployment.js +2 -20
- package/models/cluster.x-k8s.io.machineset.js +2 -20
- package/models/compliance.cattle.io.clusterscan.js +130 -2
- package/models/ext.cattle.io.kubeconfig.ts +4 -7
- package/models/fleet-application.js +3 -1
- package/models/management.cattle.io.cluster.js +417 -40
- package/models/management.cattle.io.node.js +6 -4
- package/models/management.cattle.io.nodepool.js +1 -1
- package/models/networking.k8s.io.ingress.js +12 -4
- package/models/provisioning.cattle.io.cluster.js +47 -330
- package/models/rke.cattle.io.etcdsnapshot.js +1 -2
- package/package.json +11 -29
- package/pages/__tests__/readme.test.ts +49 -0
- package/pages/auth/setup.vue +2 -3
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +76 -0
- package/pages/c/_cluster/apps/charts/chart.vue +60 -8
- package/pages/c/_cluster/apps/charts/install.vue +10 -7
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +23 -25
- package/pages/c/_cluster/explorer/index.vue +5 -49
- package/pages/c/_cluster/istio/__tests__/istio.index.test.ts +194 -0
- package/pages/c/_cluster/istio/index.vue +21 -6
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -0
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +719 -2
- package/pages/c/_cluster/uiplugins/index.vue +203 -197
- package/pages/diagnostic.vue +13 -17
- package/pages/fail-whale.vue +18 -0
- package/pages/home.vue +77 -260
- package/pages/readme.vue +88 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +88 -0
- package/plugins/dashboard-store/actions.js +40 -18
- package/plugins/dashboard-store/resource-class.js +5 -2
- package/plugins/steve/__tests__/subscribe.spec.ts +6 -3
- package/plugins/steve/steve-pagination-utils.ts +11 -3
- package/plugins/steve/subscribe.js +35 -5
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +10 -4
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -52
- package/rancher-components/RcButton/RcButton.test.ts +37 -1
- package/rancher-components/RcButton/RcButton.vue +38 -8
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -8
- package/store/__tests__/catalog.test.ts +115 -1
- package/store/__tests__/type-map.test.ts +556 -1
- package/store/action-menu.js +8 -3
- package/store/auth.js +1 -1
- package/store/aws.js +27 -16
- package/store/catalog.js +27 -3
- package/store/digitalocean.js +20 -38
- package/store/index.js +2 -0
- package/store/linode.js +25 -40
- package/store/pnap.js +1 -0
- package/store/type-map.js +111 -29
- package/tsconfig.paths.json +8 -8
- package/types/kube/kube-api.ts +14 -1
- package/types/rancher/steve.api.ts +12 -12
- package/types/resources/settings.d.ts +2 -1
- package/types/shell/index.d.ts +102 -2
- package/types/store/dashboard-store.types.ts +108 -11
- package/types/store/pagination.types.ts +6 -3
- package/utils/__tests__/alertmanagerconfig.test.ts +117 -0
- package/utils/__tests__/async.test.ts +87 -0
- package/utils/__tests__/aws.test.ts +140 -0
- package/utils/__tests__/banners.test.ts +176 -0
- package/utils/__tests__/chart.test.ts +64 -1
- package/utils/__tests__/color.test.ts +226 -0
- package/utils/__tests__/duration.test.ts +140 -0
- package/utils/__tests__/fleet.test.ts +340 -0
- package/utils/__tests__/ingress.test.ts +553 -0
- package/utils/__tests__/kube.test.ts +68 -0
- package/utils/__tests__/namespace-filter.test.ts +109 -0
- package/utils/__tests__/pagination-utils.test.ts +361 -0
- package/utils/__tests__/parse-externalid.test.ts +137 -0
- package/utils/__tests__/perf-setting.utils.test.ts +98 -0
- package/utils/__tests__/poller-sequential.test.ts +177 -0
- package/utils/__tests__/poller.test.ts +170 -0
- package/utils/__tests__/promise.test.ts +346 -0
- package/utils/__tests__/settings.test.ts +140 -0
- package/utils/__tests__/sort-utils.test.ts +301 -0
- package/utils/__tests__/string-utils.test.ts +798 -0
- package/utils/__tests__/string.test.ts +23 -1
- package/utils/__tests__/style.test.ts +154 -0
- package/utils/__tests__/svg-filter.test.ts +184 -0
- package/utils/__tests__/units.test.ts +417 -0
- package/utils/__tests__/versions.test.ts +128 -0
- package/utils/__tests__/xccdf.test.ts +391 -0
- package/utils/chart.js +36 -0
- package/utils/fleet.ts +13 -3
- package/utils/gatekeeper/__tests__/util.test.ts +174 -0
- package/utils/gc/__tests__/gc-interval.test.ts +119 -0
- package/utils/gc/__tests__/gc-root-store.test.ts +225 -0
- package/utils/gc/__tests__/gc-route-changed.test.ts +96 -0
- package/utils/gc/__tests__/gc.test.ts +487 -0
- package/utils/ingress.ts +9 -1
- package/utils/pagination-utils.ts +2 -1
- package/utils/string.js +25 -2
- package/utils/uiplugins.ts +5 -5
- package/utils/validators/__tests__/cluster-name.test.ts +110 -0
- package/utils/validators/__tests__/cron-schedule.test.ts +79 -0
- package/utils/validators/__tests__/index.test.ts +481 -0
- package/utils/validators/__tests__/kubernetes-name.test.ts +163 -0
- package/utils/validators/__tests__/misc-validators.test.ts +246 -0
- package/utils/validators/__tests__/pod-affinity.test.ts +382 -0
- package/utils/validators/__tests__/prometheusrule.test.ts +211 -0
- package/utils/validators/__tests__/role-template.test.ts +149 -0
- package/utils/validators/__tests__/service.test.ts +283 -0
- package/utils/validators/__tests__/setting.test.js +32 -0
- package/utils/validators/formRules/__tests__/index.test.ts +50 -0
- package/utils/validators/formRules/index.ts +5 -5
- package/utils/validators/machine-pool.ts +1 -1
- package/utils/validators/setting.js +18 -3
- package/utils/xccdf.ts +418 -0
- package/assets/fonts/lato/lato-v17-latin-700.woff +0 -0
- package/assets/fonts/lato/lato-v17-latin-700.woff2 +0 -0
- package/assets/fonts/lato/lato-v17-latin-regular.woff +0 -0
- package/assets/fonts/lato/lato-v17-latin-regular.woff2 +0 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import type defaultGc from '@shell/utils/gc/gc';
|
|
2
|
+
import { MANAGEMENT } from '@shell/config/types';
|
|
3
|
+
|
|
4
|
+
type GcInstance = typeof defaultGc;
|
|
5
|
+
|
|
6
|
+
const DEFAULT_PREFS = {
|
|
7
|
+
enabled: true,
|
|
8
|
+
enabledInterval: true,
|
|
9
|
+
interval: 300,
|
|
10
|
+
enabledOnNavigate: true,
|
|
11
|
+
ageThreshold: 120, // seconds → maxAge = 120_000 ms
|
|
12
|
+
countThreshold: 500,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let stampCounter = 1000;
|
|
16
|
+
|
|
17
|
+
function makeRootState(prefs = DEFAULT_PREFS, clusterReady = true): any {
|
|
18
|
+
stampCounter++;
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
clusterReady,
|
|
22
|
+
management: {
|
|
23
|
+
types: {
|
|
24
|
+
[MANAGEMENT.SETTING]: {
|
|
25
|
+
list: [
|
|
26
|
+
{
|
|
27
|
+
id: 'ui-performance',
|
|
28
|
+
value: JSON.stringify({ garbageCollection: prefs }),
|
|
29
|
+
metadata: { generation: stampCounter, resourceVersion: '1' },
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeEmptyRootState(): any {
|
|
39
|
+
return {
|
|
40
|
+
clusterReady: true,
|
|
41
|
+
management: { types: { [MANAGEMENT.SETTING]: { list: [] } } },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function makeCtx(options: {
|
|
46
|
+
namespace?: string;
|
|
47
|
+
supportsGc?: boolean;
|
|
48
|
+
gcIgnoreTypes?: Record<string, boolean>;
|
|
49
|
+
prefs?: Partial<typeof DEFAULT_PREFS>;
|
|
50
|
+
dispatch?: jest.Mock;
|
|
51
|
+
countsByType?: Record<string, number>;
|
|
52
|
+
clusterReady?: boolean;
|
|
53
|
+
} = {}): any {
|
|
54
|
+
const {
|
|
55
|
+
namespace = 'teststore',
|
|
56
|
+
supportsGc = true,
|
|
57
|
+
gcIgnoreTypes = {},
|
|
58
|
+
prefs,
|
|
59
|
+
dispatch = jest.fn(),
|
|
60
|
+
countsByType = {},
|
|
61
|
+
clusterReady = true,
|
|
62
|
+
} = options;
|
|
63
|
+
|
|
64
|
+
const rootState = makeRootState(
|
|
65
|
+
prefs ? { ...DEFAULT_PREFS, ...prefs } : DEFAULT_PREFS,
|
|
66
|
+
clusterReady
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
state: { config: { supportsGc, namespace } },
|
|
71
|
+
rootState,
|
|
72
|
+
getters: {
|
|
73
|
+
gcIgnoreTypes,
|
|
74
|
+
all: () => [{
|
|
75
|
+
counts: Object.fromEntries(
|
|
76
|
+
Object.entries(countsByType).map(([t, c]) => [t, { summary: { count: c } }])
|
|
77
|
+
),
|
|
78
|
+
}],
|
|
79
|
+
},
|
|
80
|
+
dispatch,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Each test in the outer describe gets a fresh gc instance (gcLastRun = 0,
|
|
86
|
+
* empty caches) so tests do not interfere with each other via shared singleton state.
|
|
87
|
+
*/
|
|
88
|
+
describe('gc', () => {
|
|
89
|
+
let gc: GcInstance;
|
|
90
|
+
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
jest.resetModules();
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
94
|
+
gc = require('@shell/utils/gc/gc').default as GcInstance;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
afterEach(() => {
|
|
98
|
+
jest.useRealTimers();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ─── gcEnabledForStore ─────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
describe('gcEnabledForStore', () => {
|
|
104
|
+
it.each([
|
|
105
|
+
{
|
|
106
|
+
desc: 'true when supportsGc is true', state: { config: { supportsGc: true } }, expected: true
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
desc: 'false when supportsGc is false', state: { config: { supportsGc: false } }, expected: false
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
desc: 'undefined when state is undefined', state: undefined, expected: undefined
|
|
113
|
+
},
|
|
114
|
+
])('returns $desc', ({ state, expected }) => {
|
|
115
|
+
expect(gc.gcEnabledForStore(state)).toBe(expected);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ─── gcEnabledForType ──────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
describe('gcEnabledForType', () => {
|
|
122
|
+
it.each([
|
|
123
|
+
{
|
|
124
|
+
desc: 'false when type is empty string', gcIgnoreTypes: {}, type: '', expected: false
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
desc: 'false when type is in gcIgnoreTypes', gcIgnoreTypes: { pods: true }, type: 'pods', expected: false
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
desc: 'true when type is non-empty and not ignored', gcIgnoreTypes: {}, type: 'deployments', expected: true
|
|
131
|
+
},
|
|
132
|
+
])('returns $desc', ({ gcIgnoreTypes, type, expected }) => {
|
|
133
|
+
expect(gc.gcEnabledForType({ getters: { gcIgnoreTypes } }, type)).toBe(expected);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ─── gcEnabledSetting ──────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
describe('gcEnabledSetting', () => {
|
|
140
|
+
it('returns true when GC is enabled in ui-performance setting', () => {
|
|
141
|
+
const ctx = { rootState: makeRootState({ ...DEFAULT_PREFS, enabled: true }) };
|
|
142
|
+
|
|
143
|
+
expect(gc.gcEnabledSetting(ctx)).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('returns false when GC is disabled in ui-performance setting', () => {
|
|
147
|
+
const ctx = { rootState: makeRootState({ ...DEFAULT_PREFS, enabled: false }) };
|
|
148
|
+
|
|
149
|
+
expect(gc.gcEnabledSetting(ctx)).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('returns undefined when ui-performance setting is absent', () => {
|
|
153
|
+
const ctx = { rootState: makeEmptyRootState() };
|
|
154
|
+
|
|
155
|
+
expect(gc.gcEnabledSetting(ctx)).toBeUndefined();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// ─── gcEnabledAll ──────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
describe('gcEnabledAll', () => {
|
|
162
|
+
it.each([
|
|
163
|
+
{
|
|
164
|
+
desc: 'true when all checks pass', options: {}, type: 'deployments', expected: true
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
desc: 'false when store does not support GC', options: { supportsGc: false }, type: 'deployments', expected: false
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
desc: 'false when GC is disabled in settings', options: { prefs: { enabled: false } }, type: 'deployments', expected: false
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
desc: 'false when type is in gcIgnoreTypes', options: { gcIgnoreTypes: { schema: true } }, type: 'schema', expected: false
|
|
174
|
+
},
|
|
175
|
+
])('returns $desc', ({ options, type, expected }) => {
|
|
176
|
+
expect(gc.gcEnabledAll(makeCtx(options), type)).toBe(expected);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// ─── gcEnabledInterval ─────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
describe('gcEnabledInterval', () => {
|
|
183
|
+
it('returns enabledInterval and interval from settings', () => {
|
|
184
|
+
const ctx = {
|
|
185
|
+
rootState: makeRootState({
|
|
186
|
+
...DEFAULT_PREFS, enabledInterval: true, interval: 600
|
|
187
|
+
})
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
expect(gc.gcEnabledInterval(ctx)).toStrictEqual({ enabled: true, interval: 600 });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('returns enabled: undefined and interval: 0 when setting is absent', () => {
|
|
194
|
+
const ctx = { rootState: makeEmptyRootState() };
|
|
195
|
+
|
|
196
|
+
expect(gc.gcEnabledInterval(ctx)).toStrictEqual({ enabled: undefined, interval: 0 });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('returns interval: 0 when the interval value is falsy', () => {
|
|
200
|
+
const ctx = { rootState: makeRootState({ ...DEFAULT_PREFS, interval: 0 }) };
|
|
201
|
+
|
|
202
|
+
expect(gc.gcEnabledInterval(ctx)).toStrictEqual({ enabled: true, interval: 0 });
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// ─── gcEnabledRoute ────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
describe('gcEnabledRoute', () => {
|
|
209
|
+
it('returns enabledOnNavigate from settings', () => {
|
|
210
|
+
const ctx = { rootState: makeRootState({ ...DEFAULT_PREFS, enabledOnNavigate: false }) };
|
|
211
|
+
|
|
212
|
+
expect(gc.gcEnabledRoute(ctx)).toBe(false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('returns undefined when setting is absent', () => {
|
|
216
|
+
const ctx = { rootState: makeEmptyRootState() };
|
|
217
|
+
|
|
218
|
+
expect(gc.gcEnabledRoute(ctx)).toBeUndefined();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ─── gcUpdateRouteChanged ──────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
describe('gcUpdateRouteChanged', () => {
|
|
225
|
+
/**
|
|
226
|
+
* Condition in garbageCollect:
|
|
227
|
+
* if (lastRouteChange < lastAccessed) → skip (resource is in current route)
|
|
228
|
+
*
|
|
229
|
+
* So: route changed at T1, resource accessed at T2 > T1
|
|
230
|
+
* → lastRouteChange (T1) < lastAccessed (T2) → GC skips this type.
|
|
231
|
+
*/
|
|
232
|
+
it('prevents a resource from being GC\'d when route changed before last access', () => {
|
|
233
|
+
const dispatch = jest.fn();
|
|
234
|
+
const type = 'pods';
|
|
235
|
+
const namespace = 'routetest';
|
|
236
|
+
|
|
237
|
+
jest.useFakeTimers();
|
|
238
|
+
|
|
239
|
+
// Route change at t=200_000 ms
|
|
240
|
+
jest.setSystemTime(200_000);
|
|
241
|
+
gc.gcUpdateRouteChanged();
|
|
242
|
+
|
|
243
|
+
// Resource accessed at t=300_000 ms (AFTER route change → in current route)
|
|
244
|
+
jest.setSystemTime(300_000);
|
|
245
|
+
gc.gcUpdateLastAccessed(makeCtx({ namespace }), type);
|
|
246
|
+
|
|
247
|
+
// GC runs at t=600_000 ms
|
|
248
|
+
// now - lastAccessed = 300_000 > maxAge(120_000) ✓ age check passes
|
|
249
|
+
// lastRouteChange(200_000) < lastAccessed(300_000) → skip (in current route)
|
|
250
|
+
jest.setSystemTime(600_000);
|
|
251
|
+
gc.garbageCollect(makeCtx({
|
|
252
|
+
namespace,
|
|
253
|
+
countsByType: { [type]: 1000 },
|
|
254
|
+
dispatch,
|
|
255
|
+
}));
|
|
256
|
+
|
|
257
|
+
expect(dispatch).not.toHaveBeenCalledWith('forgetType', type);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// ─── garbageCollect ────────────────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
describe('garbageCollect', () => {
|
|
264
|
+
it('skips when recently run (within GC_RE_RUN_GAP of 5 s)', () => {
|
|
265
|
+
const dispatch = jest.fn();
|
|
266
|
+
|
|
267
|
+
jest.useFakeTimers();
|
|
268
|
+
jest.setSystemTime(10_000);
|
|
269
|
+
|
|
270
|
+
// First call: cluster not ready → gcLastRun = 10_000
|
|
271
|
+
gc.garbageCollect(makeCtx({ clusterReady: false, dispatch }));
|
|
272
|
+
// Second call at same time: 10_000 - 10_000 = 0 < 5000 → skips
|
|
273
|
+
gc.garbageCollect(makeCtx({ dispatch }));
|
|
274
|
+
|
|
275
|
+
expect(dispatch).not.toHaveBeenCalledWith('forgetType', expect.any(String));
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('skips when cluster is not ready', () => {
|
|
279
|
+
const dispatch = jest.fn();
|
|
280
|
+
|
|
281
|
+
jest.useFakeTimers();
|
|
282
|
+
jest.setSystemTime(10_000);
|
|
283
|
+
|
|
284
|
+
gc.garbageCollect(makeCtx({ clusterReady: false, dispatch }));
|
|
285
|
+
|
|
286
|
+
expect(dispatch).not.toHaveBeenCalledWith('forgetType', expect.any(String));
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('skips when ui-performance setting is absent', () => {
|
|
290
|
+
const dispatch = jest.fn();
|
|
291
|
+
|
|
292
|
+
jest.useFakeTimers();
|
|
293
|
+
jest.setSystemTime(10_000);
|
|
294
|
+
|
|
295
|
+
const ctx = {
|
|
296
|
+
state: { config: { supportsGc: true, namespace: 'teststore' } },
|
|
297
|
+
rootState: makeEmptyRootState(),
|
|
298
|
+
getters: { gcIgnoreTypes: {} },
|
|
299
|
+
dispatch,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
gc.garbageCollect(ctx);
|
|
303
|
+
|
|
304
|
+
expect(dispatch).not.toHaveBeenCalledWith('forgetType', expect.any(String));
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('skips a type listed in the explicit ignoreTypes parameter', () => {
|
|
308
|
+
const dispatch = jest.fn();
|
|
309
|
+
const type = 'configmaps';
|
|
310
|
+
const namespace = 'ignoretypetest';
|
|
311
|
+
|
|
312
|
+
jest.useFakeTimers();
|
|
313
|
+
|
|
314
|
+
jest.setSystemTime(200_000);
|
|
315
|
+
gc.gcUpdateLastAccessed(makeCtx({ namespace }), type);
|
|
316
|
+
|
|
317
|
+
jest.setSystemTime(300_000);
|
|
318
|
+
gc.gcUpdateRouteChanged();
|
|
319
|
+
|
|
320
|
+
jest.setSystemTime(400_000);
|
|
321
|
+
gc.garbageCollect(
|
|
322
|
+
makeCtx({
|
|
323
|
+
namespace, countsByType: { [type]: 1000 }, dispatch
|
|
324
|
+
}),
|
|
325
|
+
{ [type]: true }
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
expect(dispatch).not.toHaveBeenCalledWith('forgetType', type);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('skips a type that was accessed within the ageThreshold', () => {
|
|
332
|
+
const dispatch = jest.fn();
|
|
333
|
+
const type = 'secrets';
|
|
334
|
+
const namespace = 'recenttest';
|
|
335
|
+
|
|
336
|
+
jest.useFakeTimers();
|
|
337
|
+
|
|
338
|
+
// Access 30 s ago (less than 120 s ageThreshold)
|
|
339
|
+
jest.setSystemTime(10_000);
|
|
340
|
+
gc.gcUpdateLastAccessed(makeCtx({ namespace }), type);
|
|
341
|
+
|
|
342
|
+
jest.setSystemTime(40_000); // 30 s later
|
|
343
|
+
gc.garbageCollect(makeCtx({
|
|
344
|
+
namespace, countsByType: { [type]: 1000 }, dispatch
|
|
345
|
+
}));
|
|
346
|
+
|
|
347
|
+
expect(dispatch).not.toHaveBeenCalledWith('forgetType', type);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('skips a type whose count is below countThreshold', () => {
|
|
351
|
+
const dispatch = jest.fn();
|
|
352
|
+
const type = 'namespaces';
|
|
353
|
+
const namespace = 'lowcounttest';
|
|
354
|
+
|
|
355
|
+
jest.useFakeTimers();
|
|
356
|
+
|
|
357
|
+
jest.setSystemTime(200_000);
|
|
358
|
+
gc.gcUpdateLastAccessed(makeCtx({ namespace }), type);
|
|
359
|
+
|
|
360
|
+
jest.setSystemTime(300_000);
|
|
361
|
+
gc.gcUpdateRouteChanged();
|
|
362
|
+
|
|
363
|
+
jest.setSystemTime(400_000);
|
|
364
|
+
// count(100) < countThreshold(500) → skip
|
|
365
|
+
gc.garbageCollect(makeCtx({
|
|
366
|
+
namespace, countsByType: { [type]: 100 }, dispatch
|
|
367
|
+
}));
|
|
368
|
+
|
|
369
|
+
expect(dispatch).not.toHaveBeenCalledWith('forgetType', type);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('dispatches forgetType for a resource that meets all GC criteria', () => {
|
|
373
|
+
const dispatch = jest.fn();
|
|
374
|
+
const type = 'events';
|
|
375
|
+
const namespace = 'gcqualifytest';
|
|
376
|
+
|
|
377
|
+
jest.useFakeTimers();
|
|
378
|
+
|
|
379
|
+
// Resource accessed at t=200_000
|
|
380
|
+
jest.setSystemTime(200_000);
|
|
381
|
+
gc.gcUpdateLastAccessed(makeCtx({ namespace }), type);
|
|
382
|
+
|
|
383
|
+
// Route changed at t=300_000 (AFTER access → lastRouteChange > lastAccessed → no skip)
|
|
384
|
+
jest.setSystemTime(300_000);
|
|
385
|
+
gc.gcUpdateRouteChanged();
|
|
386
|
+
|
|
387
|
+
// GC at t=400_000: now - lastAccessed = 200_000 > maxAge(120_000) ✓
|
|
388
|
+
jest.setSystemTime(400_000);
|
|
389
|
+
gc.garbageCollect(makeCtx({
|
|
390
|
+
namespace, countsByType: { [type]: 1000 }, dispatch
|
|
391
|
+
}));
|
|
392
|
+
|
|
393
|
+
expect(dispatch).toHaveBeenCalledWith('forgetType', type);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// ─── gcResetStore ──────────────────────────────────────────────────────────
|
|
398
|
+
|
|
399
|
+
describe('gcResetStore', () => {
|
|
400
|
+
it('removes all cached entries for the store, preventing forgetType dispatch', () => {
|
|
401
|
+
const dispatch = jest.fn();
|
|
402
|
+
const type = 'ingresses';
|
|
403
|
+
const namespace = 'resetstoretest';
|
|
404
|
+
|
|
405
|
+
jest.useFakeTimers();
|
|
406
|
+
|
|
407
|
+
jest.setSystemTime(200_000);
|
|
408
|
+
gc.gcUpdateLastAccessed(makeCtx({ namespace }), type);
|
|
409
|
+
|
|
410
|
+
jest.setSystemTime(300_000);
|
|
411
|
+
gc.gcUpdateRouteChanged();
|
|
412
|
+
|
|
413
|
+
gc.gcResetStore({ config: { namespace } });
|
|
414
|
+
|
|
415
|
+
jest.setSystemTime(400_000);
|
|
416
|
+
gc.garbageCollect(makeCtx({
|
|
417
|
+
namespace, countsByType: { [type]: 1000 }, dispatch
|
|
418
|
+
}));
|
|
419
|
+
|
|
420
|
+
expect(dispatch).not.toHaveBeenCalledWith('forgetType', type);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// ─── gcResetType ───────────────────────────────────────────────────────────
|
|
425
|
+
|
|
426
|
+
describe('gcResetType', () => {
|
|
427
|
+
it('removes a specific type so it is not GC\'d while leaving other types intact', () => {
|
|
428
|
+
const dispatch = jest.fn();
|
|
429
|
+
const typeA = 'storageclasses';
|
|
430
|
+
const typeB = 'persistentvolumes';
|
|
431
|
+
const namespace = 'resettypetest';
|
|
432
|
+
|
|
433
|
+
jest.useFakeTimers();
|
|
434
|
+
|
|
435
|
+
jest.setSystemTime(200_000);
|
|
436
|
+
gc.gcUpdateLastAccessed(makeCtx({ namespace }), typeA);
|
|
437
|
+
gc.gcUpdateLastAccessed(makeCtx({ namespace }), typeB);
|
|
438
|
+
|
|
439
|
+
jest.setSystemTime(300_000);
|
|
440
|
+
gc.gcUpdateRouteChanged();
|
|
441
|
+
|
|
442
|
+
gc.gcResetType({ config: { namespace } }, typeA);
|
|
443
|
+
|
|
444
|
+
jest.setSystemTime(400_000);
|
|
445
|
+
gc.garbageCollect(makeCtx({
|
|
446
|
+
namespace,
|
|
447
|
+
countsByType: { [typeA]: 1000, [typeB]: 1000 },
|
|
448
|
+
dispatch,
|
|
449
|
+
}));
|
|
450
|
+
|
|
451
|
+
expect(dispatch).not.toHaveBeenCalledWith('forgetType', typeA);
|
|
452
|
+
expect(dispatch).toHaveBeenCalledWith('forgetType', typeB);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('is a no-op when the store has no cached entries', () => {
|
|
456
|
+
expect(() => {
|
|
457
|
+
gc.gcResetType({ config: { namespace: 'nonexistent-store' } }, 'pods');
|
|
458
|
+
}).not.toThrow();
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// ─── gcUpdateLastAccessed ──────────────────────────────────────────────────
|
|
463
|
+
|
|
464
|
+
describe('gcUpdateLastAccessed', () => {
|
|
465
|
+
it('does not populate cache when GC is disabled for the store', () => {
|
|
466
|
+
const dispatch = jest.fn();
|
|
467
|
+
const type = 'replicasets';
|
|
468
|
+
const namespace = 'disabledgctest';
|
|
469
|
+
|
|
470
|
+
jest.useFakeTimers();
|
|
471
|
+
|
|
472
|
+
// supportsGc: false → gcEnabledAll = false → cache not updated
|
|
473
|
+
jest.setSystemTime(200_000);
|
|
474
|
+
gc.gcUpdateLastAccessed(makeCtx({ namespace, supportsGc: false }), type);
|
|
475
|
+
|
|
476
|
+
jest.setSystemTime(300_000);
|
|
477
|
+
gc.gcUpdateRouteChanged();
|
|
478
|
+
|
|
479
|
+
jest.setSystemTime(400_000);
|
|
480
|
+
gc.garbageCollect(makeCtx({
|
|
481
|
+
namespace, countsByType: { [type]: 1000 }, dispatch
|
|
482
|
+
}));
|
|
483
|
+
|
|
484
|
+
expect(dispatch).not.toHaveBeenCalledWith('forgetType', type);
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
});
|
package/utils/ingress.ts
CHANGED
|
@@ -56,7 +56,15 @@ class IngressDetailEditHelper {
|
|
|
56
56
|
return services.map((service) => ({
|
|
57
57
|
label: service.metadata.name,
|
|
58
58
|
value: service.metadata.name,
|
|
59
|
-
ports: service.spec.ports?.
|
|
59
|
+
ports: service.spec.ports?.flatMap((p: any) => {
|
|
60
|
+
const options = [p.port];
|
|
61
|
+
|
|
62
|
+
if (p.name) {
|
|
63
|
+
options.push(p.name);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return options;
|
|
67
|
+
})
|
|
60
68
|
}));
|
|
61
69
|
}
|
|
62
70
|
}
|
|
@@ -24,9 +24,10 @@ import { EXT_IDS } from '@shell/core/plugin';
|
|
|
24
24
|
import { ExtensionManager } from '@shell/types/extension-manager';
|
|
25
25
|
import { DEFAULT_PERF_SETTING } from '@shell/config/settings';
|
|
26
26
|
|
|
27
|
+
// This feature will be removed soon - https://github.com/rancher/dashboard/issues/17323
|
|
27
28
|
const homePageClusterFeature: PaginationFeature<PaginationFeatureHomePageClusterConfig> = {
|
|
28
29
|
version: 1,
|
|
29
|
-
enabled:
|
|
30
|
+
enabled: false,
|
|
30
31
|
configuration: {
|
|
31
32
|
threshold: 500, results: 250, pagesPerRow: 25
|
|
32
33
|
}
|
package/utils/string.js
CHANGED
|
@@ -349,10 +349,33 @@ export function xOfy(x, y) {
|
|
|
349
349
|
return `${ typeof x === 'number' ? x : '?' }/${ typeof y === 'number' ? y : '?' }`;
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
const BASE64_REGEX = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;
|
|
353
|
+
|
|
352
354
|
export function isBase64(value) {
|
|
353
|
-
|
|
355
|
+
return BASE64_REGEX.test(value);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Checks if a value is a valid base64-encoded CA bundle.
|
|
360
|
+
* Unlike isBase64, this handles multiline base64 (e.g. openssl wraps at 76 chars)
|
|
361
|
+
* and rejects short strings that could be false positives.
|
|
362
|
+
* @param {string} value
|
|
363
|
+
* @returns {boolean}
|
|
364
|
+
*/
|
|
365
|
+
export function isBase64EncodedCert(value) {
|
|
366
|
+
if (!value || typeof value !== 'string') {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Strip whitespace to handle line-wrapped base64 output
|
|
371
|
+
const stripped = value.replace(/\s/g, '');
|
|
372
|
+
|
|
373
|
+
// CA certs are long enough that legitimate base64 will always exceed this
|
|
374
|
+
if (stripped.length < 16) {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
354
377
|
|
|
355
|
-
return
|
|
378
|
+
return BASE64_REGEX.test(stripped);
|
|
356
379
|
}
|
|
357
380
|
|
|
358
381
|
export function generateRandomAlphaString(length) {
|
package/utils/uiplugins.ts
CHANGED
|
@@ -180,18 +180,19 @@ export async function getHelmRepositoryExact(store: any, url: string): Promise<H
|
|
|
180
180
|
/**
|
|
181
181
|
*
|
|
182
182
|
* @param store Vue store
|
|
183
|
-
* @param urlRegexes Regex to match
|
|
183
|
+
* @param urlRegexes Regex to match against the repository's urls
|
|
184
184
|
* @param catalogImages Catalog images to match against the repository's labels
|
|
185
185
|
* @returns HelmRepository
|
|
186
186
|
*/
|
|
187
187
|
export async function getHelmRepositoryMatch(store: any, urlRegexes: string[], catalogImages: string[]): Promise<HelmRepository> {
|
|
188
188
|
return await getHelmRepository(store, (repository: any) => {
|
|
189
|
-
// if installed from rancher/ui-plugin-catalog or rancher/ui-extension-harvester-ui-extension
|
|
190
189
|
const catalog = repository?.metadata?.labels?.[UI_PLUGIN_LABELS.CATALOG_IMAGE] || '';
|
|
191
190
|
|
|
192
|
-
if
|
|
191
|
+
// if installed from rancher/ui-plugin-catalog or rancher/ui-extension-harvester-ui-extension
|
|
192
|
+
if (catalog && catalogImages.includes(catalog)) {
|
|
193
193
|
return true;
|
|
194
194
|
}
|
|
195
|
+
|
|
195
196
|
const target = repository.spec?.gitBranch ? repository.spec?.gitRepo : repository.spec?.url;
|
|
196
197
|
|
|
197
198
|
return matchesSomeRegex(target, urlRegexes);
|
|
@@ -272,8 +273,7 @@ export async function createHelmRepository(store: any, name: string, url: string
|
|
|
272
273
|
});
|
|
273
274
|
|
|
274
275
|
tries++;
|
|
275
|
-
|
|
276
|
-
const downloaded = repo.status.conditions.find((s: any) => s.type === 'Downloaded');
|
|
276
|
+
const downloaded = repo.status?.conditions.find((s: any) => s.type === 'Downloaded');
|
|
277
277
|
|
|
278
278
|
console.log(`Waiting for helm repository to be downloaded... try ${ tries } time(s).`); // eslint-disable-line no-console
|
|
279
279
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { clusterName } from '@shell/utils/validators/cluster-name';
|
|
2
|
+
|
|
3
|
+
function makeGetters(): Record<string, unknown> {
|
|
4
|
+
return {
|
|
5
|
+
'i18n/t': (key: string, args?: unknown) => (args ? `${ key }:${ JSON.stringify(args) }` : key),
|
|
6
|
+
'i18n/exists': () => false,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('clusterName', () => {
|
|
11
|
+
describe('non-rke2 mode', () => {
|
|
12
|
+
it('returns no errors for any name when not rke2', () => {
|
|
13
|
+
const errors = clusterName('c-abcde', makeGetters(), [], ['false'], 'Name');
|
|
14
|
+
|
|
15
|
+
expect(errors).toStrictEqual([]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('returns no errors for local when not rke2', () => {
|
|
19
|
+
const errors = clusterName('local', makeGetters(), [], ['false'], 'Name');
|
|
20
|
+
|
|
21
|
+
expect(errors).toStrictEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('returns no errors for arbitrary name when not rke2', () => {
|
|
25
|
+
const errors = clusterName('my-cluster', makeGetters(), [], ['false'], 'Name');
|
|
26
|
+
|
|
27
|
+
expect(errors).toStrictEqual([]);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('rke2 mode', () => {
|
|
32
|
+
it('rejects name matching c-XXXXX pattern', () => {
|
|
33
|
+
const errors = clusterName('c-abcde', makeGetters(), [], ['true'], 'Name');
|
|
34
|
+
|
|
35
|
+
expect(errors).toContain('validation.cluster.name');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('rejects "local" cluster name', () => {
|
|
39
|
+
const errors = clusterName('local', makeGetters(), [], ['true'], 'Name');
|
|
40
|
+
|
|
41
|
+
expect(errors).toContain('validation.cluster.name');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('rejects "LOCAL" (case-insensitive match)', () => {
|
|
45
|
+
const errors = clusterName('LOCAL', makeGetters(), [], ['true'], 'Name');
|
|
46
|
+
|
|
47
|
+
expect(errors).toContain('validation.cluster.name');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('rejects "Local" (mixed case)', () => {
|
|
51
|
+
const errors = clusterName('Local', makeGetters(), [], ['true'], 'Name');
|
|
52
|
+
|
|
53
|
+
expect(errors).toContain('validation.cluster.name');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('rejects "c-ABCDE" (uppercase legacy id)', () => {
|
|
57
|
+
const errors = clusterName('c-ABCDE', makeGetters(), [], ['true'], 'Name');
|
|
58
|
+
|
|
59
|
+
expect(errors).toContain('validation.cluster.name');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('accepts a valid rke2 cluster name', () => {
|
|
63
|
+
const errors = clusterName('my-cluster', makeGetters(), [], ['true'], 'Name');
|
|
64
|
+
|
|
65
|
+
expect(errors).toStrictEqual([]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('accepts name with more than 5 chars after c-', () => {
|
|
69
|
+
const errors = clusterName('c-abcdef', makeGetters(), [], ['true'], 'Name');
|
|
70
|
+
|
|
71
|
+
expect(errors).toStrictEqual([]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('accepts name starting with c- but less than 5 chars after', () => {
|
|
75
|
+
const errors = clusterName('c-abc', makeGetters(), [], ['true'], 'Name');
|
|
76
|
+
|
|
77
|
+
expect(errors).toStrictEqual([]);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('empty/null pathValue', () => {
|
|
82
|
+
it('handles empty string without throwing', () => {
|
|
83
|
+
const errors = clusterName('', makeGetters(), [], ['true'], 'Name');
|
|
84
|
+
|
|
85
|
+
expect(errors).toStrictEqual([]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('handles null without throwing', () => {
|
|
89
|
+
const errors = clusterName(null, makeGetters(), [], ['true'], 'Name');
|
|
90
|
+
|
|
91
|
+
expect(errors).toStrictEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('handles undefined without throwing', () => {
|
|
95
|
+
const errors = clusterName(undefined, makeGetters(), [], ['true'], 'Name');
|
|
96
|
+
|
|
97
|
+
expect(errors).toStrictEqual([]);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('errors array accumulation', () => {
|
|
102
|
+
it('appends to existing errors array', () => {
|
|
103
|
+
const existing = ['prior-error'];
|
|
104
|
+
const errors = clusterName('local', makeGetters(), existing, ['true'], 'Name');
|
|
105
|
+
|
|
106
|
+
expect(errors[0]).toBe('prior-error');
|
|
107
|
+
expect(errors).toContain('validation.cluster.name');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|