@rancher/shell 3.0.2-rc.2 → 3.0.2-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/assets/styles/base/_basic.scss +5 -7
  2. package/assets/styles/global/_button.scss +10 -0
  3. package/assets/styles/global/_tooltip.scss +2 -2
  4. package/assets/styles/themes/_dark.scss +14 -2
  5. package/assets/styles/themes/_light.scss +7 -2
  6. package/assets/styles/vendor/vue-select.scss +4 -0
  7. package/assets/translations/en-us.yaml +44 -5
  8. package/components/BannerGraphic.vue +0 -42
  9. package/components/ButtonMultiAction.vue +1 -1
  10. package/components/Carousel.vue +36 -29
  11. package/components/CommunityLinks.vue +6 -1
  12. package/components/GrowlManager.vue +9 -2
  13. package/components/LocaleSelector.vue +8 -1
  14. package/components/PaginatedResourceTable.vue +4 -7
  15. package/components/ProgressBarMulti.vue +14 -0
  16. package/components/Questions/Reference.vue +57 -28
  17. package/components/SelectIconGrid.vue +12 -1
  18. package/components/SideNav.vue +12 -38
  19. package/components/SortableTable/index.vue +1 -0
  20. package/components/Tabbed/index.vue +12 -1
  21. package/components/YamlEditor.vue +1 -0
  22. package/components/auth/Principal.vue +5 -3
  23. package/components/fleet/FleetClusters.vue +82 -1
  24. package/components/fleet/FleetRepos.vue +13 -30
  25. package/components/fleet/ForceDirectedTreeChart/index.vue +2 -2
  26. package/components/form/ChangePassword.vue +2 -0
  27. package/components/form/ColorInput.vue +24 -1
  28. package/components/form/FileSelector.vue +2 -0
  29. package/components/form/KeyValue.vue +230 -160
  30. package/components/form/LabeledSelect.vue +1 -1
  31. package/components/form/PlusMinus.vue +14 -2
  32. package/components/form/ResourceLabeledSelect.vue +13 -53
  33. package/components/form/ResourceSelector.vue +1 -0
  34. package/components/form/ResourceTabs/index.vue +79 -36
  35. package/components/form/SecretSelector.vue +2 -2
  36. package/components/form/__tests__/KeyValue.test.ts +1 -1
  37. package/components/formatter/FleetClusterSummaryGraph.vue +2 -2
  38. package/components/formatter/FleetSummaryGraph.vue +6 -7
  39. package/components/formatter/WorkloadHealthScale.vue +7 -0
  40. package/components/nav/Group.vue +30 -4
  41. package/components/nav/Header.vue +82 -114
  42. package/components/nav/HeaderPageActionMenu.vue +27 -131
  43. package/components/nav/NamespaceFilter.vue +1 -1
  44. package/components/nav/Type.vue +15 -0
  45. package/config/home-links.js +21 -13
  46. package/config/labels-annotations.js +2 -0
  47. package/config/page-actions.js +1 -0
  48. package/config/pagination-table-headers.js +15 -1
  49. package/config/product/explorer.js +7 -17
  50. package/config/table-headers.js +6 -0
  51. package/config/version.js +5 -1
  52. package/core/plugin.ts +41 -1
  53. package/core/plugins.js +125 -72
  54. package/core/types-provisioning.ts +91 -2
  55. package/core/types.ts +55 -0
  56. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +12 -3
  57. package/detail/catalog.cattle.io.app.vue +1 -1
  58. package/detail/fleet.cattle.io.cluster.vue +3 -3
  59. package/detail/namespace.vue +13 -19
  60. package/detail/networking.k8s.io.ingress.vue +13 -53
  61. package/detail/provisioning.cattle.io.cluster.vue +12 -1
  62. package/detail/workload/index.vue +3 -3
  63. package/dialog/AddCustomBadgeDialog.vue +5 -1
  64. package/edit/auth/ldap/__tests__/config.test.ts +18 -0
  65. package/edit/auth/ldap/config.vue +24 -0
  66. package/edit/auth/saml.vue +8 -6
  67. package/edit/fleet.cattle.io.gitrepo.vue +7 -1
  68. package/edit/logging-flow/index.vue +4 -19
  69. package/edit/networking.k8s.io.ingress/index.vue +18 -65
  70. package/edit/networking.k8s.io.networkpolicy/index.vue +4 -5
  71. package/edit/provisioning.cattle.io.cluster/index.vue +13 -1
  72. package/edit/provisioning.cattle.io.cluster/rke2.vue +31 -115
  73. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +2 -2
  74. package/edit/provisioning.cattle.io.cluster/tabs/networking/ACE.vue +14 -28
  75. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +25 -12
  76. package/edit/service.vue +1 -2
  77. package/list/networking.k8s.io.ingress.vue +1 -1
  78. package/list/node.vue +15 -8
  79. package/list/persistentvolume.vue +12 -4
  80. package/list/service.vue +1 -1
  81. package/list/workload.vue +4 -0
  82. package/mixins/chart.js +4 -1
  83. package/models/catalog.cattle.io.app.js +3 -1
  84. package/models/catalog.cattle.io.clusterrepo.js +56 -7
  85. package/models/fleet.cattle.io.bundle.js +0 -11
  86. package/models/fleet.cattle.io.cluster.js +17 -1
  87. package/models/fleet.cattle.io.gitrepo.js +86 -50
  88. package/models/provisioning.cattle.io.cluster.js +47 -2
  89. package/models/service.js +1 -0
  90. package/models/workload.js +19 -1
  91. package/package.json +5 -4
  92. package/pages/c/_cluster/apps/charts/index.vue +4 -0
  93. package/pages/c/_cluster/explorer/ConfigBadge.vue +8 -7
  94. package/pages/c/_cluster/explorer/index.vue +13 -6
  95. package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +3 -3
  96. package/pages/c/_cluster/fleet/index.vue +75 -89
  97. package/pages/c/_cluster/settings/links.vue +2 -2
  98. package/pages/diagnostic.vue +17 -15
  99. package/pages/home.vue +32 -6
  100. package/plugins/clean-html.js +50 -0
  101. package/plugins/dashboard-store/resource-class.js +4 -0
  102. package/plugins/plugin.js +54 -49
  103. package/plugins/steve/mutations.js +1 -1
  104. package/plugins/steve/steve-class.js +8 -0
  105. package/plugins/steve/steve-pagination-utils.ts +3 -1
  106. package/rancher-components/Accordion/Accordion.vue +4 -4
  107. package/rancher-components/BadgeState/BadgeState.vue +7 -0
  108. package/rancher-components/Card/Card.vue +27 -1
  109. package/rancher-components/Form/Checkbox/Checkbox.vue +9 -2
  110. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +18 -1
  111. package/rancher-components/Form/LabeledInput/LabeledInput.vue +18 -1
  112. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +39 -2
  113. package/rancher-components/RcButton/RcButton.vue +90 -0
  114. package/rancher-components/RcButton/index.ts +2 -0
  115. package/rancher-components/RcButton/types.ts +17 -0
  116. package/rancher-components/RcDropdown/RcDropdown.vue +111 -0
  117. package/rancher-components/RcDropdown/RcDropdownItem.vue +127 -0
  118. package/rancher-components/RcDropdown/RcDropdownSeparator.vue +6 -0
  119. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +43 -0
  120. package/rancher-components/RcDropdown/index.ts +4 -0
  121. package/rancher-components/RcDropdown/types.ts +22 -0
  122. package/rancher-components/RcDropdown/useDropdownCollection.ts +45 -0
  123. package/rancher-components/RcDropdown/useDropdownContext.ts +83 -0
  124. package/scripts/test-plugins-build.sh +2 -0
  125. package/scripts/typegen.sh +2 -0
  126. package/store/catalog.js +1 -1
  127. package/tsconfig.json +2 -1
  128. package/types/components/paginatedResourceTable.ts +25 -0
  129. package/types/components/resourceLabeledSelect.ts +48 -0
  130. package/types/resources/fleet.d.ts +17 -0
  131. package/types/shell/index.d.ts +61 -0
  132. package/utils/auth.js +5 -1
  133. package/utils/cluster.js +106 -0
  134. package/utils/fleet.ts +35 -3
  135. package/utils/ingress.ts +64 -0
  136. package/utils/uiplugins.ts +56 -44
  137. package/utils/validators/cron-schedule.js +7 -2
  138. package/utils/validators/formRules/__tests__/index.test.ts +53 -17
  139. package/utils/validators/formRules/index.ts +20 -5
  140. package/vue.config.js +1 -1
  141. package/components/RelatedWorkloadsTable.vue +0 -50
@@ -8,10 +8,14 @@ import Tab from '@shell/components/Tabbed/Tab';
8
8
  import CreateEditView from '@shell/mixins/create-edit-view';
9
9
  import Conditions from '@shell/components/form/Conditions';
10
10
  import { EVENT } from '@shell/config/types';
11
- import SortableTable from '@shell/components/SortableTable';
11
+ import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue';
12
12
  import { _VIEW } from '@shell/config/query-params';
13
13
  import RelatedResources from '@shell/components/RelatedResources';
14
14
  import { isConditionReadyAndWaiting } from '@shell/plugins/dashboard-store/resource-class';
15
+ import { PaginationParamFilter } from '@shell/types/store/pagination.types';
16
+ import { MESSAGE, REASON } from '@shell/config/table-headers';
17
+ import { STEVE_EVENT_LAST_SEEN, STEVE_EVENT_TYPE, STEVE_NAME_COL } from '@shell/config/pagination-table-headers';
18
+ import { headerFromSchemaColString } from '@shell/store/type-map.utils';
15
19
 
16
20
  export default {
17
21
 
@@ -21,7 +25,7 @@ export default {
21
25
  Tabbed,
22
26
  Tab,
23
27
  Conditions,
24
- SortableTable,
28
+ PaginatedResourceTable,
25
29
  RelatedResources,
26
30
  },
27
31
 
@@ -69,14 +73,25 @@ export default {
69
73
 
70
74
  data() {
71
75
  const inStore = this.$store.getters['currentStore'](EVENT);
76
+ const eventSchema = this.$store.getters[`${ inStore }/schemaFor`](EVENT); // @TODO be smarter about which resources actually ever have events
72
77
 
73
78
  return {
74
- hasEvents: this.$store.getters[`${ inStore }/schemaFor`](EVENT), // @TODO be smarter about which resources actually ever have events
75
- allEvents: [],
76
- selectedTab: this.defaultTab,
77
- didLoadEvents: false,
79
+ eventSchema,
80
+ EVENT,
81
+ selectedTab: this.defaultTab,
78
82
  inStore,
79
- showConditions: false,
83
+ showConditions: false,
84
+ paginationHeaders: [
85
+ STEVE_EVENT_LAST_SEEN,
86
+ STEVE_EVENT_TYPE,
87
+ REASON,
88
+ headerFromSchemaColString('Subobject', eventSchema, this.$store.getters, true),
89
+ headerFromSchemaColString('Source', eventSchema, this.$store.getters, true),
90
+ MESSAGE,
91
+ headerFromSchemaColString('First Seen', eventSchema, this.$store.getters, true),
92
+ headerFromSchemaColString('Count', eventSchema, this.$store.getters, true),
93
+ STEVE_NAME_COL,
94
+ ]
80
95
  };
81
96
  },
82
97
 
@@ -92,7 +107,7 @@ export default {
92
107
 
93
108
  computed: {
94
109
  showEvents() {
95
- return this.isView && this.needEvents && this.hasEvents;
110
+ return this.isView && this.needEvents && this.eventSchema;
96
111
  },
97
112
  showRelated() {
98
113
  return this.isView && this.needRelated;
@@ -128,18 +143,6 @@ export default {
128
143
  },
129
144
  ];
130
145
  },
131
- events() {
132
- return this.allEvents.filter((event) => {
133
- return event.involvedObject?.uid === this.value?.metadata?.uid;
134
- }).map((event) => {
135
- return {
136
- reason: (`${ event.reason || this.t('generic.unknown') }${ event.count > 1 ? ` (${ event.count })` : '' }`).trim(),
137
- message: event.message || this.t('generic.unknown'),
138
- date: event.lastTimestamp || event.firstTimestamp || event.metadata.creationTimestamp,
139
- eventType: event.eventType
140
- };
141
- });
142
- },
143
146
  conditionsHaveIssues() {
144
147
  if (this.showConditions) {
145
148
  return this.value.status?.conditions?.filter((cond) => !isConditionReadyAndWaiting(cond)).some((cond) => cond.error);
@@ -153,15 +156,6 @@ export default {
153
156
  // Ensures we only fetch events and show the table when the events tab has been activated
154
157
  tabChange(neu) {
155
158
  this.selectedTab = neu?.selectedName;
156
-
157
- if (!this.didLoadEvents && this.selectedTab === 'events') {
158
- const inStore = this.$store.getters['currentStore'](EVENT);
159
-
160
- this.$store.dispatch(`${ inStore }/findAll`, { type: EVENT }).then((events) => {
161
- this.allEvents = events;
162
- this.didLoadEvents = true;
163
- });
164
- }
165
159
  },
166
160
 
167
161
  /**
@@ -180,6 +174,54 @@ export default {
180
174
  this.showConditions = this.$store.getters[`${ this.inStore }/pathExistsInSchema`](this.value.type, 'status.conditions');
181
175
  }
182
176
  },
177
+
178
+ /**
179
+ * Filter out hidden repos from list of all repos
180
+ */
181
+ filterEventsLocal(rows) {
182
+ return rows.filter((event) => event.involvedObject?.uid === this.value?.metadata?.uid);
183
+ },
184
+
185
+ /**
186
+ * Filter out hidden repos via api
187
+ *
188
+ * pagination: PaginationArgs
189
+ * returns: PaginationArgs
190
+ */
191
+ filterEventsApi(pagination) {
192
+ if (!pagination.filters) {
193
+ pagination.filters = [];
194
+ }
195
+
196
+ const field = `involvedObject.uid`; // Pending API Support - https://github.com/rancher/rancher/issues/48603
197
+
198
+ // of type PaginationParamFilter
199
+ let existing = null;
200
+
201
+ for (let i = 0; i < pagination.filters.length; i++) {
202
+ const filter = pagination.filters[i];
203
+
204
+ if (!!filter.fields.find((f) => f.field === field)) {
205
+ existing = filter;
206
+ break;
207
+ }
208
+ }
209
+
210
+ const required = PaginationParamFilter.createSingleField({
211
+ field,
212
+ exact: true,
213
+ value: this.value.metadata.uid,
214
+ equals: true
215
+ });
216
+
217
+ if (!!existing) {
218
+ Object.assign(existing, required);
219
+ } else {
220
+ pagination.filters.push(required);
221
+ }
222
+
223
+ return pagination;
224
+ }
183
225
  }
184
226
  };
185
227
  </script>
@@ -208,15 +250,16 @@ export default {
208
250
  name="events"
209
251
  :weight="-2"
210
252
  >
211
- <SortableTable
253
+ <!-- namespaced: false given we don't want the default handling of namespaced resource (apply header filter) -->
254
+ <PaginatedResourceTable
212
255
  v-if="selectedTab === 'events'"
213
- :rows="events"
256
+ :schema="eventSchema"
257
+ :local-filter="filterEventsLocal"
258
+ :api-filter="filterEventsApi"
259
+ :use-query-params-for-simple-filtering="false"
214
260
  :headers="eventHeaders"
215
- key-field="id"
216
- :search="false"
217
- :table-actions="false"
218
- :row-actions="false"
219
- default-sort-by="date"
261
+ :paginationHeaders="paginationHeaders"
262
+ :namespaced="false"
220
263
  />
221
264
  </Tab>
222
265
 
@@ -70,7 +70,7 @@ export default {
70
70
  secrets: null,
71
71
  SECRET,
72
72
  allSecretsSettings: {
73
- mapResult: (secrets) => {
73
+ updateResources: (secrets) => {
74
74
  const allSecretsInNamespace = secrets.filter((secret) => this.types.includes(secret._type) && secret.namespace === this.namespace);
75
75
  const mappedSecrets = this.mapSecrets(allSecretsInNamespace.sort((a, b) => a.name.localeCompare(b.name)));
76
76
 
@@ -81,7 +81,7 @@ export default {
81
81
  },
82
82
  paginateSecretsSetting: {
83
83
  requestSettings: this.paginatePageOptions,
84
- mapResult: (secrets) => {
84
+ updateResources: (secrets) => {
85
85
  const mappedSecrets = this.mapSecrets(secrets);
86
86
 
87
87
  this.secrets = secrets; // We need the key from the selected secret. When paginating we won't touch the store, so just pass back here
@@ -127,7 +127,7 @@ describe('component: KeyValue', () => {
127
127
  expect(secondKeyInput.exists()).toBe(false);
128
128
  expect(secondValueInput.exists()).toBe(false);
129
129
 
130
- const addButton = wrapper.find('[data-testid="add_link_button"]');
130
+ const addButton = wrapper.find('[data-testid="add_row_item_button"]');
131
131
 
132
132
  addButton.trigger('click');
133
133
  await nextTick();
@@ -11,7 +11,7 @@ export default {
11
11
  required: true
12
12
  },
13
13
 
14
- clusterLabel: {
14
+ clusterId: {
15
15
  type: String,
16
16
  required: true
17
17
  }
@@ -22,6 +22,6 @@ export default {
22
22
  <template>
23
23
  <FleetSummaryGraph
24
24
  :row="row"
25
- :clusterLabel="clusterLabel"
25
+ :clusterId="clusterId"
26
26
  />
27
27
  </template>
@@ -14,7 +14,7 @@ export default {
14
14
  required: true
15
15
  },
16
16
 
17
- clusterLabel: {
17
+ clusterId: {
18
18
  type: String,
19
19
  required: false,
20
20
  default: null,
@@ -23,10 +23,8 @@ export default {
23
23
 
24
24
  computed: {
25
25
  summary() {
26
- if (this.clusterLabel) {
27
- return this.row.clusterResourceStatus.find((x) => {
28
- return x.clusterLabel === this.clusterLabel;
29
- })?.status.resourceCounts || {};
26
+ if (this.clusterId) {
27
+ return this.row.statusResourceCountsForCluster(this.clusterId);
30
28
  }
31
29
 
32
30
  return this.row.status?.resourceCounts || {};
@@ -37,7 +35,8 @@ export default {
37
35
  },
38
36
 
39
37
  stateParts() {
40
- const keys = Object.keys(this.summary).filter((x) => !x.startsWith('desired'));
38
+ const summary = this.summary;
39
+ const keys = Object.keys(summary).filter((x) => !x.startsWith('desired'));
41
40
 
42
41
  const out = keys.map((key) => {
43
42
  const textColor = colorForState(key);
@@ -46,7 +45,7 @@ export default {
46
45
  label: ucFirst(key),
47
46
  color: textColor.replace(/text-/, 'bg-'),
48
47
  textColor,
49
- value: this.summary[key],
48
+ value: summary[key],
50
49
  sort: stateSort(textColor, key),
51
50
  };
52
51
  }).filter((x) => x.value > 0);
@@ -182,14 +182,21 @@ export default {
182
182
  <div
183
183
  id="trigger"
184
184
  class="hs-popover__trigger"
185
+ aria-role="button"
186
+ tabindex="0"
185
187
  :class="{expanded}"
188
+ :aria-roledescription="t('workload.scaleWorkloads')"
189
+ :aria-label="t('workload.healthScaleToggle')"
190
+ :aria-expanded="expanded"
186
191
  @click="expanded = !expanded"
192
+ @keyup.enter.space="expanded = !expanded"
187
193
  >
188
194
  <ProgressBarMulti
189
195
  v-if="parts"
190
196
  class="health"
191
197
  :values="parts"
192
198
  :show-zeros="true"
199
+ :aria-describedby="t('workload.healthWorkloads')"
193
200
  />
194
201
  <i :class="{icon: true, 'icon-chevron-up': expanded, 'icon-chevron-down': !expanded}" />
195
202
  </div>
@@ -211,26 +211,40 @@ export default {
211
211
  v-if="showHeader"
212
212
  class="header"
213
213
  :class="{'active': isOverview, 'noHover': !canCollapse}"
214
+ role="button"
215
+ tabindex="0"
216
+ :aria-label="group.labelDisplay || group.label || ''"
214
217
  @click="groupSelected()"
218
+ @keyup.enter="groupSelected()"
219
+ @keyup.space="groupSelected()"
215
220
  >
216
221
  <slot name="header">
217
222
  <router-link
218
223
  v-if="hasOverview"
219
224
  :to="group.children[0].route"
220
225
  :exact="group.children[0].exact"
226
+ :tabindex="-1"
221
227
  >
222
- <h6 v-clean-html="group.labelDisplay || group.label" />
228
+ <h6>
229
+ <span v-clean-html="group.labelDisplay || group.label" />
230
+ </h6>
223
231
  </router-link>
224
232
  <h6
225
233
  v-else
226
- v-clean-html="group.labelDisplay || group.label"
227
- />
234
+ >
235
+ <span v-clean-html="group.labelDisplay || group.label" />
236
+ </h6>
228
237
  </slot>
229
238
  <i
230
239
  v-if="!onlyHasOverview && canCollapse"
231
- class="icon toggle"
240
+ class="icon toggle toggle-accordion"
232
241
  :class="{'icon-chevron-right': !isExpanded, 'icon-chevron-down': isExpanded}"
242
+ role="button"
243
+ tabindex="0"
244
+ :aria-label="t('nav.ariaLabel.collapseExpand')"
233
245
  @click="peek($event, true)"
246
+ @keyup.enter="peek($event, true)"
247
+ @keyup.space="peek($event, true)"
234
248
  />
235
249
  </div>
236
250
  <ul
@@ -288,6 +302,7 @@ export default {
288
302
  cursor: pointer;
289
303
  color: var(--body-text);
290
304
  height: 33px;
305
+ outline: none;
291
306
 
292
307
  H6 {
293
308
  color: var(--body-text);
@@ -315,6 +330,17 @@ export default {
315
330
 
316
331
  .accordion {
317
332
  .header {
333
+ &:focus-visible {
334
+ h6 span {
335
+ @include focus-outline;
336
+ outline-offset: 2px;
337
+ }
338
+ }
339
+ .toggle-accordion:focus-visible {
340
+ @include focus-outline;
341
+ outline-offset: -6px;
342
+ }
343
+
318
344
  &.active {
319
345
  color: var(--primary-hover-text);
320
346
  background-color: var(--primary-hover-bg);
@@ -24,6 +24,12 @@ import IconOrSvg from '@shell/components/IconOrSvg';
24
24
  import { wait } from '@shell/utils/async';
25
25
  import { configType } from '@shell/models/management.cattle.io.authconfig';
26
26
  import HeaderPageActionMenu from './HeaderPageActionMenu.vue';
27
+ import {
28
+ RcDropdown,
29
+ RcDropdownItem,
30
+ RcDropdownSeparator,
31
+ RcDropdownTrigger
32
+ } from '@components/RcDropdown';
27
33
 
28
34
  export default {
29
35
 
@@ -39,6 +45,10 @@ export default {
39
45
  IconOrSvg,
40
46
  AppModal,
41
47
  HeaderPageActionMenu,
48
+ RcDropdown,
49
+ RcDropdownItem,
50
+ RcDropdownSeparator,
51
+ RcDropdownTrigger,
42
52
  },
43
53
 
44
54
  props: {
@@ -641,27 +651,18 @@ export default {
641
651
  </button>
642
652
  </div>
643
653
 
644
- <header-page-action-menu v-if="showPageActions" />
645
-
646
- <div class="header-spacer" />
647
- <div
648
- v-if="showUserMenu"
649
- class="user user-menu"
650
- data-testid="nav_header_showUserMenu"
651
- tabindex="0"
652
- @blur="showMenu(false)"
653
- @click="showMenu(true)"
654
- @focus.capture="showMenu(true)"
655
- >
656
- <v-dropdown
657
- :triggers="[]"
658
- :shown="isUserMenuOpen"
659
- :autoHide="false"
660
- :flip="false"
661
- :container="false"
662
- :placement="'bottom-end'"
654
+ <div class="center-self">
655
+ <header-page-action-menu v-if="showPageActions" />
656
+ <rc-dropdown
657
+ v-if="showUserMenu"
658
+ :aria-label="t('nav.userMenu.label')"
663
659
  >
664
- <div class="user-image text-right hand">
660
+ <rc-dropdown-trigger
661
+ ghost
662
+ small
663
+ data-testid="nav_header_showUserMenu"
664
+ :aria-label="t('nav.userMenu.button.label')"
665
+ >
665
666
  <img
666
667
  v-if="principal && principal.avatarSrc"
667
668
  :src="principal.avatarSrc"
@@ -673,88 +674,47 @@ export default {
673
674
  v-else
674
675
  class="icon icon-user icon-3x avatar"
675
676
  />
676
- </div>
677
- <template #popper>
678
- <div
679
- class="user-menu"
677
+ </rc-dropdown-trigger>
678
+ <template #dropdownCollection>
679
+ <template v-if="authEnabled">
680
+ <div class="user-info">
681
+ <div class="user-name">
682
+ <i class="icon icon-lg icon-user" /> {{ principal.loginName }}
683
+ </div>
684
+ <div class="text-small">
685
+ <template v-if="principal.loginName !== principal.name">
686
+ {{ principal.name }}
687
+ </template>
688
+ </div>
689
+ </div>
690
+ <rc-dropdown-separator />
691
+ </template>
692
+ <rc-dropdown-item
693
+ v-if="showPreferencesLink"
694
+ @click="$router.push({ name: 'prefs'})"
680
695
  >
681
- <ul
682
- class="list-unstyled dropdown"
683
- data-testid="user-menu-dropdown"
684
- @click.stop="showMenu(false)"
685
- >
686
- <li
687
- v-if="authEnabled"
688
- class="user-info"
689
- >
690
- <div class="user-name">
691
- <i class="icon icon-lg icon-user" /> {{ principal.loginName }}
692
- </div>
693
- <div class="text-small pt-5 pb-5">
694
- <template v-if="principal.loginName !== principal.name">
695
- {{ principal.name }}
696
- </template>
697
- </div>
698
- </li>
699
- <router-link
700
- v-if="showPreferencesLink"
701
- v-slot="{ href, navigate }"
702
- custom
703
- :to="{name: 'prefs'}"
704
- >
705
- <li
706
- class="user-menu-item"
707
- @click="navigate"
708
- @keypress.enter="navigate"
709
- >
710
- <a :href="href">{{ t('nav.userMenu.preferences') }}</a>
711
- </li>
712
- </router-link>
713
- <router-link
714
- v-if="showAccountAndApiKeyLink"
715
- v-slot="{ href, navigate }"
716
- custom
717
- :to="{name: 'account'}"
718
- >
719
- <li
720
- class="user-menu-item"
721
- @click="navigate"
722
- @keypress.enter="navigate"
723
- >
724
- <a :href="href">{{ t('nav.userMenu.accountAndKeys', {}, true) }}</a>
725
- </li>
726
- </router-link>
727
- <!-- SLO modal -->
728
- <li
729
- v-if="authEnabled && shouldShowSloLogoutModal"
730
- class="user-menu-item no-link"
731
- @click="showSloModal"
732
- @keypress.enter="showSloModal"
733
- >
734
- <span>{{ t('nav.userMenu.logOut') }}</span>
735
- </li>
736
- <!-- logout -->
737
- <router-link
738
- v-else-if="authEnabled"
739
- v-slot="{ href, navigate }"
740
- custom
741
- :to="generateLogoutRoute"
742
- >
743
- <li
744
- class="user-menu-item"
745
- @click="navigate"
746
- @keypress.enter="navigate"
747
- >
748
- <a
749
- :href="href"
750
- @blur="showMenu(false)"
751
- >{{ t('nav.userMenu.logOut') }}</a>
752
- </li>
753
- </router-link>
754
- </ul>
755
- </div>
696
+ {{ t('nav.userMenu.preferences') }}
697
+ </rc-dropdown-item>
698
+ <rc-dropdown-item
699
+ v-if="showAccountAndApiKeyLink"
700
+ @click="$router.push({ name: 'account'})"
701
+ >
702
+ {{ t('nav.userMenu.accountAndKeys', {}, true) }}
703
+ </rc-dropdown-item>
704
+ <rc-dropdown-item
705
+ v-if="authEnabled && shouldShowSloLogoutModal"
706
+ @click="showSloModal"
707
+ >
708
+ {{ t('nav.userMenu.logOut') }}
709
+ </rc-dropdown-item>
710
+ <rc-dropdown-item
711
+ v-else-if="authEnabled"
712
+ @click="$router.push(generateLogoutRoute)"
713
+ >
714
+ {{ t('nav.userMenu.logOut') }}
715
+ </rc-dropdown-item>
756
716
  </template>
757
- </v-dropdown>
717
+ </rc-dropdown>
758
718
  </div>
759
719
  </div>
760
720
  </header>
@@ -957,7 +917,7 @@ export default {
957
917
  width: 40px;
958
918
  }
959
919
 
960
- :deep() > div > .btn.role-tertiary {
920
+ :deep() div .btn.role-tertiary {
961
921
  border: 1px solid var(--header-btn-bg);
962
922
  border: none;
963
923
  background: var(--header-btn-bg);
@@ -1010,8 +970,9 @@ export default {
1010
970
  position: relative;
1011
971
  }
1012
972
 
1013
- .user.user-menu {
1014
- padding-top: 9.5px;
973
+ .avatar-round {
974
+ border: 0;
975
+ border-radius: 50%;
1015
976
  }
1016
977
 
1017
978
  > .user {
@@ -1047,11 +1008,14 @@ export default {
1047
1008
  }
1048
1009
 
1049
1010
  background-color: var(--header-bg);
1011
+ }
1050
1012
 
1051
- .avatar-round {
1052
- border: 0;
1053
- border-radius: 50%;
1054
- }
1013
+ > .center-self {
1014
+ align-self: center;
1015
+ display: flex;
1016
+ gap: 1rem;
1017
+ align-items: center;
1018
+ padding-right: 1rem;
1055
1019
  }
1056
1020
  }
1057
1021
  }
@@ -1063,14 +1027,17 @@ export default {
1063
1027
  justify-content: space-between;
1064
1028
  padding: 10px;
1065
1029
  }
1030
+ }
1031
+ }
1066
1032
 
1067
- &.user-info {
1068
- display: block;
1069
- margin-bottom: 10px;
1070
- padding: 10px 20px;
1071
- border-bottom: solid 1px var(--border);
1072
- min-width: 200px;
1073
- }
1033
+ div {
1034
+ &.user-info {
1035
+ padding: 0 8px;
1036
+ margin: 0 9px;
1037
+ min-width: 200px;
1038
+ display: flex;
1039
+ gap: 5px;
1040
+ flex-direction: column;
1074
1041
  }
1075
1042
  }
1076
1043
 
@@ -1152,4 +1119,5 @@ export default {
1152
1119
  }
1153
1120
  }
1154
1121
  }
1122
+
1155
1123
  </style>