@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
@@ -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>'
@@ -2731,6 +2755,7 @@ clusterBadge:
2731
2755
  iconText: Icon Text
2732
2756
  buttonAction: Apply
2733
2757
  badgeBgColor: Badge background color
2758
+ badgeBgColorInput: Badge background color selector input
2734
2759
  badgeTextColor: Badge text color
2735
2760
  badgeAsIcon: Use custom badge
2736
2761
  maxCharsTooltip: Overwrites the default cluster name abbreviation.
@@ -2930,6 +2955,8 @@ ingress:
2930
2955
 
2931
2956
  internalExternalIP:
2932
2957
  none: None
2958
+ copyInternalIp: Copy internal IP address to clipboard
2959
+ copyExternalIp: Copy external IP address to clipboard
2933
2960
 
2934
2961
  istio:
2935
2962
  links:
@@ -3137,6 +3164,7 @@ labels:
3137
3164
  hide: Hide System Labels and Annotations
3138
3165
  annotations:
3139
3166
  title: Annotations
3167
+ singular: Annotation
3140
3168
 
3141
3169
  labelSelect:
3142
3170
  noOptions:
@@ -3150,6 +3178,8 @@ labeledSelect:
3150
3178
  pressEnter: Press enter to add "{input}"
3151
3179
 
3152
3180
  landing:
3181
+ bannerImage: Homepage banner image
3182
+ homepage: Homepage
3153
3183
  clusters:
3154
3184
  title: Clusters
3155
3185
  provider: Provider
@@ -3473,8 +3503,10 @@ neuvector:
3473
3503
  na: Resource Unavailable
3474
3504
 
3475
3505
  login:
3506
+ login: Login
3476
3507
  howdy: Howdy!
3477
3508
  welcome: Welcome to {vendor}
3509
+ landscapeAlt: Landscape image
3478
3510
  loggedOut: You have been logged out.
3479
3511
  loggedOutFromSso: You've been logged out of Rancher, however you may still be logged in to your single sign-on identity provider.
3480
3512
  loggedOutFromSlo: You've been logged out of Rancher and from your single sign-on identity provider.
@@ -4879,6 +4911,7 @@ promptForceRemove:
4879
4911
  modalTitle: Are you sure?
4880
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."
4881
4913
  forceDelete: Force Delete
4914
+ ariaLabel: Copy Machine name to clipboard
4882
4915
  confirmName: "Enter in the pool name below to confirm:"
4883
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>"
4884
4917
 
@@ -5490,6 +5523,7 @@ setup:
5490
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.
5491
5524
  useManual: Set a specific password to use
5492
5525
  useRandom: Use a randomly generated password
5526
+ copyRandom: Copy random password to clipboard
5493
5527
  welcome: Welcome to {vendor}!
5494
5528
 
5495
5529
  sortableTable:
@@ -7941,12 +7975,12 @@ customLinks:
7941
7975
  commercialSupport: Commercial Support
7942
7976
  appCo: SUSE Application Collection
7943
7977
 
7944
-
7945
7978
  ##############################
7946
7979
  ### Support Page
7947
7980
  ##############################
7948
7981
 
7949
7982
  support:
7983
+ bannerImage: Support page banner image
7950
7984
  community:
7951
7985
  title: SUSE Rancher provides world-class support
7952
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