@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,39 +1,38 @@
1
1
  <script>
2
+ import { markRaw } from 'vue';
2
3
  import AsyncButton from '@shell/components/AsyncButton';
3
4
  import Loading from '@shell/components/Loading';
4
5
  import { Banner } from '@components/Banner';
5
- import Carousel from '@shell/components/Carousel';
6
- import ButtonGroup from '@shell/components/ButtonGroup';
7
- import SelectIconGrid from '@shell/components/SelectIconGrid';
8
- import TypeDescription from '@shell/components/TypeDescription';
9
6
  import {
10
- REPO_TYPE, REPO, CHART, VERSION, SEARCH_QUERY, _FLAGGED, CATEGORY, DEPRECATED as DEPRECATED_QUERY, HIDDEN, OPERATING_SYSTEM
7
+ REPO_TYPE, REPO, CHART, VERSION, SEARCH_QUERY, _FLAGGED, CATEGORY, DEPRECATED, HIDDEN, TAG, STATUS
11
8
  } from '@shell/config/query-params';
9
+ import { APP_STATUS, compatibleVersionsFor, filterAndArrangeCharts, normalizeFilterQuery } from '@shell/store/catalog';
12
10
  import { lcFirst } from '@shell/utils/string';
13
11
  import { sortBy } from '@shell/utils/sort';
12
+ import debounce from 'lodash/debounce';
14
13
  import { mapGetters } from 'vuex';
15
- import { Checkbox } from '@components/Form/Checkbox';
16
- import Select from '@shell/components/form/Select';
17
- import { mapPref, HIDE_REPOS, SHOW_PRE_RELEASE, SHOW_CHART_MODE } from '@shell/store/prefs';
18
- import { removeObject, addObject, findBy } from '@shell/utils/array';
19
- import { compatibleVersionsFor, filterAndArrangeCharts } from '@shell/store/catalog';
14
+ import { SHOW_PRE_RELEASE } from '@shell/store/prefs';
20
15
  import { CATALOG } from '@shell/config/labels-annotations';
21
16
  import { isUIPlugin } from '@shell/config/uiplugins';
22
- import TabTitle from '@shell/components/TabTitle';
17
+ import { RcItemCard } from '@components/RcItemCard';
18
+ import { get } from '@shell/utils/object';
19
+ import { CATALOG as CATALOG_TYPES } from '@shell/config/types';
20
+ import FilterPanel from '@shell/components/FilterPanel';
21
+ import AppChartCardSubHeader from '@shell/pages/c/_cluster/apps/charts/AppChartCardSubHeader';
22
+ import AppChartCardFooter from '@shell/pages/c/_cluster/apps/charts/AppChartCardFooter';
23
+ import AddRepoLink from '@shell/pages/c/_cluster/apps/charts/AddRepoLink';
24
+ import StatusLabel from '@shell/pages/c/_cluster/apps/charts/StatusLabel';
23
25
 
24
26
  export default {
25
27
  name: 'Charts',
26
28
  components: {
27
29
  AsyncButton,
28
30
  Banner,
29
- Carousel,
30
- ButtonGroup,
31
31
  Loading,
32
- Checkbox,
33
- Select,
34
- SelectIconGrid,
35
- TypeDescription,
36
- TabTitle
32
+ RcItemCard,
33
+ FilterPanel,
34
+ AppChartCardSubHeader,
35
+ AppChartCardFooter
37
36
  },
38
37
 
39
38
  async fetch() {
@@ -42,32 +41,66 @@ export default {
42
41
  const query = this.$route.query;
43
42
 
44
43
  this.searchQuery = query[SEARCH_QUERY] || '';
45
- this.showDeprecated = query[DEPRECATED_QUERY] === 'true' || query[DEPRECATED_QUERY] === _FLAGGED;
44
+ this.debouncedSearchQuery = query[SEARCH_QUERY] || '';
46
45
  this.showHidden = query[HIDDEN] === _FLAGGED;
47
- this.category = query[CATEGORY] || '';
48
- this.allRepos = this.areAllEnabled();
46
+ this.filters.repos = normalizeFilterQuery(query[REPO]) || [];
47
+ this.filters.categories = normalizeFilterQuery(query[CATEGORY]) || [];
48
+ this.filters.statuses = normalizeFilterQuery(query[STATUS]) || [];
49
+ this.filters.tags = normalizeFilterQuery(query[TAG]) || [];
50
+
51
+ this.installedApps = await this.$store.dispatch('cluster/findAll', { type: CATALOG_TYPES.APP });
49
52
  },
50
53
 
51
54
  data() {
52
55
  return {
53
- allRepos: null,
54
- category: null,
55
- operatingSystem: null,
56
- searchQuery: null,
57
- showDeprecated: null,
58
- showHidden: null,
59
- chartOptions: [
56
+ searchQuery: null,
57
+ debouncedSearchQuery: null,
58
+ showDeprecated: null,
59
+ showHidden: null,
60
+ filters: {
61
+ repos: [],
62
+ categories: [],
63
+ statuses: [],
64
+ tags: []
65
+ },
66
+ internalFilters: { // in order to update the filter checkboxes smoothly
67
+ repos: [],
68
+ categories: [],
69
+ statuses: [],
70
+ tags: []
71
+ },
72
+ installedApps: [],
73
+ statusOptions: [
74
+ {
75
+ value: APP_STATUS.INSTALLED,
76
+ label: {
77
+ component: markRaw(StatusLabel),
78
+ componentProps: {
79
+ label: this.t('generic.installed'),
80
+ icon: 'icon-warning',
81
+ iconColor: 'warning',
82
+ tooltip: this.t('catalog.charts.experimentalStatus.tooltip')
83
+ }
84
+ }
85
+ },
60
86
  {
61
- label: this.t('catalog.charts.browseBtn'),
62
- value: 'browse',
63
- ariaLabel: this.t('catalog.charts.browseAriaLabel')
87
+ value: APP_STATUS.DEPRECATED,
88
+ label: this.t('generic.deprecated'),
64
89
  },
65
90
  {
66
- label: this.t('catalog.charts.featuredBtn'),
67
- value: 'featured',
68
- ariaLabel: this.t('catalog.charts.featuredAriaLabel')
91
+ value: APP_STATUS.UPGRADEABLE,
92
+ label: {
93
+ component: markRaw(StatusLabel),
94
+ componentProps: {
95
+ label: this.t('generic.upgradeable'),
96
+ icon: 'icon-warning',
97
+ iconColor: 'warning',
98
+ tooltip: this.t('catalog.charts.experimentalStatus.tooltip')
99
+ }
100
+ }
69
101
  }
70
- ]
102
+ ],
103
+ appCardsCache: {},
71
104
  };
72
105
  },
73
106
 
@@ -75,84 +108,46 @@ export default {
75
108
  ...mapGetters(['currentCluster']),
76
109
  ...mapGetters({ allCharts: 'catalog/charts', loadingErrors: 'catalog/errors' }),
77
110
 
78
- chartMode: mapPref(SHOW_CHART_MODE),
79
-
80
- hideRepos: mapPref(HIDE_REPOS),
81
-
82
111
  repoOptions() {
83
- let nextColor = 0;
84
- // Colors 3 and 4 match `rancher` and `partner` colors, so just avoid them
85
- const colors = [1, 2, 5, 6, 7, 8];
86
-
87
112
  let out = this.$store.getters['catalog/repos'].map((r) => {
88
113
  return {
89
- _key: r._key,
90
- label: r.nameDisplay,
91
- color: r.color,
92
- weight: ( r.isRancher ? 1 : ( r.isPartner ? 2 : 3 ) ),
93
- enabled: !this.hideRepos.includes(r._key),
114
+ value: r._key,
115
+ label: r.nameDisplay,
116
+ weight: ( r.isRancher ? 1 : ( r.isPartner ? 2 : 3 ) ),
94
117
  };
95
118
  });
96
119
 
97
120
  out = sortBy(out, ['weight', 'label']);
98
121
 
99
- for ( const entry of out ) {
100
- if ( !entry.color ) {
101
- entry.color = `color${ colors[nextColor] }`;
102
- nextColor++;
103
- if ( nextColor >= colors.length ) {
104
- nextColor = 0;
105
- }
106
- }
107
- }
122
+ // not a filter, just a link for adding a new repo
123
+ out.push({
124
+ value: 'add-repo-link',
125
+ component: markRaw(AddRepoLink),
126
+ componentProps: { clusterId: this.clusterId }
127
+ });
108
128
 
109
129
  return out;
110
130
  },
111
131
 
112
- repoOptionsForDropdown() {
113
- return [{
114
- label: this.t('catalog.repo.all'), all: true, enabled: this.areAllEnabled()
115
- }, ...this.repoOptions];
116
- },
117
-
118
- flattenedRepoNames() {
119
- const allChecked = this.repoOptionsForDropdown.find((repo) => repo.all && repo.enabled);
132
+ tagOptions() {
133
+ const outSet = new Set();
120
134
 
121
- if (allChecked) {
122
- return allChecked.label;
123
- }
124
-
125
- // None checked
126
- if (!this.repoOptionsForDropdown.find((repo) => repo.enabled)) {
127
- return this.t('generic.none');
128
- }
129
-
130
- const shownRepos = this.repoOptions.filter((repo) => !this.hideRepos.includes(repo._key));
131
- const reducedRepos = shownRepos.reduce((acc, c, i) => {
132
- acc += c.label;
133
- const length = shownRepos.length;
134
-
135
- if (i < length - 1) {
136
- acc += ', ';
135
+ this.allCharts.forEach((chart) => {
136
+ if (Array.isArray(chart.tags)) {
137
+ chart.tags.forEach((tag) => outSet.add(tag));
137
138
  }
139
+ });
138
140
 
139
- return acc;
140
- }, '');
141
-
142
- return reducedRepos;
141
+ return Array.from(outSet).map((tag) => ({ value: tag.toLowerCase(), label: tag }));
143
142
  },
144
143
 
145
144
  /**
146
- * Filter allll charts by invalid entries (deprecated, hidden and ui plugin).
145
+ * Filter all charts by invalid entries (hidden and ui plugin).
147
146
  *
148
147
  * This does not include any user provided filters (like selected repos, categories and text query)
149
148
  */
150
149
  enabledCharts() {
151
150
  return (this.allCharts || []).filter((c) => {
152
- if ( c.deprecated && !this.showDeprecated ) {
153
- return false;
154
- }
155
-
156
151
  if ( c.hidden && !this.showHidden ) {
157
152
  return false;
158
153
  }
@@ -169,136 +164,145 @@ export default {
169
164
  * Filter enabled charts allll filters. These are what the user will see in the list
170
165
  */
171
166
  filteredCharts() {
172
- return this.filterCharts({
173
- category: this.category,
174
- searchQuery: this.searchQuery,
175
- hideRepos: this.hideRepos
167
+ const {
168
+ categories, repos, tags, statuses
169
+ } = this.filters;
170
+ const res = this.filterCharts({
171
+ category: categories,
172
+ searchQuery: this.debouncedSearchQuery,
173
+ repo: repos,
174
+ tag: tags
176
175
  });
177
- },
178
176
 
179
- /**
180
- * Filter valid charts (alll filters minus user provided ones) by whether they are featured or not
181
- *
182
- * This will power the carousel
183
- */
184
- featuredCharts() {
185
- const filteredCharts = this.filterCharts({});
177
+ // status filtering is separated from other filters because "isInstalled" and "upgradeable" statuses are already calculated in models/chart.js
178
+ // by doing this we won't need to re-calculate it in filterAndArrangeCharts
179
+ if (!statuses.length) {
180
+ return res;
181
+ }
186
182
 
187
- const featuredCharts = filteredCharts.filter((value) => value.featured).sort((a, b) => a.featured - b.featured);
183
+ return res.filter((chart) => {
184
+ const chartStatuses = [
185
+ chart.deprecated && APP_STATUS.DEPRECATED,
186
+ chart.isInstalled && APP_STATUS.INSTALLED,
187
+ chart.upgradeable && APP_STATUS.UPGRADEABLE
188
+ ].filter(Boolean);
188
189
 
189
- return featuredCharts.slice(0, 5);
190
+ return chartStatuses.some((status) => statuses.includes(status));
191
+ });
190
192
  },
191
193
 
192
- categories() {
194
+ categoryOptions() {
193
195
  const map = {};
194
196
 
195
- // Filter charts by everything except itself
196
- const charts = this.filterCharts({
197
- searchQuery: this.searchQuery,
198
- hideRepos: this.hideRepos
199
- });
200
-
201
- for ( const chart of charts ) {
197
+ for ( const chart of this.allCharts ) {
202
198
  for ( const c of chart.categories ) {
203
199
  if ( !map[c] ) {
204
200
  const labelKey = `catalog.charts.categories.${ lcFirst(c) }`;
205
201
 
206
202
  map[c] = {
207
203
  label: this.$store.getters['i18n/withFallback'](labelKey, null, c),
208
- value: c,
209
- count: 0
204
+ value: c.toLowerCase()
210
205
  };
211
206
  }
212
-
213
- map[c].count++;
214
207
  }
215
208
  }
216
209
 
217
210
  const out = Object.values(map);
218
211
 
219
- out.unshift({
220
- label: this.t('catalog.charts.categories.all'),
221
- value: '',
222
- count: charts.length
223
- });
224
-
225
212
  return sortBy(out, ['label']);
226
213
  },
227
214
 
228
- showCarousel() {
229
- return this.chartMode === 'featured' && this.featuredCharts.length;
230
- }
231
-
232
- },
233
-
234
- watch: {
235
- searchQuery(q) {
236
- this.$router.applyQuery({ [SEARCH_QUERY]: q || undefined });
215
+ filterPanelFilters() {
216
+ return [
217
+ {
218
+ key: 'repos',
219
+ title: this.t('gitPicker.github.repo.label'),
220
+ options: this.repoOptions
221
+ },
222
+ {
223
+ key: 'categories',
224
+ title: this.t('generic.category'),
225
+ options: this.categoryOptions
226
+ },
227
+ {
228
+ key: 'statuses',
229
+ title: this.t('tableHeaders.status'),
230
+ options: this.statusOptions
231
+ },
232
+ {
233
+ key: 'tags',
234
+ title: this.t('generic.tag'),
235
+ options: this.tagOptions
236
+ }
237
+ ];
237
238
  },
238
239
 
239
- category(cat) {
240
- this.$router.applyQuery({ [CATEGORY]: cat || undefined });
241
- },
240
+ appChartCards() {
241
+ return this.filteredCharts.map((chart) => {
242
+ if (!this.appCardsCache[chart.id]) {
243
+ // Cache the converted value. We're caching chart.cardContent anyway, so no need to worry about showing updates to state
244
+ this.appCardsCache[chart.id] = {
245
+ id: chart.id,
246
+ pill: chart.featured ? { label: { key: 'generic.shortFeatured' }, tooltip: { key: 'generic.featured' } } : undefined,
247
+ header: {
248
+ title: { text: chart.chartNameDisplay },
249
+ statuses: chart.cardContent.statuses
250
+ },
251
+ subHeaderItems: chart.cardContent.subHeaderItems,
252
+ image: { src: chart.versions[0].icon, alt: { text: this.t('catalog.charts.iconAlt', { app: get(chart, 'chartNameDisplay') }) } },
253
+ content: { text: chart.chartDescription },
254
+ footerItems: chart.cardContent.footerItems,
255
+ rawChart: chart
256
+ };
257
+ }
242
258
 
243
- operatingSystem(os) {
244
- this.$router.applyQuery({ [OPERATING_SYSTEM]: os || undefined });
259
+ return this.appCardsCache[chart.id];
260
+ });
245
261
  },
246
262
 
247
- showDeprecated(neu) {
248
- this.$router.applyQuery({ [DEPRECATED_QUERY]: neu || undefined });
263
+ clusterId() {
264
+ return this.$store.getters['clusterId'];
249
265
  }
250
266
  },
251
267
 
252
- methods: {
253
- colorForChart(chart) {
254
- const repos = this.repoOptions;
255
- const repo = findBy(repos, '_key', chart.repoKey);
256
-
257
- if ( repo ) {
258
- return repo.color;
259
- }
260
-
261
- return null;
262
- },
263
-
264
- toggleAll(on) {
265
- for ( const r of this.repoOptions ) {
266
- this.toggleRepo(r, on, false);
267
- }
268
-
269
- this.$nextTick(() => {
270
- this.allRepos = this.areAllEnabled();
271
- });
268
+ watch: {
269
+ searchQuery: {
270
+ handler: debounce(function(q) {
271
+ this.debouncedSearchQuery = q;
272
+ this.$router.applyQuery({ [SEARCH_QUERY]: q || undefined });
273
+ }, 300),
274
+ immediate: false
272
275
  },
273
276
 
274
- areAllEnabled() {
275
- const all = this.$store.getters['catalog/repos'];
277
+ filters: {
278
+ deep: true,
279
+ handler(newFilters) {
280
+ const query = {
281
+ [REPO]: normalizeFilterQuery(newFilters.repos),
282
+ [CATEGORY]: normalizeFilterQuery(newFilters.categories),
283
+ [STATUS]: normalizeFilterQuery(newFilters.statuses),
284
+ [TAG]: normalizeFilterQuery(newFilters.tags)
285
+ };
276
286
 
277
- for ( const r of all ) {
278
- if ( this.hideRepos.includes(r._key) ) {
279
- return false;
280
- }
287
+ this.$router.applyQuery(query);
288
+ this.internalFilters = JSON.parse(JSON.stringify(newFilters));
281
289
  }
290
+ }
291
+ },
282
292
 
283
- return true;
284
- },
285
-
286
- toggleRepo(repo, on, updateAll = true) {
287
- const hidden = this.hideRepos;
288
-
289
- if ( on ) {
290
- removeObject(hidden, repo._key);
291
- } else {
292
- addObject(hidden, repo._key);
293
- }
293
+ methods: {
294
+ get,
294
295
 
295
- this.hideRepos = hidden;
296
+ onFilterChange(newFilters) {
297
+ this.internalFilters = newFilters;
296
298
 
297
- if ( updateAll ) {
298
- this.allRepos = this.areAllEnabled();
299
- }
299
+ this.applyFiltersDebounced(newFilters);
300
300
  },
301
301
 
302
+ applyFiltersDebounced: debounce(function(newFilters) {
303
+ this.filters = newFilters;
304
+ }, 100),
305
+
302
306
  selectChart(chart) {
303
307
  let version;
304
308
  const OSs = this.currentCluster.workerOSs;
@@ -319,8 +323,8 @@ export default {
319
323
  [VERSION]: version
320
324
  };
321
325
 
322
- if (chart.deprecated && this.showDeprecated) {
323
- query[DEPRECATED_QUERY] = 'true';
326
+ if (chart.deprecated) {
327
+ query[DEPRECATED] = 'true';
324
328
  }
325
329
 
326
330
  this.$router.push({
@@ -333,6 +337,20 @@ export default {
333
337
  });
334
338
  },
335
339
 
340
+ handleFooterItemClick(type, value) {
341
+ if (type === REPO) {
342
+ const repoKey = this.repoOptions.find((option) => option.label === value)?.value;
343
+
344
+ if (!this.filters.repos.includes(repoKey)) {
345
+ this.filters.repos.push(repoKey);
346
+ }
347
+ } else if (type === CATEGORY && !this.filters.categories.includes(value.toLowerCase())) {
348
+ this.filters.categories.push(value.toLowerCase());
349
+ } else if (type === TAG && !this.filters.tags.includes(value.toLowerCase())) {
350
+ this.filters.tags.push(value.toLowerCase());
351
+ }
352
+ },
353
+
336
354
  focusSearch() {
337
355
  if ( this.$refs.searchQuery ) {
338
356
  this.$refs.searchQuery.focus();
@@ -350,21 +368,24 @@ export default {
350
368
  }
351
369
  },
352
370
 
353
- filterCharts({ category, searchQuery, hideRepos }) {
371
+ filterCharts({
372
+ repo, category, tag, searchQuery
373
+ }) {
354
374
  const enabledCharts = (this.enabledCharts || []);
355
375
  const clusterProvider = this.currentCluster.status.provider || 'other';
356
376
 
357
377
  return filterAndArrangeCharts(enabledCharts, {
358
378
  clusterProvider,
379
+ showRepos: repo,
359
380
  category,
381
+ tag,
360
382
  searchQuery,
361
- showDeprecated: this.showDeprecated,
383
+ showDeprecated: true,
362
384
  showHidden: this.showHidden,
363
- hideRepos,
364
385
  hideTypes: [CATALOG._CLUSTER_TPL],
365
386
  showPrerelease: this.$store.getters['prefs/get'](SHOW_PRE_RELEASE),
366
387
  });
367
- }
388
+ },
368
389
  },
369
390
  };
370
391
  </script>
@@ -372,118 +393,37 @@ export default {
372
393
  <template>
373
394
  <Loading v-if="$fetchState.pending" />
374
395
  <div v-else>
375
- <header>
376
- <div class="title">
377
- <h1
378
- data-testid="charts-header-title"
379
- class="m-0"
380
- >
381
- <TabTitle>{{ t('catalog.charts.header') }}</TabTitle>
382
- </h1>
383
- </div>
384
- <div
385
- v-if="featuredCharts.length > 0"
386
- class="actions-container"
396
+ <div class="header">
397
+ <h1
398
+ data-testid="charts-header-title"
399
+ class="m-0"
387
400
  >
388
- <ButtonGroup
389
- v-model:value="chartMode"
390
- :options="chartOptions"
391
- />
392
- </div>
393
- </header>
394
- <div v-if="showCarousel">
395
- <h3>{{ t('catalog.charts.featuredCharts') }}</h3>
396
- <Carousel
397
- :sliders="featuredCharts"
398
- data-testid="charts-carousel"
399
- @clicked="(row) => selectChart(row)"
401
+ {{ t('catalog.chart.header.charts') }}
402
+ </h1>
403
+ <AsyncButton
404
+ role="button"
405
+ :aria-label="t('catalog.charts.refresh')"
406
+ :label="t('catalog.charts.refresh')"
407
+ class="refresh-btn"
408
+ mode="refresh"
409
+ @click="refresh"
400
410
  />
401
411
  </div>
402
-
403
- <TypeDescription resource="chart" />
404
- <div class="left-right-split">
405
- <Select
406
- :searchable="false"
407
- :options="repoOptionsForDropdown"
408
- :value="flattenedRepoNames"
409
- class="checkbox-select"
410
- :close-on-select="false"
411
- data-testid="charts-filter-repos"
412
- @option:selecting="$event.all ? toggleAll(!$event.enabled) : toggleRepo($event, !$event.enabled) "
413
- >
414
- <template #selected-option="selected">
415
- {{ selected.label }}
416
- </template>
417
- <template #option="repo">
418
- <Checkbox
419
- :value="repo.enabled"
420
- :label="repo.label"
421
- class="pull-left repo in-select"
422
- :class="{ [repo.color]: true}"
423
- :color="repo.color"
424
- >
425
- <template #label>
426
- <span>{{ repo.label }}</span><i
427
- v-if="!repo.all"
428
- class=" pl-5 icon icon-dot icon-sm"
429
- :class="{[repo.color]: true}"
430
- />
431
- </template>
432
- </Checkbox>
433
- </template>
434
- </Select>
435
-
436
- <Select
437
- v-model:value="category"
438
- :clearable="false"
439
- :searchable="false"
440
- :options="categories"
441
- placement="bottom"
442
- label="label"
443
- style="min-width: 200px;"
444
- :reduce="opt => opt.value"
445
- data-testid="charts-filter-category"
446
- >
447
- <template #option="opt">
448
- {{ opt.label }} ({{ opt.count }})
449
- </template>
450
- </Select>
451
-
452
- <div class="filter-block">
453
- <input
454
- ref="searchQuery"
455
- v-model="searchQuery"
456
- type="search"
457
- class="input-sm"
458
- :placeholder="t('catalog.charts.search')"
459
- data-testid="charts-filter-input"
460
- :aria-label="t('catalog.charts.search')"
461
- role="textbox"
462
- >
463
-
464
- <button
465
- v-shortkey.once="['/']"
466
- class="hide"
467
- @shortkey="focusSearch()"
468
- />
469
- <AsyncButton
470
- role="button"
471
- :aria-label="t('catalog.charts.refresh')"
472
- class="refresh-btn"
473
- mode="refresh"
474
- size="sm"
475
- @click="refresh"
476
- />
477
- </div>
478
-
479
- <div class="mt-10">
480
- <Checkbox
481
- v-model:value="showDeprecated"
482
- :label="t('catalog.charts.deprecatedChartsFilter.label')"
483
- data-testid="charts-show-deprecated-filter"
484
- />
485
- </div>
486
- </div>
412
+ <input
413
+ ref="searchQuery"
414
+ v-model="searchQuery"
415
+ type="search"
416
+ class="input search-input"
417
+ :placeholder="t('catalog.charts.search')"
418
+ data-testid="charts-filter-input"
419
+ :aria-label="t('catalog.charts.search')"
420
+ role="textbox"
421
+ >
422
+ <button
423
+ v-shortkey.once="['/']"
424
+ class="hide"
425
+ @shortkey="focusSearch()"
426
+ />
487
427
 
488
428
  <Banner
489
429
  v-for="(err, i) in loadingErrors"
@@ -492,77 +432,69 @@ export default {
492
432
  :label="err"
493
433
  />
494
434
 
495
- <div v-if="allCharts.length">
435
+ <div class="wrapper">
436
+ <FilterPanel
437
+ :modelValue="internalFilters"
438
+ :filters="filterPanelFilters"
439
+ @update:modelValue="onFilterChange"
440
+ />
441
+
496
442
  <div
497
443
  v-if="filteredCharts.length === 0"
498
- style="width: 100%;"
444
+ class="app-chart-cards-empty-state"
499
445
  >
500
- <div class="m-50 text-center">
501
- <h1>{{ t('catalog.charts.noCharts') }}</h1>
502
- </div>
446
+ <h1>{{ t('catalog.charts.noCharts') }}</h1>
503
447
  </div>
504
- <SelectIconGrid
448
+ <div
505
449
  v-else
506
- data-testid="chart-selection-grid"
507
- component-test-id="chart-selection"
508
- :rows="filteredCharts"
509
- name-field="chartNameDisplay"
510
- description-field="chartDescription"
511
- :color-for="colorForChart"
512
- @clicked="(row) => selectChart(row)"
513
- />
514
- </div>
515
- <div
516
- v-else
517
- class="m-50 text-center"
518
- >
519
- <h1>{{ t('catalog.charts.noCharts') }}</h1>
450
+ class="app-chart-cards"
451
+ data-testid="app-chart-cards-container"
452
+ >
453
+ <rc-item-card
454
+ v-for="card in appChartCards"
455
+ :id="card.id"
456
+ :key="card.id"
457
+ :pill="card.pill"
458
+ :header="card.header"
459
+ :image="card.image"
460
+ :content="card.content"
461
+ :value="card.rawChart"
462
+ variant="medium"
463
+ :clickable="true"
464
+ @card-click="selectChart"
465
+ >
466
+ <template
467
+ v-once
468
+ #item-card-sub-header
469
+ >
470
+ <AppChartCardSubHeader :items="card.subHeaderItems" />
471
+ </template>
472
+ <template
473
+ v-once
474
+ #item-card-footer
475
+ >
476
+ <AppChartCardFooter
477
+ :items="card.footerItems"
478
+ @click:item="handleFooterItemClick"
479
+ />
480
+ </template>
481
+ </rc-item-card>
482
+ </div>
520
483
  </div>
521
484
  </div>
522
485
  </template>
523
486
 
524
487
  <style lang="scss" scoped>
525
- .left-right-split {
526
- padding: 0 0 20px 0;
527
- width: 100%;
528
- z-index: z-index('fixedTableHeader');
529
- background: transparent;
530
- display: grid;
531
- grid-template-columns: 40% auto auto;
532
- align-content: center;
533
- grid-column-gap: 10px;
534
-
535
- .filter-block {
536
- display: flex;
537
- }
538
- .refresh-btn {
539
- margin-left: 10px;
540
- }
541
-
542
- &.with-os-options {
543
- grid-template-columns: 40% auto auto auto;
544
- }
545
488
 
546
- @media only screen and (max-width: map-get($breakpoints, '--viewport-12')) {
547
- &{
548
- grid-template-columns: auto auto !important;
549
- grid-template-rows: 40px 40px;
550
- grid-row-gap: 20px;
551
- }
552
- }
553
-
554
- @media only screen and (max-width: map-get($breakpoints, '--viewport-7')) {
555
- &{
556
- &{
557
- grid-template-columns: auto !important;
558
- grid-template-rows: 40px 40px 40px !important;
489
+ .header {
490
+ display: flex;
491
+ justify-content: space-between;
492
+ align-items: center;
493
+ margin-bottom: 24px;
494
+ }
559
495
 
560
- &.with-os-options {
561
- grid-template-rows: 40px 40px 40px 40px !important;
562
- }
563
- }
564
- }
565
- }
496
+ .search-input {
497
+ margin-bottom: 24px;
566
498
  }
567
499
 
568
500
  .checkbox-select {
@@ -580,139 +512,25 @@ export default {
580
512
  }
581
513
  }
582
514
 
583
- .checkbox-outer-container.in-select {
584
- transform: translateX(-5px);
585
- padding: 7px 0 6px 13px;
586
- width: calc(100% + 10px);
587
-
588
- :deep() .checkbox-label {
589
- display: flex;
590
- align-items: center;
591
-
592
- & i {
593
- line-height: inherit;
594
- }
595
- }
596
-
597
- &:first-child {
598
- &:hover {
599
- background: var(--input-hover-bg);
600
- }
601
- }
602
-
603
- &:hover :deep() .checkbox-label {
604
- color: var(--body-text);
605
- }
606
-
607
- &.rancher {
608
- &:hover {
609
- background: var(--app-rancher-accent);
610
- }
611
- &:hover :deep() .checkbox-label {
612
- color: var(--app-rancher-accent-text);
613
- }
614
- & i {
615
- color: var(--app-rancher-accent)
616
- }
617
- }
515
+ .wrapper {
516
+ display: flex;
517
+ gap: var(--gap-lg);
518
+ }
618
519
 
619
- &.partner {
620
- &:hover {
621
- background: var(--app-partner-accent);
622
- }
623
- &:hover :deep() .checkbox-label {
624
- color: var(--app-partner-accent-text);
625
- }
626
- & i {
627
- color: var(--app-partner-accent)
628
- }
629
- }
520
+ .app-chart-cards-empty-state {
521
+ width: 100%;
522
+ margin-top: 32px;
523
+ padding: 32px;
524
+ text-align: center;
525
+ }
630
526
 
631
- &.color1 {
632
- &:hover {
633
- background: var(--app-color1-accent);
634
- }
635
- &:hover :deep() .checkbox-label {
636
- color: var(--app-color1-accent-text);
637
- }
638
- & i {
639
- color: var(--app-color1-accent)
640
- }
641
- }
642
- &.color2 {
643
- &:hover {
644
- background: var(--app-color2-accent);
645
- }
646
- &:hover :deep() .checkbox-label {
647
- color: var(--app-color2-accent-text);
648
- }
649
- & i {
650
- color: var(--app-color2-accent)
651
- }
652
- }
653
- &.color3 {
654
- &:hover {
655
- background: var(--app-color3-accent);
656
- }
657
- &:hover :deep() .checkbox-label {
658
- color: var(--app-color3-accent-text);
659
- }
660
- & i {
661
- color: var(--app-color3-accent)
662
- }
663
- }
664
- &.color4 {
665
- &:hover {
666
- background: var(--app-color4-accent);
667
- }
668
- &:hover :deep().checkbox-label {
669
- color: var(--app-color4-accent-text);
670
- }
671
- & i {
672
- color: var(--app-color4-accent)
673
- }
674
- }
675
- &.color5 {
676
- &:hover {
677
- background: var(--app-color5-accent);
678
- }
679
- &:hover :deep() .checkbox-label {
680
- color: var(--app-color5-accent-text);
681
- }
682
- & i {
683
- color: var(--app-color5-accent)
684
- }
685
- }
686
- &.color6 {
687
- &:hover {
688
- background: var(--app-color6-accent);
689
- }
690
- &:hover :deep() .checkbox-label {
691
- color: var(--app-color6-accent-text);
692
- }
693
- & i {
694
- color: var(--app-color6-accent)
695
- }
696
- }
697
- &.color7 {
698
- &:hover {
699
- background: var(--app-color7-accent);
700
- }
701
- &:hover :deep() .checkbox-label {
702
- color: var(--app-color7-accent-text);
703
- }
704
- & i {
705
- color: var(--app-color7-accent)
706
- }
707
- }
708
- &.color8 {
709
- &:hover {
710
- background: var(--app-color8-accent);
711
- }
712
- & i {
713
- color: var(--app-color8-accent)
714
- }
715
- }
527
+ .app-chart-cards {
528
+ display: grid;
529
+ grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
530
+ grid-gap: var(--gap-md);
531
+ width: 100%;
532
+ height: max-content;
533
+ overflow: hidden;
716
534
  }
717
535
 
718
536
  </style>