@rancher/shell 3.0.11 → 3.0.12-rc.2
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/entraid-black.svg +4 -0
- package/assets/images/providers/entraid.svg +9 -0
- package/assets/images/vendor/entraid.svg +9 -0
- package/assets/styles/app.scss +0 -1
- package/assets/styles/base/_mixins.scss +31 -0
- package/assets/styles/base/_variables.scss +2 -0
- package/assets/styles/themes/_modern.scss +6 -5
- package/assets/translations/en-us.yaml +24 -21
- package/assets/translations/zh-hans.yaml +4 -11
- package/chart/__tests__/S3.test.ts +10 -3
- package/components/CountBox.vue +20 -0
- package/components/CreateDriver.vue +0 -12
- package/components/DetailText.vue +12 -3
- package/components/EmptyProductPage.vue +76 -0
- package/components/Resource/Detail/CopyToClipboard.vue +1 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
- package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
- package/components/Resource/Detail/TitleBar/index.vue +1 -1
- package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
- package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
- package/components/Resource/Detail/ViewOptions/index.vue +2 -1
- package/components/ResourceList/Masthead.vue +25 -2
- package/components/SelectIconGrid.vue +5 -0
- package/components/SideNav.vue +13 -0
- package/components/__tests__/CountBox.test.ts +72 -0
- package/components/__tests__/DetailText.test.ts +113 -0
- package/components/__tests__/PromptModal.test.ts +2 -0
- package/components/fleet/FleetClusterTargets/index.vue +18 -1
- package/components/fleet/FleetClusters.vue +1 -0
- package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
- package/components/form/InputWithSelect.vue +18 -10
- package/components/form/KeyValue.vue +17 -1
- package/components/form/LabeledSelect.vue +82 -24
- package/components/form/NodeScheduling.vue +17 -3
- package/components/form/PrivateRegistry.vue +69 -0
- package/components/form/Select.vue +73 -56
- package/components/form/ServiceNameSelect.vue +13 -11
- package/components/form/__tests__/KeyValue.test.ts +66 -0
- package/components/form/__tests__/NodeScheduling.test.ts +9 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
- package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
- package/components/formatter/WorkloadHealthScale.vue +3 -1
- package/components/nav/Group.vue +33 -9
- package/components/nav/Header.vue +56 -10
- package/components/nav/NotificationCenter/Notification.vue +4 -1
- package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
- package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
- package/components/nav/TopLevelMenu.vue +15 -1
- package/components/nav/Type.vue +8 -7
- package/components/nav/WindowManager/index.vue +2 -1
- package/components/nav/WorkspaceSwitcher.vue +13 -0
- package/components/nav/__tests__/Group.test.ts +67 -0
- package/components/nav/__tests__/Header.test.ts +235 -0
- package/components/nav/__tests__/Type.test.ts +20 -3
- package/components/templates/default.vue +34 -4
- package/components/templates/home.vue +12 -25
- package/components/templates/plain.vue +13 -26
- package/composables/useLabeledFormElement.ts +10 -2
- package/composables/useLabeledSelect.ts +60 -0
- package/composables/useUserRetentionValidation.ts +1 -49
- package/config/cookies.js +0 -1
- package/config/labels-annotations.js +1 -0
- package/config/pagination-table-headers.js +8 -1
- package/config/product/apps.js +2 -1
- package/config/product/auth.js +1 -0
- package/config/product/backup.js +1 -0
- package/config/product/compliance.js +1 -1
- package/config/product/explorer.js +25 -6
- package/config/product/fleet.js +1 -0
- package/config/product/gatekeeper.js +1 -0
- package/config/product/istio.js +1 -0
- package/config/product/logging.js +1 -0
- package/config/product/longhorn.js +2 -1
- package/config/product/manager.js +1 -0
- package/config/product/monitoring.js +1 -0
- package/config/product/navlinks.js +1 -0
- package/config/product/neuvector.js +2 -1
- package/config/product/settings.js +1 -0
- package/config/product/uiplugins.js +1 -0
- package/config/query-params.js +1 -0
- package/config/router/routes.js +0 -8
- package/core/__tests__/plugin-products-helpers.test.ts +454 -0
- package/core/__tests__/plugin-products.test.ts +3810 -0
- package/core/extension-manager-impl.js +30 -1
- package/core/plugin-products-base.ts +392 -0
- package/core/plugin-products-extending.ts +44 -0
- package/core/plugin-products-helpers.ts +263 -0
- package/core/plugin-products-top-level.ts +66 -0
- package/core/plugin-products-type-guards.ts +33 -0
- package/core/plugin-products.ts +50 -0
- package/core/plugin-types.ts +237 -0
- package/core/plugin.ts +45 -10
- package/core/productDebugger.js +48 -0
- package/core/types.ts +97 -11
- package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
- package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
- package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
- package/detail/fleet.cattle.io.bundle.vue +21 -34
- package/detail/management.cattle.io.fleetworkspace.vue +49 -0
- package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
- package/dialog/InstallExtensionDialog.vue +6 -27
- package/dialog/UninstallExistingExtensionDialog.vue +141 -0
- package/dialog/UninstallExtensionDialog.vue +4 -26
- package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
- package/edit/__tests__/kontainerDriver.test.ts +0 -13
- package/edit/__tests__/nodeDriver.test.ts +5 -11
- package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auth/__tests__/oidc.test.ts +54 -0
- package/edit/auth/azuread.vue +1 -1
- package/edit/auth/oidc.vue +8 -0
- package/edit/kontainerDriver.vue +1 -2
- package/edit/nodeDriver.vue +0 -2
- package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
- package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
- package/initialize/App.vue +29 -2
- package/initialize/install-plugins.js +0 -2
- package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
- package/list/catalog.cattle.io.app.vue +25 -5
- package/list/management.cattle.io.feature.vue +1 -1
- package/list/management.cattle.io.fleetworkspace.vue +8 -0
- package/list/provisioning.cattle.io.cluster.vue +0 -1
- package/list/workload.vue +11 -4
- package/machine-config/amazonec2.vue +1 -0
- package/mixins/chart.js +40 -9
- package/mixins/resource-fetch.js +12 -3
- package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
- package/models/__tests__/chart.test.ts +99 -6
- package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
- package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
- package/models/catalog.cattle.io.app.js +21 -17
- package/models/catalog.cattle.io.clusterrepo.js +39 -11
- package/models/chart.js +33 -19
- package/models/fleet-application.js +1 -1
- package/models/fleet.cattle.io.bundle.js +1 -1
- package/models/kontainerdriver.js +11 -0
- package/models/management.cattle.io.authconfig.js +5 -1
- package/models/management.cattle.io.cluster.js +0 -53
- package/models/management.cattle.io.feature.js +3 -3
- package/models/management.cattle.io.kontainerdriver.js +1 -26
- package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
- package/models/nodedriver.js +7 -0
- package/models/pod.js +18 -0
- package/models/workload.js +20 -2
- package/package.json +13 -13
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
- package/pages/c/_cluster/apps/charts/chart.vue +217 -33
- package/pages/c/_cluster/apps/charts/index.vue +2 -2
- package/pages/c/_cluster/apps/charts/install.vue +8 -3
- package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
- package/pages/c/_cluster/settings/brand.vue +4 -4
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
- package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +246 -23
- package/pages/c/_cluster/uiplugins/index.vue +166 -62
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
- package/plugins/dashboard-store/actions.js +3 -2
- package/plugins/dashboard-store/resource-class.js +62 -6
- package/plugins/plugin.js +16 -0
- package/plugins/steve/steve-pagination-utils.ts +7 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
- package/scripts/test-plugins-build.sh +5 -2
- package/scripts/typegen.sh +13 -1
- package/server/server-middleware.js +2 -2
- package/static/humans.txt +1 -0
- package/static/robots.txt +34 -0
- package/static/welcome-cow.svg +18 -0
- package/store/__tests__/catalog.test.ts +161 -11
- package/store/__tests__/type-map.test.ts +84 -24
- package/store/auth.js +0 -3
- package/store/catalog.js +60 -8
- package/store/type-map.js +42 -3
- package/tsconfig.paths.json +1 -0
- package/types/resources/pod.ts +18 -0
- package/types/shell/index.d.ts +8539 -2938
- package/types/store/dashboard-store.types.ts +5 -0
- package/types/store/pagination.types.ts +6 -0
- package/utils/__tests__/git.test.ts +270 -0
- package/utils/__tests__/inactivity.test.ts +316 -0
- package/utils/__tests__/object.test.ts +77 -0
- package/utils/__tests__/time.test.ts +14 -1
- package/utils/__tests__/url.test.ts +246 -0
- package/utils/axios.js +1 -4
- package/utils/dynamic-importer.js +3 -2
- package/utils/object.js +33 -2
- package/utils/pagination-utils.ts +1 -1
- package/utils/time.ts +5 -0
- package/utils/uiplugins.ts +12 -16
- package/utils/validators/__tests__/private-registry.test.ts +76 -0
- package/utils/validators/private-registry.ts +28 -0
- package/vue.config.js +0 -9
- package/assets/images/providers/azuread-black.svg +0 -22
- package/assets/images/providers/azuread.svg +0 -25
- package/assets/images/vendor/azuread.svg +0 -18
- package/assets/styles/fonts/_dots.scss +0 -18
- package/components/EmberPage.vue +0 -622
- package/components/EmberPageView.vue +0 -39
- package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
- package/mixins/labeled-form-element.ts +0 -225
- package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
- package/pages/c/_cluster/manager/pages/_page.vue +0 -22
- package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
- package/plugins/ember-cookie.js +0 -17
- package/utils/ember-page.js +0 -30
|
@@ -210,6 +210,83 @@ describe('fx: diff', () => {
|
|
|
210
210
|
|
|
211
211
|
expect(result).toStrictEqual(expected);
|
|
212
212
|
});
|
|
213
|
+
it('should preserve nested object values when transitioning from empty object to populated object', () => {
|
|
214
|
+
const from = { parent: { child: { config: {} } } };
|
|
215
|
+
const to = {
|
|
216
|
+
parent: {
|
|
217
|
+
child: {
|
|
218
|
+
config: {
|
|
219
|
+
annotations: { hello: 'world' },
|
|
220
|
+
labels: { key: 'value' }
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const result = diff(from, to);
|
|
227
|
+
const expected = {
|
|
228
|
+
parent: {
|
|
229
|
+
child: {
|
|
230
|
+
config: {
|
|
231
|
+
annotations: { hello: 'world' },
|
|
232
|
+
labels: { key: 'value' }
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
expect(result).toStrictEqual(expected);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should preserve explicit empty object when clearing nested object values', () => {
|
|
242
|
+
const from = { parent: { child: { config: { annotations: { hello: 'world' } } } } };
|
|
243
|
+
const to = { parent: { child: { config: {} } } };
|
|
244
|
+
|
|
245
|
+
const result = diff(from, to);
|
|
246
|
+
const expected = { parent: { child: { config: {} } } };
|
|
247
|
+
|
|
248
|
+
expect(result).toStrictEqual(expected);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should not emit nested empty objects when there are no differences', () => {
|
|
252
|
+
const from = { parent: { child: { config: { annotations: { hello: 'world' } } } } };
|
|
253
|
+
const to = { parent: { child: { config: { annotations: { hello: 'world' } } } } };
|
|
254
|
+
|
|
255
|
+
const result = diff(from, to);
|
|
256
|
+
const expected = {};
|
|
257
|
+
|
|
258
|
+
expect(result).toStrictEqual(expected);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should correctly nullify nested keys when removed', () => {
|
|
262
|
+
const from = {
|
|
263
|
+
parent: {
|
|
264
|
+
child: {
|
|
265
|
+
config: {
|
|
266
|
+
annotations: { hello: 'world' },
|
|
267
|
+
labels: { key: 'value' }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
const to = { parent: { child: { config: { annotations: { hello: 'world' } } } } };
|
|
273
|
+
|
|
274
|
+
const result = diff(from, to);
|
|
275
|
+
const expected = { parent: { child: { config: { labels: { key: null } } } } };
|
|
276
|
+
|
|
277
|
+
expect(result).toStrictEqual(expected);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should not nullify child keys when parent object is updated', () => {
|
|
281
|
+
const from = { parent: { child: { config: {} } } };
|
|
282
|
+
const to = { parent: { child: { config: { annotations: { hello: 'world' } } } } };
|
|
283
|
+
|
|
284
|
+
const result = diff(from, to);
|
|
285
|
+
const expected = { parent: { child: { config: { annotations: { hello: 'world' } } } } };
|
|
286
|
+
|
|
287
|
+
expect(result).toStrictEqual(expected);
|
|
288
|
+
expect(result.parent.child.config).not.toHaveProperty('annotations', null);
|
|
289
|
+
});
|
|
213
290
|
});
|
|
214
291
|
|
|
215
292
|
describe('fx: definedKeys', () => {
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { DATE_FORMAT, TIME_FORMAT } from '@shell/store/prefs';
|
|
2
|
-
import { dateTimeFormat } from '@shell/utils/time';
|
|
2
|
+
import { dateTimeFormat, isMissingDate } from '@shell/utils/time';
|
|
3
3
|
import { type Store } from 'vuex';
|
|
4
|
+
import { ZERO_TIME } from '@shell/config/types';
|
|
5
|
+
|
|
6
|
+
describe('function: isMissingDate', () => {
|
|
7
|
+
it.each([
|
|
8
|
+
[undefined, true],
|
|
9
|
+
[null, true],
|
|
10
|
+
['', true],
|
|
11
|
+
[ZERO_TIME, true],
|
|
12
|
+
['2010-10-21T04:29:00Z', false],
|
|
13
|
+
])('given %p, returns %p', (date, expected) => {
|
|
14
|
+
expect(isMissingDate(date)).toBe(expected);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
4
17
|
|
|
5
18
|
describe('function: dateTimeFormat', () => {
|
|
6
19
|
jest.useFakeTimers()
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addParam, addParams, removeParam, parseLinkHeader, isMaybeSecure, portMatch, parse, stringify
|
|
3
|
+
} from '@shell/utils/url';
|
|
4
|
+
|
|
5
|
+
describe('fx: addParam', () => {
|
|
6
|
+
it('should add a query parameter to a URL without existing params', () => {
|
|
7
|
+
expect(addParam('https://example.com/path', 'foo', 'bar')).toStrictEqual('https://example.com/path?foo=bar');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should append a query parameter to a URL with existing params', () => {
|
|
11
|
+
expect(addParam('https://example.com/path?a=1', 'b', '2')).toStrictEqual('https://example.com/path?a=1&b=2');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should encode special characters in key and value', () => {
|
|
15
|
+
expect(addParam('https://example.com', 'my key', 'hello world')).toStrictEqual('https://example.com?my%20key=hello%20world');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should add multiple values from an array', () => {
|
|
19
|
+
expect(addParam('https://example.com', 'tag', ['a', 'b'])).toStrictEqual('https://example.com?tag=a&tag=b');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should add a key-only param when value is null', () => {
|
|
23
|
+
expect(addParam('https://example.com', 'flag', null)).toStrictEqual('https://example.com?flag');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should handle an array with a null value', () => {
|
|
27
|
+
expect(addParam('https://example.com', 'flag', [null])).toStrictEqual('https://example.com?flag');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should add a param with an empty string value', () => {
|
|
31
|
+
expect(addParam('https://example.com', 'key', '')).toStrictEqual('https://example.com?key=');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should add a duplicate key as an additional param', () => {
|
|
35
|
+
expect(addParam('https://example.com?a=1', 'a', '2')).toStrictEqual('https://example.com?a=1&a=2');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('fx: addParams', () => {
|
|
40
|
+
it('should add multiple parameters to a URL', () => {
|
|
41
|
+
expect(addParams('https://example.com', { a: '1', b: '2' })).toStrictEqual('https://example.com?a=1&b=2');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should return the URL unchanged if params is empty', () => {
|
|
45
|
+
expect(addParams('https://example.com', {})).toStrictEqual('https://example.com');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should return the URL unchanged if params is null', () => {
|
|
49
|
+
expect(addParams('https://example.com', null)).toStrictEqual('https://example.com');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should return the URL unchanged if params is a non-object value', () => {
|
|
53
|
+
expect(addParams('https://example.com', 'not-an-object')).toStrictEqual('https://example.com');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('fx: removeParam', () => {
|
|
58
|
+
it('should remove a query parameter from a URL', () => {
|
|
59
|
+
expect(removeParam('https://example.com?foo=bar&baz=qux', 'foo')).toStrictEqual('https://example.com/?baz=qux');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should return a normalized URL if the param does not exist', () => {
|
|
63
|
+
expect(removeParam('https://example.com?a=1', 'nonexistent')).toStrictEqual('https://example.com/?a=1');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should remove the only query parameter', () => {
|
|
67
|
+
expect(removeParam('https://example.com?only=param', 'only')).toStrictEqual('https://example.com/');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should normalize a key-only query parameter to key= (parser treats it as empty value)', () => {
|
|
71
|
+
expect(removeParam('https://example.com?flag', 'flag')).toStrictEqual('https://example.com/?flag=');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('fx: parseLinkHeader', () => {
|
|
76
|
+
it('should parse a single link header entry', () => {
|
|
77
|
+
expect(parseLinkHeader('<https://example.com/page2>; rel="next"')).toStrictEqual({ next: 'https://example.com/page2' });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should parse multiple link header entries', () => {
|
|
81
|
+
const header = '<https://example.com/page2>; rel="next", <https://example.com/page1>; rel="prev"';
|
|
82
|
+
|
|
83
|
+
expect(parseLinkHeader(header)).toStrictEqual({
|
|
84
|
+
next: 'https://example.com/page2',
|
|
85
|
+
prev: 'https://example.com/page1',
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should return an empty object for an empty string', () => {
|
|
90
|
+
expect(parseLinkHeader('')).toStrictEqual({});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should return an empty object for a malformed header', () => {
|
|
94
|
+
expect(parseLinkHeader('not a valid link header')).toStrictEqual({});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should lowercase the rel value', () => {
|
|
98
|
+
expect(parseLinkHeader('<https://example.com>; rel="Next"')).toStrictEqual({ next: 'https://example.com' });
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('fx: portMatch', () => {
|
|
103
|
+
it.each([
|
|
104
|
+
{
|
|
105
|
+
ports: [443], equals: [443, 8443], endsWith: [], expected: true, desc: 'port is in the equals list'
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
ports: [8080], equals: [443, 8443], endsWith: ['443'], expected: false, desc: 'port is not in equals or endsWith lists'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
ports: [8443], equals: [], endsWith: ['443'], expected: true, desc: 'port string ends with the given suffix'
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
ports: [443], equals: [], endsWith: ['443'], expected: false, desc: 'port equals the suffix exactly (endsWith excludes exact match)'
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
ports: [], equals: [443], endsWith: ['443'], expected: false, desc: 'ports array is empty'
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
ports: [80, 443], equals: [443], endsWith: [], expected: true, desc: 'any port in the array matches equals'
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
ports: [18443], equals: [], endsWith: ['443'], expected: true, desc: 'multi-digit port ending with suffix'
|
|
124
|
+
},
|
|
125
|
+
])('should return $expected when $desc', ({
|
|
126
|
+
ports, equals, endsWith, expected
|
|
127
|
+
}) => {
|
|
128
|
+
expect(portMatch(ports, equals, endsWith)).toBe(expected);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('fx: isMaybeSecure', () => {
|
|
133
|
+
it.each([
|
|
134
|
+
{
|
|
135
|
+
port: 80, proto: 'https', expected: true, desc: 'https protocol'
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
port: 80, proto: 'HTTPS', expected: true, desc: 'HTTPS protocol (case-insensitive)'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
port: 443, proto: 'http', expected: true, desc: 'port 443'
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
port: 8443, proto: 'http', expected: true, desc: 'port 8443'
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
port: 18443, proto: 'http', expected: true, desc: 'port 18443 (endsWith 443)'
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
port: 80, proto: 'http', expected: false, desc: 'http on non-secure port'
|
|
151
|
+
},
|
|
152
|
+
])('should return $expected for $desc', ({ port, proto, expected }) => {
|
|
153
|
+
expect(isMaybeSecure(port, proto)).toBe(expected);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('fx: parse', () => {
|
|
158
|
+
it('should parse a simple URL', () => {
|
|
159
|
+
const result = parse('https://example.com/path?foo=bar');
|
|
160
|
+
|
|
161
|
+
expect(result.protocol).toStrictEqual('https');
|
|
162
|
+
expect(result.host).toStrictEqual('example.com');
|
|
163
|
+
expect(result.path).toStrictEqual('/path');
|
|
164
|
+
expect(result.query).toStrictEqual({ foo: 'bar' });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should parse a URL with port', () => {
|
|
168
|
+
const result = parse('https://example.com:8080/');
|
|
169
|
+
|
|
170
|
+
expect(result.host).toStrictEqual('example.com');
|
|
171
|
+
expect(result.port).toStrictEqual('8080');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should parse a URL with user credentials', () => {
|
|
175
|
+
const result = parse('https://user:pass@example.com/');
|
|
176
|
+
|
|
177
|
+
expect(result.user).toStrictEqual('user');
|
|
178
|
+
expect(result.password).toStrictEqual('pass');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should parse a URL with anchor', () => {
|
|
182
|
+
const result = parse('https://example.com/page#section1');
|
|
183
|
+
|
|
184
|
+
expect(result.anchor).toStrictEqual('section1');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should parse a URL with multiple query params', () => {
|
|
188
|
+
expect(parse('https://example.com?a=1&b=2').query).toStrictEqual({ a: '1', b: '2' });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should parse a URL with user only (no password)', () => {
|
|
192
|
+
const result = parse('https://admin@example.com/');
|
|
193
|
+
|
|
194
|
+
expect(result.user).toStrictEqual('admin');
|
|
195
|
+
expect(result.password).toStrictEqual('');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should set empty strings for missing optional fields', () => {
|
|
199
|
+
const result = parse('https://example.com/path');
|
|
200
|
+
|
|
201
|
+
expect(result.port).toStrictEqual('');
|
|
202
|
+
expect(result.anchor).toStrictEqual('');
|
|
203
|
+
expect(result.user).toStrictEqual('');
|
|
204
|
+
expect(result.password).toStrictEqual('');
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('fx: stringify', () => {
|
|
209
|
+
it('should reconstruct a simple URL', () => {
|
|
210
|
+
expect(stringify(parse('https://example.com/path'))).toStrictEqual('https://example.com/path');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should include user and password when both present', () => {
|
|
214
|
+
expect(stringify(parse('https://user:pass@example.com/'))).toStrictEqual('https://user:pass@example.com/');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should include user only when password is absent', () => {
|
|
218
|
+
expect(stringify(parse('https://admin@example.com/'))).toStrictEqual('https://admin@example.com/');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should include port when present', () => {
|
|
222
|
+
expect(stringify(parse('https://example.com:9090/'))).toStrictEqual('https://example.com:9090/');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should include anchor when present', () => {
|
|
226
|
+
expect(stringify(parse('https://example.com/page#section'))).toStrictEqual('https://example.com/page#section');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should default path to / when path is empty', () => {
|
|
230
|
+
const parsed = parse('https://example.com');
|
|
231
|
+
|
|
232
|
+
parsed.path = '';
|
|
233
|
+
|
|
234
|
+
expect(stringify(parsed)).toStrictEqual('https://example.com/');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should include query parameters', () => {
|
|
238
|
+
expect(stringify(parse('https://example.com/path?a=1&b=2'))).toStrictEqual('https://example.com/path?a=1&b=2');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should round-trip a complex URL', () => {
|
|
242
|
+
const url = 'https://user:pass@example.com:8080/some/path?key=value&other=test#anchor';
|
|
243
|
+
|
|
244
|
+
expect(stringify(parse(url))).toStrictEqual(url);
|
|
245
|
+
});
|
|
246
|
+
});
|
package/utils/axios.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Axios from 'axios';
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import axiosRetry from 'axios-retry';
|
|
4
4
|
|
|
5
5
|
// Axios.prototype cannot be modified
|
|
@@ -37,9 +37,6 @@ const axiosExtra = {
|
|
|
37
37
|
onError(fn) {
|
|
38
38
|
this.onRequestError(fn);
|
|
39
39
|
this.onResponseError(fn);
|
|
40
|
-
},
|
|
41
|
-
create(options) {
|
|
42
|
-
return createAxiosInstance(defu(options, this.defaults));
|
|
43
40
|
}
|
|
44
41
|
};
|
|
45
42
|
|
|
@@ -94,8 +94,9 @@ export function loadProduct(name) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
export function listProducts() {
|
|
97
|
-
|
|
98
|
-
const
|
|
97
|
+
// We just want the .js or .ts files
|
|
98
|
+
const ctx = require.context('@shell/config/product', true, /\.\/.*\.[js|ts]/);
|
|
99
|
+
const products = ctx.keys().map(path => path.substr(2)).map(path => path.slice(0, -3));
|
|
99
100
|
|
|
100
101
|
return products;
|
|
101
102
|
}
|
package/utils/object.js
CHANGED
|
@@ -220,8 +220,14 @@ export function diff(from, to, preventNull = false) {
|
|
|
220
220
|
if ( Array.isArray(toVal) || Array.isArray(fromVal) ) {
|
|
221
221
|
// Don't diff arrays, just use the whole value
|
|
222
222
|
res[k] = toVal;
|
|
223
|
-
} else if ( isObject(toVal) && isObject(
|
|
224
|
-
|
|
223
|
+
} else if ( isObject(toVal) && isObject(fromVal) ) {
|
|
224
|
+
const nested = diff(fromVal, toVal, preventNull);
|
|
225
|
+
|
|
226
|
+
if (isEmpty(toVal) && !isEmpty(fromVal)) {
|
|
227
|
+
res[k] = {};
|
|
228
|
+
} else if (!isEmpty(nested)) {
|
|
229
|
+
res[k] = nested;
|
|
230
|
+
}
|
|
225
231
|
} else {
|
|
226
232
|
res[k] = toVal;
|
|
227
233
|
}
|
|
@@ -235,6 +241,31 @@ export function diff(from, to, preventNull = false) {
|
|
|
235
241
|
|
|
236
242
|
for ( const k of missing ) {
|
|
237
243
|
// we need to gate this in order to prevent unforseen problems with addonConfig
|
|
244
|
+
const hasDescendant = toKeys.some(
|
|
245
|
+
(tk) => tk.startsWith(`${ k }.`)
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
if (hasDescendant) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// If we already have a value in 'out' for any parent of this missing key, do NOT nullify the child.
|
|
253
|
+
const parts = splitObjectPath(k);
|
|
254
|
+
let hasUpdatedParent = false;
|
|
255
|
+
|
|
256
|
+
for (let i = 1; i < parts.length; i++) {
|
|
257
|
+
const parentPath = joinObjectPath(parts.slice(0, i));
|
|
258
|
+
|
|
259
|
+
if (get(out, parentPath) !== undefined) {
|
|
260
|
+
hasUpdatedParent = true;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (hasUpdatedParent) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
238
269
|
if (preventNull) {
|
|
239
270
|
// keys that come from "definedKeys" method are strings with "" chars inside... We need to clean them up
|
|
240
271
|
// so that we can access the value of the obj property
|
|
@@ -299,7 +299,7 @@ class PaginationUtils {
|
|
|
299
299
|
return isEqual(aPrimitiveTypes, bPrimitiveTypes) &&
|
|
300
300
|
this.paginationFiltersEqual(aFilter, bFilter) &&
|
|
301
301
|
this.paginationFiltersEqual(aPN, bPN) &&
|
|
302
|
-
sameArrayObjects<PaginationSort>(aSort, bSort,
|
|
302
|
+
sameArrayObjects<PaginationSort>(aSort, bSort, false);
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
305
|
|
package/utils/time.ts
CHANGED
|
@@ -2,6 +2,11 @@ import day from 'dayjs';
|
|
|
2
2
|
import { escapeHtml } from '@shell/utils/string';
|
|
3
3
|
import { DATE_FORMAT, TIME_FORMAT } from '@shell/store/prefs';
|
|
4
4
|
import { type Store } from 'vuex';
|
|
5
|
+
import { ZERO_TIME } from '@shell/config/types';
|
|
6
|
+
|
|
7
|
+
export function isMissingDate(date: string | undefined | null): boolean {
|
|
8
|
+
return !date || date === ZERO_TIME;
|
|
9
|
+
}
|
|
5
10
|
|
|
6
11
|
const FACTORS = [60, 60, 24];
|
|
7
12
|
const LABELS = ['sec', 'min', 'hour', 'day'];
|
package/utils/uiplugins.ts
CHANGED
|
@@ -5,14 +5,15 @@ import { UI_PLUGIN_BASE_URL, isSupportedChartVersion, UI_PLUGIN_LABELS } from '@
|
|
|
5
5
|
import { Plugin, Version } from '@shell/types/uiplugins';
|
|
6
6
|
|
|
7
7
|
const MAX_RETRIES = 10;
|
|
8
|
-
const RETRY_WAIT = 2500;
|
|
8
|
+
const RETRY_WAIT = 2500; // 2.5 seconds
|
|
9
|
+
const ACTIVE_STATUS_TIMEOUT = 200000; // 20 seconds
|
|
9
10
|
|
|
10
11
|
type Action = 'install' | 'upgrade';
|
|
11
12
|
export type HelmRepository = any;
|
|
12
13
|
export type HelmChart = any;
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
+
* Get the latest compatible version of a Helm Chart extension
|
|
16
17
|
* @param store Vue store
|
|
17
18
|
* @param chartName The chartName
|
|
18
19
|
* @param rancherVersion Rancher version
|
|
@@ -68,8 +69,8 @@ export async function waitForUIExtension(store: any, name: string, maxRetries =
|
|
|
68
69
|
return extension;
|
|
69
70
|
}
|
|
70
71
|
} catch (e) {
|
|
72
|
+
console.error('waiting for UI extension to be available: error =', e); // eslint-disable-line no-console
|
|
71
73
|
}
|
|
72
|
-
|
|
73
74
|
tries++;
|
|
74
75
|
|
|
75
76
|
if (tries > maxRetries) {
|
|
@@ -106,7 +107,6 @@ export async function waitForUIPackage(store: any, extension: any, maxRetries =
|
|
|
106
107
|
return true;
|
|
107
108
|
} catch (error) {
|
|
108
109
|
}
|
|
109
|
-
|
|
110
110
|
tries++;
|
|
111
111
|
|
|
112
112
|
if (tries > maxRetries) {
|
|
@@ -199,7 +199,7 @@ export async function getHelmRepositoryMatch(store: any, urlRegexes: string[], c
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
/**
|
|
202
|
-
*
|
|
202
|
+
* Get a Helm Repository matching the given criteria.
|
|
203
203
|
* @param store Vue store
|
|
204
204
|
* @param matchFn Match function for repository's urls
|
|
205
205
|
* @returns HelmRepository
|
|
@@ -225,15 +225,15 @@ export async function refreshHelmRepository(store: any, url: string): Promise<vo
|
|
|
225
225
|
const now = (new Date()).toISOString().replace(/\.\d+Z$/, 'Z');
|
|
226
226
|
|
|
227
227
|
repository.spec.forceUpdate = now;
|
|
228
|
-
|
|
229
228
|
await repository.save();
|
|
230
229
|
|
|
231
|
-
await repository.waitForState('active',
|
|
230
|
+
await repository.waitForState('active', ACTIVE_STATUS_TIMEOUT, RETRY_WAIT);
|
|
232
231
|
|
|
233
232
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
234
233
|
}
|
|
235
234
|
|
|
236
235
|
/**
|
|
236
|
+
* Create a Helm Repository and wait for it to be downloaded
|
|
237
237
|
*
|
|
238
238
|
* @param store Vue store
|
|
239
239
|
* @param name Repository name
|
|
@@ -260,7 +260,7 @@ export async function createHelmRepository(store: any, name: string, url: string
|
|
|
260
260
|
|
|
261
261
|
const helmRepo = await repo.save();
|
|
262
262
|
|
|
263
|
-
// Poll the repository
|
|
263
|
+
// Poll the repository status MAX_RETRIES times until it has been downloaded
|
|
264
264
|
let fetched = false;
|
|
265
265
|
let tries = 0;
|
|
266
266
|
|
|
@@ -275,23 +275,19 @@ export async function createHelmRepository(store: any, name: string, url: string
|
|
|
275
275
|
|
|
276
276
|
const downloaded = repo.status.conditions.find((s: any) => s.type === 'Downloaded');
|
|
277
277
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
278
|
+
console.log(`Waiting for helm repository to be downloaded... try ${ tries } time(s).`); // eslint-disable-line no-console
|
|
279
|
+
|
|
280
|
+
if (downloaded && downloaded.status === 'True') {
|
|
281
|
+
fetched = true;
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
if (!fetched) {
|
|
285
|
-
tries++;
|
|
286
|
-
|
|
287
285
|
if (tries > MAX_RETRIES) {
|
|
288
286
|
throw new Error('Failed to add Helm Chart Repository');
|
|
289
287
|
}
|
|
290
288
|
|
|
291
289
|
await new Promise((resolve) => setTimeout(resolve, RETRY_WAIT));
|
|
292
290
|
}
|
|
293
|
-
|
|
294
|
-
fetched = true;
|
|
295
291
|
}
|
|
296
292
|
|
|
297
293
|
// Return the Helm Repository
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { privateRegistryRequired } from '@shell/utils/validators/private-registry';
|
|
2
|
+
|
|
3
|
+
const makeCtx = (overrides: any = {}) => ({
|
|
4
|
+
t: jest.fn((key: string, params?: any) => (params ? `${ key }:${ JSON.stringify(params) }` : key)),
|
|
5
|
+
privateRegistryEnabled: false,
|
|
6
|
+
normanCluster: { importedConfig: { privateRegistryURL: null } },
|
|
7
|
+
isImportedCluster: true,
|
|
8
|
+
...overrides,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('privateRegistryRequired', () => {
|
|
12
|
+
it('should return undefined when the cluster is not imported', () => {
|
|
13
|
+
const ctx = makeCtx({ isImportedCluster: false, privateRegistryEnabled: true });
|
|
14
|
+
const rule = privateRegistryRequired(ctx);
|
|
15
|
+
|
|
16
|
+
expect(rule()).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should default isImportedCluster to true when the property is absent from the context', () => {
|
|
20
|
+
const ctx: any = {
|
|
21
|
+
t: jest.fn((key: string, params?: any) => (params ? `${ key }:${ JSON.stringify(params) }` : key)),
|
|
22
|
+
privateRegistryEnabled: true,
|
|
23
|
+
normanCluster: { importedConfig: { privateRegistryURL: null } },
|
|
24
|
+
};
|
|
25
|
+
const rule = privateRegistryRequired(ctx);
|
|
26
|
+
|
|
27
|
+
expect(rule()).toStrictEqual('validation.required:{"key":"cluster.privateRegistry.label"}');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should return undefined when the registry toggle is off', () => {
|
|
31
|
+
const ctx = makeCtx({ privateRegistryEnabled: false });
|
|
32
|
+
const rule = privateRegistryRequired(ctx);
|
|
33
|
+
|
|
34
|
+
expect(rule()).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should return a required error when enabled but the url is empty', () => {
|
|
38
|
+
const ctx = makeCtx({
|
|
39
|
+
privateRegistryEnabled: true,
|
|
40
|
+
normanCluster: { importedConfig: { privateRegistryURL: '' } },
|
|
41
|
+
});
|
|
42
|
+
const rule = privateRegistryRequired(ctx);
|
|
43
|
+
|
|
44
|
+
expect(rule()).toStrictEqual('validation.required:{"key":"cluster.privateRegistry.label"}');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should return a required error when normanCluster.importedConfig is missing', () => {
|
|
48
|
+
const ctx = makeCtx({
|
|
49
|
+
privateRegistryEnabled: true,
|
|
50
|
+
normanCluster: {},
|
|
51
|
+
});
|
|
52
|
+
const rule = privateRegistryRequired(ctx);
|
|
53
|
+
|
|
54
|
+
expect(rule()).toStrictEqual('validation.required:{"key":"cluster.privateRegistry.label"}');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should return a format error when enabled and the url is malformed', () => {
|
|
58
|
+
const ctx = makeCtx({
|
|
59
|
+
privateRegistryEnabled: true,
|
|
60
|
+
normanCluster: { importedConfig: { privateRegistryURL: 'goober' } },
|
|
61
|
+
});
|
|
62
|
+
const rule = privateRegistryRequired(ctx);
|
|
63
|
+
|
|
64
|
+
expect(rule()).toStrictEqual('cluster.privateRegistry.privateRegistryUrlError');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should return undefined when enabled and the url is valid', () => {
|
|
68
|
+
const ctx = makeCtx({
|
|
69
|
+
privateRegistryEnabled: true,
|
|
70
|
+
normanCluster: { importedConfig: { privateRegistryURL: 'registry.io:5000' } },
|
|
71
|
+
});
|
|
72
|
+
const rule = privateRegistryRequired(ctx);
|
|
73
|
+
|
|
74
|
+
expect(rule()).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Translation } from '@shell/types/t';
|
|
2
|
+
import formRulesGenerator from '@shell/utils/validators/formRules';
|
|
3
|
+
|
|
4
|
+
interface PrivateRegistryRuleContext {
|
|
5
|
+
t: Translation;
|
|
6
|
+
privateRegistryEnabled: boolean;
|
|
7
|
+
normanCluster: { importedConfig?: { privateRegistryURL?: string | null } } | null;
|
|
8
|
+
isImportedCluster?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function privateRegistryRequired(ctx: PrivateRegistryRuleContext) {
|
|
12
|
+
return () => {
|
|
13
|
+
// Check existence using `in` rather than direct access to avoid Vue's render-time warning
|
|
14
|
+
const isImported = 'isImportedCluster' in ctx ? !!ctx.isImportedCluster : true;
|
|
15
|
+
|
|
16
|
+
if (!isImported || !ctx.privateRegistryEnabled) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const url = ctx.normanCluster?.importedConfig?.privateRegistryURL;
|
|
20
|
+
|
|
21
|
+
if (!url) {
|
|
22
|
+
return ctx.t('validation.required', { key: ctx.t('cluster.privateRegistry.label') });
|
|
23
|
+
}
|
|
24
|
+
const { registryUrl } = formRulesGenerator(ctx.t, { key: ctx.t('cluster.privateRegistry.label') });
|
|
25
|
+
|
|
26
|
+
return (registryUrl as (val: string) => string | undefined)(url);
|
|
27
|
+
};
|
|
28
|
+
}
|
package/vue.config.js
CHANGED
|
@@ -75,15 +75,6 @@ const getProxyConfig = (proxyConfig) => ({
|
|
|
75
75
|
'/v1-*': proxyOpts(api), // SAML, KDM, etc
|
|
76
76
|
'/rancherversion': proxyPrimeOpts(api), // Rancher version endpoint
|
|
77
77
|
'/version': proxyPrimeOpts(api), // Rancher Kube version endpoint
|
|
78
|
-
// These are for Ember embedding
|
|
79
|
-
'/c/*/edit': proxyOpts('https://127.0.0.1:8000'), // Can't proxy all of /c because that's used by Vue too
|
|
80
|
-
'/k/': proxyOpts('https://127.0.0.1:8000'),
|
|
81
|
-
'/g/': proxyOpts('https://127.0.0.1:8000'),
|
|
82
|
-
'/n/': proxyOpts('https://127.0.0.1:8000'),
|
|
83
|
-
'/p/': proxyOpts('https://127.0.0.1:8000'),
|
|
84
|
-
'/assets': proxyOpts('https://127.0.0.1:8000'),
|
|
85
|
-
'/translations': proxyOpts('https://127.0.0.1:8000'),
|
|
86
|
-
'/engines-dist': proxyOpts('https://127.0.0.1:8000'),
|
|
87
78
|
});
|
|
88
79
|
|
|
89
80
|
/**
|