@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
|
@@ -68,6 +68,14 @@ export function importDialog(name) {
|
|
|
68
68
|
return defineAsyncComponent(() => import(/* webpackChunkName: "dialog" */ `@shell/dialog/${name}`));
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
export function importDrawer(name) {
|
|
72
|
+
if ( !name ) {
|
|
73
|
+
throw new Error('Name required');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return defineAsyncComponent(() => import(/* webpackChunkName: "drawer" */ `@shell/components/Drawer/${name}`));
|
|
77
|
+
}
|
|
78
|
+
|
|
71
79
|
export function importWindowComponent(name) {
|
|
72
80
|
if ( !name ) {
|
|
73
81
|
throw new Error('Name required');
|
package/utils/kontainer.ts
CHANGED
|
@@ -17,11 +17,9 @@ export function syncUpstreamConfig(configPrefix: string, normanCluster: {[key: s
|
|
|
17
17
|
|
|
18
18
|
if (!isEmpty(upstreamConfig)) {
|
|
19
19
|
Object.keys(upstreamConfig).forEach((key) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
} else if ((rancherConfig[key] === null || rancherConfig[key] === undefined) && upstreamConfig[key] !== null && upstreamConfig[key] !== undefined) {
|
|
20
|
+
const empty = typeof upstreamConfig[key] === 'object' && isEmpty(upstreamConfig[key]);
|
|
21
|
+
|
|
22
|
+
if ((rancherConfig[key] === null || rancherConfig[key] === undefined) && upstreamConfig[key] !== null && upstreamConfig[key] !== undefined && !empty) {
|
|
25
23
|
set(rancherConfig, key, upstreamConfig[key]);
|
|
26
24
|
}
|
|
27
25
|
});
|
|
@@ -15,6 +15,7 @@ import { isEqual } from '@shell/utils/object';
|
|
|
15
15
|
import { STEVE_CACHE } from '@shell/store/features';
|
|
16
16
|
import { getPerformanceSetting } from '@shell/utils/settings';
|
|
17
17
|
import { PAGINATION_SETTINGS_STORE_DEFAULTS } from '@shell/plugins/steve/steve-pagination-utils';
|
|
18
|
+
import { MANAGEMENT } from '@shell/config/types';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Helper functions for server side pagination
|
|
@@ -54,6 +55,23 @@ class PaginationUtils {
|
|
|
54
55
|
return rootGetters['features/get']?.(STEVE_CACHE);
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Determine if the downstream cluster has vai enabled
|
|
60
|
+
*
|
|
61
|
+
* Almost all the time the downstream cluster vai state will align with upstream (it manages it)
|
|
62
|
+
* ... unless it's harvester then weird things happen
|
|
63
|
+
*/
|
|
64
|
+
async isDownstreamSteveCacheEnabled({ dispatch }: any, clusterId: string): Promise<boolean> {
|
|
65
|
+
const url = `/k8s/clusters/${ clusterId }/v1/${ MANAGEMENT.FEATURE }s/${ STEVE_CACHE }`;
|
|
66
|
+
const entry = await dispatch('cluster/request', { url });
|
|
67
|
+
|
|
68
|
+
if (entry.status.lockedValue !== null) {
|
|
69
|
+
return entry.status.lockedValue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (entry.spec.value !== null) ? entry.spec.value : entry.status.default;
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
/**
|
|
58
76
|
* Is pagination enabled at a global level or for a specific resource
|
|
59
77
|
*/
|
package/utils/style.ts
CHANGED
package/utils/uiplugins.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { matchesSomeRegex } from '@shell/utils/string';
|
|
2
2
|
import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
|
|
3
3
|
import { CATALOG } from '@shell/config/types';
|
|
4
|
-
import { UI_PLUGIN_BASE_URL, isSupportedChartVersion } from '@shell/config/uiplugins';
|
|
4
|
+
import { UI_PLUGIN_BASE_URL, isSupportedChartVersion, UI_PLUGIN_LABELS } from '@shell/config/uiplugins';
|
|
5
|
+
import { Plugin, Version } from '@shell/types/uiplugins';
|
|
5
6
|
|
|
6
7
|
const MAX_RETRIES = 10;
|
|
7
8
|
const RETRY_WAIT = 2500;
|
|
@@ -180,10 +181,17 @@ export async function getHelmRepositoryExact(store: any, url: string): Promise<H
|
|
|
180
181
|
*
|
|
181
182
|
* @param store Vue store
|
|
182
183
|
* @param urlRegexes Regex to match a community repository
|
|
184
|
+
* @param catalogImages Catalog images to match against the repository's labels
|
|
183
185
|
* @returns HelmRepository
|
|
184
186
|
*/
|
|
185
|
-
export async function getHelmRepositoryMatch(store: any, urlRegexes: string[]): Promise<HelmRepository> {
|
|
187
|
+
export async function getHelmRepositoryMatch(store: any, urlRegexes: string[], catalogImages: string[]): Promise<HelmRepository> {
|
|
186
188
|
return await getHelmRepository(store, (repository: any) => {
|
|
189
|
+
// if installed from rancher/ui-plugin-catalog or rancher/ui-extension-harvester-ui-extension
|
|
190
|
+
const catalog = repository?.metadata?.labels?.[UI_PLUGIN_LABELS.CATALOG_IMAGE] || '';
|
|
191
|
+
|
|
192
|
+
if (catalogImages.includes(catalog)) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
187
195
|
const target = repository.spec?.gitBranch ? repository.spec?.gitRepo : repository.spec?.url;
|
|
188
196
|
|
|
189
197
|
return matchesSomeRegex(target, urlRegexes);
|
|
@@ -343,3 +351,22 @@ export async function onExtensionsReady(store: any) {
|
|
|
343
351
|
|
|
344
352
|
await store.dispatch('uiplugins/setReady', true);
|
|
345
353
|
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Finds a Helm Chart version which matches plugin displayVersion. First it checks against Chart.appVersion and
|
|
357
|
+
* falls back to Chart.version if appVersion is not present.
|
|
358
|
+
*
|
|
359
|
+
* @param plugin A data object constructed from UIPlugin and Helm Chart versions
|
|
360
|
+
* @returns string Helm Chart version
|
|
361
|
+
*/
|
|
362
|
+
export function getPluginChartVersion(plugin?: Plugin) {
|
|
363
|
+
const pluginVersion = plugin?.displayVersion;
|
|
364
|
+
|
|
365
|
+
return plugin?.versions?.find((v) => pluginVersion === (v.appVersion ?? v.version))?.version ?? pluginVersion;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function getPluginChartVersionLabel(version: Version) {
|
|
369
|
+
if (version.appVersion === version.version) return `${ version.version }`;
|
|
370
|
+
|
|
371
|
+
return `${ version.appVersion } (${ version.version })`;
|
|
372
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isServerUrl,
|
|
3
|
+
isHttps,
|
|
4
|
+
isDomainWithoutProtocol,
|
|
5
|
+
isLocalhost,
|
|
6
|
+
hasTrailingForwardSlash,
|
|
7
|
+
} from '@shell/utils/validators/setting';
|
|
8
|
+
|
|
9
|
+
describe('isServerUrl', () => {
|
|
10
|
+
it.each([
|
|
11
|
+
['server-url', true],
|
|
12
|
+
['SERVER-URL', false],
|
|
13
|
+
['server-url/', false],
|
|
14
|
+
['not-server-url', false],
|
|
15
|
+
['', false],
|
|
16
|
+
])('should validate that isServerUrl("%s") returns %s', (input, expected) => {
|
|
17
|
+
expect(isServerUrl(input)).toBe(expected);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('isHttps', () => {
|
|
22
|
+
it.each([
|
|
23
|
+
['https://example.com', true],
|
|
24
|
+
['HTTPS://EXAMPLE.COM', true],
|
|
25
|
+
['http://example.com', false],
|
|
26
|
+
['ftp://example.com', false],
|
|
27
|
+
['example.com', false],
|
|
28
|
+
['', false],
|
|
29
|
+
])('should validate that isHttps("%s") returns %s', (input, expected) => {
|
|
30
|
+
expect(isHttps(input)).toBe(expected);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('isDomainWithoutProtocol (follows domain format, no protocol)', () => {
|
|
35
|
+
it.each([
|
|
36
|
+
['ec2.us-west-2.amazonaws.com', true],
|
|
37
|
+
['ec2.us-west-2.api.aws', true],
|
|
38
|
+
['ec2.us-west-2.amazonaws.com.cn', true],
|
|
39
|
+
['s3.eu-central-1.amazonaws.com.cn', true],
|
|
40
|
+
['my-service.internal.net', true],
|
|
41
|
+
['db.example.org:5432', true],
|
|
42
|
+
['ex.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.com', true],
|
|
43
|
+
['service.company.local/path/to/resource', true],
|
|
44
|
+
['example.org:443', true],
|
|
45
|
+
['https://ec2.us-west-2.amazonaws.com', false],
|
|
46
|
+
['http://example.com', false],
|
|
47
|
+
['udp://example.com', false],
|
|
48
|
+
['ftp://example.com', false],
|
|
49
|
+
['ftps://example.com', false],
|
|
50
|
+
['example://example.com', false],
|
|
51
|
+
['example', false],
|
|
52
|
+
['-bad.example.com', false],
|
|
53
|
+
['bad-.example.com', false],
|
|
54
|
+
['example.c', false],
|
|
55
|
+
['exa mple.com', false],
|
|
56
|
+
['exa.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.com', false],
|
|
57
|
+
['', false],
|
|
58
|
+
])('should validate that isDomainWithoutProtocol("%s") returns %s', (input, expected) => {
|
|
59
|
+
expect(isDomainWithoutProtocol(input)).toBe(expected);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('isLocalhost', () => {
|
|
64
|
+
it.each([
|
|
65
|
+
['localhost', true],
|
|
66
|
+
['LOCALHOST', true],
|
|
67
|
+
['http://localhost', true],
|
|
68
|
+
['https://localhost:3000', true],
|
|
69
|
+
['127.0.0.1', true],
|
|
70
|
+
['http://127.0.0.1:8080/path', true],
|
|
71
|
+
['HTTPS://127.0.0.1/Health', true],
|
|
72
|
+
['127.0.0.2', false],
|
|
73
|
+
['http://127.0.0.2', false],
|
|
74
|
+
['mylocalhost', false],
|
|
75
|
+
])('should validate that isLocalhost("%s") returns %s', (input, expected) => {
|
|
76
|
+
expect(isLocalhost(input)).toBe(expected);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('hasTrailingForwardSlash', () => {
|
|
81
|
+
it.each([
|
|
82
|
+
['https://example.com/', true],
|
|
83
|
+
['http://example.com/', true],
|
|
84
|
+
['HTTPS://EXAMPLE.COM/', true],
|
|
85
|
+
['https://example.com/path/', true],
|
|
86
|
+
['https://example.com', false],
|
|
87
|
+
['http://example.com/path', false],
|
|
88
|
+
['example.com/', false],
|
|
89
|
+
])('should validate that hasTrailingForwardSlash("%s") returns %s', (input, expected) => {
|
|
90
|
+
expect(hasTrailingForwardSlash(input)).toBe(expected);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -97,32 +97,54 @@ describe('formRules', () => {
|
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
describe('urlRepository', () => {
|
|
100
|
-
const message = JSON.stringify({ message: 'validation.
|
|
100
|
+
const message = JSON.stringify({ message: 'validation.repository.url' });
|
|
101
101
|
const testCases = [
|
|
102
102
|
// Valid HTTP(s)
|
|
103
103
|
['https://github.com/rancher/dashboard.git', undefined],
|
|
104
104
|
['http://github.com/rancher/dashboard.git', undefined],
|
|
105
105
|
['https://github.com/rancher/dashboard', undefined],
|
|
106
106
|
['https://github.com/rancher/dashboard/', undefined],
|
|
107
|
+
['https://github.com/rancher/%20dashboard/', undefined],
|
|
108
|
+
['https://github.com/rancher/dashboard/%20', undefined],
|
|
109
|
+
['https://localhost:8005', undefined],
|
|
107
110
|
|
|
108
111
|
// Valid SSH
|
|
109
112
|
['git@github.com:rancher/dashboard.git', undefined],
|
|
110
113
|
['git@github.com:rancher/dashboard', undefined],
|
|
111
114
|
['git@github.com:rancher/dashboard/', undefined],
|
|
115
|
+
['git@github.com:rancher/%20dashboard/', undefined],
|
|
116
|
+
['git@github.com:rancher/dashboard/%20', undefined],
|
|
112
117
|
|
|
113
118
|
// Not valid HTTP(s)
|
|
114
119
|
['https://github.com/rancher/ dashboard.git', message],
|
|
115
120
|
['http://github.com/rancher/ dashboard.git', message],
|
|
121
|
+
['http://github.com/ rancher/dashboard.git', message],
|
|
122
|
+
['http://github.com /rancher/dashboard.git', message],
|
|
116
123
|
['https://github.com/rancher/dashboard ', message],
|
|
124
|
+
['https%20://github.com/rancher/dashboard ', message],
|
|
125
|
+
['ht%20tps://github.com/rancher/dashboard ', message],
|
|
126
|
+
['https://git%20hub.com/rancher/dashboard/%20', message],
|
|
127
|
+
['https://https://', message],
|
|
128
|
+
['http:/ww.abc.com', message],
|
|
129
|
+
['http:ww.abc.com', message],
|
|
117
130
|
['foo://github.com/rancher/dashboard/', message],
|
|
118
131
|
['github.com/rancher/dashboard/', message],
|
|
119
132
|
|
|
120
133
|
// Not valid SSH
|
|
121
134
|
['git@github.com:rancher/ dashboard.git', message],
|
|
122
135
|
['git@github.com:rancher/dashboard ', message],
|
|
136
|
+
['git@github.com:rancher/ dashboard', message],
|
|
137
|
+
['git @github.com:rancher/dashboard', message],
|
|
138
|
+
['git@github.com: rancher/dashboard', message],
|
|
123
139
|
['git@github.comrancher/dashboard', message],
|
|
140
|
+
['git@githubcomrancher/dashboard', message],
|
|
141
|
+
['%20git@github.comrancher/dashboard', message],
|
|
142
|
+
['git@git%20hub.comrancher/dashboard', message],
|
|
143
|
+
['git@.git', message],
|
|
144
|
+
['git@', message],
|
|
124
145
|
|
|
125
|
-
[undefined,
|
|
146
|
+
[undefined, message],
|
|
147
|
+
['', message]
|
|
126
148
|
];
|
|
127
149
|
|
|
128
150
|
it.each(testCases)(
|
|
@@ -139,20 +161,26 @@ describe('formRules', () => {
|
|
|
139
161
|
const message = JSON.stringify({ message: 'validation.oci.url' });
|
|
140
162
|
const testCases = [
|
|
141
163
|
// Valid
|
|
142
|
-
['oci://
|
|
164
|
+
['oci://registry.example.com', undefined],
|
|
165
|
+
['oci://myregistry.dev:5000', undefined],
|
|
166
|
+
['oci://192.168.1.100', undefined],
|
|
167
|
+
['oci://my.domain.com/my/image:tag', undefined],
|
|
168
|
+
['oci://localhost:5000', undefined],
|
|
143
169
|
['oci://region.objectstorage.example.com/n', undefined],
|
|
144
|
-
['oci://a', undefined],
|
|
145
|
-
['oci://UPPERCASE/path', undefined],
|
|
146
170
|
|
|
147
171
|
// Invalid
|
|
148
172
|
['http://example.com/oci', message],
|
|
149
173
|
['https://oci.cloud.com', message],
|
|
150
174
|
['ftp://oci.server.net', message],
|
|
151
|
-
['
|
|
175
|
+
['path/to/oci', message],
|
|
176
|
+
['oci://a', message],
|
|
152
177
|
['oci:/missing/slash', message],
|
|
153
178
|
['oci:', message],
|
|
154
179
|
['oci://', message],
|
|
155
|
-
['oci://
|
|
180
|
+
['oci://oci://duplicate/protocol', message],
|
|
181
|
+
['oci ://registry.example.com/foo/bar', message],
|
|
182
|
+
['oci://registry.example. com/foo/bar', message],
|
|
183
|
+
['oci://registry.example.com/ foo/bar', message],
|
|
156
184
|
['oci://resource multiple spaces', message],
|
|
157
185
|
['', message],
|
|
158
186
|
[undefined, message],
|
|
@@ -168,6 +196,59 @@ describe('formRules', () => {
|
|
|
168
196
|
);
|
|
169
197
|
});
|
|
170
198
|
|
|
199
|
+
describe('version', () => {
|
|
200
|
+
const message = JSON.stringify({ message: 'validation.version' });
|
|
201
|
+
const testCases: (null | string | undefined)[][] = [
|
|
202
|
+
// Valid
|
|
203
|
+
['1.2.3', undefined],
|
|
204
|
+
['', undefined],
|
|
205
|
+
[null, undefined],
|
|
206
|
+
|
|
207
|
+
// Invalid
|
|
208
|
+
['1.2.x', message],
|
|
209
|
+
['foo', message],
|
|
210
|
+
['1.2', message],
|
|
211
|
+
['1.2.', message],
|
|
212
|
+
['.', message],
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
it.each(testCases)(
|
|
216
|
+
'should return undefined or correct message based on the provided Version: %p',
|
|
217
|
+
(version, expected) => {
|
|
218
|
+
const formRuleResult = formRules.version(version);
|
|
219
|
+
|
|
220
|
+
expect(formRuleResult).toStrictEqual(expected);
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('semanticVersion', () => {
|
|
226
|
+
const message = JSON.stringify({ message: 'validation.semanticVersion' });
|
|
227
|
+
const testCases: (null | string | undefined)[][] = [
|
|
228
|
+
// Valid
|
|
229
|
+
['1.2.x', undefined],
|
|
230
|
+
['1.2.3', undefined],
|
|
231
|
+
['1.2', undefined],
|
|
232
|
+
['> 1', undefined],
|
|
233
|
+
['', undefined],
|
|
234
|
+
[null, undefined],
|
|
235
|
+
|
|
236
|
+
// Invalid
|
|
237
|
+
['foo', message],
|
|
238
|
+
['1.2.', message],
|
|
239
|
+
['.', message],
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
it.each(testCases)(
|
|
243
|
+
'should return undefined or correct message based on the provided Semantic Version: %p',
|
|
244
|
+
(version, expected) => {
|
|
245
|
+
const formRuleResult = formRules.semanticVersion(version);
|
|
246
|
+
|
|
247
|
+
expect(formRuleResult).toStrictEqual(expected);
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
171
252
|
describe('alphanumeric', () => {
|
|
172
253
|
const message = JSON.stringify({ message: 'validation.alphanumeric', key: 'testDisplayKey' });
|
|
173
254
|
const testCases = [
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import semver from 'semver';
|
|
2
|
+
import { parse } from '@shell/utils/url';
|
|
1
3
|
import { RBAC } from '@shell/config/types';
|
|
2
4
|
import { HCI } from '@shell/config/labels-annotations';
|
|
3
5
|
import isEmpty from 'lodash/isEmpty';
|
|
@@ -5,7 +7,7 @@ import has from 'lodash/has';
|
|
|
5
7
|
import isUrl from 'is-url';
|
|
6
8
|
// import uniq from 'lodash/uniq';
|
|
7
9
|
import { Translation } from '@shell/types/t';
|
|
8
|
-
import { isHttps, isLocalhost, hasTrailingForwardSlash } from '@shell/utils/validators/setting';
|
|
10
|
+
import { isHttps, isLocalhost, hasTrailingForwardSlash, isDomainWithoutProtocol } from '@shell/utils/validators/setting';
|
|
9
11
|
import { cronScheduleRule } from '@shell/utils/validators/cron-schedule';
|
|
10
12
|
|
|
11
13
|
// import uniq from 'lodash/uniq';
|
|
@@ -164,6 +166,8 @@ export default function(
|
|
|
164
166
|
|
|
165
167
|
const https: Validator = (val: string) => val && !isHttps(val) ? t('validation.setting.serverUrl.https') : undefined;
|
|
166
168
|
|
|
169
|
+
const awsStyleEndpoint: Validator = (val: string) => val && !isDomainWithoutProtocol(val) ? t('validation.setting.serverUrl.awsStyleEndpoint') : undefined;
|
|
170
|
+
|
|
167
171
|
const localhost: Validator = (val: string) => isLocalhost(val) ? t('validation.setting.serverUrl.localhost') : undefined;
|
|
168
172
|
|
|
169
173
|
const trailingForwardSlash: Validator = (val: string) => hasTrailingForwardSlash(val) ? t('validation.setting.serverUrl.trailingForwardSlash') : undefined;
|
|
@@ -173,22 +177,90 @@ export default function(
|
|
|
173
177
|
const genericUrl: Validator = (val: string) => val && !isUrl(val) ? t('validation.genericUrl') : undefined;
|
|
174
178
|
|
|
175
179
|
const urlRepository: Validator = (url: string) => {
|
|
176
|
-
const
|
|
177
|
-
|
|
180
|
+
const message = t('validation.repository.url');
|
|
181
|
+
|
|
182
|
+
if (!url) {
|
|
183
|
+
return message;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (url.includes(' ')) {
|
|
187
|
+
return message;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const {
|
|
191
|
+
protocol,
|
|
192
|
+
authority,
|
|
193
|
+
host,
|
|
194
|
+
path
|
|
195
|
+
} = parse(url);
|
|
196
|
+
|
|
197
|
+
// Test duplicate protocol
|
|
198
|
+
if (!host || protocol === host) {
|
|
199
|
+
return message;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Test http(s) protocol
|
|
203
|
+
if (protocol && (!/^(http|http(s))/gm.test(protocol) || (!url.startsWith('https://') && !url.startsWith('http://')))) {
|
|
204
|
+
return message;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Test ssh, authority must be valid (SSH user + host)
|
|
208
|
+
if (!protocol && !authority.endsWith(':')) {
|
|
209
|
+
return message;
|
|
210
|
+
}
|
|
178
211
|
|
|
179
|
-
|
|
180
|
-
|
|
212
|
+
// Encoded space characters (%20) are allowed only in the path
|
|
213
|
+
const hostAndPath = `${ host }${ path.replaceAll('%20', '') }`;
|
|
181
214
|
|
|
182
|
-
|
|
215
|
+
// Test host/path
|
|
216
|
+
if (!/^([\w\.@\:\/\-]+)([\d\/\w.-]+?)(.git){0,1}(\/)?$/gm.test(hostAndPath)) {
|
|
217
|
+
return message;
|
|
183
218
|
}
|
|
184
219
|
|
|
185
220
|
return undefined;
|
|
186
221
|
};
|
|
187
222
|
|
|
188
223
|
const ociRegistry: Validator = (url: string) => {
|
|
189
|
-
const
|
|
224
|
+
const message = t('validation.oci.url');
|
|
225
|
+
|
|
226
|
+
if (!url) {
|
|
227
|
+
return message;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (url.includes(' ')) {
|
|
231
|
+
return message;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const {
|
|
235
|
+
protocol,
|
|
236
|
+
host,
|
|
237
|
+
path
|
|
238
|
+
} = parse(url);
|
|
239
|
+
|
|
240
|
+
// Test duplicate protocol
|
|
241
|
+
if (!host || protocol === host) {
|
|
242
|
+
return message;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Test oci protocol
|
|
246
|
+
if (!url.startsWith('oci://')) {
|
|
247
|
+
return message;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Test host/path
|
|
251
|
+
if (!/^([\w\.@\:\/\-]+)([\d\/\w.-]+?)(\/)?$/gm.test(`${ host }${ path }`)) {
|
|
252
|
+
return message;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return undefined;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const version: Validator = (value: string) => {
|
|
259
|
+
return value && !semver.valid(value) ? t('validation.version') : undefined;
|
|
260
|
+
};
|
|
190
261
|
|
|
191
|
-
|
|
262
|
+
const semanticVersion: Validator = (value: string) => {
|
|
263
|
+
return value && !semver.validRange(value) ? t('validation.semanticVersion') : undefined;
|
|
192
264
|
};
|
|
193
265
|
|
|
194
266
|
const alphanumeric: Validator = (val: string) => val && !/^[a-zA-Z0-9]+$/.test(val) ? t('validation.alphanumeric', { key }) : undefined;
|
|
@@ -541,6 +613,7 @@ export default function(
|
|
|
541
613
|
imageUrl,
|
|
542
614
|
interval,
|
|
543
615
|
https,
|
|
616
|
+
awsStyleEndpoint,
|
|
544
617
|
localhost,
|
|
545
618
|
trailingForwardSlash,
|
|
546
619
|
url,
|
|
@@ -561,9 +634,11 @@ export default function(
|
|
|
561
634
|
isOctal,
|
|
562
635
|
roleTemplateRules,
|
|
563
636
|
ruleGroups,
|
|
637
|
+
semanticVersion,
|
|
564
638
|
servicePort,
|
|
565
639
|
subDomain,
|
|
566
640
|
testRule,
|
|
641
|
+
version,
|
|
567
642
|
wildcardHostname
|
|
568
643
|
};
|
|
569
644
|
}
|
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
import isUrl from 'is-url';
|
|
2
2
|
|
|
3
|
+
// Note that these function cover specific use cases and you need to make sure it works for your use case before using them.
|
|
4
|
+
// ie they would consider empty values as valid, not all endpoint formatting is enforced
|
|
5
|
+
|
|
3
6
|
export const isServerUrl = (value) => value === 'server-url';
|
|
4
7
|
|
|
5
8
|
export const isHttps = (value) => value.toLowerCase().startsWith('https://');
|
|
6
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Checks that provided string is a domain without protocol (case insensitive):
|
|
12
|
+
* - Cannot start with any protocol, such as http://, https://, ftp://, ftps://, udp://
|
|
13
|
+
* - Must only use the letters a to z, the numbers 0 to 9, and the dot (.) and hyphen (-) characters.
|
|
14
|
+
* - if the hyphen character is used in a domain name, it cannot be the first or the last character in the name.
|
|
15
|
+
* - The length of each label can be 2-63 characters
|
|
16
|
+
* - TLD is at least 2 characters
|
|
17
|
+
* - The total length of a domain name, including the dot at the end, cannot exceed 254 characters.
|
|
18
|
+
* - Allows for optional port and path
|
|
19
|
+
* @param {*} value
|
|
20
|
+
* @returns boolean indicating if the value is a domain without protocol
|
|
21
|
+
*/
|
|
22
|
+
export const isDomainWithoutProtocol = (value) => (/^(?=.{1,254}$)(?![a-z][a-z0-9+.-]*:\/\/)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}(?::\d{1,5})?(?:\/\S*)?$/i).test(value);
|
|
23
|
+
|
|
7
24
|
export const isLocalhost = (value) => (/^(?:https?:\/\/)?(?:localhost|127\.0\.0\.1)/i).test(value);
|
|
8
25
|
|
|
9
26
|
export const hasTrailingForwardSlash = (value) => isUrl(value) && value?.toLowerCase().endsWith('/');
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { mount } from '@vue/test-utils';
|
|
2
|
-
import HarvesterCloudCreds from '@shell/cloud-credential/harvester.vue';
|
|
3
|
-
|
|
4
|
-
const mockStore = { getters: { 'i18n/t': jest.fn() } };
|
|
5
|
-
|
|
6
|
-
describe('cloud credentials: Harvester', () => {
|
|
7
|
-
const wrapper = mount(HarvesterCloudCreds, {
|
|
8
|
-
props: { value: {} },
|
|
9
|
-
global: { mocks: { $store: mockStore } }
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('should display the warning banner for token expiration', async() => {
|
|
13
|
-
const warningBanner = wrapper.find('[data-testid="harvester-token-expiration-warning-banner"]');
|
|
14
|
-
|
|
15
|
-
expect(warningBanner.exists()).toBe(true);
|
|
16
|
-
expect(warningBanner.isVisible()).toBe(true);
|
|
17
|
-
});
|
|
18
|
-
});
|