@rancher/shell 0.5.2 → 0.5.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.
Files changed (66) hide show
  1. package/assets/translations/en-us.yaml +8 -4
  2. package/components/ClusterIconMenu.vue +24 -9
  3. package/components/CodeMirror.vue +79 -18
  4. package/components/FixedBanner.vue +1 -0
  5. package/components/ResourceDetail/index.vue +1 -4
  6. package/components/ResourceYaml.vue +29 -5
  7. package/components/SideNav.vue +42 -64
  8. package/components/SortableTable/index.vue +1 -1
  9. package/components/YamlEditor.vue +1 -0
  10. package/components/__tests__/CodeMirror.spec.ts +99 -0
  11. package/components/form/BannerSettings.vue +3 -0
  12. package/components/form/FileSelector.vue +1 -0
  13. package/components/form/KeyValue.vue +1 -0
  14. package/components/formatter/WorkloadDetailEndpoints.vue +12 -22
  15. package/components/formatter/__tests__/WorkloadDetailEndpoints.test.ts +81 -0
  16. package/components/nav/Header.vue +1 -0
  17. package/components/nav/Jump.vue +19 -9
  18. package/components/nav/TopLevelMenu.vue +37 -15
  19. package/components/nav/Type.vue +15 -4
  20. package/components/nav/__tests__/TopLevelMenu.test.ts +1 -1
  21. package/components/nav/__tests__/Type.test.ts +30 -0
  22. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +77 -0
  23. package/detail/fleet.cattle.io.bundle.vue +1 -1
  24. package/detail/provisioning.cattle.io.cluster.vue +19 -4
  25. package/edit/management.cattle.io.setting.vue +1 -0
  26. package/edit/monitoring.coreos.com.alertmanagerconfig/types/opsgenie.vue +1 -1
  27. package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +1 -2
  28. package/edit/monitoring.coreos.com.alertmanagerconfig/types/slack.vue +1 -1
  29. package/edit/provisioning.cattle.io.cluster/index.vue +14 -7
  30. package/edit/provisioning.cattle.io.cluster/rke2.vue +22 -50
  31. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +9 -11
  32. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +3 -1
  33. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +3 -0
  34. package/edit/token.vue +1 -0
  35. package/list/catalog.cattle.io.app.vue +1 -0
  36. package/list/management.cattle.io.setting.vue +1 -0
  37. package/machine-config/amazonec2.vue +1 -0
  38. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +151 -0
  39. package/models/__tests__/secret.test.ts +37 -0
  40. package/models/__tests__/storage.k8s.io.storageclass.test.ts +22 -0
  41. package/models/provisioning.cattle.io.cluster.js +36 -1
  42. package/models/secret.js +9 -0
  43. package/models/storage.k8s.io.storageclass.js +1 -1
  44. package/package.json +1 -1
  45. package/pages/c/_cluster/settings/DefaultLinksEditor.vue +1 -0
  46. package/pages/c/_cluster/settings/brand.vue +3 -0
  47. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +4 -4
  48. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +5 -2
  49. package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +96 -0
  50. package/pages/c/_cluster/uiplugins/__tests__/SetupUIPlugins.test.ts +128 -0
  51. package/plugins/dashboard-store/__tests__/actions.test.ts +196 -111
  52. package/plugins/dashboard-store/actions.js +4 -6
  53. package/plugins/dashboard-store/getters.js +60 -2
  54. package/plugins/dashboard-store/resource-class.js +6 -2
  55. package/plugins/steve/__tests__/getters.spec.ts +10 -0
  56. package/plugins/steve/__tests__/resource-utils.test.ts +159 -0
  57. package/plugins/steve/actions.js +3 -37
  58. package/plugins/steve/getters.js +6 -0
  59. package/plugins/steve/resource-utils.ts +38 -0
  60. package/store/__tests__/type-map.test.ts +1122 -0
  61. package/store/index.js +3 -2
  62. package/store/type-map.js +145 -75
  63. package/types/shell/index.d.ts +2 -0
  64. package/utils/__tests__/create-yaml.test.ts +10 -0
  65. package/utils/create-yaml.js +5 -1
  66. package/utils/object.js +10 -0
@@ -7,6 +7,7 @@ import { classify } from '@shell/plugins/dashboard-store/classify';
7
7
  import { normalizeType } from './normalize';
8
8
  import garbageCollect from '@shell/utils/gc/gc';
9
9
  import { addSchemaIndexFields } from '@shell/plugins/steve/schema.utils';
10
+ import { addParam } from '@shell/utils/url';
10
11
 
11
12
  export const _ALL = 'all';
12
13
  export const _MERGE = 'merge';
@@ -208,12 +209,12 @@ export default {
208
209
 
209
210
  const pageFetchOpts = {
210
211
  ...opt,
211
- url: `${ opt.url }?limit=${ opt.incremental }`
212
+ url: addParam(opt.url, 'limit', `${ opt.incremental }`),
212
213
  };
213
214
 
214
215
  // this is where we "hijack" the limit for the dispatch('request') some lines below
215
216
  // and therefore have 2 initial requests in parallel
216
- opt.url = `${ opt.url }?limit=100`;
217
+ opt.url = addParam(opt.url, 'limit', '100');
217
218
  skipHaveAll = true;
218
219
 
219
220
  // since we are forcing a request, clear the haveAll
@@ -368,10 +369,7 @@ export default {
368
369
  const typeOptions = rootGetters['type-map/optionsFor'](type);
369
370
 
370
371
  opt = opt || {};
371
-
372
- opt.filter = opt.filter || {};
373
- opt.filter['labelSelector'] = selector;
374
-
372
+ opt.labelSelector = selector;
375
373
  opt.url = getters.urlFor(type, null, opt);
376
374
  opt.depaginate = typeOptions?.depaginate;
377
375
 
@@ -1,5 +1,5 @@
1
1
 
2
- import { SCHEMA } from '@shell/config/types';
2
+ import { SCHEMA, COUNT } from '@shell/config/types';
3
3
 
4
4
  import { matches } from '@shell/utils/selector';
5
5
  import { typeMunge, typeRef, SIMPLE_TYPES } from '@shell/utils/create-yaml';
@@ -45,6 +45,29 @@ export const urlFor = (state, getters) => (type, id, opt) => {
45
45
  return url;
46
46
  };
47
47
 
48
+ /**
49
+ * Find the number of resources given
50
+ * - if the type is namespaced
51
+ * - if there are any counts per namespace
52
+ * - if there are no namespaces
53
+ * - if there is no total count
54
+ */
55
+ function matchingCounts(typeObj, namespaces) {
56
+ // That was easy
57
+ if ( !typeObj.namespaced || !typeObj.byNamespace || namespaces === null || typeObj.count === null) {
58
+ return typeObj.count;
59
+ }
60
+
61
+ let out = 0;
62
+
63
+ // Otherwise start with 0 and count up
64
+ for ( const namespace of namespaces ) {
65
+ out += typeObj.byNamespace[namespace]?.count || 0;
66
+ }
67
+
68
+ return out;
69
+ }
70
+
48
71
  export default {
49
72
 
50
73
  all: (state, getters, rootState) => (type) => {
@@ -345,5 +368,40 @@ export default {
345
368
 
346
369
  gcIgnoreTypes: () => {
347
370
  return {};
348
- }
371
+ },
372
+
373
+ /**
374
+ * For the given type, and it's settings, find the number of resources associated with it
375
+ *
376
+ * This takes into account if the type is namespaced.
377
+ *
378
+ * @param typeObj see inners for properties. must have at least `name` (resource type)
379
+ *
380
+ */
381
+ count: (state, getters, rootState, rootGetters) => (typeObj) => {
382
+ let _typeObj = typeObj;
383
+ const { name: type, count } = _typeObj;
384
+
385
+ if (!type) {
386
+ throw new Error(`Resource type required to calc count: ${ JSON.stringify(typeObj) }`);
387
+ }
388
+
389
+ if (!count) {
390
+ const schema = getters.schemaFor(type);
391
+ const counts = getters.all(COUNT)?.[0]?.counts || {};
392
+ const count = counts[type];
393
+
394
+ _typeObj = {
395
+ count: count ? count.summary.count || 0 : null,
396
+ byNamespace: count ? count.namespaces : {},
397
+ revision: count ? count.revision : null,
398
+ namespaced: schema?.attributes?.namespaced
399
+ };
400
+ }
401
+
402
+ const namespaces = Object.keys(rootGetters.activeNamespaceCache || {});
403
+
404
+ return matchingCounts(_typeObj, namespaces.length ? namespaces : null);
405
+ },
406
+
349
407
  };
@@ -1381,7 +1381,7 @@ export default class Resource {
1381
1381
 
1382
1382
  async download() {
1383
1383
  const value = await this.followLink('view', { headers: { accept: 'application/yaml' } });
1384
- const data = await this.$dispatch('cleanForDownload', value.data);
1384
+ const data = await this.cleanForDownload(value.data);
1385
1385
 
1386
1386
  downloadFile(`${ this.nameDisplay }.yaml`, data, 'application/yaml');
1387
1387
  }
@@ -1404,7 +1404,7 @@ export default class Resource {
1404
1404
  await eachLimit(items, 10, (item, idx) => {
1405
1405
  return item.followLink('view', { headers: { accept: 'application/yaml' } } ).then(async(data) => {
1406
1406
  const yaml = data.data || data;
1407
- const cleanedYaml = await this.$dispatch('cleanForDownload', yaml);
1407
+ const cleanedYaml = await this.cleanForDownload(yaml);
1408
1408
 
1409
1409
  files[`resources/${ names[idx] }`] = cleanedYaml;
1410
1410
  });
@@ -1481,6 +1481,10 @@ export default class Resource {
1481
1481
  this.$dispatch(`cleanForDiff`, this.toJSON());
1482
1482
  }
1483
1483
 
1484
+ async cleanForDownload(yaml) {
1485
+ return this.$dispatch(`cleanForDownload`, yaml);
1486
+ }
1487
+
1484
1488
  yamlForSave(yaml) {
1485
1489
  try {
1486
1490
  const obj = jsyaml.load(yaml);
@@ -37,6 +37,7 @@ describe('steve: getters', () => {
37
37
  expect(urlForGetter('typeFoo', undefined, { namespaced: ['nsBar', 'nsBaz'] })).toBe('protocol/urlFoo');
38
38
  });
39
39
  });
40
+
40
41
  describe('steve > getters > urlOptions', () => {
41
42
  // we're not testing function output based off of state or getter inputs here since they are dependencies
42
43
  const state = { config: { baseUrl: 'protocol' } };
@@ -71,12 +72,21 @@ describe('steve: getters', () => {
71
72
  it('returns a string with a single filter statement applied and formatted for steve if a single filter statement is applied and the url starts with "/v1"', () => {
72
73
  expect(urlOptionsGetter('/v1/foo', { filter: { bar: 'baz' } })).toBe('/v1/foo?filter=bar=baz&exclude=metadata.managedFields');
73
74
  });
75
+ it('returns a string with a single filter statement applied and formatted for steve if a single filter statement is applied and the url starts with "/k8s/clusters/c-m-n4x45x4b/v1/"', () => {
76
+ expect(urlOptionsGetter('/k8s/clusters/c-m-n4x45x4b/v1/foo', { filter: { bar: 'baz' } })).toBe('/k8s/clusters/c-m-n4x45x4b/v1/foo?filter=bar=baz&exclude=metadata.managedFields');
77
+ });
74
78
  it('returns a string with a multiple filter statements applied if a single filter statement is applied', () => {
75
79
  expect(urlOptionsGetter('foo', { filter: { bar: 'baz', far: 'faz' } })).toBe('foo?bar=baz&far=faz');
76
80
  });
77
81
  it('returns a string with a multiple filter statements applied and formatted for steve if a single filter statement is applied and the url starts with "/v1"', () => {
78
82
  expect(urlOptionsGetter('/v1/foo', { filter: { bar: 'baz', far: 'faz' } })).toBe('/v1/foo?filter=bar=baz&far=faz&exclude=metadata.managedFields');
79
83
  });
84
+ it('returns a string with a labelSelector and formatted for steve if the url starts with "/v1"', () => {
85
+ expect(urlOptionsGetter('/v1/foo', { labelSelector: 'a=b' })).toBe('/v1/foo?labelSelector=a=b&exclude=metadata.managedFields');
86
+ });
87
+ it('returns a string with a labelSelector and filter, and formatted for steve if the url starts with "/v1"', () => {
88
+ expect(urlOptionsGetter('/v1/foo', { labelSelector: 'a=b', filter: { bar: 'baz', far: 'faz' } })).toBe('/v1/foo?labelSelector=a=b&filter=bar=baz&far=faz&exclude=metadata.managedFields');
89
+ });
80
90
  it('returns a string with an exclude statement for "bar" and "metadata.managedFields" if excludeFields is a single element array with the string "bar" and the url starts with "/v1/"', () => {
81
91
  expect(urlOptionsGetter('/v1/foo', { excludeFields: ['bar'] })).toBe('/v1/foo?exclude=bar&exclude=metadata.managedFields');
82
92
  });
@@ -0,0 +1,159 @@
1
+ import { steveCleanForDownload } from '@shell/plugins/steve/resource-utils';
2
+
3
+ describe('steve: ressource-utils', () => {
4
+ it('should do nothing if the yaml is not passed', () => {
5
+ const r = steveCleanForDownload();
6
+
7
+ expect(r).toBeUndefined();
8
+ });
9
+ it('should remove all default rootKeys', () => {
10
+ const expectedYamlStr = `apiVersion: v1
11
+ kind: ConfigMap
12
+ metadata:
13
+ name: my-configmap
14
+ `;
15
+ const yamlStr = `
16
+ id: test_id
17
+ links:
18
+ view: https://example.com2
19
+ type: test_type
20
+ actions:
21
+ remove: https://example.com
22
+ ${ expectedYamlStr }
23
+ `;
24
+ const cleanedYamlStr = steveCleanForDownload(yamlStr);
25
+
26
+ expect(cleanedYamlStr).toBe(expectedYamlStr);
27
+ });
28
+ it('should remove all the specified root keys', () => {
29
+ const part = `apiVersion: v1
30
+ kind: Secret
31
+ metadata:
32
+ name: my-secret`;
33
+
34
+ const rootKeyToYamlStringMap = {
35
+ id: 'id: test_id',
36
+ links: `links:
37
+ view: https://example.com`,
38
+ actions: `actions:
39
+ remove: https://example.com`,
40
+ type: 'type: Opaque'
41
+ };
42
+
43
+ const entries = Object.entries(rootKeyToYamlStringMap);
44
+ const yamlStr = `${ part }
45
+ ${ entries.map(([_, str]) => str).join('\n') }`;
46
+
47
+ entries.forEach(([key, str]) => {
48
+ const expectedYamlStr = `${ part }
49
+ ${ entries.filter(([k]) => k !== key).map(([_, str]) => str).join('\n') }\n`;
50
+ const cleanedYamlStr = steveCleanForDownload(yamlStr, { rootKeys: [key] });
51
+
52
+ expect(cleanedYamlStr).toBe(expectedYamlStr);
53
+ });
54
+ });
55
+ it('should remove all default metadata keys', () => {
56
+ const expectedYamlStr = `apiVersion: v1
57
+ kind: ConfigMap
58
+ metadata:
59
+ name: my-configmap
60
+ `;
61
+ const yamlStr = `apiVersion: v1
62
+ kind: ConfigMap
63
+ metadata:
64
+ name: my-configmap
65
+ fields:
66
+ - kube-root-ca.crt
67
+ - 1
68
+ - 7d23h
69
+ relationships:
70
+ - rel: 'owner'
71
+ state: 'active'
72
+ `;
73
+ const cleanedYamlStr = steveCleanForDownload(yamlStr);
74
+
75
+ expect(cleanedYamlStr).toBe(expectedYamlStr);
76
+ });
77
+
78
+ it('should remove all the specified metadata keys', () => {
79
+ const part = `apiVersion: v1
80
+ kind: ConfigMap
81
+ metadata:
82
+ name: my-configmap`;
83
+
84
+ const metadataKeyToYamlStringMap = {
85
+ fields:
86
+ ` fields:
87
+ - kube-root-ca.crt
88
+ - 1
89
+ - 7d23h`,
90
+ relationships:
91
+ ` relationships:
92
+ - rel: owner`,
93
+ state: ` state: active`
94
+ };
95
+
96
+ const entries = Object.entries(metadataKeyToYamlStringMap);
97
+ const yamlStr = `${ part }
98
+ ${ entries.map(([_, str]) => str).join('\n') }`;
99
+
100
+ entries.forEach(([key, str]) => {
101
+ const expectedYamlStr = `${ part }
102
+ ${ entries.filter(([k]) => k !== key).map(([_, str]) => str).join('\n') }\n`;
103
+ const cleanedYamlStr = steveCleanForDownload(yamlStr, { metadataKeys: [key] });
104
+
105
+ expect(cleanedYamlStr).toBe(expectedYamlStr);
106
+ });
107
+ });
108
+ it('should remove all defalut condition keys', () => {
109
+ const expectedYamlStr = `apiVersion: v1
110
+ kind: ConfigMap
111
+ metadata:
112
+ name: my-configmap
113
+ status:
114
+ conditions:
115
+ - {}
116
+ - {}
117
+ - message: message
118
+ `;
119
+ const yamlStr = `apiVersion: v1
120
+ kind: ConfigMap
121
+ metadata:
122
+ name: my-configmap
123
+ status:
124
+ conditions:
125
+ - error: 'error'
126
+ - transitioning: false
127
+ - message: message
128
+ `;
129
+ const cleanedYamlStr = steveCleanForDownload(yamlStr);
130
+
131
+ expect(cleanedYamlStr).toBe(expectedYamlStr);
132
+ });
133
+ it('should remove all the specified condition keys', () => {
134
+ const part = `apiVersion: v1
135
+ kind: ConfigMap
136
+ metadata:
137
+ name: my-configmap
138
+ status:
139
+ conditions:
140
+ - message: message`;
141
+
142
+ const conditionKeyToYamlStringMap = {
143
+ error: ' - error: error',
144
+ transitioning: ' - transitioning: false'
145
+ };
146
+
147
+ const entries = Object.entries(conditionKeyToYamlStringMap);
148
+ const yamlStr = `${ part }
149
+ ${ entries.map(([_, str]) => str).join('\n') }`;
150
+
151
+ entries.forEach(([key, str]) => {
152
+ const expectedYamlStr = `${ part }
153
+ ${ entries.map(([k, str]) => k === key ? ' - {}' : str).join('\n') }\n`;
154
+ const cleanedYamlStr = steveCleanForDownload(yamlStr, { conditionKeys: [key] });
155
+
156
+ expect(cleanedYamlStr).toBe(expectedYamlStr);
157
+ });
158
+ });
159
+ });
@@ -1,14 +1,14 @@
1
1
  import https from 'https';
2
2
  import { addParam, parse as parseUrl, stringify as unParseUrl } from '@shell/utils/url';
3
3
  import { handleSpoofedRequest, loadSchemas } from '@shell/plugins/dashboard-store/actions';
4
- import { set } from '@shell/utils/object';
4
+ import { dropKeys, set } from '@shell/utils/object';
5
5
  import { deferred } from '@shell/utils/promise';
6
6
  import { streamJson, streamingSupported } from '@shell/utils/stream';
7
7
  import isObject from 'lodash/isObject';
8
8
  import { classify } from '@shell/plugins/dashboard-store/classify';
9
9
  import { NAMESPACE } from '@shell/config/types';
10
- import jsyaml from 'js-yaml';
11
10
  import { handleKubeApiHeaderWarnings } from '@shell/plugins/steve/header-warnings';
11
+ import { steveCleanForDownload } from '@shell/plugins/steve/resource-utils';
12
12
 
13
13
  export default {
14
14
 
@@ -330,31 +330,7 @@ export default {
330
330
 
331
331
  // remove fields added by steve before showing/downloading yamls
332
332
  cleanForDownload(ctx, yaml) {
333
- if (!yaml) {
334
- return;
335
- }
336
- const rootKeys = [
337
- 'id',
338
- 'links',
339
- 'type',
340
- 'actions'
341
- ];
342
- const metadataKeys = [
343
- 'fields',
344
- 'relationships',
345
- 'state',
346
- ];
347
- const conditionKeys = [
348
- 'error',
349
- 'transitioning',
350
- ];
351
- const obj = jsyaml.load(yaml);
352
-
353
- dropKeys(obj, rootKeys);
354
- dropKeys(obj?.metadata, metadataKeys);
355
- (obj?.status?.conditions || []).forEach((condition) => dropKeys(condition, conditionKeys));
356
-
357
- return jsyaml.dump(obj);
333
+ return steveCleanForDownload(yaml);
358
334
  }
359
335
  };
360
336
 
@@ -398,16 +374,6 @@ function dropUnderscores(obj) {
398
374
  }
399
375
  }
400
376
 
401
- function dropKeys(obj, keys) {
402
- if ( !obj ) {
403
- return;
404
- }
405
-
406
- for ( const k of keys ) {
407
- delete obj[k];
408
- }
409
- }
410
-
411
377
  function dropCattleKeys(obj) {
412
378
  if ( !obj ) {
413
379
  return;
@@ -33,6 +33,12 @@ export default {
33
33
  const parsedUrl = parse(url);
34
34
  const isSteve = steveRegEx.test(parsedUrl.path);
35
35
 
36
+ // labelSelector
37
+ if ( opt.labelSelector ) {
38
+ url += `${ url.includes('?') ? '&' : '?' }labelSelector=${ opt.labelSelector }`;
39
+ }
40
+ // End: labelSelector
41
+
36
42
  // Filter
37
43
  if ( opt.filter ) {
38
44
  url += `${ (url.includes('?') ? '&' : '?') }`;
@@ -0,0 +1,38 @@
1
+ import { dropKeys } from '@shell/utils/object';
2
+ import jsyaml from 'js-yaml';
3
+
4
+ export function steveCleanForDownload(yaml: string, keys?: {
5
+ rootKeys?: string[],
6
+ metadataKeys?: string[],
7
+ conditionKeys?: string[]
8
+ }): string | undefined {
9
+ if (!yaml) {
10
+ return;
11
+ }
12
+
13
+ const {
14
+ rootKeys = [
15
+ 'id',
16
+ 'links',
17
+ 'type',
18
+ 'actions'
19
+ ],
20
+ metadataKeys = [
21
+ 'fields',
22
+ 'relationships',
23
+ 'state',
24
+ ],
25
+ conditionKeys = [
26
+ 'error',
27
+ 'transitioning',
28
+ ]
29
+ } = keys || {};
30
+
31
+ const obj: any = jsyaml.load(yaml);
32
+
33
+ dropKeys(obj, rootKeys);
34
+ dropKeys(obj?.metadata, metadataKeys);
35
+ (obj?.status?.conditions || []).forEach((condition: any) => dropKeys(condition, conditionKeys));
36
+
37
+ return jsyaml.dump(obj);
38
+ }