@rancher/shell 3.0.8-rc.9 → 3.0.8
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.
- package/apis/impl/apis.ts +61 -0
- package/apis/index.ts +40 -0
- package/apis/intf/modal.ts +90 -0
- package/apis/intf/shell.ts +36 -0
- package/apis/intf/slide-in.ts +98 -0
- package/apis/intf/system.ts +41 -0
- package/apis/shell/__tests__/modal.test.ts +80 -0
- package/apis/shell/__tests__/notifications.test.ts +71 -0
- package/apis/shell/__tests__/slide-in.test.ts +54 -0
- package/apis/shell/__tests__/system.test.ts +129 -0
- package/apis/shell/index.ts +38 -0
- package/apis/shell/modal.ts +41 -0
- package/apis/shell/notifications.ts +65 -0
- package/apis/shell/slide-in.ts +33 -0
- package/apis/shell/system.ts +65 -0
- package/apis/vue-shim.d.ts +11 -0
- package/assets/styles/global/_tooltip.scss +6 -1
- package/assets/translations/en-us.yaml +5 -0
- package/components/ActionMenuShell.vue +3 -1
- package/components/CruResource.vue +8 -1
- package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
- package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
- package/components/Drawer/ResourceDetailDrawer/index.vue +3 -1
- package/components/LocaleSelector.vue +2 -2
- package/components/ModalManager.vue +11 -1
- package/components/Questions/__tests__/Yaml.test.ts +1 -1
- package/components/RelatedResources.vue +5 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +5 -1
- package/components/ResourceDetail/Masthead/latest.vue +23 -21
- package/components/ResourceDetail/index.vue +3 -0
- package/components/ResourceTable.vue +54 -21
- package/components/SlideInPanelManager.vue +16 -11
- package/components/SortableTable/THead.vue +2 -1
- package/components/SortableTable/index.vue +20 -2
- package/components/Tabbed/index.vue +37 -2
- package/components/__tests__/NamespaceFilter.test.ts +49 -0
- package/components/auth/SelectPrincipal.vue +4 -0
- package/components/auth/login/ldap.vue +3 -3
- package/components/fleet/FleetSecretSelector.vue +1 -1
- package/components/form/KeyValue.vue +1 -1
- package/components/form/NameNsDescription.vue +1 -1
- package/components/form/NodeScheduling.vue +2 -2
- package/components/form/ResourceTabs/composable.ts +2 -2
- package/components/form/ResourceTabs/index.vue +0 -2
- package/components/form/__tests__/NameNsDescription.test.ts +42 -0
- package/components/formatter/LinkName.vue +5 -0
- package/components/nav/Group.vue +25 -7
- package/components/nav/Header.vue +1 -1
- package/components/nav/NamespaceFilter.vue +1 -0
- package/components/nav/Type.vue +17 -6
- package/components/nav/WindowManager/panels/TabBodyContainer.vue +1 -1
- package/components/nav/__tests__/Type.test.ts +59 -0
- package/composables/cruResource.ts +27 -0
- package/composables/focusTrap.ts +3 -1
- package/composables/resourceDetail.ts +15 -0
- package/composables/useLabeledFormElement.ts +3 -4
- package/config/product/fleet.js +1 -1
- package/config/router/navigation-guards/clusters.js +3 -3
- package/config/router/navigation-guards/products.js +1 -1
- package/config/router/routes.js +1 -5
- package/core/__tests__/extension-manager-impl.test.js +437 -0
- package/core/extension-manager-impl.js +6 -27
- package/core/plugin-helpers.ts +2 -2
- package/core/plugin.ts +9 -1
- package/core/plugins-loader.js +2 -2
- package/core/types-provisioning.ts +4 -0
- package/core/types.ts +35 -0
- package/detail/provisioning.cattle.io.cluster.vue +8 -6
- package/dialog/DeveloperLoadExtensionDialog.vue +1 -1
- package/dialog/MoveNamespaceDialog.vue +20 -4
- package/dialog/SearchDialog.vue +1 -0
- package/dialog/__tests__/MoveNamespaceDialog.test.ts +249 -0
- package/directives/__tests__/clean-tooltip.test.ts +298 -0
- package/directives/clean-tooltip.ts +234 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -2
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +98 -1
- package/edit/fleet.cattle.io.helmop.vue +5 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
- package/edit/provisioning.cattle.io.cluster/index.vue +5 -5
- package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -8
- package/edit/resources.cattle.io.restore.vue +1 -1
- package/edit/workload/Job.vue +2 -2
- package/edit/workload/index.vue +1 -1
- package/initialize/install-plugins.js +4 -5
- package/machine-config/azure.vue +1 -1
- package/machine-config/components/GCEImage.vue +1 -1
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +16 -0
- package/models/chart.js +70 -74
- package/models/management.cattle.io.cluster.js +1 -1
- package/models/provisioning.cattle.io.cluster.js +11 -3
- package/package.json +7 -7
- package/pages/auth/login.vue +3 -3
- package/pages/auth/setup.vue +1 -1
- package/pages/auth/verify.vue +3 -3
- package/pages/c/_cluster/apps/charts/index.vue +122 -24
- package/pages/c/_cluster/apps/charts/install.vue +33 -0
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
- package/pages/c/_cluster/fleet/index.vue +4 -7
- package/pages/c/_cluster/settings/index.vue +5 -0
- package/pkg/auto-import.js +3 -3
- package/pkg/dynamic-importer.lib.js +1 -1
- package/pkg/import.js +1 -1
- package/plugins/__tests__/mutations.tests.ts +179 -0
- package/plugins/dashboard-store/getters.js +1 -1
- package/plugins/dashboard-store/model-loader.js +1 -1
- package/plugins/dashboard-store/mutations.js +23 -2
- package/plugins/dashboard-store/resource-class.js +8 -3
- package/plugins/plugin.js +2 -2
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +301 -128
- package/plugins/steve/steve-class.js +1 -1
- package/plugins/steve/steve-pagination-utils.ts +108 -43
- package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +1 -1
- package/rancher-components/RcDropdown/useDropdownContext.ts +2 -4
- package/rancher-components/RcItemCard/RcItemCard.vue +1 -1
- package/scripts/publish-shell.sh +25 -0
- package/store/__tests__/catalog.test.ts +1 -1
- package/store/__tests__/type-map.test.ts +164 -2
- package/store/auth.js +23 -11
- package/store/i18n.js +3 -3
- package/store/index.js +5 -3
- package/store/notifications.ts +2 -0
- package/store/prefs.js +2 -2
- package/store/type-map.js +17 -7
- package/types/internal-api/shell/modal.d.ts +6 -6
- package/types/notifications/index.ts +126 -15
- package/types/rancher/index.d.ts +9 -0
- package/types/shell/index.d.ts +16 -1
- package/types/vue-shim.d.ts +5 -4
- package/utils/__tests__/router.test.js +238 -0
- package/utils/cluster.js +4 -1
- package/utils/fleet.ts +8 -1
- package/utils/pagination-utils.ts +2 -2
- package/utils/pagination-wrapper.ts +1 -1
- package/utils/router.js +50 -0
- package/utils/unit-tests/pagination-utils.spec.ts +8 -8
- package/vue.config.js +3 -3
- package/composables/useExtensionManager.ts +0 -17
- package/core/__test__/extension-manager-impl.test.js +0 -236
- package/core/plugins.js +0 -38
- package/directives/clean-tooltip.js +0 -32
- package/plugins/internal-api/index.ts +0 -37
- package/plugins/internal-api/shared/base-api.ts +0 -13
- package/plugins/internal-api/shell/shell.api.ts +0 -108
- package/types/internal-api/shell/growl.d.ts +0 -25
- package/types/internal-api/shell/slideIn.d.ts +0 -15
|
@@ -69,6 +69,22 @@ describe('class ProvCluster', () => {
|
|
|
69
69
|
},
|
|
70
70
|
expected: true
|
|
71
71
|
},
|
|
72
|
+
{
|
|
73
|
+
description: 'should return true for an imported k3s cluster in waiting state',
|
|
74
|
+
clusterData: {
|
|
75
|
+
isLocal: false,
|
|
76
|
+
mgmt: { status: { provider: undefined, driver: 'k3s' } }
|
|
77
|
+
},
|
|
78
|
+
expected: true
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
description: 'should return true for an imported rke2 cluster in waiting state',
|
|
82
|
+
clusterData: {
|
|
83
|
+
isLocal: false,
|
|
84
|
+
mgmt: { status: { provider: undefined, driver: 'rke2' } }
|
|
85
|
+
},
|
|
86
|
+
expected: true
|
|
87
|
+
},
|
|
72
88
|
{
|
|
73
89
|
description: 'should return false for a provisioned k3s cluster',
|
|
74
90
|
clusterData: {
|
package/models/chart.js
CHANGED
|
@@ -136,91 +136,87 @@ export default class Chart extends SteveModel {
|
|
|
136
136
|
* @returns {Object} Card content object with `subHeaderItems`, `footerItems`, and `statuses` arrays.
|
|
137
137
|
*/
|
|
138
138
|
get cardContent() {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
if (hasZeroTime) {
|
|
159
|
-
lastUpdatedItem.labelTooltip = this.t('catalog.charts.appChartCard.subHeaderItem.missingVersionDate');
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
subHeaderItems.push(lastUpdatedItem);
|
|
163
|
-
}
|
|
139
|
+
const latestVersion = this.latestCompatibleVersion;
|
|
140
|
+
const subHeaderItems = [];
|
|
141
|
+
|
|
142
|
+
if (latestVersion) {
|
|
143
|
+
const hasZeroTime = latestVersion.created === ZERO_TIME;
|
|
144
|
+
|
|
145
|
+
subHeaderItems.push({
|
|
146
|
+
icon: 'icon-version-alt',
|
|
147
|
+
iconTooltip: { key: 'tableHeaders.version' },
|
|
148
|
+
label: latestVersion.version
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const lastUpdatedItem = {
|
|
152
|
+
icon: 'icon-refresh-alt',
|
|
153
|
+
iconTooltip: { key: 'tableHeaders.lastUpdated' },
|
|
154
|
+
label: hasZeroTime ? this.t('generic.na') : day(latestVersion.created).format('MMM D, YYYY')
|
|
155
|
+
};
|
|
164
156
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
type: REPO,
|
|
168
|
-
icon: 'icon-repository-alt',
|
|
169
|
-
iconTooltip: { key: 'tableHeaders.repoName' },
|
|
170
|
-
labels: [this.repoNameDisplay],
|
|
171
|
-
labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.repo') }, true)
|
|
172
|
-
}
|
|
173
|
-
];
|
|
174
|
-
|
|
175
|
-
if (this.categories.length) {
|
|
176
|
-
footerItems.push( {
|
|
177
|
-
type: CATEGORY,
|
|
178
|
-
icon: 'icon-category-alt',
|
|
179
|
-
iconTooltip: { key: 'generic.category' },
|
|
180
|
-
labels: this.categories,
|
|
181
|
-
labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.category') }, true)
|
|
182
|
-
});
|
|
157
|
+
if (hasZeroTime) {
|
|
158
|
+
lastUpdatedItem.labelTooltip = this.t('catalog.charts.appChartCard.subHeaderItem.missingVersionDate');
|
|
183
159
|
}
|
|
184
160
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
161
|
+
subHeaderItems.push(lastUpdatedItem);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const footerItems = [
|
|
165
|
+
{
|
|
166
|
+
type: REPO,
|
|
167
|
+
icon: 'icon-repository-alt',
|
|
168
|
+
iconTooltip: { key: 'tableHeaders.repoName' },
|
|
169
|
+
labels: [this.repoNameDisplay],
|
|
170
|
+
labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.repo') }, true)
|
|
193
171
|
}
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
if (this.categories.length) {
|
|
175
|
+
footerItems.push( {
|
|
176
|
+
type: CATEGORY,
|
|
177
|
+
icon: 'icon-category-alt',
|
|
178
|
+
iconTooltip: { key: 'generic.category' },
|
|
179
|
+
labels: this.categories,
|
|
180
|
+
labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.category') }, true)
|
|
181
|
+
});
|
|
182
|
+
}
|
|
194
183
|
|
|
195
|
-
|
|
184
|
+
if (this.tags.length) {
|
|
185
|
+
footerItems.push({
|
|
186
|
+
type: TAG,
|
|
187
|
+
icon: 'icon-tag-alt',
|
|
188
|
+
iconTooltip: { key: 'generic.tags' },
|
|
189
|
+
labels: this.tags,
|
|
190
|
+
labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.tag') }, true)
|
|
191
|
+
});
|
|
192
|
+
}
|
|
196
193
|
|
|
197
|
-
|
|
198
|
-
statuses.push({
|
|
199
|
-
icon: 'icon-alert-alt', color: 'error', tooltip: { key: 'generic.deprecated' }
|
|
200
|
-
});
|
|
201
|
-
}
|
|
194
|
+
const statuses = [];
|
|
202
195
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
196
|
+
if (this.deprecated) {
|
|
197
|
+
statuses.push({
|
|
198
|
+
icon: 'icon-alert-alt', color: 'error', tooltip: { key: 'generic.deprecated' }
|
|
199
|
+
});
|
|
200
|
+
}
|
|
208
201
|
|
|
209
|
-
|
|
210
|
-
|
|
202
|
+
if (this.upgradeable) {
|
|
203
|
+
statuses.push({
|
|
204
|
+
icon: 'icon-upgrade-alt', color: 'info', tooltip: { key: 'generic.upgradeable' }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
211
207
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
});
|
|
215
|
-
}
|
|
208
|
+
if (this.isInstalled) {
|
|
209
|
+
const installedVersion = this.matchingInstalledApps[0]?.spec?.chart?.metadata?.version;
|
|
216
210
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
statuses
|
|
221
|
-
};
|
|
211
|
+
statuses.push({
|
|
212
|
+
icon: 'icon-confirmation-alt', color: 'success', tooltip: { text: `${ this.t('generic.installed') } (${ installedVersion })` }
|
|
213
|
+
});
|
|
222
214
|
}
|
|
223
215
|
|
|
224
|
-
return
|
|
216
|
+
return {
|
|
217
|
+
subHeaderItems,
|
|
218
|
+
footerItems,
|
|
219
|
+
statuses
|
|
220
|
+
};
|
|
225
221
|
}
|
|
226
222
|
}
|
|
@@ -255,7 +255,7 @@ export default class MgmtCluster extends SteveModel {
|
|
|
255
255
|
dispatch: this.$dispatch,
|
|
256
256
|
getters: this.$getters,
|
|
257
257
|
axios: this.$axios,
|
|
258
|
-
$extension: this.$
|
|
258
|
+
$extension: this.$extension,
|
|
259
259
|
t: (...args) => this.t.apply(this, args),
|
|
260
260
|
};
|
|
261
261
|
|
|
@@ -277,7 +277,7 @@ export default class ProvCluster extends SteveModel {
|
|
|
277
277
|
dispatch: this.$dispatch,
|
|
278
278
|
getters: this.$getters,
|
|
279
279
|
axios: this.$axios,
|
|
280
|
-
$extension: this.$
|
|
280
|
+
$extension: this.$extension,
|
|
281
281
|
t: (...args) => this.t.apply(this, args),
|
|
282
282
|
};
|
|
283
283
|
|
|
@@ -326,8 +326,16 @@ export default class ProvCluster extends SteveModel {
|
|
|
326
326
|
|
|
327
327
|
// imported rke2 and k3s have status.driver === rke2 and k3s respectively
|
|
328
328
|
// Provisioned rke2 and k3s have status.driver === imported
|
|
329
|
-
|
|
330
|
-
|
|
329
|
+
const provider = this.mgmt?.status?.provider;
|
|
330
|
+
const driver = this.mgmt?.status?.driver;
|
|
331
|
+
|
|
332
|
+
// The main case
|
|
333
|
+
if (provider === 'k3s' || provider === 'rke2') {
|
|
334
|
+
return driver === provider;
|
|
335
|
+
}
|
|
336
|
+
// The 'waiting' case
|
|
337
|
+
if (!provider && (driver === 'k3s' || driver === 'rke2')) {
|
|
338
|
+
return true;
|
|
331
339
|
}
|
|
332
340
|
|
|
333
341
|
// imported KEv2
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rancher/shell",
|
|
3
|
-
"version": "3.0.8
|
|
3
|
+
"version": "3.0.8",
|
|
4
4
|
"description": "Rancher Dashboard Shell",
|
|
5
|
-
"repository": "https://github.com/
|
|
5
|
+
"repository": "https://github.com/rancher/dashboard",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "SUSE",
|
|
8
8
|
"private": false,
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@babel/preset-typescript": "7.16.7",
|
|
40
40
|
"@novnc/novnc": "1.2.0",
|
|
41
41
|
"@popperjs/core": "2.11.8",
|
|
42
|
-
"@rancher/icons": "2.0.
|
|
42
|
+
"@rancher/icons": "2.0.54",
|
|
43
43
|
"@types/is-url": "1.2.30",
|
|
44
44
|
"@types/node": "20.10.8",
|
|
45
45
|
"@types/semver": "^7.5.8",
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
"add": "2.0.6",
|
|
54
54
|
"ansi_up": "5.0.0",
|
|
55
55
|
"axios-retry": "3.1.9",
|
|
56
|
-
"axios": "1.
|
|
56
|
+
"axios": "1.13.2",
|
|
57
57
|
"babel-eslint": "10.1.0",
|
|
58
|
-
"babel-plugin-module-resolver": "
|
|
58
|
+
"babel-plugin-module-resolver": "5.0.2",
|
|
59
59
|
"babel-preset-vue": "2.0.2",
|
|
60
60
|
"cache-loader": "4.1.0",
|
|
61
|
-
"chart.js": "4.
|
|
61
|
+
"chart.js": "4.5.1",
|
|
62
62
|
"clipboard-polyfill": "4.0.1",
|
|
63
63
|
"codemirror-editor-vue3": "2.8.0",
|
|
64
64
|
"codemirror": ">=5.64.0 <6",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"vuedraggable": "4.1.0",
|
|
141
141
|
"vuex": "4.1.0",
|
|
142
142
|
"webpack-bundle-analyzer": "4.10.2",
|
|
143
|
-
"webpack-virtual-modules": "0.
|
|
143
|
+
"webpack-virtual-modules": "0.6.2",
|
|
144
144
|
"worker-loader": "3.0.8",
|
|
145
145
|
"xterm-addon-canvas": "0.5.0",
|
|
146
146
|
"xterm-addon-fit": "0.8.0",
|
package/pages/auth/login.vue
CHANGED
|
@@ -315,9 +315,9 @@ export default {
|
|
|
315
315
|
// so we manually load them here - other SSO auth providers bounce out and back to the Dashboard, so on the bounce-back
|
|
316
316
|
// the plugins will load via the boot-time plugin
|
|
317
317
|
await loadPlugins({
|
|
318
|
-
app:
|
|
319
|
-
store:
|
|
320
|
-
$
|
|
318
|
+
app: this.$store.app,
|
|
319
|
+
store: this.$store,
|
|
320
|
+
$extension: this.$store.$extension,
|
|
321
321
|
});
|
|
322
322
|
|
|
323
323
|
if (this.firstLogin || user[0]?.mustChangePassword) {
|
package/pages/auth/setup.vue
CHANGED
|
@@ -209,7 +209,7 @@ export default {
|
|
|
209
209
|
const promises = [];
|
|
210
210
|
|
|
211
211
|
try {
|
|
212
|
-
await applyProducts(this.$store, this.$
|
|
212
|
+
await applyProducts(this.$store, this.$extension);
|
|
213
213
|
await this.$store.dispatch('loadManagement');
|
|
214
214
|
|
|
215
215
|
if ( this.mustChangePassword ) {
|
package/pages/auth/verify.vue
CHANGED
|
@@ -119,9 +119,9 @@ export default {
|
|
|
119
119
|
|
|
120
120
|
// Load plugins
|
|
121
121
|
await loadPlugins({
|
|
122
|
-
app:
|
|
123
|
-
store:
|
|
124
|
-
$
|
|
122
|
+
app: this.$store.app,
|
|
123
|
+
store: this.$store,
|
|
124
|
+
$extension: this.$store.$extension,
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
this.$router.replace(backTo);
|
|
@@ -65,6 +65,19 @@ export default {
|
|
|
65
65
|
this.installedApps = await this.$store.dispatch('cluster/findAll', { type: CATALOG_TYPES.APP });
|
|
66
66
|
},
|
|
67
67
|
|
|
68
|
+
updated() {
|
|
69
|
+
if (!this.observerInitialized && this.filteredCharts.length > 0) {
|
|
70
|
+
this.initIntersectionObserver();
|
|
71
|
+
}
|
|
72
|
+
this.ensureOverflow();
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
beforeUnmount() {
|
|
76
|
+
if (this.observer) {
|
|
77
|
+
this.observer.disconnect();
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
68
81
|
data() {
|
|
69
82
|
return {
|
|
70
83
|
DOCS_BASE,
|
|
@@ -107,7 +120,6 @@ export default {
|
|
|
107
120
|
}
|
|
108
121
|
}
|
|
109
122
|
],
|
|
110
|
-
appCardsCache: {},
|
|
111
123
|
selectedSortOption: CATALOG_SORT_OPTIONS.RECOMMENDED,
|
|
112
124
|
sortOptions: [
|
|
113
125
|
{ kind: 'group', label: this.t('catalog.charts.sort.prefix') },
|
|
@@ -115,7 +127,10 @@ export default {
|
|
|
115
127
|
{ value: CATALOG_SORT_OPTIONS.LAST_UPDATED_DESC, label: this.t('catalog.charts.sort.lastUpdatedDesc') },
|
|
116
128
|
{ value: CATALOG_SORT_OPTIONS.ALPHABETICAL_ASC, label: this.t('catalog.charts.sort.alphaAscending') },
|
|
117
129
|
{ value: CATALOG_SORT_OPTIONS.ALPHABETICAL_DESC, label: this.t('catalog.charts.sort.alphaDescending') },
|
|
118
|
-
]
|
|
130
|
+
],
|
|
131
|
+
initialVisibleChartsCount: 30,
|
|
132
|
+
visibleChartsCount: 20,
|
|
133
|
+
hasOverflow: false
|
|
119
134
|
};
|
|
120
135
|
},
|
|
121
136
|
|
|
@@ -262,26 +277,21 @@ export default {
|
|
|
262
277
|
},
|
|
263
278
|
|
|
264
279
|
appChartCards() {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return this.appCardsCache[chart.id];
|
|
284
|
-
});
|
|
280
|
+
const charts = this.filteredCharts.slice(0, this.visibleChartsCount);
|
|
281
|
+
|
|
282
|
+
return charts.map((chart) => ({
|
|
283
|
+
id: chart.id,
|
|
284
|
+
pill: chart.featured ? { label: { key: 'generic.shortFeatured' }, tooltip: { key: 'generic.featured' } } : undefined,
|
|
285
|
+
header: {
|
|
286
|
+
title: { text: chart.chartNameDisplay },
|
|
287
|
+
statuses: chart.cardContent.statuses
|
|
288
|
+
},
|
|
289
|
+
subHeaderItems: chart.cardContent.subHeaderItems,
|
|
290
|
+
image: { src: chart.latestCompatibleVersion.icon, alt: { text: this.t('catalog.charts.iconAlt', { app: get(chart, 'chartNameDisplay') }) } },
|
|
291
|
+
content: { text: chart.chartDescription },
|
|
292
|
+
footerItems: chart.cardContent.footerItems,
|
|
293
|
+
rawChart: chart
|
|
294
|
+
}));
|
|
285
295
|
},
|
|
286
296
|
|
|
287
297
|
clusterId() {
|
|
@@ -293,7 +303,7 @@ export default {
|
|
|
293
303
|
},
|
|
294
304
|
|
|
295
305
|
totalMessage() {
|
|
296
|
-
const count = !this.isFilterUpdating ? this.
|
|
306
|
+
const count = !this.isFilterUpdating ? this.filteredCharts.length : '. . .';
|
|
297
307
|
|
|
298
308
|
if (this.noFiltersApplied) {
|
|
299
309
|
return this.t('catalog.charts.totalChartsMessage', { count });
|
|
@@ -304,6 +314,10 @@ export default {
|
|
|
304
314
|
},
|
|
305
315
|
|
|
306
316
|
watch: {
|
|
317
|
+
debouncedSearchQuery() {
|
|
318
|
+
this.resetLazyLoadState();
|
|
319
|
+
},
|
|
320
|
+
|
|
307
321
|
searchQuery: {
|
|
308
322
|
handler: debounce(function(q) {
|
|
309
323
|
this.debouncedSearchQuery = q;
|
|
@@ -315,6 +329,8 @@ export default {
|
|
|
315
329
|
filters: {
|
|
316
330
|
deep: true,
|
|
317
331
|
handler(newFilters) {
|
|
332
|
+
this.resetLazyLoadState();
|
|
333
|
+
|
|
318
334
|
const query = {
|
|
319
335
|
[REPO]: normalizeFilterQuery(newFilters.repos),
|
|
320
336
|
[CATEGORY]: normalizeFilterQuery(newFilters.categories),
|
|
@@ -425,11 +441,80 @@ export default {
|
|
|
425
441
|
});
|
|
426
442
|
},
|
|
427
443
|
|
|
444
|
+
resetLazyLoadState() {
|
|
445
|
+
this.visibleChartsCount = this.initialVisibleChartsCount;
|
|
446
|
+
this.observerInitialized = false;
|
|
447
|
+
this.hasOverflow = false;
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
// The lazy loading implementation has two parts
|
|
451
|
+
// 1. Initial Load (ensureOverflow): Having a simple calculation of how many items to load
|
|
452
|
+
// can fail in edge cases like browser zoom, where element sizing and viewport
|
|
453
|
+
// height can lead to miscalculations. If not enough content is loaded, the page
|
|
454
|
+
// won't be scrollable, breaking the IntersectionObserver. This method, called
|
|
455
|
+
// iteratively by the `updated` lifecycle hook, adds batches of charts and
|
|
456
|
+
// re-measures until the content height factually overflows the container,
|
|
457
|
+
// guaranteeing a scrollbar. It then sets `hasOverflow = true` to stop itself.
|
|
458
|
+
// 2. Scroll-based Load (IntersectionObserver): Once the page is scrollable, a standard
|
|
459
|
+
// IntersectionObserver (`initIntersectionObserver` and `loadMore`) takes care of
|
|
460
|
+
// loading new batches of charts as the user scrolls to the bottom.
|
|
461
|
+
ensureOverflow() {
|
|
462
|
+
this.$nextTick(() => {
|
|
463
|
+
if (this.hasOverflow || !this.$refs.chartsContainer) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const mainLayout = document.querySelector('.main-layout');
|
|
468
|
+
|
|
469
|
+
if (!mainLayout) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const contentHeight = this.$refs.chartsContainer.offsetHeight;
|
|
474
|
+
const containerHeight = mainLayout.offsetHeight;
|
|
475
|
+
|
|
476
|
+
if (contentHeight > containerHeight) {
|
|
477
|
+
this.hasOverflow = true;
|
|
478
|
+
} else if (this.visibleChartsCount < this.filteredCharts.length) {
|
|
479
|
+
// Load another batch
|
|
480
|
+
this.visibleChartsCount += this.initialVisibleChartsCount;
|
|
481
|
+
} else {
|
|
482
|
+
// All charts are visible
|
|
483
|
+
this.hasOverflow = true;
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
},
|
|
487
|
+
|
|
428
488
|
resetAllFilters() {
|
|
429
489
|
this.internalFilters = createInitialFilters();
|
|
430
490
|
this.filters = createInitialFilters();
|
|
431
491
|
this.searchQuery = '';
|
|
432
492
|
},
|
|
493
|
+
|
|
494
|
+
loadMore() {
|
|
495
|
+
if (this.visibleChartsCount >= this.filteredCharts.length) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
this.visibleChartsCount += this.initialVisibleChartsCount;
|
|
499
|
+
},
|
|
500
|
+
|
|
501
|
+
initIntersectionObserver() {
|
|
502
|
+
if (this.observer) {
|
|
503
|
+
this.observer.disconnect();
|
|
504
|
+
}
|
|
505
|
+
const mainLayout = document.querySelector('.main-layout');
|
|
506
|
+
const sentinel = this.$refs.sentinel;
|
|
507
|
+
|
|
508
|
+
if (sentinel && mainLayout) {
|
|
509
|
+
this.observer = new IntersectionObserver((entries) => {
|
|
510
|
+
if (entries[0].isIntersecting) {
|
|
511
|
+
this.loadMore();
|
|
512
|
+
}
|
|
513
|
+
}, { mainLayout });
|
|
514
|
+
this.observer.observe(sentinel);
|
|
515
|
+
this.observerInitialized = true;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
433
518
|
},
|
|
434
519
|
};
|
|
435
520
|
</script>
|
|
@@ -552,7 +637,10 @@ export default {
|
|
|
552
637
|
>
|
|
553
638
|
<div class="total-and-sort">
|
|
554
639
|
<div class="total">
|
|
555
|
-
<p
|
|
640
|
+
<p
|
|
641
|
+
class="total-message"
|
|
642
|
+
data-testid="charts-total-message"
|
|
643
|
+
>
|
|
556
644
|
{{ totalMessage }}
|
|
557
645
|
</p>
|
|
558
646
|
<a
|
|
@@ -594,6 +682,7 @@ export default {
|
|
|
594
682
|
</Select>
|
|
595
683
|
</div>
|
|
596
684
|
<div
|
|
685
|
+
ref="chartsContainer"
|
|
597
686
|
class="app-chart-cards"
|
|
598
687
|
data-testid="app-chart-cards-container"
|
|
599
688
|
>
|
|
@@ -629,6 +718,11 @@ export default {
|
|
|
629
718
|
</template>
|
|
630
719
|
</rc-item-card>
|
|
631
720
|
</div>
|
|
721
|
+
<div
|
|
722
|
+
ref="sentinel"
|
|
723
|
+
class="sentinel-charts"
|
|
724
|
+
data-testid="charts-lazy-load-sentinel"
|
|
725
|
+
/>
|
|
632
726
|
</div>
|
|
633
727
|
</div>
|
|
634
728
|
</div>
|
|
@@ -673,6 +767,10 @@ export default {
|
|
|
673
767
|
flex-direction: column;
|
|
674
768
|
gap: var(--gap-md);
|
|
675
769
|
flex: 1;
|
|
770
|
+
|
|
771
|
+
.sentinel-charts {
|
|
772
|
+
height: 1px;
|
|
773
|
+
}
|
|
676
774
|
}
|
|
677
775
|
|
|
678
776
|
.total-and-sort {
|
|
@@ -310,6 +310,7 @@ export default {
|
|
|
310
310
|
two different Helm chart versions is a "user value," or
|
|
311
311
|
a user-selected customization.
|
|
312
312
|
*/
|
|
313
|
+
this.preserveCustomRegistryValue();
|
|
313
314
|
userValues = diff(this.loadedVersionValues, this.chartValues);
|
|
314
315
|
} else if ( this.existing ) {
|
|
315
316
|
await this.existing.fetchValues(); // In theory this has already been called, but do again to be safe
|
|
@@ -824,6 +825,35 @@ export default {
|
|
|
824
825
|
},
|
|
825
826
|
|
|
826
827
|
methods: {
|
|
828
|
+
/**
|
|
829
|
+
* The custom registry UI fields (checkbox and input) are not directly bound to chartValues.
|
|
830
|
+
* Before calculating the diff to carry over user customizations, we must
|
|
831
|
+
* first synchronize the state of these UI fields with chartValues. This
|
|
832
|
+
* ensures any user changes to the custom registry settings are
|
|
833
|
+
* included in the diff and preserved when changing versions.
|
|
834
|
+
*/
|
|
835
|
+
preserveCustomRegistryValue() {
|
|
836
|
+
if (!this.showCustomRegistry) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (this.showCustomRegistryInput) {
|
|
841
|
+
set(this.chartValues, 'global.systemDefaultRegistry', this.customRegistrySetting);
|
|
842
|
+
set(this.chartValues, 'global.cattle.systemDefaultRegistry', this.customRegistrySetting);
|
|
843
|
+
} else {
|
|
844
|
+
// Note: Using `delete` here is safe because this is not a reactive property update
|
|
845
|
+
// that the UI needs to track. This is a one-time mutation before a diff.
|
|
846
|
+
if (get(this.chartValues, 'global.systemDefaultRegistry')) {
|
|
847
|
+
delete this.chartValues.global.systemDefaultRegistry;
|
|
848
|
+
}
|
|
849
|
+
if (get(this.chartValues, 'global.cattle.systemDefaultRegistry')) {
|
|
850
|
+
// It's possible `this.chartValues.global.cattle` doesn't exist,
|
|
851
|
+
// but `get` ensures we only proceed if the full path exists.
|
|
852
|
+
delete this.chartValues.global.cattle.systemDefaultRegistry;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
},
|
|
856
|
+
|
|
827
857
|
async getClusterRegistry() {
|
|
828
858
|
const hasPermissionToSeeProvCluster = this.$store.getters[`management/schemaFor`](CAPI.RANCHER_CLUSTER);
|
|
829
859
|
|
|
@@ -1367,6 +1397,7 @@ export default {
|
|
|
1367
1397
|
<!-- We have a chart for the app, let the user select a new version -->
|
|
1368
1398
|
<LabeledSelect
|
|
1369
1399
|
v-if="chart"
|
|
1400
|
+
data-testid="chart-version-selector"
|
|
1370
1401
|
:label="t('catalog.install.version')"
|
|
1371
1402
|
:value="query.versionName"
|
|
1372
1403
|
:options="filteredVersions"
|
|
@@ -1435,6 +1466,7 @@ export default {
|
|
|
1435
1466
|
v-if="showCustomRegistry"
|
|
1436
1467
|
v-model:value="showCustomRegistryInput"
|
|
1437
1468
|
class="mb-20"
|
|
1469
|
+
data-testid="custom-registry-checkbox"
|
|
1438
1470
|
:label="t('catalog.chart.registry.custom.checkBoxLabel')"
|
|
1439
1471
|
:tooltip="t('catalog.chart.registry.tooltip')"
|
|
1440
1472
|
/>
|
|
@@ -1443,6 +1475,7 @@ export default {
|
|
|
1443
1475
|
<LabeledInput
|
|
1444
1476
|
v-if="showCustomRegistryInput"
|
|
1445
1477
|
v-model:value="customRegistrySetting"
|
|
1478
|
+
data-testid="custom-registry-input"
|
|
1446
1479
|
label-key="catalog.chart.registry.custom.inputLabel"
|
|
1447
1480
|
placeholder-key="catalog.chart.registry.custom.placeholder"
|
|
1448
1481
|
:min-height="30"
|
|
@@ -211,7 +211,7 @@ describe('page: cluster dashboard', () => {
|
|
|
211
211
|
|
|
212
212
|
expect(box.element).toBeDefined();
|
|
213
213
|
expect(box.element.classList).toContain(status);
|
|
214
|
-
expect(!!(box.element as any)
|
|
214
|
+
expect(!!(box.element as any).__tooltipOptions__?.content).toBe(clickable);
|
|
215
215
|
expect(icon.element.classList).toContain(iconClass);
|
|
216
216
|
|
|
217
217
|
await box.trigger('click');
|
|
@@ -321,16 +321,13 @@ export default {
|
|
|
321
321
|
|
|
322
322
|
this.selectedCard = selected;
|
|
323
323
|
|
|
324
|
-
this.$shell.
|
|
325
|
-
component: ResourceDetails,
|
|
324
|
+
this.$shell.slideIn.open(ResourceDetails, {
|
|
326
325
|
componentProps: {
|
|
326
|
+
showHeader: false,
|
|
327
|
+
width: window.innerWidth / 3 > 530 ? `${ window.innerWidth / 3 }px` : '530px',
|
|
327
328
|
value,
|
|
328
329
|
statePanel,
|
|
329
|
-
workspace
|
|
330
|
-
showHeader: false,
|
|
331
|
-
width: window.innerWidth / 3 > 530 ? `${ window.innerWidth / 3 }px` : '530px',
|
|
332
|
-
triggerFocusTrap: true,
|
|
333
|
-
returnFocusSelector: `[data-testid="resource-card-${ value.id }"]`
|
|
330
|
+
workspace
|
|
334
331
|
}
|
|
335
332
|
});
|
|
336
333
|
},
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
<script>
|
|
2
|
+
import { h } from 'vue';
|
|
2
3
|
import { NAME as SETTINGS } from '@shell/config/product/settings';
|
|
3
4
|
import { MANAGEMENT } from '@shell/config/types';
|
|
4
5
|
|
|
5
6
|
export default {
|
|
7
|
+
render() {
|
|
8
|
+
// Suppress warning: Component is missing template or render function
|
|
9
|
+
return h('div');
|
|
10
|
+
},
|
|
6
11
|
beforeCreate() {
|
|
7
12
|
const hasSettings = !!this.$store.getters[`management/schemaFor`](MANAGEMENT.SETTING);
|
|
8
13
|
|