@rancher/shell 3.0.2-rc.2 → 3.0.2-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/assets/styles/base/_basic.scss +5 -7
- package/assets/styles/global/_button.scss +10 -0
- package/assets/styles/global/_tooltip.scss +2 -2
- package/assets/styles/themes/_dark.scss +14 -2
- package/assets/styles/themes/_light.scss +7 -2
- package/assets/styles/vendor/vue-select.scss +4 -0
- package/assets/translations/en-us.yaml +44 -5
- package/components/BannerGraphic.vue +0 -42
- package/components/ButtonMultiAction.vue +1 -1
- package/components/Carousel.vue +36 -29
- package/components/CommunityLinks.vue +6 -1
- package/components/GrowlManager.vue +9 -2
- package/components/LocaleSelector.vue +8 -1
- package/components/PaginatedResourceTable.vue +4 -7
- package/components/ProgressBarMulti.vue +14 -0
- package/components/Questions/Reference.vue +57 -28
- package/components/SelectIconGrid.vue +12 -1
- package/components/SideNav.vue +12 -38
- package/components/SortableTable/index.vue +1 -0
- package/components/Tabbed/index.vue +12 -1
- package/components/YamlEditor.vue +1 -0
- package/components/auth/Principal.vue +5 -3
- package/components/fleet/FleetClusters.vue +82 -1
- package/components/fleet/FleetRepos.vue +13 -30
- package/components/fleet/ForceDirectedTreeChart/index.vue +2 -2
- package/components/form/ChangePassword.vue +2 -0
- package/components/form/ColorInput.vue +24 -1
- package/components/form/FileSelector.vue +2 -0
- package/components/form/KeyValue.vue +230 -160
- package/components/form/LabeledSelect.vue +1 -1
- package/components/form/PlusMinus.vue +14 -2
- package/components/form/ResourceLabeledSelect.vue +13 -53
- package/components/form/ResourceSelector.vue +1 -0
- package/components/form/ResourceTabs/index.vue +79 -36
- package/components/form/SecretSelector.vue +2 -2
- package/components/form/__tests__/KeyValue.test.ts +1 -1
- package/components/formatter/FleetClusterSummaryGraph.vue +2 -2
- package/components/formatter/FleetSummaryGraph.vue +6 -7
- package/components/formatter/WorkloadHealthScale.vue +7 -0
- package/components/nav/Group.vue +30 -4
- package/components/nav/Header.vue +82 -114
- package/components/nav/HeaderPageActionMenu.vue +27 -131
- package/components/nav/NamespaceFilter.vue +1 -1
- package/components/nav/Type.vue +15 -0
- package/config/home-links.js +21 -13
- package/config/labels-annotations.js +2 -0
- package/config/page-actions.js +1 -0
- package/config/pagination-table-headers.js +15 -1
- package/config/product/explorer.js +7 -17
- package/config/table-headers.js +6 -0
- package/config/version.js +5 -1
- package/core/plugin.ts +41 -1
- package/core/plugins.js +125 -72
- package/core/types-provisioning.ts +91 -2
- package/core/types.ts +55 -0
- package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +12 -3
- package/detail/catalog.cattle.io.app.vue +1 -1
- package/detail/fleet.cattle.io.cluster.vue +3 -3
- package/detail/namespace.vue +13 -19
- package/detail/networking.k8s.io.ingress.vue +13 -53
- package/detail/provisioning.cattle.io.cluster.vue +12 -1
- package/detail/workload/index.vue +3 -3
- package/dialog/AddCustomBadgeDialog.vue +5 -1
- package/edit/auth/ldap/__tests__/config.test.ts +18 -0
- package/edit/auth/ldap/config.vue +24 -0
- package/edit/auth/saml.vue +8 -6
- package/edit/fleet.cattle.io.gitrepo.vue +7 -1
- package/edit/logging-flow/index.vue +4 -19
- package/edit/networking.k8s.io.ingress/index.vue +18 -65
- package/edit/networking.k8s.io.networkpolicy/index.vue +4 -5
- package/edit/provisioning.cattle.io.cluster/index.vue +13 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +31 -115
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +2 -2
- package/edit/provisioning.cattle.io.cluster/tabs/networking/ACE.vue +14 -28
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +25 -12
- package/edit/service.vue +1 -2
- package/list/networking.k8s.io.ingress.vue +1 -1
- package/list/node.vue +15 -8
- package/list/persistentvolume.vue +12 -4
- package/list/service.vue +1 -1
- package/list/workload.vue +4 -0
- package/mixins/chart.js +4 -1
- package/models/catalog.cattle.io.app.js +3 -1
- package/models/catalog.cattle.io.clusterrepo.js +56 -7
- package/models/fleet.cattle.io.bundle.js +0 -11
- package/models/fleet.cattle.io.cluster.js +17 -1
- package/models/fleet.cattle.io.gitrepo.js +86 -50
- package/models/provisioning.cattle.io.cluster.js +47 -2
- package/models/service.js +1 -0
- package/models/workload.js +19 -1
- package/package.json +5 -4
- package/pages/c/_cluster/apps/charts/index.vue +4 -0
- package/pages/c/_cluster/explorer/ConfigBadge.vue +8 -7
- package/pages/c/_cluster/explorer/index.vue +13 -6
- package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +3 -3
- package/pages/c/_cluster/fleet/index.vue +75 -89
- package/pages/c/_cluster/settings/links.vue +2 -2
- package/pages/diagnostic.vue +17 -15
- package/pages/home.vue +32 -6
- package/plugins/clean-html.js +50 -0
- package/plugins/dashboard-store/resource-class.js +4 -0
- package/plugins/plugin.js +54 -49
- package/plugins/steve/mutations.js +1 -1
- package/plugins/steve/steve-class.js +8 -0
- package/plugins/steve/steve-pagination-utils.ts +3 -1
- package/rancher-components/Accordion/Accordion.vue +4 -4
- package/rancher-components/BadgeState/BadgeState.vue +7 -0
- package/rancher-components/Card/Card.vue +27 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +9 -2
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +18 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +18 -1
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +39 -2
- package/rancher-components/RcButton/RcButton.vue +90 -0
- package/rancher-components/RcButton/index.ts +2 -0
- package/rancher-components/RcButton/types.ts +17 -0
- package/rancher-components/RcDropdown/RcDropdown.vue +111 -0
- package/rancher-components/RcDropdown/RcDropdownItem.vue +127 -0
- package/rancher-components/RcDropdown/RcDropdownSeparator.vue +6 -0
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +43 -0
- package/rancher-components/RcDropdown/index.ts +4 -0
- package/rancher-components/RcDropdown/types.ts +22 -0
- package/rancher-components/RcDropdown/useDropdownCollection.ts +45 -0
- package/rancher-components/RcDropdown/useDropdownContext.ts +83 -0
- package/scripts/test-plugins-build.sh +2 -0
- package/scripts/typegen.sh +2 -0
- package/store/catalog.js +1 -1
- package/tsconfig.json +2 -1
- package/types/components/paginatedResourceTable.ts +25 -0
- package/types/components/resourceLabeledSelect.ts +48 -0
- package/types/resources/fleet.d.ts +17 -0
- package/types/shell/index.d.ts +61 -0
- package/utils/auth.js +5 -1
- package/utils/cluster.js +106 -0
- package/utils/fleet.ts +35 -3
- package/utils/ingress.ts +64 -0
- package/utils/uiplugins.ts +56 -44
- package/utils/validators/cron-schedule.js +7 -2
- package/utils/validators/formRules/__tests__/index.test.ts +53 -17
- package/utils/validators/formRules/index.ts +20 -5
- package/vue.config.js +1 -1
- package/components/RelatedWorkloadsTable.vue +0 -50
package/utils/ingress.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { SECRET, SERVICE } from '@shell/config/types';
|
|
2
|
+
import { SECRET_TYPES as TYPES } from '@shell/config/secret';
|
|
3
|
+
import { VuexStore } from '@shell/types/store/vuex';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Helper class for common functionality shared between the detail and edit ingress pages
|
|
7
|
+
*
|
|
8
|
+
* This could be an untyped mixin.. but this setups up us better for the future
|
|
9
|
+
*/
|
|
10
|
+
class IngressDetailEditHelper {
|
|
11
|
+
private $store: VuexStore;
|
|
12
|
+
private namespace: string;
|
|
13
|
+
|
|
14
|
+
constructor({
|
|
15
|
+
$store,
|
|
16
|
+
namespace,
|
|
17
|
+
}: {
|
|
18
|
+
$store: VuexStore
|
|
19
|
+
namespace: string,
|
|
20
|
+
}) {
|
|
21
|
+
this.$store = $store;
|
|
22
|
+
this.namespace = namespace;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fetch services that will either be used to show
|
|
27
|
+
* - Create - the possible rule's target service
|
|
28
|
+
* - Edit - the selected and possible rule's target service
|
|
29
|
+
* - Detail - the selected rule's target service
|
|
30
|
+
*/
|
|
31
|
+
async fetchServices(args?: { namespace: string}): Promise<any[]> {
|
|
32
|
+
return this.$store.dispatch('cluster/findAll', { type: SERVICE, opt: { namespaced: args?.namespace || this.namespace } });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fetch secrets that will either be used to show
|
|
37
|
+
* - Create - the possible secrets to use as a certificates
|
|
38
|
+
* - Edit - the selected and possible secrets to use as a certificates
|
|
39
|
+
* - Detail - the selected secrets to use as certificates
|
|
40
|
+
*/
|
|
41
|
+
async fetchSecrets(args?: { namespace: string}): Promise<any[]> {
|
|
42
|
+
return this.$store.dispatch('cluster/findAll', { type: SECRET, opt: { namespaced: args?.namespace || this.namespace } });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
findAndMapCerts(secrets: any[]) {
|
|
46
|
+
return secrets
|
|
47
|
+
.filter((secret) => secret._type === TYPES.TLS)
|
|
48
|
+
.map((secret) => {
|
|
49
|
+
const { id } = secret;
|
|
50
|
+
|
|
51
|
+
return id.slice(id.indexOf('/') + 1);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
findAndMapServiceTargets(services: any[]) {
|
|
56
|
+
return services.map((service) => ({
|
|
57
|
+
label: service.metadata.name,
|
|
58
|
+
value: service.metadata.name,
|
|
59
|
+
ports: service.spec.ports?.map((p: any) => p.port)
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default IngressDetailEditHelper;
|
package/utils/uiplugins.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { matchesSomeRegex } from '@shell/utils/string';
|
|
1
2
|
import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
|
|
2
3
|
import { CATALOG } from '@shell/config/types';
|
|
3
4
|
import { UI_PLUGIN_BASE_URL, isSupportedChartVersion } from '@shell/config/uiplugins';
|
|
@@ -158,37 +159,56 @@ export async function installHelmChart(repo: any, chart: any, values: any = {},
|
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
/**
|
|
161
|
-
* Get the Helm repository object
|
|
162
162
|
*
|
|
163
163
|
* @param store Vue store
|
|
164
|
-
* @param url
|
|
165
|
-
* @param branch The branch of the Helm repository
|
|
164
|
+
* @param url Repository Url
|
|
166
165
|
* @returns HelmRepository
|
|
167
166
|
*/
|
|
168
|
-
export async function
|
|
167
|
+
export async function getHelmRepositoryExact(store: any, url: string): Promise<HelmRepository> {
|
|
168
|
+
return await getHelmRepository(store, (repository: any) => {
|
|
169
|
+
const target = repository.spec?.gitRepo || repository.spec?.url;
|
|
170
|
+
|
|
171
|
+
return target === url;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
*
|
|
177
|
+
* @param store Vue store
|
|
178
|
+
* @param urlRegexes Regex to match a community repository
|
|
179
|
+
* @returns HelmRepository
|
|
180
|
+
*/
|
|
181
|
+
export async function getHelmRepositoryMatch(store: any, urlRegexes: string[]): Promise<HelmRepository> {
|
|
182
|
+
return await getHelmRepository(store, (repository: any) => {
|
|
183
|
+
const target = repository.spec?.gitBranch ? repository.spec?.gitRepo : repository.spec?.url;
|
|
184
|
+
|
|
185
|
+
return matchesSomeRegex(target, urlRegexes);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
*
|
|
191
|
+
* @param store Vue store
|
|
192
|
+
* @param matchFn Match function for repository's urls
|
|
193
|
+
* @returns HelmRepository
|
|
194
|
+
*/
|
|
195
|
+
async function getHelmRepository(store: any, matchFn: (repository: any) => boolean): Promise<HelmRepository> {
|
|
169
196
|
if (store.getters['management/schemaFor'](CATALOG.CLUSTER_REPO)) {
|
|
170
197
|
const repos = await store.dispatch('management/findAll', { type: CATALOG.CLUSTER_REPO, opt: { force: true, watch: false } });
|
|
171
198
|
|
|
172
|
-
return repos.find(
|
|
173
|
-
const target = branch ? r.spec?.gitRepo : r.spec?.url ;
|
|
174
|
-
|
|
175
|
-
return target === url;
|
|
176
|
-
});
|
|
199
|
+
return repos.find(matchFn);
|
|
177
200
|
} else {
|
|
178
201
|
throw new Error('No permissions');
|
|
179
202
|
}
|
|
180
203
|
}
|
|
181
204
|
|
|
182
205
|
/**
|
|
183
|
-
* Refresh the Helm repository
|
|
184
|
-
* Ensures that we find the latest extension versions
|
|
185
206
|
*
|
|
186
207
|
* @param store Vue store
|
|
187
|
-
* @param
|
|
188
|
-
* @param gitBranch Extension Repository branch
|
|
208
|
+
* @param url Repository Url
|
|
189
209
|
*/
|
|
190
|
-
export async function refreshHelmRepository(store: any,
|
|
191
|
-
const repository = await
|
|
210
|
+
export async function refreshHelmRepository(store: any, url: string): Promise<void> {
|
|
211
|
+
const repository = await getHelmRepositoryExact(store, url);
|
|
192
212
|
|
|
193
213
|
const now = (new Date()).toISOString().replace(/\.\d+Z$/, 'Z');
|
|
194
214
|
|
|
@@ -202,40 +222,32 @@ export async function refreshHelmRepository(store: any, gitRepo: string, gitBran
|
|
|
202
222
|
}
|
|
203
223
|
|
|
204
224
|
/**
|
|
205
|
-
* Ensure the required Helm Repository exits, if it does not, add it with the specified name
|
|
206
|
-
*
|
|
207
|
-
* Wait until the newly added repository has been downloaded
|
|
208
225
|
*
|
|
209
226
|
* @param store Vue store
|
|
210
|
-
* @param
|
|
211
|
-
* @param
|
|
212
|
-
* @param branch
|
|
213
|
-
* @returns HelmRepository
|
|
227
|
+
* @param name Repository name
|
|
228
|
+
* @param url Repository Url
|
|
229
|
+
* @param branch Repository Branch
|
|
230
|
+
* @returns HelmRepository
|
|
214
231
|
*/
|
|
215
|
-
export async function
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
type: CATALOG.CLUSTER_REPO,
|
|
222
|
-
metadata: { name },
|
|
223
|
-
spec: {} as any
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
if (branch) {
|
|
227
|
-
data.spec.gitBranch = branch;
|
|
228
|
-
data.spec.gitRepo = url;
|
|
229
|
-
} else {
|
|
230
|
-
data.spec.url = url;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Create a model for the new repository and save it
|
|
234
|
-
const repo = await store.dispatch('management/create', data);
|
|
232
|
+
export async function createHelmRepository(store: any, name: string, url: string, branch?: string): Promise<HelmRepository> {
|
|
233
|
+
const data = {
|
|
234
|
+
type: CATALOG.CLUSTER_REPO,
|
|
235
|
+
metadata: { name },
|
|
236
|
+
spec: {} as any
|
|
237
|
+
};
|
|
235
238
|
|
|
236
|
-
|
|
239
|
+
if (branch) {
|
|
240
|
+
data.spec.gitBranch = branch;
|
|
241
|
+
data.spec.gitRepo = url;
|
|
242
|
+
} else {
|
|
243
|
+
data.spec.url = url;
|
|
237
244
|
}
|
|
238
245
|
|
|
246
|
+
// Create a model for the new repository and save it
|
|
247
|
+
const repo = await store.dispatch('management/create', data);
|
|
248
|
+
|
|
249
|
+
const helmRepo = await repo.save();
|
|
250
|
+
|
|
239
251
|
// Poll the repository until it says it has been downloaded
|
|
240
252
|
let fetched = false;
|
|
241
253
|
let tries = 0;
|
|
@@ -278,7 +290,7 @@ export async function ensureHelmRepository(store: any, url: string, name: string
|
|
|
278
290
|
* Get the given Helm Chart from the specified Helm Repository
|
|
279
291
|
*
|
|
280
292
|
* @param store Vue store
|
|
281
|
-
* @param repository
|
|
293
|
+
* @param repository Repository Url
|
|
282
294
|
* @param chartName Helm Chart name
|
|
283
295
|
* @returns Helm Chart
|
|
284
296
|
*/
|
|
@@ -2,8 +2,13 @@ import cronstrue from 'cronstrue';
|
|
|
2
2
|
|
|
3
3
|
export function cronSchedule(schedule = '', getters, errors) {
|
|
4
4
|
try {
|
|
5
|
-
|
|
5
|
+
cronScheduleRule.validation(schedule);
|
|
6
6
|
} catch (e) {
|
|
7
|
-
errors.push(getters['i18n/t'](
|
|
7
|
+
errors.push(getters['i18n/t'](cronScheduleRule.message));
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
+
|
|
11
|
+
export const cronScheduleRule = {
|
|
12
|
+
validation: (text) => cronstrue.toString(text, { verbose: true }),
|
|
13
|
+
message: 'validation.invalidCron'
|
|
14
|
+
};
|
|
@@ -27,22 +27,6 @@ describe('formRules', () => {
|
|
|
27
27
|
expect(formRuleResult).toStrictEqual(expectedResult);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
it('"cronSchedule" : returns undefined when valid cron string value supplied', () => {
|
|
31
|
-
const testValue = '0 * * * *';
|
|
32
|
-
const formRuleResult = formRules.cronSchedule(testValue);
|
|
33
|
-
|
|
34
|
-
expect(formRuleResult).toBeUndefined();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('"cronSchedule" : returns the correct message when invalid cron string value supplied', () => {
|
|
38
|
-
// specific logic of what constitutes a cron string is in the "cronstrue" function in an external library and not tested here
|
|
39
|
-
const testValue = '0 * * **';
|
|
40
|
-
const formRuleResult = formRules.cronSchedule(testValue);
|
|
41
|
-
const expectedResult = JSON.stringify({ message: 'validation.invalidCron' });
|
|
42
|
-
|
|
43
|
-
expect(formRuleResult).toStrictEqual(expectedResult);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
30
|
it('"https" : returns undefined when valid https url value is supplied', () => {
|
|
47
31
|
const testValue = 'https://url.com';
|
|
48
32
|
const formRuleResult = formRules.https(testValue);
|
|
@@ -103,6 +87,45 @@ describe('formRules', () => {
|
|
|
103
87
|
);
|
|
104
88
|
});
|
|
105
89
|
|
|
90
|
+
describe('gitRepository', () => {
|
|
91
|
+
const message = JSON.stringify({ message: 'validation.git.repository' });
|
|
92
|
+
const testCases = [
|
|
93
|
+
// Valid HTTP(s)
|
|
94
|
+
['https://github.com/rancher/dashboard.git', undefined],
|
|
95
|
+
['http://github.com/rancher/dashboard.git', undefined],
|
|
96
|
+
['https://github.com/rancher/dashboard', undefined],
|
|
97
|
+
['https://github.com/rancher/dashboard/', undefined],
|
|
98
|
+
|
|
99
|
+
// Valid SSH
|
|
100
|
+
['git@github.com:rancher/dashboard.git', undefined],
|
|
101
|
+
['git@github.com:rancher/dashboard', undefined],
|
|
102
|
+
['git@github.com:rancher/dashboard/', undefined],
|
|
103
|
+
|
|
104
|
+
// Not valid HTTP(s)
|
|
105
|
+
['https://github.com/rancher/ dashboard.git', message],
|
|
106
|
+
['http://github.com/rancher/ dashboard.git', message],
|
|
107
|
+
['https://github.com/rancher/dashboard ', message],
|
|
108
|
+
['foo://github.com/rancher/dashboard/', message],
|
|
109
|
+
['github.com/rancher/dashboard/', message],
|
|
110
|
+
|
|
111
|
+
// Not valid SSH
|
|
112
|
+
['git@github.com:rancher/ dashboard.git', message],
|
|
113
|
+
['git@github.com:rancher/dashboard ', message],
|
|
114
|
+
['git@github.comrancher/dashboard', message],
|
|
115
|
+
|
|
116
|
+
[undefined, undefined]
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
it.each(testCases)(
|
|
120
|
+
'should return undefined or correct message based on the provided Git url: %p',
|
|
121
|
+
(url, expected) => {
|
|
122
|
+
const formRuleResult = formRules.gitRepository(url);
|
|
123
|
+
|
|
124
|
+
expect(formRuleResult).toStrictEqual(expected);
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
106
129
|
describe('alphanumeric', () => {
|
|
107
130
|
const message = JSON.stringify({ message: 'validation.alphanumeric', key: 'testDisplayKey' });
|
|
108
131
|
const testCases = [
|
|
@@ -1112,6 +1135,13 @@ describe('formRules', () => {
|
|
|
1112
1135
|
expect(formRuleResult).toStrictEqual(expectedResult);
|
|
1113
1136
|
});
|
|
1114
1137
|
|
|
1138
|
+
/**
|
|
1139
|
+
* Test all factory validators
|
|
1140
|
+
* @param rule - the name of the factory validator
|
|
1141
|
+
* @param argument - the value to validate
|
|
1142
|
+
* @param correctValues - an array of values that should pass the validation
|
|
1143
|
+
* @param wrongValues - an array of values that should fail the validation
|
|
1144
|
+
*/
|
|
1115
1145
|
describe.each([
|
|
1116
1146
|
['minValue', 2, [3], [1]],
|
|
1117
1147
|
['maxValue', 256, [1], [300]],
|
|
@@ -1133,12 +1163,18 @@ describe('formRules', () => {
|
|
|
1133
1163
|
});
|
|
1134
1164
|
});
|
|
1135
1165
|
|
|
1166
|
+
/**
|
|
1167
|
+
* Test all standard validators
|
|
1168
|
+
* @param rule - the name of the standard validator
|
|
1169
|
+
* @param correctValues - an array of values that should pass the validation
|
|
1170
|
+
* @param wrongValues - an array of values that should fail the validation
|
|
1171
|
+
*/
|
|
1136
1172
|
describe.each([
|
|
1137
1173
|
['requiredInt', [2, 2.2], ['e']],
|
|
1138
1174
|
['isInteger', ['2', 2, 0], [2.2, 'e', '1.0']],
|
|
1139
1175
|
['isPositive', ['0', 1], [-1]],
|
|
1140
1176
|
['isOctal', ['0', 0, 10], ['01']],
|
|
1141
|
-
|
|
1177
|
+
['cronSchedule', ['0 * * * *', '@daily'], ['0 * * **']],
|
|
1142
1178
|
])('given validator %p', (rule, correctValues, wrongValues) => {
|
|
1143
1179
|
it.each(wrongValues as [])('should return error for value %p', (wrong) => {
|
|
1144
1180
|
const formRuleResult = (formRules as any)[rule](wrong);
|
|
@@ -4,14 +4,26 @@ import isEmpty from 'lodash/isEmpty';
|
|
|
4
4
|
import has from 'lodash/has';
|
|
5
5
|
import isUrl from 'is-url';
|
|
6
6
|
// import uniq from 'lodash/uniq';
|
|
7
|
-
import cronstrue from 'cronstrue';
|
|
8
7
|
import { Translation } from '@shell/types/t';
|
|
9
8
|
import { isHttps, isLocalhost, hasTrailingForwardSlash } from '@shell/utils/validators/setting';
|
|
9
|
+
import { cronScheduleRule } from '@shell/utils/validators/cron-schedule';
|
|
10
10
|
|
|
11
11
|
// import uniq from 'lodash/uniq';
|
|
12
|
-
export type Validator<T = undefined | string> = (val: any, arg?: any) => T;
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Fixed validation rule which require only the value to be evaluated
|
|
15
|
+
* @param value
|
|
16
|
+
* @returns { string | undefined }
|
|
17
|
+
*/
|
|
18
|
+
export type Validator<T = undefined | string> = (value: any, arg?: any) => T;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Factory function which returns a validation rule
|
|
22
|
+
* @param arg Argument used as part of the validation rule process, not necessarily as parameter of the validation rule
|
|
23
|
+
* @param value Value to be evaluated
|
|
24
|
+
* @returns { Validator }
|
|
25
|
+
*/
|
|
26
|
+
export type ValidatorFactory = (arg: any, value?: any) => Validator
|
|
15
27
|
|
|
16
28
|
type ServicePort = {
|
|
17
29
|
name?: string,
|
|
@@ -131,9 +143,9 @@ export default function(t: Translation, { key = 'Value' }: ValidationOptions): {
|
|
|
131
143
|
|
|
132
144
|
const cronSchedule: Validator = (val: string) => {
|
|
133
145
|
try {
|
|
134
|
-
|
|
146
|
+
cronScheduleRule.validation(val);
|
|
135
147
|
} catch (e) {
|
|
136
|
-
return t(
|
|
148
|
+
return t(cronScheduleRule.message);
|
|
137
149
|
}
|
|
138
150
|
};
|
|
139
151
|
|
|
@@ -145,6 +157,8 @@ export default function(t: Translation, { key = 'Value' }: ValidationOptions): {
|
|
|
145
157
|
|
|
146
158
|
const url: Validator = (val: string) => val && !isUrl(val) ? t('validation.setting.serverUrl.url') : undefined;
|
|
147
159
|
|
|
160
|
+
const gitRepository: Validator = (val: string) => val && !/^((http|git|ssh|http(s)|file|\/?)|(git@[\w\.]+))(:(\/\/)?)([\w\.@\:\/\-]+)([\d\/\w.-]+?)(.git){0,1}(\/)?$/gm.test(val) ? t('validation.git.repository') : undefined;
|
|
161
|
+
|
|
148
162
|
const alphanumeric: Validator = (val: string) => val && !/^[a-zA-Z0-9]+$/.test(val) ? t('validation.alphanumeric', { key }) : undefined;
|
|
149
163
|
|
|
150
164
|
const interval: Validator = (val: string) => !/^\d+[hms]$/.test(val) ? t('validation.monitoring.route.interval', { key }) : undefined;
|
|
@@ -474,6 +488,7 @@ export default function(t: Translation, { key = 'Value' }: ValidationOptions): {
|
|
|
474
488
|
dnsLabelRestricted,
|
|
475
489
|
externalName,
|
|
476
490
|
fileRequired,
|
|
491
|
+
gitRepository,
|
|
477
492
|
groupsAreValid,
|
|
478
493
|
hostname,
|
|
479
494
|
imageUrl,
|
package/vue.config.js
CHANGED
|
@@ -323,7 +323,7 @@ const getVirtualModules = (dir, includePkg) => {
|
|
|
323
323
|
|
|
324
324
|
// Package file must have rancher field to be a plugin
|
|
325
325
|
if (includePkg(name) && library.rancher) {
|
|
326
|
-
reqs += `$plugin.
|
|
326
|
+
reqs += `$plugin.registerBuiltinExtension('${ name }', require(\'~/pkg/${ name }\')); `;
|
|
327
327
|
}
|
|
328
328
|
});
|
|
329
329
|
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import {
|
|
3
|
-
STATE, NAME, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, TYPE, AGE
|
|
4
|
-
} from '@shell/config/table-headers';
|
|
5
|
-
import { WORKLOAD_TYPES } from '@shell/config/types';
|
|
6
|
-
import ResourceTable from '@shell/components/ResourceTable';
|
|
7
|
-
|
|
8
|
-
export default {
|
|
9
|
-
components: { ResourceTable },
|
|
10
|
-
props: {
|
|
11
|
-
filter: {
|
|
12
|
-
type: Function,
|
|
13
|
-
required: true
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
async fetch() {
|
|
17
|
-
// Enumerating instead of using Object.values() because it looks like we don't want to include replica sets for this.
|
|
18
|
-
const types = [
|
|
19
|
-
WORKLOAD_TYPES.DEPLOYMENT,
|
|
20
|
-
WORKLOAD_TYPES.CRON_JOB,
|
|
21
|
-
WORKLOAD_TYPES.DAEMON_SET,
|
|
22
|
-
WORKLOAD_TYPES.JOB,
|
|
23
|
-
WORKLOAD_TYPES.STATEFUL_SET
|
|
24
|
-
];
|
|
25
|
-
const allWorkloadsNested = await Promise.all(types.map((type) => this.$store.dispatch('cluster/findAll', { type })));
|
|
26
|
-
const allWorkloads = allWorkloadsNested.flat();
|
|
27
|
-
|
|
28
|
-
this.relatedWorkloadRows = allWorkloads.filter(this.filter);
|
|
29
|
-
},
|
|
30
|
-
data() {
|
|
31
|
-
const relatedWorkloadHeaders = [STATE, NAME, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, TYPE, AGE];
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
relatedWorkloadRows: [],
|
|
35
|
-
relatedWorkloadHeaders,
|
|
36
|
-
schema: this.$store.getters['cluster/schemaFor'](WORKLOAD_TYPES.DEPLOYMENT)
|
|
37
|
-
};
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
</script>
|
|
41
|
-
|
|
42
|
-
<template>
|
|
43
|
-
<div>
|
|
44
|
-
<ResourceTable
|
|
45
|
-
:schema="schema"
|
|
46
|
-
:rows="relatedWorkloadRows"
|
|
47
|
-
:headers="relatedWorkloadHeaders"
|
|
48
|
-
/>
|
|
49
|
-
</div>
|
|
50
|
-
</template>
|