@rancher/shell 3.0.4 → 3.0.5-rc.1

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 (122) hide show
  1. package/assets/styles/base/_basic.scss +6 -0
  2. package/assets/styles/global/_button.scss +1 -0
  3. package/assets/translations/en-us.yaml +37 -3
  4. package/cloud-credential/aws.vue +2 -0
  5. package/components/AssignTo.vue +25 -11
  6. package/components/AsyncButton.vue +24 -7
  7. package/components/BannerGraphic.vue +1 -0
  8. package/components/CommunityLinks.vue +3 -3
  9. package/components/CopyToClipboardText.vue +2 -1
  10. package/components/DetailText.vue +5 -0
  11. package/components/DisableAuthProviderModal.vue +1 -0
  12. package/components/ExplorerMembers.vue +1 -1
  13. package/components/ExplorerProjectsNamespaces.vue +56 -14
  14. package/components/LandingPagePreference.vue +5 -3
  15. package/components/LocaleSelector.vue +38 -94
  16. package/components/ModalWithCard.vue +1 -0
  17. package/components/MoveModal.vue +1 -0
  18. package/components/PromptRemove.vue +1 -0
  19. package/components/PromptRestore.vue +1 -0
  20. package/components/ResourceCancelModal.vue +1 -0
  21. package/components/SortableTable/index.vue +10 -11
  22. package/components/__tests__/AsyncButton.test.ts +2 -2
  23. package/components/auth/__tests__/RoleDetailEdit.test.ts +3 -2
  24. package/components/form/ArrayList.vue +66 -54
  25. package/components/form/Command.vue +6 -15
  26. package/components/form/EnvVars.vue +15 -8
  27. package/components/form/HealthCheck.vue +3 -3
  28. package/components/form/HookOption.vue +11 -16
  29. package/components/form/LabeledSelect.vue +2 -1
  30. package/components/form/LifecycleHooks.vue +3 -3
  31. package/components/form/MatchExpressions.vue +10 -7
  32. package/components/form/NameNsDescription.vue +123 -103
  33. package/components/form/Networking.vue +20 -12
  34. package/components/form/NodeAffinity.vue +31 -23
  35. package/components/form/NodeScheduling.vue +13 -3
  36. package/components/form/PodAffinity.vue +43 -43
  37. package/components/form/Probe.vue +67 -66
  38. package/components/form/ResourceQuota/Project.vue +5 -1
  39. package/components/form/ResourceSelector.vue +7 -9
  40. package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +6 -3
  41. package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +12 -1
  42. package/components/form/SSHKnownHosts/index.vue +16 -2
  43. package/components/form/Security.vue +54 -56
  44. package/components/form/Select.vue +31 -6
  45. package/components/form/ShellInput.vue +5 -1
  46. package/components/form/Tolerations.vue +5 -1
  47. package/components/form/ValueFromResource.vue +134 -121
  48. package/components/form/WorkloadPorts.vue +18 -18
  49. package/components/form/__tests__/ArrayList.test.ts +3 -0
  50. package/components/form/__tests__/MatchExpressions.test.ts +12 -12
  51. package/components/form/__tests__/NameNsDescription.test.ts +115 -14
  52. package/components/form/__tests__/Probe.test.ts +12 -8
  53. package/components/form/__tests__/SSHKnownHosts.test.ts +11 -0
  54. package/components/form/__tests__/Select.test.ts +37 -0
  55. package/components/formatter/InternalExternalIP.vue +2 -0
  56. package/components/formatter/SecretData.vue +20 -7
  57. package/components/nav/Group.vue +15 -1
  58. package/components/nav/Header.vue +1 -0
  59. package/components/nav/Type.vue +12 -1
  60. package/components/templates/blank.vue +4 -1
  61. package/components/templates/default.vue +2 -0
  62. package/components/templates/home.vue +4 -1
  63. package/components/templates/plain.vue +4 -1
  64. package/composables/useRuntimeFlag.ts +29 -0
  65. package/config/router/routes.js +20 -13
  66. package/core/types.ts +5 -0
  67. package/dialog/AddCustomBadgeDialog.vue +1 -0
  68. package/dialog/DeactivateDriverDialog.vue +1 -0
  69. package/dialog/ForceMachineRemoveDialog.vue +4 -1
  70. package/edit/__tests__/monitoring.coreos.com.prometheusrule.test.ts +16 -3
  71. package/edit/auth/__tests__/oidc.test.ts +152 -109
  72. package/edit/auth/azuread.vue +1 -0
  73. package/edit/auth/googleoauth.vue +4 -0
  74. package/edit/auth/oidc.vue +37 -4
  75. package/edit/cloudcredential.vue +1 -0
  76. package/edit/logging.banzaicloud.io.output/__tests__/logging.banzaicloud.io.output.test.ts +40 -9
  77. package/edit/networking.k8s.io.ingress/IngressClass.vue +7 -3
  78. package/edit/networking.k8s.io.ingress/__tests__/IngressClass.test.ts +58 -0
  79. package/edit/persistentvolume/__tests__/persistentvolume.test.ts +14 -2
  80. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +1 -0
  81. package/edit/provisioning.cattle.io.cluster/rke2.vue +25 -34
  82. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +6 -1
  83. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +29 -1
  84. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +2 -2
  85. package/edit/token.vue +2 -0
  86. package/edit/workload/index.vue +1 -0
  87. package/edit/workload/mixins/workload.js +0 -2
  88. package/list/management.cattle.io.feature.vue +1 -0
  89. package/list/provisioning.cattle.io.cluster.vue +20 -12
  90. package/models/__tests__/namespace.test.ts +25 -1
  91. package/models/cloudcredential.js +5 -0
  92. package/models/kontainerdriver.js +6 -3
  93. package/models/management.cattle.io.node.js +3 -3
  94. package/models/namespace.js +4 -5
  95. package/models/nodedriver.js +6 -3
  96. package/models/workload.js +4 -1
  97. package/package.json +3 -3
  98. package/pages/account/index.vue +4 -1
  99. package/pages/auth/login.vue +11 -3
  100. package/pages/auth/logout.vue +4 -1
  101. package/pages/auth/setup.vue +1 -0
  102. package/pages/auth/verify.vue +4 -1
  103. package/pages/c/_cluster/apps/charts/chart.vue +1 -1
  104. package/pages/diagnostic.vue +47 -2
  105. package/pages/fail-whale.vue +6 -3
  106. package/pages/home.vue +24 -18
  107. package/pages/support/index.vue +4 -1
  108. package/rancher-components/Form/Radio/RadioGroup.vue +25 -23
  109. package/rancher-components/RcDropdown/RcDropdown.vue +3 -2
  110. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -0
  111. package/rancher-components/RcDropdown/useDropdownCollection.ts +8 -0
  112. package/rancher-components/RcDropdown/useDropdownContext.ts +9 -3
  113. package/scripts/extension/publish +1 -0
  114. package/server/har-file.js +25 -3
  115. package/store/features.js +2 -1
  116. package/store/type-map.js +4 -0
  117. package/types/shell/index.d.ts +8 -1
  118. package/utils/cluster.js +35 -0
  119. package/utils/validators/machine-pool.ts +20 -0
  120. package/components/formatter/ExtensionCache.vue +0 -74
  121. package/components/formatter/Port.vue +0 -24
  122. package/components/formatter/SecretType.vue +0 -41
@@ -27,4 +27,41 @@ describe('select.vue', () => {
27
27
  // eslint-disable-next-line no-console
28
28
  expect(console.warn).not.toHaveBeenCalled();
29
29
  });
30
+
31
+ it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', async() => {
32
+ const label = 'Foo';
33
+ const value = 'foo';
34
+ const ariaDescribedById = 'some-described-by-id';
35
+ const ariaLabelText = 'some-aria-label';
36
+
37
+ const wrapper = shallowMount(SelectComponent, {
38
+ props: {
39
+ value,
40
+ options: [
41
+ { label, value },
42
+ ],
43
+ },
44
+ attrs: {
45
+ 'aria-describedby': ariaDescribedById,
46
+ 'aria-label': ariaLabelText,
47
+ }
48
+ });
49
+
50
+ const labeledSelectContainer = wrapper.find('.unlabeled-select');
51
+ const ariaExpanded = labeledSelectContainer.attributes('aria-expanded');
52
+ const ariaDescribedBy = labeledSelectContainer.attributes('aria-describedby');
53
+ const ariaLabel = labeledSelectContainer.attributes('aria-label');
54
+
55
+ const vSelectInput = wrapper.find('.inline');
56
+
57
+ expect(ariaExpanded).toBe('false');
58
+ expect(ariaDescribedBy).toBe(ariaDescribedById);
59
+ expect(ariaLabel).toBe(ariaLabelText);
60
+
61
+ // make sure it's hardcoded to a "neutral" value so that
62
+ // in the current architecture of the component
63
+ // screen readers won't pick up the default "Select option" aria-label
64
+ // from the library
65
+ expect(vSelectInput.attributes('aria-label')).toBe('-');
66
+ });
30
67
  });
@@ -28,6 +28,7 @@ export default {
28
28
  <span>
29
29
  <template v-if="isIp(row.externalIp)">
30
30
  {{ row.externalIp }} <CopyToClipboard
31
+ :aria-label="t('internalExternalIP.copyExternalIp')"
31
32
  label-as="tooltip"
32
33
  :text="row.externalIp"
33
34
  class="icon-btn"
@@ -43,6 +44,7 @@ export default {
43
44
  </template>
44
45
  <template v-else-if="isIp(row.internalIp)">
45
46
  {{ row.internalIp }}<CopyToClipboard
47
+ :aria-label="t('internalExternalIP.copyInternalIp')"
46
48
  label-as="tooltip"
47
49
  :text="row.internalIp"
48
50
  class="icon-btn"
@@ -13,17 +13,25 @@ export default {
13
13
  },
14
14
  },
15
15
 
16
- data() {
16
+ beforeMount() {
17
17
  if (this.value.issuer) {
18
18
  const { cn, notAfter, sans = [] } = this.value;
19
19
 
20
- return {
21
- cn, expiration: notAfter, sans, isTLS: true
22
- };
23
- } else {
24
- return { isTLS: false };
20
+ this.expiration = notAfter;
21
+ this.sans = sans;
22
+ this.cn = cn;
23
+ this.isTLS = true;
25
24
  }
26
25
  },
26
+
27
+ data() {
28
+ return {
29
+ isTLS: false,
30
+ cn: null,
31
+ sans: [],
32
+ expiration: null,
33
+ };
34
+ },
27
35
  computed: {
28
36
  // use 'text-warning' or 'text-error' classes if the cert is <8 days from expiring or expired respectively
29
37
  dateClass() {
@@ -43,7 +51,12 @@ export default {
43
51
 
44
52
  <template>
45
53
  <div v-if="isTLS">
46
- <t k="secret.certificate.cn" /> {{ cn }} <span v-if="row.unrepeatedSans && row.unrepeatedSans.length">{{ t('secret.certificate.plusMore', {n:row.unrepeatedSans.length}) }}</span><br>
54
+ <t k="secret.certificate.cn" />
55
+ {{ cn }}
56
+ <span v-if="row.unrepeatedSans && row.unrepeatedSans.length">
57
+ {{ t('secret.certificate.plusMore', {n:row.unrepeatedSans.length}) }}
58
+ </span>
59
+ <br>
47
60
  <t k="secret.certificate.expires" />: <DateComponent
48
61
  :class="dateClass"
49
62
  :value="expiration"
@@ -158,6 +158,17 @@ export default {
158
158
  items = this.group;
159
159
  }
160
160
 
161
+ let parentPath = '';
162
+ const cluster = this.$route.params?.cluster;
163
+
164
+ // Where we use nested route configuration, consider the parent route when trying to identify the nav location
165
+ if (this.$route.matched.length > 1) {
166
+ const parentRoute = this.$route.matched[this.$route.matched.length - 2];
167
+
168
+ parentPath = parentRoute.path.replace(':cluster', cluster);
169
+ parentPath = parentPath === '/' ? undefined : parentPath;
170
+ }
171
+
161
172
  for (const item of items.children) {
162
173
  if (item.children && this.hasActiveRoute(item)) {
163
174
  return true;
@@ -166,8 +177,11 @@ export default {
166
177
  const matchesNavLevel = navLevels.filter((param) => !this.$route.params[param] || this.$route.params[param] !== item.route.params[param]).length === 0;
167
178
  const withoutHash = this.$route.hash ? this.$route.fullPath.slice(0, this.$route.fullPath.indexOf(this.$route.hash)) : this.$route.fullPath;
168
179
  const withoutQuery = withoutHash.split('?')[0];
180
+ const itemFullPath = this.$router.resolve(item.route).fullPath;
169
181
 
170
- if (matchesNavLevel || this.$router.resolve(item.route).fullPath === withoutQuery) {
182
+ if (matchesNavLevel || itemFullPath === withoutQuery) {
183
+ return true;
184
+ } else if (parentPath && itemFullPath === parentPath) {
171
185
  return true;
172
186
  }
173
187
  }
@@ -702,6 +702,7 @@ export default {
702
702
  :class="{'avatar-round': principal.roundAvatar}"
703
703
  width="36"
704
704
  height="36"
705
+ :alt="t('nav.alt.userAvatar')"
705
706
  >
706
707
  <i
707
708
  v-else
@@ -58,7 +58,18 @@ export default {
58
58
 
59
59
  isActive() {
60
60
  const typeFullPath = this.$router.resolve(this.type.route)?.fullPath.toLowerCase();
61
- const pageFullPath = this.$route.fullPath?.toLowerCase();
61
+ const pageFullPath = this.$route.fullPath?.toLowerCase().split('#')[0]; // Ignore the shebang when comparing routes
62
+ const routeMetaNav = this.$route.meta?.nav;
63
+
64
+ // If the route explicitly declares the nav path that should be highlighted, then use that
65
+ if (routeMetaNav) {
66
+ const cluster = this.$route.params?.cluster;
67
+ const navPath = routeMetaNav.replace(':cluster', cluster);
68
+
69
+ if (navPath === typeFullPath) {
70
+ return true;
71
+ }
72
+ }
62
73
 
63
74
  if ( !this.type.exact) {
64
75
  const typeSplit = typeFullPath.split('/');
@@ -9,7 +9,10 @@ export default {
9
9
  </script>
10
10
 
11
11
  <template>
12
- <main class="main-layout">
12
+ <main
13
+ class="main-layout"
14
+ :aria-label="t('layouts.blank')"
15
+ >
13
16
  <router-view :key="$route.path" />
14
17
 
15
18
  <Inactivity />
@@ -198,6 +198,7 @@ export default {
198
198
  <main
199
199
  v-if="clusterAndRouteReady"
200
200
  class="main-layout"
201
+ :aria-label="t('layouts.default')"
201
202
  >
202
203
  <router-view
203
204
  :key="$route.path"
@@ -235,6 +236,7 @@ export default {
235
236
  <main
236
237
  v-else-if="unmatchedRoute"
237
238
  class="main-layout"
239
+ :aria-label="t('layouts.default')"
238
240
  >
239
241
  <router-view
240
242
  :key="$route.path"
@@ -67,7 +67,10 @@ export default {
67
67
  :simple="true"
68
68
  />
69
69
 
70
- <main class="main-layout">
70
+ <main
71
+ class="main-layout"
72
+ :aria-label="t('layouts.home')"
73
+ >
71
74
  <router-view
72
75
  :key="$route.path"
73
76
  class="outlet"
@@ -68,7 +68,10 @@ export default {
68
68
  :class="{'dashboard-padding-left': showTopLevelMenu}"
69
69
  >
70
70
  <Header :simple="true" />
71
- <main class="main-layout">
71
+ <main
72
+ class="main-layout"
73
+ :aria-label="t('layouts.plain')"
74
+ >
72
75
  <IndentedPanel class="pt-20">
73
76
  <router-view
74
77
  :key="$route.path"
@@ -0,0 +1,29 @@
1
+ import { computed } from 'vue';
2
+ import { Store } from 'vuex';
3
+ import semver from 'semver';
4
+
5
+ import { getVersionInfo } from '@shell/utils/version';
6
+
7
+ let store: Store<any>;
8
+
9
+ /**
10
+ * Initializes runtime flags.
11
+ * @param vuexStore The Vuex store instance
12
+ */
13
+ export const useRuntimeFlag = (vuexStore: Store<any>) => {
14
+ store = vuexStore;
15
+
16
+ return { featureDropdownMenu };
17
+ };
18
+
19
+ /**
20
+ * Check if the dropdown menu feature is enabled
21
+ * @returns A boolean indicating whether the dropdownMenu feature is enabled.
22
+ */
23
+ const featureDropdownMenu = computed(() => {
24
+ const { fullVersion } = getVersionInfo(store);
25
+
26
+ const coerced = semver.coerce(fullVersion) || { version: '0.0.0' };
27
+
28
+ return semver.gte(coerced.version, '2.11.0');
29
+ });
@@ -276,14 +276,24 @@ export default [
276
276
  name: 'c-cluster-neuvector',
277
277
  meta: { ...installRedirectRouteMeta(NEUVECTOR_NAME, NEUVECTOR_CHART_NAME, undefined, false) }
278
278
  }, {
279
- path: '/c/:cluster/apps/charts',
280
- component: () => interopDefault(import('@shell/pages/c/_cluster/apps/charts/index.vue')),
281
- name: 'c-cluster-apps-charts'
282
- },
283
- {
284
- path: '/c/:cluster/apps/charts/install',
285
- component: () => interopDefault(import('@shell/pages/c/_cluster/apps/charts/install.vue')),
286
- name: 'c-cluster-apps-charts-install'
279
+ path: '/c/:cluster/apps/charts',
280
+ children: [
281
+ {
282
+ path: '',
283
+ component: () => interopDefault(import('@shell/pages/c/_cluster/apps/charts/index.vue')),
284
+ name: 'c-cluster-apps-charts'
285
+ },
286
+ {
287
+ path: 'chart',
288
+ component: () => interopDefault(import('@shell/pages/c/_cluster/apps/charts/chart.vue')),
289
+ name: 'c-cluster-apps-charts-chart',
290
+ },
291
+ {
292
+ path: 'install',
293
+ component: () => interopDefault(import('@shell/pages/c/_cluster/apps/charts/install.vue')),
294
+ name: 'c-cluster-apps-charts-install',
295
+ },
296
+ ]
287
297
  },
288
298
  {
289
299
  path: '/c/:cluster/auth/config',
@@ -353,10 +363,6 @@ export default [
353
363
  path: '/c/:cluster/settings/performance',
354
364
  component: () => interopDefault(import('@shell/pages/c/_cluster/settings/performance.vue')),
355
365
  name: 'c-cluster-settings-performance'
356
- }, {
357
- path: '/c/:cluster/apps/charts/chart',
358
- component: () => interopDefault(import('@shell/pages/c/_cluster/apps/charts/chart.vue')),
359
- name: 'c-cluster-apps-charts-chart'
360
366
  }, {
361
367
  path: '/c/:cluster/auth/group.principal/assign-edit',
362
368
  component: () => interopDefault(import('@shell/pages/c/_cluster/auth/group.principal/assign-edit.vue')),
@@ -364,7 +370,8 @@ export default [
364
370
  }, {
365
371
  path: '/c/:cluster/auth/user.retention',
366
372
  component: () => interopDefault(import('@shell/pages/c/_cluster/auth/user.retention/index.vue')),
367
- name: 'c-cluster-auth-user.retention'
373
+ name: 'c-cluster-auth-user.retention',
374
+ meta: { nav: '/c/:cluster/auth/management.cattle.io.user' }
368
375
  }, {
369
376
  path: '/c/:cluster/manager/cloudCredential/create',
370
377
  component: () => interopDefault(import('@shell/pages/c/_cluster/manager/cloudCredential/create.vue')),
package/core/types.ts CHANGED
@@ -373,6 +373,11 @@ export interface ConfigureTypeOptions {
373
373
  */
374
374
  customRoute?: Object;
375
375
 
376
+ /**
377
+ * Custom options vary pre resource type
378
+ */
379
+ custom?: any;
380
+
376
381
  /**
377
382
  * Leaving these here for completeness but I don't think these should be advertised as useable to plugin creators.
378
383
  */
@@ -296,6 +296,7 @@ export default {
296
296
  v-model:value="badgeBgColor"
297
297
  :disabled="!badgeColorPicker"
298
298
  :default-value="badgeBgColor"
299
+ :aria-label="t('clusterBadge.modal.badgeBgColorInput')"
299
300
  />
300
301
  </div>
301
302
  </div>
@@ -91,6 +91,7 @@ export default {
91
91
  :key="i"
92
92
  color="error"
93
93
  :label="err"
94
+ data-testid="deactivate-driver-error-banner"
94
95
  />
95
96
  </div>
96
97
  </template>
@@ -88,7 +88,10 @@ export default {
88
88
  {{ t('promptForceRemove.confirmName') }}
89
89
  </div>
90
90
  <div class="mb-10">
91
- <CopyToClipboardText :text="nameToMatch" />
91
+ <CopyToClipboardText
92
+ :aria-label="t('promptForceRemove.ariaLabel')"
93
+ :text="nameToMatch"
94
+ />
92
95
  </div>
93
96
  <input
94
97
  id="confirm"
@@ -2,14 +2,23 @@ import { mount } from '@vue/test-utils';
2
2
  import FormValidation from '@shell/mixins/form-validation';
3
3
  import Monitoring from '@shell/edit/monitoring.coreos.com.prometheusrule/index.vue';
4
4
  import { _EDIT } from '@shell/config/query-params';
5
+ import { createStore } from 'vuex';
5
6
 
6
7
  describe('edit: management.cattle.io.setting should', () => {
7
8
  const MOCKED_ERRORS = ['error1', 'error2', 'error3', 'error4', 'error5'];
8
9
  const ERROR_BANNER_SELECTOR = '[data-testid="banner-close"]';
10
+ const store = createStore({
11
+ getters: {
12
+ namespaces: () => () => ({}),
13
+ currentStore: () => () => 'current_store',
14
+ 'current_store/schemaFor': () => jest.fn()
15
+ }
16
+ });
9
17
  const requiredSetup = () => ({
10
18
  // Remove all these mocks after migration to Vue 2.7/3 due mixin logic
11
19
  global: {
12
- mocks: {
20
+ provide: { store },
21
+ mocks: {
13
22
  $store: {
14
23
  dispatch: jest.fn(),
15
24
  getters: {
@@ -35,8 +44,12 @@ describe('edit: management.cattle.io.setting should', () => {
35
44
  canYaml: false,
36
45
  mode: _EDIT,
37
46
  resource: {},
38
- value: { value: 'anything' },
39
- name: ''
47
+ value: {
48
+ setAnnotation: jest.fn(),
49
+ value: 'anything',
50
+ metadata: {},
51
+ },
52
+ name: ''
40
53
  },
41
54
  ...requiredSetup()
42
55
  });