@rancher/shell 3.0.5-rc.3 → 3.0.5-rc.5

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 (200) hide show
  1. package/assets/images/icons/document.svg +3 -0
  2. package/assets/images/vendor/cognito.svg +1 -0
  3. package/assets/styles/app.scss +1 -0
  4. package/assets/styles/base/_basic.scss +10 -0
  5. package/assets/styles/base/_spacing.scss +29 -0
  6. package/assets/styles/global/_layout.scss +1 -1
  7. package/assets/styles/themes/_dark.scss +25 -0
  8. package/assets/styles/themes/_light.scss +65 -0
  9. package/assets/translations/en-us.yaml +322 -24
  10. package/assets/translations/zh-hans.yaml +8 -5
  11. package/components/Certificates.vue +5 -0
  12. package/components/FilterPanel.vue +156 -0
  13. package/components/{fleet/ForceDirectedTreeChart/index.vue → ForceDirectedTreeChart.vue} +47 -41
  14. package/components/IconOrSvg.vue +14 -35
  15. package/components/PromptRemove.vue +5 -1
  16. package/components/Resource/Detail/Card/PodsCard/Bubble.vue +13 -0
  17. package/components/Resource/Detail/Card/PodsCard/composable.ts +30 -0
  18. package/components/Resource/Detail/Card/PodsCard/index.vue +118 -0
  19. package/components/Resource/Detail/Card/ResourceUsageCard/composable.ts +51 -0
  20. package/components/Resource/Detail/Card/ResourceUsageCard/index.vue +79 -0
  21. package/components/Resource/Detail/Card/Scaler.vue +89 -0
  22. package/components/Resource/Detail/Card/StateCard/composables.ts +112 -0
  23. package/components/Resource/Detail/Card/StateCard/index.vue +39 -0
  24. package/components/Resource/Detail/Card/VerticalGap.vue +11 -0
  25. package/components/Resource/Detail/Card/__tests__/Card.test.ts +36 -0
  26. package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +84 -0
  27. package/components/Resource/Detail/Card/__tests__/ResourceUsageCard.test.ts +72 -0
  28. package/components/Resource/Detail/Card/__tests__/Scaler.test.ts +87 -0
  29. package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +53 -0
  30. package/components/Resource/Detail/Card/__tests__/VerticalGap.test.ts +14 -0
  31. package/components/Resource/Detail/Card/__tests__/index.test.ts +36 -0
  32. package/components/Resource/Detail/Card/index.vue +56 -0
  33. package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +19 -0
  34. package/components/Resource/Detail/Metadata/Annotations/composable.ts +12 -0
  35. package/components/Resource/Detail/Metadata/Annotations/index.vue +26 -0
  36. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +103 -0
  37. package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +281 -0
  38. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +111 -0
  39. package/components/Resource/Detail/Metadata/KeyValue.vue +130 -0
  40. package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +18 -0
  41. package/components/Resource/Detail/Metadata/Labels/composable.ts +12 -0
  42. package/components/Resource/Detail/Metadata/Labels/index.vue +27 -0
  43. package/components/Resource/Detail/Metadata/Rectangle.vue +32 -0
  44. package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +107 -0
  45. package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +24 -0
  46. package/components/Resource/Detail/Metadata/__tests__/index.test.ts +91 -0
  47. package/components/Resource/Detail/Metadata/composables.ts +29 -0
  48. package/components/Resource/Detail/Metadata/index.vue +66 -0
  49. package/components/Resource/Detail/Page.vue +22 -0
  50. package/components/Resource/Detail/PercentageBar.vue +40 -0
  51. package/components/Resource/Detail/ResourceRow.vue +119 -0
  52. package/components/Resource/Detail/SpacedRow.vue +14 -0
  53. package/components/Resource/Detail/StatusBar.vue +59 -0
  54. package/components/Resource/Detail/StatusRow.vue +61 -0
  55. package/components/Resource/Detail/TitleBar/Title.vue +13 -0
  56. package/components/Resource/Detail/TitleBar/Top.vue +14 -0
  57. package/components/Resource/Detail/TitleBar/__tests__/Title.test.ts +17 -0
  58. package/components/Resource/Detail/TitleBar/__tests__/Top.test.ts +17 -0
  59. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +142 -0
  60. package/components/Resource/Detail/TitleBar/composable.ts +31 -0
  61. package/components/Resource/Detail/TitleBar/index.vue +124 -0
  62. package/components/Resource/Detail/Top/index.vue +34 -0
  63. package/components/Resource/Detail/__tests__/Page.test.ts +32 -0
  64. package/components/ResourceDetail/__tests__/index.test.ts +114 -0
  65. package/components/ResourceDetail/index.vue +64 -562
  66. package/components/ResourceDetail/legacy.vue +545 -0
  67. package/components/ResourceTable.vue +41 -7
  68. package/components/SlideInPanelManager.vue +76 -8
  69. package/components/SortableTable/index.vue +13 -2
  70. package/components/SortableTable/selection.js +21 -8
  71. package/components/StatusBadge.vue +6 -4
  72. package/components/SubtleLink.vue +25 -0
  73. package/components/Wizard.vue +12 -1
  74. package/components/YamlEditor.vue +1 -1
  75. package/components/__tests__/FilterPanel.test.ts +81 -0
  76. package/components/auth/AuthBanner.vue +2 -3
  77. package/components/auth/RoleDetailEdit.vue +45 -3
  78. package/components/auth/login/oidc.vue +6 -1
  79. package/components/fleet/FleetApplications.vue +181 -0
  80. package/components/fleet/FleetHelmOps.vue +115 -0
  81. package/components/fleet/FleetIntro.vue +58 -28
  82. package/components/fleet/FleetNoWorkspaces.vue +5 -1
  83. package/components/fleet/FleetOCIStorageSecret.vue +171 -0
  84. package/components/fleet/FleetRepos.vue +38 -76
  85. package/components/fleet/FleetResources.vue +50 -22
  86. package/components/fleet/FleetSummary.vue +26 -51
  87. package/components/fleet/__tests__/FleetOCIStorageSecret.test.ts +213 -0
  88. package/components/fleet/__tests__/FleetSummary.test.ts +39 -39
  89. package/components/fleet/dashboard/Empty.vue +73 -0
  90. package/components/fleet/dashboard/ResourceCard.vue +183 -0
  91. package/components/fleet/dashboard/ResourceCardSummary.vue +199 -0
  92. package/components/fleet/dashboard/ResourceDetails.vue +196 -0
  93. package/components/fleet/dashboard/ResourcePanel.vue +376 -0
  94. package/components/form/ArrayList.vue +6 -0
  95. package/components/form/SimpleSecretSelector.vue +8 -2
  96. package/components/form/ValueFromResource.vue +31 -19
  97. package/components/formatter/FleetApplicationClustersReady.vue +77 -0
  98. package/components/formatter/FleetApplicationSource.vue +71 -0
  99. package/components/formatter/FleetSummaryGraph.vue +7 -0
  100. package/components/nav/Header.vue +8 -7
  101. package/components/nav/TopLevelMenu.helper.ts +55 -34
  102. package/components/nav/TopLevelMenu.vue +11 -0
  103. package/components/nav/Type.vue +4 -1
  104. package/composables/useI18n.ts +12 -11
  105. package/config/labels-annotations.js +14 -11
  106. package/config/product/auth.js +1 -0
  107. package/config/product/fleet.js +70 -17
  108. package/config/query-params.js +3 -1
  109. package/config/roles.ts +1 -0
  110. package/config/router/routes.js +20 -2
  111. package/config/secret.ts +15 -0
  112. package/config/settings.ts +3 -2
  113. package/config/table-headers.js +52 -22
  114. package/config/types.js +2 -0
  115. package/core/plugin-helpers.ts +3 -2
  116. package/detail/fleet.cattle.io.cluster.vue +28 -15
  117. package/detail/fleet.cattle.io.gitrepo.vue +10 -1
  118. package/detail/fleet.cattle.io.helmop.vue +157 -0
  119. package/dialog/HelmOpForceUpdateDialog.vue +132 -0
  120. package/dialog/RedeployWorkloadDialog.vue +164 -0
  121. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +56 -67
  122. package/edit/auth/oidc.vue +159 -93
  123. package/edit/fleet.cattle.io.gitrepo.vue +26 -33
  124. package/edit/fleet.cattle.io.helmop.vue +997 -0
  125. package/edit/management.cattle.io.fleetworkspace.vue +43 -10
  126. package/list/fleet.cattle.io.gitrepo.vue +1 -1
  127. package/list/fleet.cattle.io.helmop.vue +108 -0
  128. package/list/namespace.vue +5 -2
  129. package/mixins/auth-config.js +8 -1
  130. package/mixins/preset.js +100 -0
  131. package/mixins/resource-fetch-api-pagination.js +2 -0
  132. package/mixins/resource-fetch.js +1 -1
  133. package/mixins/resource-table-watch.js +45 -0
  134. package/models/__tests__/chart.test.ts +273 -0
  135. package/models/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
  136. package/models/chart.js +144 -2
  137. package/models/fleet-application.js +385 -0
  138. package/models/fleet.cattle.io.bundle.js +9 -8
  139. package/models/fleet.cattle.io.gitrepo.js +41 -365
  140. package/models/fleet.cattle.io.helmop.js +228 -0
  141. package/models/management.cattle.io.authconfig.js +1 -0
  142. package/models/management.cattle.io.fleetworkspace.js +12 -0
  143. package/models/workload.js +14 -18
  144. package/package.json +2 -1
  145. package/pages/auth/verify.vue +13 -1
  146. package/pages/c/_cluster/apps/charts/AddRepoLink.vue +37 -0
  147. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +80 -0
  148. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +54 -0
  149. package/pages/c/_cluster/apps/charts/StatusLabel.vue +33 -0
  150. package/pages/c/_cluster/apps/charts/index.vue +302 -484
  151. package/pages/c/_cluster/explorer/EventsTable.vue +1 -1
  152. package/pages/c/_cluster/fleet/__tests__/index.test.ts +426 -0
  153. package/pages/c/_cluster/fleet/application/_resource/_id.vue +14 -0
  154. package/pages/c/_cluster/fleet/application/_resource/create.vue +14 -0
  155. package/pages/c/_cluster/fleet/application/create.vue +340 -0
  156. package/pages/c/_cluster/fleet/application/index.vue +139 -0
  157. package/pages/c/_cluster/fleet/graph/config.js +277 -0
  158. package/pages/c/_cluster/fleet/index.vue +772 -330
  159. package/pages/explorer/resource/detail/configmap.vue +19 -0
  160. package/plugins/dashboard-store/actions.js +31 -9
  161. package/plugins/dashboard-store/getters.js +34 -21
  162. package/plugins/dashboard-store/mutations.js +51 -7
  163. package/plugins/dashboard-store/resource-class.js +14 -2
  164. package/plugins/steve/__tests__/subscribe.spec.ts +66 -1
  165. package/plugins/steve/actions.js +3 -0
  166. package/plugins/steve/steve-pagination-utils.ts +14 -13
  167. package/plugins/steve/subscribe.js +229 -42
  168. package/rancher-components/BadgeState/BadgeState.vue +3 -1
  169. package/rancher-components/Form/Checkbox/Checkbox.vue +2 -2
  170. package/rancher-components/RcItemCard/RcItemCard.test.ts +189 -0
  171. package/rancher-components/RcItemCard/RcItemCard.vue +425 -0
  172. package/rancher-components/RcItemCard/RcItemCardAction.vue +24 -0
  173. package/rancher-components/RcItemCard/index.ts +2 -0
  174. package/store/auth.js +1 -0
  175. package/store/catalog.js +62 -24
  176. package/store/index.js +33 -14
  177. package/store/slideInPanel.ts +6 -0
  178. package/store/type-map.js +1 -0
  179. package/types/fleet.d.ts +35 -0
  180. package/types/resources/settings.d.ts +19 -1
  181. package/types/shell/index.d.ts +339 -272
  182. package/types/store/dashboard-store.types.ts +17 -3
  183. package/types/store/pagination.types.ts +6 -1
  184. package/types/store/subscribe.types.ts +50 -0
  185. package/utils/auth.js +32 -3
  186. package/utils/fleet-types.ts +0 -0
  187. package/utils/fleet.ts +200 -1
  188. package/utils/pagination-utils.ts +26 -1
  189. package/utils/pagination-wrapper.ts +132 -50
  190. package/utils/settings.ts +4 -1
  191. package/utils/style.ts +39 -0
  192. package/utils/validators/formRules/__tests__/index.test.ts +36 -3
  193. package/utils/validators/formRules/index.ts +10 -3
  194. package/utils/window.js +11 -7
  195. package/components/__tests__/ApplicationCard.test.ts +0 -27
  196. package/components/cards/ApplicationCard.vue +0 -145
  197. package/components/fleet/ForceDirectedTreeChart/chartIcons.js +0 -17
  198. package/config/secret.js +0 -14
  199. package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +0 -249
  200. /package/{components/form/SSHKnownHosts → dialog}/__tests__/KnownHostsEditDialog.test.ts +0 -0
@@ -1,11 +1,16 @@
1
1
  <script lang="ts" setup>
2
- import { computed } from 'vue';
2
+ import { computed, watch } from 'vue';
3
3
  import { useStore } from 'vuex';
4
+ import {
5
+ DEFAULT_FOCUS_TRAP_OPTS,
6
+ useWatcherBasedSetupFocusTrapWithDestroyIncluded
7
+ } from '@shell/composables/focusTrap';
4
8
 
5
9
  const HEADER_HEIGHT = 55;
6
10
 
7
11
  const store = useStore();
8
12
  const isOpen = computed(() => store.getters['slideInPanel/isOpen']);
13
+ const isClosing = computed(() => store.getters['slideInPanel/isClosing']);
9
14
  const currentComponent = computed(() => store.getters['slideInPanel/component']);
10
15
  const currentProps = computed(() => store.getters['slideInPanel/componentProps']);
11
16
 
@@ -23,8 +28,61 @@ const panelTop = computed(() => {
23
28
  const panelHeight = computed(() => `calc(100vh - ${ panelTop?.value })`);
24
29
  const panelWidth = computed(() => currentProps?.value?.width || '33%');
25
30
  const panelRight = computed(() => (isOpen?.value ? '0' : `-${ panelWidth?.value }`));
26
-
27
- const panelTitle = computed(() => currentProps?.value?.title || 'Details');
31
+ const panelZIndex = computed(() => `${ (isOpen?.value ? 1 : 2) * (currentProps?.value?.zIndex ?? 1000) }`);
32
+
33
+ const showHeader = computed(() => currentProps?.value?.showHeader ?? true);
34
+ const panelTitle = showHeader.value ? computed(() => currentProps?.value?.title || 'Details') : null;
35
+
36
+ watch(
37
+ /**
38
+ * trigger focus trap
39
+ */
40
+ () => currentProps?.value?.triggerFocusTrap,
41
+ (neu) => {
42
+ if (neu) {
43
+ const opts = {
44
+ ...DEFAULT_FOCUS_TRAP_OPTS,
45
+ /**
46
+ * will return focus to the first iterable node of this container select
47
+ */
48
+ setReturnFocus: () => {
49
+ const returnFocusSelector = currentProps?.value?.returnFocusSelector;
50
+
51
+ if (returnFocusSelector && !document.querySelector(returnFocusSelector)) {
52
+ console.warn('SlideInPanelManager: cannot find elem with "returnFocusSelector", returning focus to main view'); // eslint-disable-line no-console
53
+
54
+ return '.dashboard-root';
55
+ }
56
+
57
+ return returnFocusSelector || '.dashboard-root';
58
+ }
59
+ };
60
+
61
+ useWatcherBasedSetupFocusTrapWithDestroyIncluded(
62
+ () => {
63
+ if (currentProps?.value?.focusTrapWatcherBasedVariable) {
64
+ return currentProps.value.focusTrapWatcherBasedVariable;
65
+ }
66
+
67
+ return isOpen?.value && !isClosing?.value;
68
+ },
69
+ '#slide-in-panel-manager',
70
+ opts,
71
+ false
72
+ );
73
+ }
74
+ }
75
+ );
76
+
77
+ watch(
78
+ () => (store as any).$router?.currentRoute,
79
+ () => {
80
+ if (isOpen?.value && currentProps?.value.closeOnRouteChange !== false) {
81
+ closePanel();
82
+ }
83
+ },
84
+ { deep: true }
85
+ );
28
86
 
29
87
  function closePanel() {
30
88
  store.commit('slideInPanel/close');
@@ -33,7 +91,10 @@ function closePanel() {
33
91
 
34
92
  <template>
35
93
  <Teleport to="#slides">
36
- <div id="slide-in-panel-manager">
94
+ <div
95
+ id="slide-in-panel-manager"
96
+ @keydown.escape="closePanel"
97
+ >
37
98
  <div
38
99
  v-show="isOpen"
39
100
  data-testid="slide-in-glass"
@@ -44,9 +105,18 @@ function closePanel() {
44
105
  <div
45
106
  class="slide-in"
46
107
  :class="{ 'slide-in-open': isOpen }"
47
- :style="{ width: panelWidth, right: panelRight, top: panelTop, height: panelHeight }"
108
+ :style="{
109
+ width: panelWidth,
110
+ right: panelRight,
111
+ top: panelTop,
112
+ height: panelHeight,
113
+ ['z-index']: panelZIndex
114
+ }"
48
115
  >
49
- <div class="header">
116
+ <div
117
+ v-if="showHeader"
118
+ class="header"
119
+ >
50
120
  <div class="title">
51
121
  {{ panelTitle }}
52
122
  </div>
@@ -84,7 +154,6 @@ function closePanel() {
84
154
  background-color: var(--body-bg);
85
155
  display: block;
86
156
  opacity: 0.5;
87
- z-index: 1000;
88
157
  }
89
158
 
90
159
  .slide-in {
@@ -92,7 +161,6 @@ function closePanel() {
92
161
  flex-direction: column;
93
162
  position: fixed;
94
163
  top: 0;
95
- z-index: 2000;
96
164
  transition: right 0.5s ease;
97
165
  border-left: 1px solid var(--border);
98
166
  background-color: var(--body-bg);
@@ -64,6 +64,7 @@ export default {
64
64
  ActionMenu,
65
65
  ActionDropdownShell,
66
66
  },
67
+
67
68
  mixins: [
68
69
  filtering,
69
70
  sorting,
@@ -378,8 +379,17 @@ export default {
378
379
  manualRefreshButtonSize: {
379
380
  type: String,
380
381
  default: ''
381
- }
382
+ },
382
383
 
384
+ /**
385
+ * Usually the manual refresh button is controlled via isTooManyItemsToAutoUpdate
386
+ *
387
+ * However this is singular on page. In some places there's more than one...
388
+ */
389
+ hideManualRefreshButton: {
390
+ type: Boolean,
391
+ default: false
392
+ }
383
393
  },
384
394
 
385
395
  data() {
@@ -1196,9 +1206,10 @@ export default {
1196
1206
  <div class="bg" />
1197
1207
  </li>
1198
1208
  </ul>
1209
+ <slot name="watch-controls" />
1199
1210
  <slot name="header-right" />
1200
1211
  <AsyncButton
1201
- v-if="isTooManyItemsToAutoUpdate"
1212
+ v-if="!hideManualRefreshButton && isTooManyItemsToAutoUpdate"
1202
1213
  mode="manual-refresh"
1203
1214
  :size="manualRefreshButtonSize"
1204
1215
  :current-phase="refreshButtonPhase"
@@ -127,25 +127,38 @@ export default {
127
127
  },
128
128
 
129
129
  watch: {
130
- // On page change
130
+ /**
131
+ * Handle changes to the page (SSP enabled)
132
+ */
133
+ externalPaginationResult() {
134
+ // Handle changes to the page (SSP enabled)
135
+ this.pageChanged(this.pagedRows);
136
+ },
137
+
138
+ /**
139
+ * Handle changes to the page (SSP disabled)
140
+ */
131
141
  pagedRows() {
132
- // When the table contents changes:
133
- // - Remove items that are in the selection but no longer in the table.
142
+ this.pageChanged(this.pagedRows);
143
+ }
144
+ },
134
145
 
135
- const content = this.pagedRows;
146
+ methods: {
147
+ /**
148
+ * Remove items that are in the selection but no longer in the table.
149
+ */
150
+ pageChanged(page) {
136
151
  const toRemove = [];
137
152
 
138
153
  for (const node of this.selectedRows) {
139
- if (!content.includes(node) ) {
154
+ if (!page.includes(node) ) {
140
155
  toRemove.push(node);
141
156
  }
142
157
  }
143
158
 
144
159
  this.update([], toRemove);
145
- }
146
- },
160
+ },
147
161
 
148
- methods: {
149
162
  onToggleAll(value) {
150
163
  if ( value ) {
151
164
  this.update(this.pagedRows, []);
@@ -19,11 +19,13 @@ const STATUS = {
19
19
  }
20
20
  };
21
21
 
22
+ export interface Props {
23
+ status?: 'success' | 'warning' | 'info' | 'error',
24
+ label?: string
25
+ }
26
+
22
27
  withDefaults(
23
- defineProps<{
24
- status?: 'success' | 'warning' | 'info' | 'error',
25
- label?: string
26
- }>(),
28
+ defineProps<Props>(),
27
29
  {
28
30
  status: 'success',
29
31
  label: '',
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import { RouterLink, RouteLocationRaw } from 'vue-router';
3
+
4
+ export interface Props {
5
+ to: RouteLocationRaw;
6
+ }
7
+
8
+ const { to } = defineProps<Props>();
9
+ </script>
10
+
11
+ <template>
12
+ <RouterLink
13
+ class="subtle-link"
14
+ :to="to"
15
+ >
16
+ <slot name="default" />
17
+ </RouterLink>
18
+ </template>
19
+
20
+ <style lang="scss" scoped>
21
+ .subtle-link {
22
+ text-decoration: underline;
23
+ color: var(--body-text);
24
+ }
25
+ </style>
@@ -1,5 +1,5 @@
1
1
  <script>
2
-
2
+ import { _CREATE, _VIEW } from '@shell/config/query-params';
3
3
  import AsyncButton from '@shell/components/AsyncButton';
4
4
  import { Banner } from '@components/Banner';
5
5
  import Loading from '@shell/components/Loading';
@@ -56,6 +56,11 @@ export default {
56
56
  required: true
57
57
  },
58
58
 
59
+ mode: {
60
+ type: String,
61
+ default: _CREATE
62
+ },
63
+
59
64
  // Initial step to show when Wizard loads.
60
65
  initStepIndex: {
61
66
  type: Number,
@@ -121,6 +126,11 @@ export default {
121
126
  },
122
127
 
123
128
  computed: {
129
+
130
+ isView() {
131
+ return this.mode === _VIEW;
132
+ },
133
+
124
134
  errorStrings() {
125
135
  return ( this.errors || [] ).map((x) => stringify(x));
126
136
  },
@@ -461,6 +471,7 @@ export default {
461
471
  :finish="finish"
462
472
  >
463
473
  <AsyncButton
474
+ v-if="!isView"
464
475
  :disabled="!activeStep.ready"
465
476
  :mode="finishMode"
466
477
  @click="finish"
@@ -203,7 +203,7 @@ export default {
203
203
 
204
204
  updateValue(value) {
205
205
  this.curValue = value;
206
- this.$refs.cm.updateValue(value);
206
+ this.$refs.cm?.updateValue(value);
207
207
  }
208
208
  }
209
209
  };
@@ -0,0 +1,81 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import FilterPanel from '@shell/components/FilterPanel.vue';
3
+ import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
4
+
5
+ const filters = [
6
+ {
7
+ key: 'status',
8
+ title: 'Status',
9
+ options: [
10
+ { label: 'Installed', value: 'installed' },
11
+ { label: 'Deprecated', value: 'deprecated' }
12
+ ]
13
+ },
14
+ {
15
+ key: 'repos',
16
+ title: 'Repository',
17
+ options: [
18
+ { label: 'Repo A', value: 'repo-a' },
19
+ { label: 'Repo B', value: 'repo-b' }
20
+ ]
21
+ }
22
+ ];
23
+
24
+ describe('component: FilterPanel', () => {
25
+ it('renders all filter groups and options with correct labels', () => {
26
+ const wrapper = mount(FilterPanel, {
27
+ props: {
28
+ modelValue: {},
29
+ filters
30
+ }
31
+ });
32
+
33
+ expect(wrapper.findAll('.filter-panel-filter-group-title')).toHaveLength(2);
34
+ expect(wrapper.text()).toContain('Status');
35
+ expect(wrapper.text()).toContain('Repository');
36
+ expect(wrapper.text()).toContain('Installed');
37
+ expect(wrapper.text()).toContain('Repo A');
38
+ expect(wrapper.text()).toContain('Repo B');
39
+ });
40
+
41
+ it('emits update:modelValue when a new status is added via checkbox emit', async() => {
42
+ const wrapper = mount(FilterPanel, {
43
+ props: {
44
+ modelValue: { status: ['installed'] },
45
+ filters
46
+ }
47
+ });
48
+
49
+ const checkboxes = wrapper.findAllComponents(Checkbox);
50
+ const deprecatedCheckbox = checkboxes.find((c) => c.props('label') === 'Deprecated');
51
+
52
+ expect(deprecatedCheckbox).toBeTruthy();
53
+
54
+ deprecatedCheckbox?.vm.$emit('update:value', ['installed', 'deprecated']);
55
+ await wrapper.vm.$nextTick();
56
+
57
+ const emitted = wrapper.emitted('update:modelValue');
58
+
59
+ expect(emitted).toBeTruthy();
60
+ expect(emitted?.[0][0].status).toStrictEqual(['installed', 'deprecated']);
61
+ });
62
+
63
+ it('renders a custom component if provided in option', () => {
64
+ const CustomComponent = { template: '<div>Custom content</div>' };
65
+
66
+ const wrapper = mount(FilterPanel, {
67
+ props: {
68
+ modelValue: {},
69
+ filters: [
70
+ {
71
+ key: 'custom',
72
+ title: 'Custom Filters',
73
+ options: [{ component: CustomComponent }]
74
+ }
75
+ ]
76
+ }
77
+ });
78
+
79
+ expect(wrapper.text()).toContain('Custom content');
80
+ });
81
+ });
@@ -98,9 +98,8 @@ export default {
98
98
  }
99
99
 
100
100
  .values {
101
- tr td:not(:first-of-type) {
102
- padding-left: 10px;
103
- }
101
+ border-spacing: 8px 8px; // Add spacing between columns and rows
102
+ margin-left: -8px; // Move the table back to the left, to compensate for the spacing from above on the left-hand column
104
103
  }
105
104
 
106
105
  </style>
@@ -18,6 +18,7 @@ import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
18
18
 
19
19
  import { SUBTYPE_MAPPING, VERBS } from '@shell/models/management.cattle.io.roletemplate';
20
20
  import Loading from '@shell/components/Loading';
21
+ import Banner from '@components/Banner/Banner.vue';
21
22
 
22
23
  const GLOBAL = SUBTYPE_MAPPING.GLOBAL.key;
23
24
  const CLUSTER = SUBTYPE_MAPPING.CLUSTER.key;
@@ -64,7 +65,8 @@ export default {
64
65
  SortableTable,
65
66
  Loading,
66
67
  Error,
67
- LabeledInput
68
+ LabeledInput,
69
+ Banner,
68
70
  },
69
71
 
70
72
  mixins: [CreateEditView, FormValidation],
@@ -497,7 +499,8 @@ export default {
497
499
  index: i,
498
500
  apiGroups: rule.apiGroups || [''],
499
501
  resources: rule.resources || [],
500
- nonResourceURLs: rule.nonResourceURLs || []
502
+ nonResourceURLs: rule.nonResourceURLs || [],
503
+ verbs: rule.verbs,
501
504
  };
502
505
 
503
506
  VERBS.forEach((verb) => {
@@ -531,6 +534,13 @@ export default {
531
534
 
532
535
  return res;
533
536
  },
537
+
538
+ showPsaWarning(grant) {
539
+ return this.value.subtype === NAMESPACE &&
540
+ grant.verbs?.includes('updatepsa') &&
541
+ grant.resources?.includes('projects') &&
542
+ grant.apiGroups?.includes('management.cattle.io');
543
+ }
534
544
  }
535
545
  };
536
546
  </script>
@@ -558,7 +568,28 @@ export default {
558
568
  :table-actions="false"
559
569
  :row-actions="false"
560
570
  :search="false"
561
- />
571
+ :sub-rows="true"
572
+ >
573
+ <template #sub-row="{row, fullColspan, onRowMouseEnter, onRowMouseLeave}">
574
+ <tr
575
+
576
+ class="sub-row"
577
+ @mouseenter="onRowMouseEnter"
578
+ @mouseleave="onRowMouseLeave"
579
+ >
580
+ <td
581
+ v-if="showPsaWarning(row)"
582
+ :colspan="fullColspan"
583
+ >
584
+ <Banner
585
+ style="margin: 0px"
586
+ label-key="rbac.roletemplate.tabs.grantResources.psaWarning"
587
+ color="warning"
588
+ />
589
+ </td>
590
+ </tr>
591
+ </template>
592
+ </SortableTable>
562
593
  <div
563
594
  v-for="(inherited, index) of inheritedRules"
564
595
  :key="index"
@@ -732,6 +763,17 @@ export default {
732
763
  </div>
733
764
  </div>
734
765
  </template>
766
+ <template #value-sub-row="{row}">
767
+ <div
768
+ v-if="showPsaWarning(row.value)"
769
+ class="mr-20"
770
+ >
771
+ <Banner
772
+ label-key="rbac.roletemplate.tabs.grantResources.psaWarning"
773
+ color="warning"
774
+ />
775
+ </div>
776
+ </template>
735
777
  </ArrayList>
736
778
  </Tab>
737
779
  <Tab
@@ -6,7 +6,12 @@ export default {
6
6
 
7
7
  computed: {
8
8
  uniqueDisplayName() {
9
- return this.t('model.authConfig.description.oidc');
9
+ switch (this.name) {
10
+ case 'cognito':
11
+ return this.t('model.authConfig.description.cognito');
12
+ default:
13
+ return this.t('model.authConfig.description.oidc');
14
+ }
10
15
  },
11
16
  },
12
17
 
@@ -0,0 +1,181 @@
1
+ <script lang="ts">
2
+ import { PropType } from 'vue';
3
+ import { checkPermissions } from '@shell/utils/auth';
4
+ import { FLEET } from '@shell/config/types';
5
+ import { Application } from '@shell/types/fleet';
6
+ import ResourceTable from '@shell/components/ResourceTable.vue';
7
+ import FleetIntro from '@shell/components/fleet/FleetIntro.vue';
8
+ import {
9
+ AGE,
10
+ NAME,
11
+ STATE,
12
+ FLEET_APPLICATION_TYPE,
13
+ FLEET_APPLICATION_SOURCE,
14
+ FLEET_APPLICATION_TARGET,
15
+ FLEET_APPLICATION_CLUSTERS_READY,
16
+ FLEET_APPLICATION_RESOURCES_SUMMARY,
17
+ } from '@shell/config/table-headers';
18
+
19
+ interface Permissions {
20
+ gitRepos: boolean,
21
+ helmOps: boolean,
22
+ }
23
+
24
+ interface DataType {
25
+ permissions: Permissions
26
+ }
27
+
28
+ export default {
29
+ name: 'FleetApplications',
30
+
31
+ components: {
32
+ FleetIntro,
33
+ ResourceTable,
34
+ },
35
+
36
+ props: {
37
+ schema: {
38
+ type: Object,
39
+ required: true,
40
+ },
41
+
42
+ workspace: {
43
+ type: String,
44
+ default: ''
45
+ },
46
+
47
+ clusterId: {
48
+ type: String,
49
+ required: false,
50
+ default: null,
51
+ },
52
+
53
+ rows: {
54
+ type: Array as PropType<Application[]>,
55
+ required: true,
56
+ },
57
+
58
+ loading: {
59
+ type: Boolean,
60
+ required: false,
61
+ },
62
+
63
+ useQueryParamsForSimpleFiltering: {
64
+ type: Boolean,
65
+ default: false
66
+ },
67
+
68
+ showIntro: {
69
+ type: Boolean,
70
+ default: true,
71
+ }
72
+ },
73
+
74
+ async fetch() {
75
+ try {
76
+ const permissionsSchemas = {
77
+ gitRepos: {
78
+ type: FLEET.GIT_REPO,
79
+ schemaValidator: (schema: any) => schema.resourceMethods.includes('PUT')
80
+ },
81
+ helmOps: {
82
+ type: FLEET.HELM_OP,
83
+ schemaValidator: (schema: any) => schema.resourceMethods.includes('PUT')
84
+ },
85
+ };
86
+
87
+ const permissions = await checkPermissions(permissionsSchemas, this.$store.getters) as Permissions;
88
+
89
+ this.permissions = permissions;
90
+ } catch (e) {
91
+ console.error(e); // eslint-disable-line no-console
92
+ }
93
+ },
94
+
95
+ data(): DataType {
96
+ return {
97
+ permissions: {
98
+ gitRepos: true,
99
+ helmOps: true
100
+ }
101
+ };
102
+ },
103
+
104
+ computed: {
105
+ createLocation() {
106
+ return { name: 'c-cluster-fleet-application-create' };
107
+ },
108
+
109
+ filteredRows() {
110
+ if (!this.rows) {
111
+ return [];
112
+ }
113
+
114
+ const selectedNamespaces = this.$store.getters['namespaces']();
115
+
116
+ return this.rows.filter((row) => {
117
+ if (this.workspace) {
118
+ return this.workspace === row.metadata.namespace;
119
+ }
120
+
121
+ return !!selectedNamespaces[row.metadata.namespace];
122
+ });
123
+ },
124
+
125
+ isClusterView() {
126
+ return !!this.clusterId;
127
+ },
128
+
129
+ shouldShowIntro() {
130
+ return this.showIntro && !this.filteredRows.length;
131
+ },
132
+
133
+ headers() {
134
+ return [
135
+ STATE,
136
+ NAME,
137
+ FLEET_APPLICATION_TYPE,
138
+ FLEET_APPLICATION_SOURCE,
139
+ FLEET_APPLICATION_TARGET,
140
+ FLEET_APPLICATION_CLUSTERS_READY,
141
+ FLEET_APPLICATION_RESOURCES_SUMMARY,
142
+ AGE
143
+ ];
144
+ },
145
+ },
146
+
147
+ methods: {
148
+ getDetailLocation(row: Application) {
149
+ return row._detailLocation;
150
+ }
151
+ },
152
+ };
153
+ </script>
154
+
155
+ <template>
156
+ <div>
157
+ <FleetIntro
158
+ v-if="shouldShowIntro && !loading"
159
+ :schema="schema"
160
+ :is-creatable="permissions.gitRepos || permissions.helmOps"
161
+ :labelKey="'application'"
162
+ :route="createLocation"
163
+ :icon="'icon-repository'"
164
+ />
165
+ <ResourceTable
166
+ v-if="!shouldShowIntro"
167
+ v-bind="$attrs"
168
+ key-field="_key"
169
+ :schema="schema"
170
+ :headers="headers"
171
+ :rows="filteredRows"
172
+ :get-custom-detail-link="getDetailLocation"
173
+ :loading="loading"
174
+ :use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
175
+ :namespaced="!workspace"
176
+ />
177
+ </div>
178
+ </template>
179
+
180
+ <style lang="scss" scoped>
181
+ </style>