@rancher/shell 3.0.8-rc.5 → 3.0.8-rc.6

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.
@@ -5,6 +5,9 @@
5
5
  "textAlign": "left"
6
6
  },
7
7
  "login": {
8
- "bannerClass": "suse-login-banner-graphic"
9
- }
8
+ "bannerClass": "suse-login-banner-graphic",
9
+ "logo": "rancher-logo.svg",
10
+ "logoClass": "suse-logo-login",
11
+ "welcomeLabelKey": "login.login"
12
+ }
10
13
  }
@@ -97,6 +97,16 @@
97
97
  .suse-login-banner-graphic {
98
98
  object-position: left;
99
99
  }
100
+
101
+ // Styling for logo image on the login screen
102
+ .suse-logo-login {
103
+ $suse-logo-width: 280px;
104
+
105
+ width: $suse-logo-width;
106
+ max-width: $suse-logo-width;
107
+ align-self: center;
108
+ margin-bottom: 30px;
109
+ }
100
110
  }
101
111
 
102
112
  // Ensure that with a small window size, the banner centers, so that the text does not overlay
@@ -310,7 +310,7 @@ export default {
310
310
  }), {});
311
311
  },
312
312
  isFullPageOverride() {
313
- return this.isView && this.value.fullDetailPageOverride;
313
+ return this.isView && this.value.fullDetailPageOverride && !this.isYaml;
314
314
  }
315
315
  },
316
316
 
@@ -89,15 +89,15 @@ export default {
89
89
 
90
90
  if (store) {
91
91
  delete context.store;
92
- el._uiContextId = await store.dispatch('ui-context/add', context);
92
+ const id = await store.dispatch('ui-context/add', context);
93
+
94
+ el._uiContextRemove = async() => await store.dispatch('ui-context/remove', id);
93
95
  }
94
96
  },
95
97
 
96
- async beforeUnmount(el: any, binding: { value: Context, instance: any }) {
97
- const store = binding.value?.store || binding.instance.$store || binding.instance.store;
98
-
99
- if (store && el._uiContextId) {
100
- await store.dispatch('ui-context/remove', el._uiContextId);
98
+ async beforeUnmount(el: any) {
99
+ if (el._uiContextRemove) {
100
+ await el._uiContextRemove();
101
101
  }
102
102
  }
103
103
  };
@@ -2,11 +2,16 @@ import Chart from '@shell/models/chart';
2
2
  import { APP_UPGRADE_STATUS } from '@shell/store/catalog';
3
3
  import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
4
4
  import { ZERO_TIME } from '@shell/config/types';
5
+ import { getLatestCompatibleVersion } from '@shell/utils/chart';
6
+
7
+ jest.mock('@shell/utils/chart', () => ({ getLatestCompatibleVersion: jest.fn() }));
5
8
 
6
9
  type MockChartContext = {
7
10
  rootGetters: {
8
11
  'cluster/all': () => any[];
9
12
  'i18n/t': (key: string) => string;
13
+ currentCluster: { workerOSs: string[] };
14
+ 'prefs/get': (key: string) => boolean;
10
15
  };
11
16
  dispatch?: jest.Mock;
12
17
  };
@@ -58,11 +63,32 @@ describe('class Chart', () => {
58
63
  beforeEach(() => {
59
64
  ctx = {
60
65
  rootGetters: {
61
- 'cluster/all': () => [],
62
- 'i18n/t': t
66
+ 'cluster/all': () => [],
67
+ 'i18n/t': t,
68
+ currentCluster: { workerOSs: [] },
69
+ 'prefs/get': () => false,
63
70
  },
64
71
  dispatch
65
72
  };
73
+ (getLatestCompatibleVersion as jest.Mock).mockImplementation((chart) => chart.versions[0]);
74
+ });
75
+
76
+ describe('queryParams', () => {
77
+ it('should return query params with the latest compatible version', () => {
78
+ const chart = new Chart(base, ctx);
79
+ const query = chart.queryParams();
80
+
81
+ expect(getLatestCompatibleVersion).toHaveBeenCalledWith(chart, [], false);
82
+ expect(query).toHaveProperty('version', '1.3.0');
83
+ });
84
+
85
+ it('should reflect a different latest version from the mock', () => {
86
+ (getLatestCompatibleVersion as jest.Mock).mockImplementation((chart) => chart.versions[1]);
87
+ const chart = new Chart(base, ctx);
88
+ const query = chart.queryParams();
89
+
90
+ expect(query).toHaveProperty('version', '1.2.3');
91
+ });
66
92
  });
67
93
 
68
94
  describe('matchingInstalledApps', () => {
@@ -188,6 +214,7 @@ describe('class Chart', () => {
188
214
 
189
215
  const result = chart.cardContent as CardContent;
190
216
 
217
+ expect(getLatestCompatibleVersion).toHaveBeenCalledWith(chart, [], false);
191
218
  expect(result.subHeaderItems).toHaveLength(2);
192
219
  expect(result.subHeaderItems[0].label).toBe('1.3.0');
193
220
  expect(result.subHeaderItems[1].label).toBe('Mar 10, 2024');
@@ -297,8 +324,10 @@ describe('class Chart', () => {
297
324
  };
298
325
  const chart = new Chart(chartWithZeroTime, {
299
326
  rootGetters: {
300
- 'cluster/all': () => [],
301
- 'i18n/t': (key: string) => key
327
+ 'cluster/all': () => [],
328
+ 'i18n/t': (key: string) => key,
329
+ currentCluster: { workerOSs: [] },
330
+ 'prefs/get': () => false,
302
331
  },
303
332
  });
304
333
 
package/models/chart.js CHANGED
@@ -1,8 +1,10 @@
1
- import { compatibleVersionsFor, APP_UPGRADE_STATUS } from '@shell/store/catalog';
1
+ import { APP_UPGRADE_STATUS } from '@shell/store/catalog';
2
2
  import {
3
3
  REPO_TYPE, REPO, CHART, VERSION, _FLAGGED, HIDE_SIDE_NAV, CATEGORY, TAG, DEPRECATED as DEPRECATED_QUERY
4
4
  } from '@shell/config/query-params';
5
5
  import { BLANK_CLUSTER } from '@shell/store/store-types.js';
6
+ import { SHOW_PRE_RELEASE } from '@shell/store/prefs';
7
+ import { getLatestCompatibleVersion } from '@shell/utils/chart';
6
8
  import SteveModel from '@shell/plugins/steve/steve-class';
7
9
  import { CATALOG, ZERO_TIME } from '@shell/config/types';
8
10
  import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
@@ -10,17 +12,7 @@ import day from 'dayjs';
10
12
 
11
13
  export default class Chart extends SteveModel {
12
14
  queryParams(from, hideSideNav) {
13
- let version;
14
- const chartVersions = this.versions;
15
- const currentCluster = this.$rootGetters['currentCluster'];
16
- const workerOSs = currentCluster?.workerOSs;
17
- const compatibleVersions = compatibleVersionsFor(this, workerOSs);
18
-
19
- if (compatibleVersions.length) {
20
- version = compatibleVersions[0].version;
21
- } else {
22
- version = chartVersions[0].version;
23
- }
15
+ const version = this.latestCompatibleVersion.version;
24
16
 
25
17
  const out = {
26
18
  [REPO_TYPE]: this.repoType,
@@ -115,6 +107,26 @@ export default class Chart extends SteveModel {
115
107
  return this.isInstalled && this.matchingInstalledApps[0].upgradeAvailable === APP_UPGRADE_STATUS.SINGLE_UPGRADE;
116
108
  }
117
109
 
110
+ /**
111
+ * Retrieves the latest chart version that is compatible with the current cluster's OS and user's pre-release preference.
112
+ * It caches the result for efficiency.
113
+ *
114
+ * @returns {Object} The latest compatible chart version object.
115
+ */
116
+ get latestCompatibleVersion() {
117
+ if (this._latestCompatibleVersion) {
118
+ return this._latestCompatibleVersion;
119
+ }
120
+
121
+ const currentCluster = this.$rootGetters['currentCluster'];
122
+ const workerOSs = currentCluster?.workerOSs;
123
+ const showPrerelease = this.$rootGetters['prefs/get'](SHOW_PRE_RELEASE);
124
+
125
+ this._latestCompatibleVersion = getLatestCompatibleVersion(this, workerOSs, showPrerelease);
126
+
127
+ return this._latestCompatibleVersion;
128
+ }
129
+
118
130
  /**
119
131
  * Builds structured metadata for display in RcItemCard.vue:
120
132
  * - Sub-header (version and last updated)
@@ -125,7 +137,7 @@ export default class Chart extends SteveModel {
125
137
  */
126
138
  get cardContent() {
127
139
  if (!this._cardContent) {
128
- const latestVersion = this.versions?.[0] || {};
140
+ const latestVersion = this.latestCompatibleVersion;
129
141
  const subHeaderItems = [];
130
142
 
131
143
  if (latestVersion) {
@@ -9,7 +9,6 @@ import { get, set } from '@shell/utils/object';
9
9
  import { sortBy } from '@shell/utils/sort';
10
10
  import { ucFirst } from '@shell/utils/string';
11
11
  import { compare } from '@shell/utils/version';
12
- import { AS, MODE, _VIEW, _YAML } from '@shell/config/query-params';
13
12
  import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
14
13
  import { CAPI as CAPI_ANNOTATIONS, NODE_ARCHITECTURE } from '@shell/config/labels-annotations';
15
14
  import { KEV1 } from '@shell/models/management.cattle.io.kontainerdriver';
@@ -260,26 +259,6 @@ export default class ProvCluster extends SteveModel {
260
259
  }
261
260
  }
262
261
 
263
- goToViewYaml() {
264
- let location;
265
-
266
- if ( !this.isRke2 ) {
267
- location = this.mgmt?.detailLocation;
268
- }
269
-
270
- if ( !location ) {
271
- location = this.detailLocation;
272
- }
273
-
274
- location.query = {
275
- ...location.query,
276
- [MODE]: _VIEW,
277
- [AS]: _YAML
278
- };
279
-
280
- this.currentRouter().push(location);
281
- }
282
-
283
262
  get canDelete() {
284
263
  return super.canDelete && this.stateObj?.name !== 'removing';
285
264
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "3.0.8-rc.5",
3
+ "version": "3.0.8-rc.6",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -142,11 +142,19 @@ export default {
142
142
  const brandMeta = getBrandMeta(setting?.value);
143
143
  const login = brandMeta?.login || {};
144
144
 
145
- return login;
145
+ return {
146
+ welcomeLabelKey: 'login.welcome',
147
+ logoClass: 'login-logo',
148
+ ...login,
149
+ };
146
150
  },
147
151
 
148
152
  bannerClass() {
149
153
  return this.customizations.bannerClass;
154
+ },
155
+
156
+ brandLogo() {
157
+ return this.customizations.logo;
150
158
  }
151
159
  },
152
160
 
@@ -349,11 +357,20 @@ export default {
349
357
  </TabTitle>
350
358
  <div class="row gutless mb-20">
351
359
  <div class="col span-6 p-20">
352
- <p class="text-center">
360
+ <p
361
+ v-if="!brandLogo"
362
+ class="text-center"
363
+ >
353
364
  {{ t('login.howdy') }}
354
365
  </p>
366
+ <BrandImage
367
+ v-else
368
+ :class="{[customizations.logoClass]: !!customizations.logoClass}"
369
+ :file-name="brandLogo"
370
+ :alt="t('login.landscapeAlt')"
371
+ />
355
372
  <h1 class="text-center login-welcome">
356
- {{ t('login.welcome', {vendor}) }}
373
+ {{ t(customizations.welcomeLabelKey, {vendor}) }}
357
374
  </h1>
358
375
  <div
359
376
  class="login-messages"
@@ -567,6 +584,12 @@ export default {
567
584
  margin: 0
568
585
  }
569
586
 
587
+ .login-logo {
588
+ align-self: center;
589
+ max-width: 260px;
590
+ margin-bottom: 20px;
591
+ }
592
+
570
593
  .login-messages {
571
594
  display: flex;
572
595
  justify-content: center;
@@ -7,7 +7,7 @@ import {
7
7
  REPO_TYPE, REPO, CHART, VERSION, SEARCH_QUERY, SORT_BY, _FLAGGED, CATEGORY, DEPRECATED, HIDDEN, TAG, STATUS
8
8
  } from '@shell/config/query-params';
9
9
  import { DOCS_BASE } from '@shell/config/private-label';
10
- import { APP_STATUS, compatibleVersionsFor, filterAndArrangeCharts, normalizeFilterQuery } from '@shell/store/catalog';
10
+ import { APP_STATUS, filterAndArrangeCharts, normalizeFilterQuery } from '@shell/store/catalog';
11
11
  import { lcFirst } from '@shell/utils/string';
12
12
  import { sortBy } from '@shell/utils/sort';
13
13
  import debounce from 'lodash/debounce';
@@ -24,6 +24,7 @@ import AppChartCardFooter from '@shell/pages/c/_cluster/apps/charts/AppChartCard
24
24
  import AddRepoLink from '@shell/pages/c/_cluster/apps/charts/AddRepoLink';
25
25
  import StatusLabel from '@shell/pages/c/_cluster/apps/charts/StatusLabel';
26
26
  import RichTranslation from '@shell/components/RichTranslation.vue';
27
+ import { getLatestCompatibleVersion } from '@shell/utils/chart';
27
28
  import Select from '@shell/components/form/Select';
28
29
 
29
30
  const createInitialFilters = () => ({
@@ -190,6 +191,13 @@ export default {
190
191
  sort: this.selectedSortOption
191
192
  });
192
193
 
194
+ const OSs = this.currentCluster.workerOSs;
195
+ const showPrerelease = this.$store.getters['prefs/get'](SHOW_PRE_RELEASE);
196
+
197
+ res.forEach((chart) => {
198
+ chart._latestCompatibleVersion = getLatestCompatibleVersion(chart, OSs, showPrerelease);
199
+ });
200
+
193
201
  // status filtering is separated from other filters because "isInstalled" and "upgradeable" statuses are already calculated in models/chart.js
194
202
  // by doing this we won't need to re-calculate it in filterAndArrangeCharts
195
203
  if (!statuses.length) {
@@ -265,7 +273,7 @@ export default {
265
273
  statuses: chart.cardContent.statuses
266
274
  },
267
275
  subHeaderItems: chart.cardContent.subHeaderItems,
268
- image: { src: chart.versions[0].icon, alt: { text: this.t('catalog.charts.iconAlt', { app: get(chart, 'chartNameDisplay') }) } },
276
+ image: { src: chart.latestCompatibleVersion.icon, alt: { text: this.t('catalog.charts.iconAlt', { app: get(chart, 'chartNameDisplay') }) } },
269
277
  content: { text: chart.chartDescription },
270
278
  footerItems: chart.cardContent.footerItems,
271
279
  rawChart: chart
@@ -343,17 +351,7 @@ export default {
343
351
  }, 100),
344
352
 
345
353
  selectChart(chart) {
346
- let version;
347
- const OSs = this.currentCluster.workerOSs;
348
- const showPrerelease = this.$store.getters['prefs/get'](SHOW_PRE_RELEASE);
349
- const compatibleVersions = compatibleVersionsFor(chart, OSs, showPrerelease);
350
- const versions = chart.versions;
351
-
352
- if (compatibleVersions.length > 0) {
353
- version = compatibleVersions[0].version;
354
- } else {
355
- version = versions[0].version;
356
- }
354
+ const version = chart.latestCompatibleVersion.version;
357
355
 
358
356
  const query = {
359
357
  [REPO_TYPE]: chart.repoType,
@@ -3950,6 +3950,19 @@ export function getIndividualBanners(store: any): {};
3950
3950
  export function overlayIndividualBanners(parsedBanner: any, banners: any): void;
3951
3951
  }
3952
3952
 
3953
+ // @shell/utils/chart
3954
+
3955
+ declare module '@shell/utils/chart' {
3956
+ /**
3957
+ * Get the latest chart version that is compatible with the cluster's OS and user's pre-release preference.
3958
+ * @param {Object} chart - The chart object.
3959
+ * @param {Array<string>} workerOSs - The list of worker OSs for the cluster.
3960
+ * @param {boolean} showPrerelease - Whether to include pre-release versions.
3961
+ * @returns {Object} The latest compatible chart version object.
3962
+ */
3963
+ export function getLatestCompatibleVersion(chart: any, workerOSs: Array<string>, showPrerelease: boolean): any;
3964
+ }
3965
+
3953
3966
  // @shell/utils/clipboard
3954
3967
 
3955
3968
  declare module '@shell/utils/clipboard' {
package/utils/chart.js ADDED
@@ -0,0 +1,18 @@
1
+ import { compatibleVersionsFor } from '@shell/store/catalog';
2
+
3
+ /**
4
+ * Get the latest chart version that is compatible with the cluster's OS and user's pre-release preference.
5
+ * @param {Object} chart - The chart object.
6
+ * @param {Array<string>} workerOSs - The list of worker OSs for the cluster.
7
+ * @param {boolean} showPrerelease - Whether to include pre-release versions.
8
+ * @returns {Object} The latest compatible chart version object.
9
+ */
10
+ export function getLatestCompatibleVersion(chart, workerOSs, showPrerelease) {
11
+ if (!chart?.versions?.length) {
12
+ return {};
13
+ }
14
+
15
+ const compatible = compatibleVersionsFor(chart, workerOSs, showPrerelease);
16
+
17
+ return (compatible.length ? compatible[0] : chart.versions[0]) || {};
18
+ }