@rancher/shell 3.0.3 → 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 (139) 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 +38 -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 +2 -1
  19. package/components/PromptRestore.vue +1 -0
  20. package/components/ResourceCancelModal.vue +1 -0
  21. package/components/SortableTable/index.vue +35 -10
  22. package/components/StatusBadge.vue +10 -4
  23. package/components/__tests__/AsyncButton.test.ts +2 -2
  24. package/components/auth/Principal.vue +9 -3
  25. package/components/auth/__tests__/RoleDetailEdit.test.ts +3 -2
  26. package/components/form/ArrayList.vue +75 -54
  27. package/components/form/Command.vue +6 -15
  28. package/components/form/EnvVars.vue +15 -8
  29. package/components/form/HealthCheck.vue +3 -3
  30. package/components/form/HookOption.vue +11 -16
  31. package/components/form/KeyValue.vue +1 -1
  32. package/components/form/LabeledSelect.vue +2 -1
  33. package/components/form/LifecycleHooks.vue +3 -3
  34. package/components/form/MatchExpressions.vue +10 -7
  35. package/components/form/NameNsDescription.vue +123 -103
  36. package/components/form/Networking.vue +20 -12
  37. package/components/form/NodeAffinity.vue +31 -23
  38. package/components/form/NodeScheduling.vue +13 -3
  39. package/components/form/PodAffinity.vue +43 -43
  40. package/components/form/Probe.vue +67 -66
  41. package/components/form/ResourceQuota/Project.vue +5 -1
  42. package/components/form/ResourceSelector.vue +7 -9
  43. package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +6 -3
  44. package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +12 -1
  45. package/components/form/SSHKnownHosts/index.vue +16 -2
  46. package/components/form/Security.vue +54 -56
  47. package/components/form/Select.vue +31 -6
  48. package/components/form/ShellInput.vue +5 -1
  49. package/components/form/Tolerations.vue +5 -1
  50. package/components/form/ValueFromResource.vue +134 -121
  51. package/components/form/WorkloadPorts.vue +18 -18
  52. package/components/form/__tests__/ArrayList.test.ts +3 -0
  53. package/components/form/__tests__/MatchExpressions.test.ts +12 -12
  54. package/components/form/__tests__/NameNsDescription.test.ts +115 -14
  55. package/components/form/__tests__/Probe.test.ts +12 -8
  56. package/components/form/__tests__/SSHKnownHosts.test.ts +11 -0
  57. package/components/form/__tests__/Select.test.ts +37 -0
  58. package/components/formatter/InternalExternalIP.vue +2 -0
  59. package/components/formatter/SecretData.vue +20 -7
  60. package/components/nav/Group.vue +15 -1
  61. package/components/nav/Header.vue +1 -0
  62. package/components/nav/Type.vue +12 -1
  63. package/components/templates/blank.vue +4 -1
  64. package/components/templates/default.vue +2 -0
  65. package/components/templates/home.vue +4 -1
  66. package/components/templates/plain.vue +4 -1
  67. package/composables/useRuntimeFlag.ts +29 -0
  68. package/config/router/routes.js +20 -13
  69. package/core/types.ts +5 -0
  70. package/dialog/AddCustomBadgeDialog.vue +1 -0
  71. package/dialog/DeactivateDriverDialog.vue +5 -4
  72. package/dialog/ForceMachineRemoveDialog.vue +4 -1
  73. package/dialog/GitRepoForceUpdateDialog.vue +1 -1
  74. package/edit/__tests__/monitoring.coreos.com.prometheusrule.test.ts +16 -3
  75. package/edit/auth/__tests__/oidc.test.ts +152 -109
  76. package/edit/auth/azuread.vue +1 -0
  77. package/edit/auth/googleoauth.vue +4 -0
  78. package/edit/auth/oidc.vue +37 -4
  79. package/edit/cloudcredential.vue +1 -0
  80. package/edit/fleet.cattle.io.gitrepo.vue +1 -0
  81. package/edit/logging.banzaicloud.io.output/__tests__/logging.banzaicloud.io.output.test.ts +40 -9
  82. package/edit/networking.k8s.io.ingress/IngressClass.vue +7 -3
  83. package/edit/networking.k8s.io.ingress/__tests__/IngressClass.test.ts +58 -0
  84. package/edit/persistentvolume/__tests__/persistentvolume.test.ts +14 -2
  85. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +1 -0
  86. package/edit/provisioning.cattle.io.cluster/rke2.vue +25 -34
  87. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +6 -1
  88. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +29 -1
  89. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +2 -2
  90. package/edit/token.vue +2 -0
  91. package/edit/workload/index.vue +1 -0
  92. package/edit/workload/mixins/workload.js +0 -2
  93. package/list/management.cattle.io.feature.vue +1 -0
  94. package/list/provisioning.cattle.io.cluster.vue +20 -12
  95. package/machine-config/__tests__/vmwarevsphere.test.ts +48 -3
  96. package/machine-config/vmwarevsphere.vue +16 -0
  97. package/models/__tests__/namespace.test.ts +25 -1
  98. package/models/cloudcredential.js +5 -0
  99. package/models/kontainerdriver.js +6 -3
  100. package/models/management.cattle.io.node.js +3 -3
  101. package/models/namespace.js +4 -5
  102. package/models/nodedriver.js +6 -3
  103. package/models/workload.js +4 -1
  104. package/package.json +4 -4
  105. package/pages/about.vue +16 -8
  106. package/pages/account/index.vue +4 -1
  107. package/pages/auth/login.vue +11 -3
  108. package/pages/auth/logout.vue +4 -1
  109. package/pages/auth/setup.vue +1 -0
  110. package/pages/auth/verify.vue +4 -1
  111. package/pages/c/_cluster/apps/charts/chart.vue +1 -1
  112. package/pages/diagnostic.vue +47 -2
  113. package/pages/fail-whale.vue +6 -3
  114. package/pages/home.vue +24 -18
  115. package/pages/support/index.vue +4 -1
  116. package/promptRemove/management.cattle.io.fleetworkspace.vue +1 -1
  117. package/promptRemove/management.cattle.io.globalrole.vue +1 -1
  118. package/promptRemove/management.cattle.io.project.vue +2 -2
  119. package/promptRemove/management.cattle.io.roletemplate.vue +1 -1
  120. package/promptRemove/pod.vue +1 -1
  121. package/rancher-components/Form/Radio/RadioGroup.vue +25 -23
  122. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +3 -3
  123. package/rancher-components/RcDropdown/RcDropdown.vue +6 -5
  124. package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -2
  125. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +12 -2
  126. package/rancher-components/RcDropdown/useDropdownCollection.ts +8 -0
  127. package/rancher-components/RcDropdown/useDropdownContext.ts +9 -3
  128. package/scripts/extension/publish +1 -0
  129. package/server/har-file.js +25 -3
  130. package/store/features.js +2 -1
  131. package/store/type-map.js +4 -0
  132. package/types/shell/index.d.ts +9 -2
  133. package/utils/__tests__/string.test.ts +2 -2
  134. package/utils/cluster.js +35 -0
  135. package/utils/string.js +1 -3
  136. package/utils/validators/machine-pool.ts +20 -0
  137. package/components/formatter/ExtensionCache.vue +0 -74
  138. package/components/formatter/Port.vue +0 -24
  139. package/components/formatter/SecretType.vue +0 -41
@@ -144,3 +144,9 @@ HR.vertical {
144
144
  .force-wrap { @include force-wrap }
145
145
  .bordered-section { @include bordered-section }
146
146
  .section-divider { @include section-divider }
147
+
148
+ fieldset {
149
+ border: 0;
150
+ padding: 0;
151
+ margin-inline: 0;
152
+ }
@@ -215,6 +215,7 @@ fieldset[disabled] .btn {
215
215
  }
216
216
  }
217
217
 
218
+ .btn-disabled,
218
219
  .btn[disabled] {
219
220
  // Ensure disabled button's border remains as-is, otherwise button appears vertically shorter than others in group
220
221
  border: inherit;
@@ -158,6 +158,7 @@ nav:
158
158
  alt:
159
159
  mainMenuIcon: Main menu icon
160
160
  mainMenuRancherLogo: Main menu Rancher logo
161
+ userAvatar: User avatar image
161
162
  expandCollapseAppBar: Expand/Collapse the Application Bar
162
163
  harvesterDashboard: Harvester Dashboard
163
164
  backToRancher: Cluster Manager
@@ -319,6 +320,15 @@ suffix:
319
320
  ##############################
320
321
  # Components & Pages
321
322
  ##############################
323
+ layouts:
324
+ home: home layout
325
+ plain: plain layout
326
+ default: default layout
327
+ blank: blank layout
328
+ unauthenticated: unauthenticated layout
329
+ logout: logout layout
330
+ verify: verify layout
331
+
322
332
  windowmanager:
323
333
  closeTab: Close tab {tabId}
324
334
  about:
@@ -379,11 +389,15 @@ accountAndKeys:
379
389
  title: API Keys
380
390
  notAllowed: You do not have permission to manage API Keys
381
391
  apiEndpoint: "API Endpoint:"
392
+ copyApiEnpoint: Copy API Endpoint to clipboard
382
393
  add:
383
394
  description:
384
395
  label: Description
385
396
  placeholder: Optionally enter a description to help you identify this API Key
386
397
  label: Create API Key
398
+ ariaLabel:
399
+ expiration: Expiration duration - number input
400
+ expirationUnits: Expiration unit - time unit
387
401
  expiry:
388
402
  label: Automatically expire
389
403
  options:
@@ -483,6 +497,9 @@ authConfig:
483
497
  4: 'Under Scopes for Google APIs, enable "email", "profile", and "openid".'
484
498
  5: 'Click on "Save".'
485
499
  topPrivateDomain: 'Top private domain of:'
500
+ ariaLabel:
501
+ hostname: Copy Hostname to clipboard
502
+ serverUrl: Copy Server URL to clipboard
486
503
  2:
487
504
  title: 'Navigate to the "Credentials" tab to create your OAuth client ID'
488
505
  body:
@@ -491,6 +508,8 @@ authConfig:
491
508
  3: 'Authorized redirect URIs:'
492
509
  4: 'Click "Create", and then click on the "Download JSON" button.'
493
510
  5: 'Upload the downloaded JSON file in the OAuth credentials box.'
511
+ ariaLabel:
512
+ serverUrlVerify: Copy Server Auth Verfication URL to clipboard
494
513
  3:
495
514
  title: 'Create Service Account credentials'
496
515
  introduction: 'Follow <a href="{docsBase}/how-to-guides/new-user-guides/authentication-permissions-and-global-configuration/authentication-config/configure-google-oauth#3-creating-service-account-credentials" target="_blank" rel="noopener noreferrer nofollow">this</a> guide to:'
@@ -624,6 +643,7 @@ authConfig:
624
643
  reply:
625
644
  info: 'Azure AD requires a whitelisted URL for your Rancher server before beginning this setup. Please ensure that the following URL is set in the Reply URL section of your Azure Portal. Please note that it may take up to 5 minutes for the whitelisted URL to propagate.'
626
645
  label: Reply URL
646
+ ariaLabel: Copy Reply URL to clipboard
627
647
  updateEndpoint:
628
648
  button: Update Endpoint
629
649
  banner:
@@ -656,7 +676,7 @@ authConfig:
656
676
  scope:
657
677
  label: Scopes
658
678
  placeholder: openid
659
- protip: The <code>openid</code> scope is required. If you are using custom scopes, ensure that it is included.
679
+ protip: The <code>openid</code>, <code>profile</code>, and <code>email</code> scopes are required and cannot be removed.
660
680
  cert:
661
681
  label: Certificate
662
682
  placeholder: Paste in the certificate, starting with -----BEGIN CERTIFICATE-----
@@ -2097,7 +2117,8 @@ cluster:
2097
2117
  title: Save Machine Configurations
2098
2118
  body: Machine Configurations define how machines in Pools are deployed.<br><br> They will be saved upfront to ensure valid Cluster YAML can be saved.
2099
2119
  snapshots:
2100
- suffix: Snapshots
2120
+ suffix: Snapshots per node
2121
+ s3Suffix: Snapshots
2101
2122
  systemService:
2102
2123
  rke2-coredns: 'CoreDNS'
2103
2124
  rke2-ingress-nginx: 'NGINX Ingress'
@@ -2398,8 +2419,11 @@ drivers:
2398
2419
  =1 { {warningDrivers} Resources in the corresponding provider will not be automatically removed.}
2399
2420
  other { {warningDrivers} Resources in the corresponding providers will not be automatically removed.}
2400
2421
  }
2422
+ error:
2423
+ activate: Failed to activate { name } driver
2401
2424
 
2402
2425
  detailText:
2426
+ copyAriaLabel: Copy {item} value to clipboard
2403
2427
  collapse: Hide
2404
2428
  binary: '<Binary Data: {n, number} bytes>'
2405
2429
  empty: '<Empty>'
@@ -2521,6 +2545,7 @@ fleet:
2521
2545
  placeholder: "Paste in one or more certificates, starting with -----BEGIN CERTIFICATE----"
2522
2546
  paths:
2523
2547
  label: Paths
2548
+ ariaLabel: Enter path for Git Repo
2524
2549
  placeholder: e.g. /directory/in/your/repo
2525
2550
  addLabel: Add Path
2526
2551
  empty: The root of the repo is used by default. Multiple different directories can be provided.
@@ -2730,6 +2755,7 @@ clusterBadge:
2730
2755
  iconText: Icon Text
2731
2756
  buttonAction: Apply
2732
2757
  badgeBgColor: Badge background color
2758
+ badgeBgColorInput: Badge background color selector input
2733
2759
  badgeTextColor: Badge text color
2734
2760
  badgeAsIcon: Use custom badge
2735
2761
  maxCharsTooltip: Overwrites the default cluster name abbreviation.
@@ -2929,6 +2955,8 @@ ingress:
2929
2955
 
2930
2956
  internalExternalIP:
2931
2957
  none: None
2958
+ copyInternalIp: Copy internal IP address to clipboard
2959
+ copyExternalIp: Copy external IP address to clipboard
2932
2960
 
2933
2961
  istio:
2934
2962
  links:
@@ -3136,6 +3164,7 @@ labels:
3136
3164
  hide: Hide System Labels and Annotations
3137
3165
  annotations:
3138
3166
  title: Annotations
3167
+ singular: Annotation
3139
3168
 
3140
3169
  labelSelect:
3141
3170
  noOptions:
@@ -3149,6 +3178,8 @@ labeledSelect:
3149
3178
  pressEnter: Press enter to add "{input}"
3150
3179
 
3151
3180
  landing:
3181
+ bannerImage: Homepage banner image
3182
+ homepage: Homepage
3152
3183
  clusters:
3153
3184
  title: Clusters
3154
3185
  provider: Provider
@@ -3472,8 +3503,10 @@ neuvector:
3472
3503
  na: Resource Unavailable
3473
3504
 
3474
3505
  login:
3506
+ login: Login
3475
3507
  howdy: Howdy!
3476
3508
  welcome: Welcome to {vendor}
3509
+ landscapeAlt: Landscape image
3477
3510
  loggedOut: You have been logged out.
3478
3511
  loggedOutFromSso: You've been logged out of Rancher, however you may still be logged in to your single sign-on identity provider.
3479
3512
  loggedOutFromSlo: You've been logged out of Rancher and from your single sign-on identity provider.
@@ -4878,6 +4911,7 @@ promptForceRemove:
4878
4911
  modalTitle: Are you sure?
4879
4912
  removeWarning: "There was an issue with deleting underlying infrastructure. If you proceed with this action, the Machine <b>{nameToMatch}</b> will be deleted from Rancher only. It's highly recommended to manually delete any referenced infrastructure."
4880
4913
  forceDelete: Force Delete
4914
+ ariaLabel: Copy Machine name to clipboard
4881
4915
  confirmName: "Enter in the pool name below to confirm:"
4882
4916
  podRemoveWarning: "Force deleting pods does not wait for confirmation that the pod's processes have been terminated. This may result in <strong>data corruption or inconsistencies</strong>"
4883
4917
 
@@ -5489,6 +5523,7 @@ setup:
5489
5523
  setPassword: The first order of business is to set a strong password for the default <code>{username}</code> user. We suggest using this random one generated just for you, but enter your own if you like.
5490
5524
  useManual: Set a specific password to use
5491
5525
  useRandom: Use a randomly generated password
5526
+ copyRandom: Copy random password to clipboard
5492
5527
  welcome: Welcome to {vendor}!
5493
5528
 
5494
5529
  sortableTable:
@@ -7940,12 +7975,12 @@ customLinks:
7940
7975
  commercialSupport: Commercial Support
7941
7976
  appCo: SUSE Application Collection
7942
7977
 
7943
-
7944
7978
  ##############################
7945
7979
  ### Support Page
7946
7980
  ##############################
7947
7981
 
7948
7982
  support:
7983
+ bannerImage: Support page banner image
7949
7984
  community:
7950
7985
  title: SUSE Rancher provides world-class support
7951
7986
  linksTitle: Community Support
@@ -78,6 +78,7 @@ export default {
78
78
  :rules="fvGetAndReportPathRules('decodedData.accessKey')"
79
79
  :mode="mode"
80
80
  :required="true"
81
+ data-testid="access-key"
81
82
  @update:value="$emit('valueChanged', 'accessKey', $event)"
82
83
  />
83
84
  <LabeledInput
@@ -89,6 +90,7 @@ export default {
89
90
  :rules="fvGetAndReportPathRules('decodedData.secretKey')"
90
91
  :mode="mode"
91
92
  :required="true"
93
+ data-testid="secret-key"
92
94
  @update:value="$emit('valueChanged', 'secretKey', $event)"
93
95
  />
94
96
  <LabeledSelect
@@ -115,6 +115,7 @@ export default {
115
115
  styles="background-color: var(--nav-bg); border-radius: var(--border-radius); max-height: 100vh;"
116
116
  height="auto"
117
117
  :scrollable="true"
118
+ :trigger-focus-trap="true"
118
119
  @close="close"
119
120
  >
120
121
  <Card
@@ -157,17 +158,20 @@ export default {
157
158
  </template>
158
159
 
159
160
  <template #actions>
160
- <button
161
- class="btn role-secondary"
162
- @click="close"
163
- >
164
- {{ t('generic.cancel') }}
165
- </button>
166
-
167
- <AsyncButton
168
- mode="apply"
169
- @click="apply"
170
- />
161
+ <div class="actions-container">
162
+ <button
163
+ class="btn role-secondary"
164
+ @click="close"
165
+ >
166
+ {{ t('generic.cancel') }}
167
+ </button>
168
+
169
+ <AsyncButton
170
+ class="apply-btn"
171
+ mode="apply"
172
+ @click="apply"
173
+ />
174
+ </div>
171
175
  </template>
172
176
  </Card>
173
177
  </app-modal>
@@ -181,5 +185,15 @@ export default {
181
185
  & ::-webkit-scrollbar-corner {
182
186
  background: rgba(0,0,0,0);
183
187
  }
188
+
189
+ .actions-container {
190
+ display: flex;
191
+ justify-content: flex-end;
192
+ width: 100%;
193
+
194
+ .apply-btn {
195
+ margin-left: 20px;
196
+ }
197
+ }
184
198
  }
185
199
  </style>
@@ -11,6 +11,7 @@ export const ASYNC_BUTTON_STATES = {
11
11
 
12
12
  const TEXT = 'text';
13
13
  const TOOLTIP = 'tooltip';
14
+ const DISABLED_CLASS_STYLE = 'btn-disabled';
14
15
 
15
16
  export type AsyncButtonCallback = (success: boolean) => void;
16
17
 
@@ -152,9 +153,29 @@ export default defineComponent({
152
153
  out[`btn-${ this.size }`] = true;
153
154
  }
154
155
 
156
+ // while we are waiting for the async button to get
157
+ // it's callback we want to the button to appear as disabled
158
+ // but not being actually disabled as need it to be
159
+ // able to return the keyboard navigation focus back to it
160
+ // which can't be done while actually disabled, as per
161
+ // https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols
162
+ if (this.phase === ASYNC_BUTTON_STATES.WAITING) {
163
+ out[DISABLED_CLASS_STYLE] = true;
164
+ }
165
+
166
+ // used to assist e2e testing mostly when waiting for button to return
167
+ // to it's normal state/phase
168
+ if (this.phase === ASYNC_BUTTON_STATES.ACTION) {
169
+ out['ready-for-action'] = true;
170
+ }
171
+
155
172
  return out;
156
173
  },
157
174
 
175
+ appearsDisabled(): boolean {
176
+ return this.disabled || this.phase === ASYNC_BUTTON_STATES.WAITING;
177
+ },
178
+
158
179
  displayIcon(): string {
159
180
  const exists = this.$store.getters['i18n/exists'];
160
181
  const t = this.$store.getters['i18n/t'];
@@ -204,10 +225,6 @@ export default defineComponent({
204
225
  return this.phase === ASYNC_BUTTON_STATES.WAITING;
205
226
  },
206
227
 
207
- isDisabled(): boolean {
208
- return this.disabled || this.phase === ASYNC_BUTTON_STATES.WAITING;
209
- },
210
-
211
228
  isManualRefresh() {
212
229
  return this.mode === 'manual-refresh';
213
230
  },
@@ -232,7 +249,7 @@ export default defineComponent({
232
249
 
233
250
  methods: {
234
251
  clicked() {
235
- if ( this.isDisabled ) {
252
+ if ( this.appearsDisabled ) {
236
253
  return;
237
254
  }
238
255
 
@@ -283,8 +300,8 @@ export default defineComponent({
283
300
  :class="classes"
284
301
  :name="name"
285
302
  :type="type"
286
- :disabled="isDisabled"
287
- :aria-disabled="isDisabled"
303
+ :disabled="disabled"
304
+ :aria-disabled="appearsDisabled"
288
305
  :tab-index="tabIndex"
289
306
  :data-testid="componentTestid + '-async-button'"
290
307
  @click="clicked"
@@ -36,6 +36,7 @@ export default {
36
36
  data-testid="banner-brand__img"
37
37
  file-name="banner.svg"
38
38
  :draggable="false"
39
+ :alt="t('landing.bannerImage')"
39
40
  />
40
41
  </div>
41
42
  <div
@@ -140,7 +140,7 @@ export default {
140
140
  :aria-label="t('footer.wechat.title')"
141
141
  role="link"
142
142
  @click="show"
143
- @keyup.enter="show"
143
+ @keydown.enter="show"
144
144
  >
145
145
  {{ t('footer.wechat.title') }}
146
146
  </a>
@@ -151,6 +151,7 @@ export default {
151
151
  name="wechat-modal"
152
152
  height="auto"
153
153
  :width="640"
154
+ :trigger-focus-trap="true"
154
155
  @close="close"
155
156
  >
156
157
  <div class="wechat-modal">
@@ -164,8 +165,7 @@ export default {
164
165
  :aria-label="t('generic.close')"
165
166
  role="button"
166
167
  @click="close"
167
- @keyup.enter="close"
168
- @keyup.space="close"
168
+ @keydown.enter.stop
169
169
  >
170
170
  {{ t('generic.close') }}
171
171
  </button>
@@ -49,9 +49,10 @@ export default {
49
49
  v-if="text"
50
50
  class="copy-to-clipboard-text"
51
51
  role="button"
52
- :aria-label="t('generic.copyToClipboard')"
53
52
  :class="{ 'copied': copied, 'plain': plain}"
54
53
  href="#"
54
+ :aria-label="t('generic.copyToClipboard')"
55
+ v-bind="$attrs"
55
56
  @click="clicked"
56
57
  @keyup.space="clicked"
57
58
  >
@@ -58,6 +58,10 @@ export default {
58
58
  },
59
59
 
60
60
  computed: {
61
+ itemLabel() {
62
+ return this.labelKey ? this.t(this.labelKey) : this.label ? this.label : this.t('labels.annotations.singular');
63
+ },
64
+
61
65
  isBinary() {
62
66
  if ( this.binary === null ) {
63
67
  return typeof this.value === 'string' && !asciiLike(this.value);
@@ -186,6 +190,7 @@ export default {
186
190
  :text="value"
187
191
  class="role-tertiary"
188
192
  action-color=""
193
+ :aria-label="t('detailText.copyAriaLabel', {item: itemLabel })"
189
194
  />
190
195
  </div>
191
196
  </template>
@@ -44,6 +44,7 @@ export default {
44
44
  :width="400"
45
45
  height="auto"
46
46
  styles="max-height: 100vh;"
47
+ :trigger-focus-trap="true"
47
48
  @close="close"
48
49
  >
49
50
  <Card
@@ -63,7 +63,7 @@ export default {
63
63
  }
64
64
 
65
65
  if (projectRoleTemplateBindingSchema) {
66
- this.$store.dispatch('rancher/findAll', { type: NORMAN.PROJECT_ROLE_TEMPLATE_BINDING }, { root: true })
66
+ this.$store.dispatch('rancher/findAll', { type: NORMAN.PROJECT_ROLE_TEMPLATE_BINDING, opt: { force: true } }, { root: true })
67
67
  .then((bindings) => {
68
68
  this['projectRoleTemplateBindings'] = bindings;
69
69
  this.loadingProjectBindings = false;
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import { mapGetters } from 'vuex';
2
+ import { mapGetters, useStore } from 'vuex';
3
3
  import ResourceTable, { defaultTableSortGenerationFn } from '@shell/components/ResourceTable';
4
4
  import { STATE, AGE, NAME, NS_SNAPSHOT_QUOTA } from '@shell/config/table-headers';
5
5
  import { uniq } from '@shell/utils/array';
@@ -11,11 +11,13 @@ import Masthead from '@shell/components/ResourceList/Masthead';
11
11
  import { mapPref, GROUP_RESOURCES, ALL_NAMESPACES, DEV } from '@shell/store/prefs';
12
12
  import MoveModal from '@shell/components/MoveModal';
13
13
  import ButtonMultiAction from '@shell/components/ButtonMultiAction.vue';
14
-
14
+ import { escapeHtml } from '@shell/utils/string';
15
15
  import { NAMESPACE_FILTER_ALL_ORPHANS } from '@shell/utils/namespace-filter';
16
16
  import ResourceFetch from '@shell/mixins/resource-fetch';
17
17
  import DOMPurify from 'dompurify';
18
18
  import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
19
+ import ActionMenu from '@shell/components/ActionMenuShell.vue';
20
+ import { useRuntimeFlag } from '@shell/composables/useRuntimeFlag';
19
21
 
20
22
  export default {
21
23
  name: 'ListProjectNamespace',
@@ -25,6 +27,7 @@ export default {
25
27
  MoveModal,
26
28
  ResourceTable,
27
29
  ButtonMultiAction,
30
+ ActionMenu,
28
31
  },
29
32
  mixins: [ResourceFetch],
30
33
 
@@ -58,6 +61,13 @@ export default {
58
61
  this.projects = await this.$store.dispatch('management/findAll', { type: MANAGEMENT.PROJECT, opt: { force: true } });
59
62
  },
60
63
 
64
+ setup() {
65
+ const store = useStore();
66
+ const { featureDropdownMenu } = useRuntimeFlag(store);
67
+
68
+ return { featureDropdownMenu };
69
+ },
70
+
61
71
  data() {
62
72
  return {
63
73
  loadResources: [NAMESPACE],
@@ -155,7 +165,7 @@ export default {
155
165
  rowsWithFakeNamespaces() {
156
166
  const fakeRows = this.projectsWithoutNamespaces.map((project) => {
157
167
  return {
158
- groupByLabel: `${ ('resourceTable.groupLabel.notInAProject') }-${ project.id }`,
168
+ groupById: `${ ('resourceTable.groupLabel.notInAProject') }-${ project.id }`,
159
169
  isFake: true,
160
170
  mainRowKey: project.id,
161
171
  nameDisplay: project.spec?.displayName, // Enable filtering by the project name
@@ -166,8 +176,8 @@ export default {
166
176
 
167
177
  if (this.showMockNotInProjectGroup) {
168
178
  fakeRows.push( {
169
- groupByLabel: this.t('resourceTable.groupLabel.notInAProject'), // Same as the groupByLabel for the namespace model
170
- mainRowKey: 'fake-empty',
179
+ groupById: this.t('resourceTable.groupLabel.notInAProject'),
180
+ mainRowKey: 'fake-empty',
171
181
  });
172
182
  }
173
183
 
@@ -273,6 +283,9 @@ export default {
273
283
  },
274
284
  showCreateNsButton() {
275
285
  return this.groupPreference !== 'namespace';
286
+ },
287
+ projectGroupBy() {
288
+ return this.groupPreference === 'none' ? null : 'groupById';
276
289
  }
277
290
  },
278
291
  methods: {
@@ -336,6 +349,10 @@ export default {
336
349
  return location;
337
350
  },
338
351
 
352
+ getProjectActions(group) {
353
+ return group.rows[0].project;
354
+ },
355
+
339
356
  showProjectAction(event, group) {
340
357
  const project = group.rows[0].project;
341
358
 
@@ -359,7 +376,13 @@ export default {
359
376
  );
360
377
  }
361
378
 
362
- return row.groupByLabel;
379
+ if ( row.groupById === this.notInProjectKey) {
380
+ return this.t('resourceTable.groupLabel.notInAProject');
381
+ }
382
+
383
+ const project = row.project?.nameDisplay || row.project?.id || '';
384
+
385
+ return this.t('resourceTable.groupLabel.project', { name: escapeHtml(project) }, true);
363
386
  },
364
387
 
365
388
  projectDescription(group) {
@@ -431,6 +454,7 @@ export default {
431
454
  :schema="schema"
432
455
  :headers="headers"
433
456
  :rows="filteredRows"
457
+ :group-by="projectGroupBy"
434
458
  :groupable="true"
435
459
  :sort-generation-fn="sortGenerationFn"
436
460
  :loading="loading"
@@ -457,7 +481,7 @@ export default {
457
481
  {{ projectDescription(group.group) }}
458
482
  </div>
459
483
  </div>
460
- <div class="right">
484
+ <div class="right mr-10">
461
485
  <router-link
462
486
  v-if="isNamespaceCreatable && (canSeeProjectlessNamespaces || group.group.key !== notInProjectKey)"
463
487
  class="create-namespace btn btn-sm role-secondary mr-5"
@@ -465,13 +489,26 @@ export default {
465
489
  >
466
490
  {{ t('projectNamespaces.createNamespace') }}
467
491
  </router-link>
468
- <ButtonMultiAction
469
- class="project-action mr-10"
470
- :borderless="true"
471
- :aria-label="t('projectNamespaces.tableActionsLabel', { resource: projectResource(group.group) })"
472
- :invisible="!showProjectActionButton(group.group)"
473
- @click="showProjectAction($event, group.group)"
474
- />
492
+ <template v-if="featureDropdownMenu">
493
+ <ActionMenu
494
+ v-if="showProjectActionButton(group.group)"
495
+ :resource="getProjectActions(group.group)"
496
+ :button-aria-label="t('projectNamespaces.tableActionsLabel', { resource: projectResource(group.group) })"
497
+ />
498
+ <div
499
+ v-else
500
+ class="invisible"
501
+ />
502
+ </template>
503
+ <template v-else>
504
+ <ButtonMultiAction
505
+ class="project-action"
506
+ :borderless="true"
507
+ :aria-label="t('projectNamespaces.tableActionsLabel', { resource: projectResource(group.group) })"
508
+ :invisible="!showProjectActionButton(group.group)"
509
+ @click="showProjectAction($event, group.group)"
510
+ />
511
+ </template>
475
512
  </div>
476
513
  </div>
477
514
  </template>
@@ -536,6 +573,11 @@ export default {
536
573
  </div>
537
574
  </template>
538
575
  <style lang="scss" scoped>
576
+ .invisible {
577
+ display: inline-block;
578
+ min-width: 28px;
579
+ }
580
+
539
581
  .project-namespaces {
540
582
  & :deep() {
541
583
  .project-namespaces-table table {
@@ -103,9 +103,6 @@ export default {
103
103
 
104
104
  <template>
105
105
  <div>
106
- <p class="set-landing-leadin">
107
- {{ t('landing.landingPrefs.body') }}
108
- </p>
109
106
  <RadioGroup
110
107
  id="login-route"
111
108
  :value="afterLoginRoute"
@@ -113,6 +110,11 @@ export default {
113
110
  :options="routeRadioOptions"
114
111
  @update:value="updateLoginRoute"
115
112
  >
113
+ <template #label>
114
+ <p class="set-landing-leadin">
115
+ {{ t('landing.landingPrefs.body') }}
116
+ </p>
117
+ </template>
116
118
  <template #2="{option}">
117
119
  <div class="custom-page">
118
120
  <RadioButton