@rancher/shell 3.0.11 → 3.0.12-rc.2
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/assets/images/providers/entraid-black.svg +4 -0
- package/assets/images/providers/entraid.svg +9 -0
- package/assets/images/vendor/entraid.svg +9 -0
- package/assets/styles/app.scss +0 -1
- package/assets/styles/base/_mixins.scss +31 -0
- package/assets/styles/base/_variables.scss +2 -0
- package/assets/styles/themes/_modern.scss +6 -5
- package/assets/translations/en-us.yaml +24 -21
- package/assets/translations/zh-hans.yaml +4 -11
- package/chart/__tests__/S3.test.ts +10 -3
- package/components/CountBox.vue +20 -0
- package/components/CreateDriver.vue +0 -12
- package/components/DetailText.vue +12 -3
- package/components/EmptyProductPage.vue +76 -0
- package/components/Resource/Detail/CopyToClipboard.vue +1 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
- package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
- package/components/Resource/Detail/TitleBar/index.vue +1 -1
- package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
- package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
- package/components/Resource/Detail/ViewOptions/index.vue +2 -1
- package/components/ResourceList/Masthead.vue +25 -2
- package/components/SelectIconGrid.vue +5 -0
- package/components/SideNav.vue +13 -0
- package/components/__tests__/CountBox.test.ts +72 -0
- package/components/__tests__/DetailText.test.ts +113 -0
- package/components/__tests__/PromptModal.test.ts +2 -0
- package/components/fleet/FleetClusterTargets/index.vue +18 -1
- package/components/fleet/FleetClusters.vue +1 -0
- package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
- package/components/form/InputWithSelect.vue +18 -10
- package/components/form/KeyValue.vue +17 -1
- package/components/form/LabeledSelect.vue +82 -24
- package/components/form/NodeScheduling.vue +17 -3
- package/components/form/PrivateRegistry.vue +69 -0
- package/components/form/Select.vue +73 -56
- package/components/form/ServiceNameSelect.vue +13 -11
- package/components/form/__tests__/KeyValue.test.ts +66 -0
- package/components/form/__tests__/NodeScheduling.test.ts +9 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
- package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
- package/components/formatter/WorkloadHealthScale.vue +3 -1
- package/components/nav/Group.vue +33 -9
- package/components/nav/Header.vue +56 -10
- package/components/nav/NotificationCenter/Notification.vue +4 -1
- package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
- package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
- package/components/nav/TopLevelMenu.vue +15 -1
- package/components/nav/Type.vue +8 -7
- package/components/nav/WindowManager/index.vue +2 -1
- package/components/nav/WorkspaceSwitcher.vue +13 -0
- package/components/nav/__tests__/Group.test.ts +67 -0
- package/components/nav/__tests__/Header.test.ts +235 -0
- package/components/nav/__tests__/Type.test.ts +20 -3
- package/components/templates/default.vue +34 -4
- package/components/templates/home.vue +12 -25
- package/components/templates/plain.vue +13 -26
- package/composables/useLabeledFormElement.ts +10 -2
- package/composables/useLabeledSelect.ts +60 -0
- package/composables/useUserRetentionValidation.ts +1 -49
- package/config/cookies.js +0 -1
- package/config/labels-annotations.js +1 -0
- package/config/pagination-table-headers.js +8 -1
- package/config/product/apps.js +2 -1
- package/config/product/auth.js +1 -0
- package/config/product/backup.js +1 -0
- package/config/product/compliance.js +1 -1
- package/config/product/explorer.js +25 -6
- package/config/product/fleet.js +1 -0
- package/config/product/gatekeeper.js +1 -0
- package/config/product/istio.js +1 -0
- package/config/product/logging.js +1 -0
- package/config/product/longhorn.js +2 -1
- package/config/product/manager.js +1 -0
- package/config/product/monitoring.js +1 -0
- package/config/product/navlinks.js +1 -0
- package/config/product/neuvector.js +2 -1
- package/config/product/settings.js +1 -0
- package/config/product/uiplugins.js +1 -0
- package/config/query-params.js +1 -0
- package/config/router/routes.js +0 -8
- package/core/__tests__/plugin-products-helpers.test.ts +454 -0
- package/core/__tests__/plugin-products.test.ts +3810 -0
- package/core/extension-manager-impl.js +30 -1
- package/core/plugin-products-base.ts +392 -0
- package/core/plugin-products-extending.ts +44 -0
- package/core/plugin-products-helpers.ts +263 -0
- package/core/plugin-products-top-level.ts +66 -0
- package/core/plugin-products-type-guards.ts +33 -0
- package/core/plugin-products.ts +50 -0
- package/core/plugin-types.ts +237 -0
- package/core/plugin.ts +45 -10
- package/core/productDebugger.js +48 -0
- package/core/types.ts +97 -11
- package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
- package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
- package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
- package/detail/fleet.cattle.io.bundle.vue +21 -34
- package/detail/management.cattle.io.fleetworkspace.vue +49 -0
- package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
- package/dialog/InstallExtensionDialog.vue +6 -27
- package/dialog/UninstallExistingExtensionDialog.vue +141 -0
- package/dialog/UninstallExtensionDialog.vue +4 -26
- package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
- package/edit/__tests__/kontainerDriver.test.ts +0 -13
- package/edit/__tests__/nodeDriver.test.ts +5 -11
- package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auth/__tests__/oidc.test.ts +54 -0
- package/edit/auth/azuread.vue +1 -1
- package/edit/auth/oidc.vue +8 -0
- package/edit/kontainerDriver.vue +1 -2
- package/edit/nodeDriver.vue +0 -2
- package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
- package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
- package/initialize/App.vue +29 -2
- package/initialize/install-plugins.js +0 -2
- package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
- package/list/catalog.cattle.io.app.vue +25 -5
- package/list/management.cattle.io.feature.vue +1 -1
- package/list/management.cattle.io.fleetworkspace.vue +8 -0
- package/list/provisioning.cattle.io.cluster.vue +0 -1
- package/list/workload.vue +11 -4
- package/machine-config/amazonec2.vue +1 -0
- package/mixins/chart.js +40 -9
- package/mixins/resource-fetch.js +12 -3
- package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
- package/models/__tests__/chart.test.ts +99 -6
- package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
- package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
- package/models/catalog.cattle.io.app.js +21 -17
- package/models/catalog.cattle.io.clusterrepo.js +39 -11
- package/models/chart.js +33 -19
- package/models/fleet-application.js +1 -1
- package/models/fleet.cattle.io.bundle.js +1 -1
- package/models/kontainerdriver.js +11 -0
- package/models/management.cattle.io.authconfig.js +5 -1
- package/models/management.cattle.io.cluster.js +0 -53
- package/models/management.cattle.io.feature.js +3 -3
- package/models/management.cattle.io.kontainerdriver.js +1 -26
- package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
- package/models/nodedriver.js +7 -0
- package/models/pod.js +18 -0
- package/models/workload.js +20 -2
- package/package.json +13 -13
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
- package/pages/c/_cluster/apps/charts/chart.vue +217 -33
- package/pages/c/_cluster/apps/charts/index.vue +2 -2
- package/pages/c/_cluster/apps/charts/install.vue +8 -3
- package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
- package/pages/c/_cluster/settings/brand.vue +4 -4
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
- package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +246 -23
- package/pages/c/_cluster/uiplugins/index.vue +166 -62
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
- package/plugins/dashboard-store/actions.js +3 -2
- package/plugins/dashboard-store/resource-class.js +62 -6
- package/plugins/plugin.js +16 -0
- package/plugins/steve/steve-pagination-utils.ts +7 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
- package/scripts/test-plugins-build.sh +5 -2
- package/scripts/typegen.sh +13 -1
- package/server/server-middleware.js +2 -2
- package/static/humans.txt +1 -0
- package/static/robots.txt +34 -0
- package/static/welcome-cow.svg +18 -0
- package/store/__tests__/catalog.test.ts +161 -11
- package/store/__tests__/type-map.test.ts +84 -24
- package/store/auth.js +0 -3
- package/store/catalog.js +60 -8
- package/store/type-map.js +42 -3
- package/tsconfig.paths.json +1 -0
- package/types/resources/pod.ts +18 -0
- package/types/shell/index.d.ts +8539 -2938
- package/types/store/dashboard-store.types.ts +5 -0
- package/types/store/pagination.types.ts +6 -0
- package/utils/__tests__/git.test.ts +270 -0
- package/utils/__tests__/inactivity.test.ts +316 -0
- package/utils/__tests__/object.test.ts +77 -0
- package/utils/__tests__/time.test.ts +14 -1
- package/utils/__tests__/url.test.ts +246 -0
- package/utils/axios.js +1 -4
- package/utils/dynamic-importer.js +3 -2
- package/utils/object.js +33 -2
- package/utils/pagination-utils.ts +1 -1
- package/utils/time.ts +5 -0
- package/utils/uiplugins.ts +12 -16
- package/utils/validators/__tests__/private-registry.test.ts +76 -0
- package/utils/validators/private-registry.ts +28 -0
- package/vue.config.js +0 -9
- package/assets/images/providers/azuread-black.svg +0 -22
- package/assets/images/providers/azuread.svg +0 -25
- package/assets/images/vendor/azuread.svg +0 -18
- package/assets/styles/fonts/_dots.scss +0 -18
- package/components/EmberPage.vue +0 -622
- package/components/EmberPageView.vue +0 -39
- package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
- package/mixins/labeled-form-element.ts +0 -225
- package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
- package/pages/c/_cluster/manager/pages/_page.vue +0 -22
- package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
- package/plugins/ember-cookie.js +0 -17
- package/utils/ember-page.js +0 -30
|
@@ -41,26 +41,54 @@ export default class ClusterRepo extends SteveModel {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
insertAt(out, 0, {
|
|
44
|
-
action:
|
|
45
|
-
label:
|
|
46
|
-
icon:
|
|
47
|
-
enabled:
|
|
48
|
-
bulkable:
|
|
44
|
+
action: 'refresh',
|
|
45
|
+
label: this.t('action.refresh'),
|
|
46
|
+
icon: 'icon icon-refresh',
|
|
47
|
+
enabled: !!this.links.update,
|
|
48
|
+
bulkable: true,
|
|
49
|
+
bulkAction: 'refreshBulk',
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
return out;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Refreshes the repository by updating its forceUpdate annotation and waiting for it to become active.
|
|
58
|
+
* @param {boolean} dispatchLoad - Whether to dispatch the catalog/load action after refreshing. Defaults to true.
|
|
59
|
+
*/
|
|
60
|
+
async refresh(dispatchLoad = true) {
|
|
61
|
+
try {
|
|
62
|
+
const now = (new Date()).toISOString().replace(/\.\d+Z$/, 'Z');
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
this.spec.forceUpdate = now;
|
|
65
|
+
await this.save();
|
|
66
|
+
|
|
67
|
+
await this.waitForState('active', 10000, 1000);
|
|
60
68
|
|
|
61
|
-
|
|
69
|
+
if (dispatchLoad) {
|
|
70
|
+
this.$dispatch('catalog/load', { force: true, repoKeys: [this._key] }, { root: true });
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
this.$dispatch('growl/fromError', {
|
|
74
|
+
title: this.t('catalog.repo.error.refresh', {}, 'Error refreshing repository'),
|
|
75
|
+
err,
|
|
76
|
+
}, { root: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
62
79
|
|
|
63
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Performs a bulk refresh on multiple repositories concurrently, bypassing individual
|
|
82
|
+
* catalog loads, and dispatches a single catalog/load for all repositories once they are active.
|
|
83
|
+
* @param {ClusterRepo[]} items - Array of repository instances to refresh.
|
|
84
|
+
*/
|
|
85
|
+
async refreshBulk(items) {
|
|
86
|
+
await Promise.allSettled(items.map((item) => item.refresh(false)));
|
|
87
|
+
|
|
88
|
+
this.$dispatch('catalog/load', {
|
|
89
|
+
force: true,
|
|
90
|
+
repoKeys: items.map((item) => item._key)
|
|
91
|
+
}, { root: true });
|
|
64
92
|
}
|
|
65
93
|
|
|
66
94
|
async disableClusterRepo() {
|
package/models/chart.js
CHANGED
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
6
6
|
import { SHOW_PRE_RELEASE } from '@shell/store/prefs';
|
|
7
7
|
import { getLatestCompatibleVersion } from '@shell/utils/chart';
|
|
8
|
+
import { isMissingDate } from '@shell/utils/time';
|
|
8
9
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
9
|
-
import { CATALOG
|
|
10
|
+
import { CATALOG } from '@shell/config/types';
|
|
10
11
|
import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
|
|
11
12
|
import day from 'dayjs';
|
|
12
13
|
|
|
@@ -89,22 +90,33 @@ export default class Chart extends SteveModel {
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
/**
|
|
92
|
-
* Determines if the chart is installed by checking if
|
|
93
|
+
* Determines if the chart is installed by checking if at least one matching installed app is found.
|
|
93
94
|
*
|
|
94
95
|
* @returns {boolean} `true` if the chart is currently installed.
|
|
95
96
|
*/
|
|
96
97
|
get isInstalled() {
|
|
97
|
-
return this.matchingInstalledApps.length
|
|
98
|
+
return this.matchingInstalledApps.length >= 1;
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
/**
|
|
101
|
-
* Determines if
|
|
102
|
+
* Determines if at least one installed instance has an available upgrade.
|
|
102
103
|
* Requires the chart to be installed.
|
|
103
104
|
*
|
|
104
|
-
* @returns {boolean} `true` if the app is installed and has
|
|
105
|
+
* @returns {boolean} `true` if the app is installed and at least one instance has an upgrade available.
|
|
105
106
|
*/
|
|
106
107
|
get upgradeable() {
|
|
107
|
-
return this.isInstalled && this.matchingInstalledApps
|
|
108
|
+
return this.isInstalled && this.matchingInstalledApps.some(
|
|
109
|
+
(app) => app.upgradeAvailable === APP_UPGRADE_STATUS.SINGLE_UPGRADE
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Returns the number of installed instances of this chart.
|
|
115
|
+
*
|
|
116
|
+
* @returns {number} The count of installed instances.
|
|
117
|
+
*/
|
|
118
|
+
get installedCount() {
|
|
119
|
+
return this.matchingInstalledApps.length;
|
|
108
120
|
}
|
|
109
121
|
|
|
110
122
|
/**
|
|
@@ -140,25 +152,19 @@ export default class Chart extends SteveModel {
|
|
|
140
152
|
const subHeaderItems = [];
|
|
141
153
|
|
|
142
154
|
if (latestVersion) {
|
|
143
|
-
const hasZeroTime = latestVersion.created === ZERO_TIME;
|
|
144
|
-
|
|
145
155
|
subHeaderItems.push({
|
|
146
156
|
icon: 'icon-version-alt',
|
|
147
157
|
iconTooltip: { key: 'tableHeaders.version' },
|
|
148
158
|
label: latestVersion.version
|
|
149
159
|
});
|
|
150
160
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (hasZeroTime) {
|
|
158
|
-
lastUpdatedItem.labelTooltip = this.t('catalog.charts.appChartCard.subHeaderItem.missingVersionDate');
|
|
161
|
+
if (!isMissingDate(latestVersion.created)) {
|
|
162
|
+
subHeaderItems.push({
|
|
163
|
+
icon: 'icon-refresh-alt',
|
|
164
|
+
iconTooltip: { key: 'tableHeaders.lastUpdated' },
|
|
165
|
+
label: day(latestVersion.created).format('MMM D, YYYY')
|
|
166
|
+
});
|
|
159
167
|
}
|
|
160
|
-
|
|
161
|
-
subHeaderItems.push(lastUpdatedItem);
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
const footerItems = [
|
|
@@ -206,10 +212,18 @@ export default class Chart extends SteveModel {
|
|
|
206
212
|
}
|
|
207
213
|
|
|
208
214
|
if (this.isInstalled) {
|
|
215
|
+
const hasMultipleInstances = this.installedCount > 1;
|
|
209
216
|
const installedVersion = this.matchingInstalledApps[0]?.spec?.chart?.metadata?.version;
|
|
210
217
|
|
|
218
|
+
// When multiple instances, don't show version in tooltip since each may have different versions
|
|
219
|
+
let tooltipText = hasMultipleInstances ? this.t('generic.installedMultiple') : this.t('generic.installed');
|
|
220
|
+
|
|
221
|
+
if (!hasMultipleInstances) {
|
|
222
|
+
tooltipText = `${ tooltipText } (${ installedVersion })`;
|
|
223
|
+
}
|
|
224
|
+
|
|
211
225
|
statuses.push({
|
|
212
|
-
icon: 'icon-confirmation-alt', color: 'success', tooltip: { text:
|
|
226
|
+
icon: 'icon-confirmation-alt', color: 'success', tooltip: { text: tooltipText }
|
|
213
227
|
});
|
|
214
228
|
}
|
|
215
229
|
|
|
@@ -105,7 +105,7 @@ export default class FleetApplication extends SteveModel {
|
|
|
105
105
|
|
|
106
106
|
for (const tgt of this.spec.targets) {
|
|
107
107
|
if (tgt.clusterName) {
|
|
108
|
-
const cluster = findBy(clusters, 'metadata.name', tgt.clusterName);
|
|
108
|
+
const cluster = findBy(clusters, 'metadata.name', tgt.clusterName) || findBy(clusters, 'nameDisplay', tgt.clusterName);
|
|
109
109
|
|
|
110
110
|
if (cluster) {
|
|
111
111
|
addObject(out, cluster);
|
|
@@ -47,7 +47,7 @@ export default class FleetBundle extends SteveModel {
|
|
|
47
47
|
|
|
48
48
|
for (const tgt of this.spec.targets) {
|
|
49
49
|
if (tgt.clusterName) {
|
|
50
|
-
const cluster = findBy(clusters, 'metadata.name', tgt.clusterName);
|
|
50
|
+
const cluster = findBy(clusters, 'metadata.name', tgt.clusterName) || findBy(clusters, 'nameDisplay', tgt.clusterName);
|
|
51
51
|
|
|
52
52
|
if (cluster) {
|
|
53
53
|
addObject(out, cluster);
|
|
@@ -5,6 +5,13 @@ export default class KontainerDriver extends Driver {
|
|
|
5
5
|
return 'c-cluster-manager-driver-kontainerdriver';
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
get parentLocationOverride() {
|
|
9
|
+
return {
|
|
10
|
+
name: 'c-cluster-manager-driver-kontainerdriver',
|
|
11
|
+
params: { cluster: this.$rootGetters['clusterId'] }
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
get _availableActions() {
|
|
9
16
|
const out = [
|
|
10
17
|
{
|
|
@@ -54,6 +61,10 @@ export default class KontainerDriver extends Driver {
|
|
|
54
61
|
return out;
|
|
55
62
|
}
|
|
56
63
|
|
|
64
|
+
get isEmber() {
|
|
65
|
+
return !this.builtIn && !this.builtin;
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
deactivate(resources = [this]) {
|
|
58
69
|
this.$dispatch('promptModal', {
|
|
59
70
|
componentProps: { drivers: resources, driverType: 'kontainerDrivers' },
|
|
@@ -21,7 +21,11 @@ export const configType = {
|
|
|
21
21
|
cognito: 'oidc',
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
const imageOverrides = {
|
|
24
|
+
const imageOverrides = {
|
|
25
|
+
azuread: 'entraid',
|
|
26
|
+
keycloakoidc: 'keycloak',
|
|
27
|
+
genericoidc: 'openid',
|
|
28
|
+
};
|
|
25
29
|
|
|
26
30
|
export default class AuthConfig extends SteveModel {
|
|
27
31
|
get _availableActions() {
|
|
@@ -6,13 +6,11 @@ import { insertAt, addObject, removeObject } from '@shell/utils/array';
|
|
|
6
6
|
import { downloadFile } from '@shell/utils/download';
|
|
7
7
|
import { parseSi } from '@shell/utils/units';
|
|
8
8
|
import { parseColor, textColor } from '@shell/utils/color';
|
|
9
|
-
import { addParams } from '@shell/utils/url';
|
|
10
9
|
import { isEmpty } from '@shell/utils/object';
|
|
11
10
|
import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
|
|
12
11
|
import { isHarvesterCluster } from '@shell/utils/cluster';
|
|
13
12
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
14
13
|
import { LINUX, WINDOWS } from '@shell/store/catalog';
|
|
15
|
-
import { KONTAINER_TO_DRIVER } from './management.cattle.io.kontainerdriver';
|
|
16
14
|
import { requireAsset } from '@shell/utils/require-asset';
|
|
17
15
|
import { PINNED_CLUSTERS } from '@shell/store/prefs';
|
|
18
16
|
import { copyTextToClipboard } from '@shell/utils/clipboard';
|
|
@@ -114,57 +112,6 @@ export default class MgmtCluster extends SteveModel {
|
|
|
114
112
|
return null;
|
|
115
113
|
}
|
|
116
114
|
|
|
117
|
-
get providerForEmberParam() {
|
|
118
|
-
// Ember wants one word called provider to tell what component to show, but has much indirect mapping to figure out what it is.
|
|
119
|
-
let provider;
|
|
120
|
-
|
|
121
|
-
// provisioner is status.driver
|
|
122
|
-
const provisioner = KONTAINER_TO_DRIVER[(this.provisioner || '').toLowerCase()] || this.provisioner;
|
|
123
|
-
|
|
124
|
-
if ( provisioner === 'rancherKubernetesEngine') {
|
|
125
|
-
// Look for a cloud provider in one of the node templates
|
|
126
|
-
if ( this.machinePools?.[0] ) {
|
|
127
|
-
provider = this.machinePools[0]?.nodeTemplate?.spec?.driver || null;
|
|
128
|
-
} else {
|
|
129
|
-
provider = 'custom';
|
|
130
|
-
}
|
|
131
|
-
} else if ( this.driver ) {
|
|
132
|
-
provider = this.driver;
|
|
133
|
-
} else if ( provisioner && provisioner.endsWith('v2') ) {
|
|
134
|
-
provider = provisioner;
|
|
135
|
-
} else {
|
|
136
|
-
provider = 'import';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return provider;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
get emberEditPath() {
|
|
143
|
-
const provider = this.providerForEmberParam;
|
|
144
|
-
|
|
145
|
-
// Avoid passing falsy values as query parameters
|
|
146
|
-
const qp = { };
|
|
147
|
-
|
|
148
|
-
if (provider) {
|
|
149
|
-
qp['provider'] = provider;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Copied out of https://github.com/rancher/ui/blob/20f56dc54c4fc09b5f911e533cb751c13609adaf/app/models/cluster.js#L844
|
|
153
|
-
if ( provider === 'import' && isEmpty(this.eksConfig) && isEmpty(this.gkeConfig) ) {
|
|
154
|
-
qp.importProvider = 'other';
|
|
155
|
-
} else if (
|
|
156
|
-
(provider === 'amazoneks' && !isEmpty(this.eksConfig) ) ||
|
|
157
|
-
(provider === 'gke' && !isEmpty(this.gkeConfig) )
|
|
158
|
-
// || something for aks v2
|
|
159
|
-
) {
|
|
160
|
-
qp.importProvider = KONTAINER_TO_DRIVER[provider];
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const path = addParams(`/c/${ escape(this.id) }/edit`, qp);
|
|
164
|
-
|
|
165
|
-
return path;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
115
|
get groupByLabel() {
|
|
169
116
|
return this.$rootGetters['i18n/t']('resourceTable.groupLabel.notInAWorkspace');
|
|
170
117
|
}
|
|
@@ -8,7 +8,7 @@ export default class Feature extends HybridModel {
|
|
|
8
8
|
|
|
9
9
|
get enabled() {
|
|
10
10
|
// If lockedValue is not null, then this is the value that the flag is locked to, so that should be used
|
|
11
|
-
if (this.status.lockedValue !== null) {
|
|
11
|
+
if (this.status && this.status.lockedValue !== null) {
|
|
12
12
|
return this.status.lockedValue;
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -17,7 +17,7 @@ export default class Feature extends HybridModel {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
get restartRequired() {
|
|
20
|
-
return !this.status
|
|
20
|
+
return !this.status?.dynamic;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
get canYaml() {
|
|
@@ -43,7 +43,7 @@ export default class Feature extends HybridModel {
|
|
|
43
43
|
// User can not disable or enable if the feature flag is locked
|
|
44
44
|
// Note: lockedValue is the value that the feature flag is locked to, so it can be true or false
|
|
45
45
|
// It can also be null, which indicates that the feature flag is not locked
|
|
46
|
-
enableAction.enabled = enableAction.enabled && (this.status.lockedValue === null);
|
|
46
|
+
enableAction.enabled = enableAction.enabled && (this.status && this.status.lockedValue === null);
|
|
47
47
|
|
|
48
48
|
out.unshift(enableAction);
|
|
49
49
|
|
|
@@ -2,10 +2,9 @@ import HybridModel from '@shell/plugins/steve/hybrid-class';
|
|
|
2
2
|
|
|
3
3
|
const HIDDEN = ['rke', 'rancherkubernetesengine'];
|
|
4
4
|
|
|
5
|
-
const V2 = ['amazoneks', 'googlegke', 'azureaks'];
|
|
6
5
|
const IMPORTABLE = ['amazoneks', 'googlegke', 'azureaks'];
|
|
7
6
|
|
|
8
|
-
//
|
|
7
|
+
// Short names that map from the full kontainer driver name
|
|
9
8
|
export const KONTAINER_TO_DRIVER = {
|
|
10
9
|
amazonelasticcontainerservice: 'amazoneks',
|
|
11
10
|
azurekubernetesservice: 'azureaks',
|
|
@@ -36,14 +35,6 @@ export const KEV1 = [
|
|
|
36
35
|
'googlekubernetesengine',
|
|
37
36
|
];
|
|
38
37
|
|
|
39
|
-
// And the Import page has even shorter ones that don't match kontainer or create...
|
|
40
|
-
export const DRIVER_TO_IMPORT = {
|
|
41
|
-
googlegke: 'gke',
|
|
42
|
-
amazoneks: 'eks',
|
|
43
|
-
azureaks: 'aks',
|
|
44
|
-
alibaba: 'alibabacloud'
|
|
45
|
-
};
|
|
46
|
-
|
|
47
38
|
export default class KontainerDriver extends HybridModel {
|
|
48
39
|
get showCreate() {
|
|
49
40
|
if ( HIDDEN.includes(this.driverName) ) {
|
|
@@ -57,22 +48,6 @@ export default class KontainerDriver extends HybridModel {
|
|
|
57
48
|
return this.showCreate && IMPORTABLE.includes(this.driverName);
|
|
58
49
|
}
|
|
59
50
|
|
|
60
|
-
get emberCreatePath() {
|
|
61
|
-
let driver = this.driverName;
|
|
62
|
-
|
|
63
|
-
if ( V2.includes(driver) && !driver.endsWith('v2') ) {
|
|
64
|
-
driver += 'v2';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return `/g/clusters/add/launch/${ driver }`;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
get emberImportPath() {
|
|
71
|
-
const provider = DRIVER_TO_IMPORT[this.driverName] || this.driverName;
|
|
72
|
-
|
|
73
|
-
return `/g/clusters/add/launch/import?importProvider=${ provider }`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
51
|
get driverName() {
|
|
77
52
|
if (!this.spec.builtIn) {
|
|
78
53
|
// if the driver is not built in, there's a good change its a custom one
|
|
@@ -5,25 +5,39 @@ import { set } from '@shell/utils/object';
|
|
|
5
5
|
|
|
6
6
|
export default class AlertmanagerConfig extends SteveModel {
|
|
7
7
|
applyDefaults() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
const spec = this.spec || {};
|
|
9
|
+
|
|
10
|
+
spec.receivers = spec.receivers || [];
|
|
11
|
+
|
|
12
|
+
// Always provide a route object so the Route tab has something to bind to,
|
|
13
|
+
// even when loading a resource that was saved without `spec.route`.
|
|
14
|
+
const route = { ...(spec.route || {}) };
|
|
15
|
+
|
|
16
|
+
route.groupBy = route.groupBy || [];
|
|
17
|
+
route.groupWait = route.groupWait || '30s';
|
|
18
|
+
route.groupInterval = route.groupInterval || '5m';
|
|
19
|
+
route.repeatInterval = route.repeatInterval || '4h';
|
|
20
|
+
route.matchers = route.matchers || [];
|
|
21
|
+
|
|
22
|
+
spec.route = route;
|
|
23
|
+
|
|
24
|
+
set(this, 'spec', spec);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
cleanForSave(data, forNew) {
|
|
28
|
+
const val = super.cleanForSave(data, forNew);
|
|
29
|
+
const route = val?.spec?.route;
|
|
30
|
+
|
|
31
|
+
if (route) {
|
|
32
|
+
// When a route is present its `receiver` is required and must match an
|
|
33
|
+
// entry in `spec.receivers`. Until the user has defined a receiver the
|
|
34
|
+
// root route can't direct alerts anywhere, so drop it entirely
|
|
35
|
+
if (!route.receiver) {
|
|
36
|
+
delete val.spec.route;
|
|
23
37
|
}
|
|
24
|
-
}
|
|
38
|
+
}
|
|
25
39
|
|
|
26
|
-
|
|
40
|
+
return val;
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
get _availableActions() {
|
package/models/nodedriver.js
CHANGED
|
@@ -13,6 +13,13 @@ export default class NodeDriver extends Driver {
|
|
|
13
13
|
return 'c-cluster-manager-driver-nodedriver';
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
get parentLocationOverride() {
|
|
17
|
+
return {
|
|
18
|
+
name: 'c-cluster-manager-driver-nodedriver',
|
|
19
|
+
params: { cluster: this.$rootGetters['clusterId'] }
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
get _availableActions() {
|
|
17
24
|
const out = [
|
|
18
25
|
{
|
package/models/pod.js
CHANGED
|
@@ -4,6 +4,7 @@ import { NODE, WORKLOAD_TYPES } from '@shell/config/types';
|
|
|
4
4
|
import { escapeHtml, shortenedImage } from '@shell/utils/string';
|
|
5
5
|
import WorkloadService from '@shell/models/workload.service';
|
|
6
6
|
import { deleteProperty } from '@shell/utils/object';
|
|
7
|
+
import { POD_RESTARTS_REG_EX } from '@shell/types/resources/pod';
|
|
7
8
|
|
|
8
9
|
export const WORKLOAD_PRIORITY = {
|
|
9
10
|
[WORKLOAD_TYPES.DEPLOYMENT]: 1,
|
|
@@ -222,6 +223,9 @@ export default class Pod extends WorkloadService {
|
|
|
222
223
|
return this.$rootGetters['i18n/t']('resourceTable.groupLabel.node', { name: escapeHtml(name) });
|
|
223
224
|
}
|
|
224
225
|
|
|
226
|
+
/**
|
|
227
|
+
* How many times has the first container restarted
|
|
228
|
+
*/
|
|
225
229
|
get restartCount() {
|
|
226
230
|
if (this.status.containerStatuses) {
|
|
227
231
|
return this.status?.containerStatuses[0].restartCount || 0;
|
|
@@ -230,6 +234,20 @@ export default class Pod extends WorkloadService {
|
|
|
230
234
|
return 0;
|
|
231
235
|
}
|
|
232
236
|
|
|
237
|
+
/**
|
|
238
|
+
* How many times does native kube report this pod has restarted
|
|
239
|
+
*/
|
|
240
|
+
get restartsCount() {
|
|
241
|
+
return this.metadata?.fields?.[3]?.match(POD_RESTARTS_REG_EX)?.[1] || '';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* When does native kube think the last pod restart happen?
|
|
246
|
+
*/
|
|
247
|
+
get restartsLaster() {
|
|
248
|
+
return this.metadata?.fields?.[3]?.match(POD_RESTARTS_REG_EX)?.[2] || '';
|
|
249
|
+
}
|
|
250
|
+
|
|
233
251
|
processSaveResponse(res) {
|
|
234
252
|
if (res._headers && res._headers.warning) {
|
|
235
253
|
const warnings = res._headers.warning.split('299') || [];
|
package/models/workload.js
CHANGED
|
@@ -9,6 +9,7 @@ import WorkloadService from '@shell/models/workload.service';
|
|
|
9
9
|
import { matching } from '@shell/utils/selector-typed';
|
|
10
10
|
import { defineAsyncComponent, markRaw } from 'vue';
|
|
11
11
|
import { useResourceCardRow } from '@shell/components/Resource/Detail/Card/StateCard/composables';
|
|
12
|
+
import { colorForState as colorForStateFn, stateDisplay as stateDisplayFn } from '@shell/plugins/dashboard-store/resource-class';
|
|
12
13
|
|
|
13
14
|
export const defaultContainer = {
|
|
14
15
|
imagePullPolicy: 'Always',
|
|
@@ -622,12 +623,29 @@ export default class Workload extends WorkloadService {
|
|
|
622
623
|
|
|
623
624
|
calcPodGauges(pods) {
|
|
624
625
|
const out = { };
|
|
626
|
+
let refPods = pods;
|
|
625
627
|
|
|
626
|
-
if (
|
|
628
|
+
if (this.metadata.associatedData) {
|
|
629
|
+
refPods = [];
|
|
630
|
+
this.metadata.associatedData.forEach((w) => {
|
|
631
|
+
if (w.gvk.kind.toLowerCase() !== POD) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return w.data.forEach((p) => {
|
|
636
|
+
refPods.push({
|
|
637
|
+
stateColor: colorForStateFn(p.state.name, p.state.error === 'true', p.state.transitioning === 'true'),
|
|
638
|
+
stateDisplay: stateDisplayFn(p.state.name),
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (!refPods) {
|
|
627
645
|
return out;
|
|
628
646
|
}
|
|
629
647
|
|
|
630
|
-
|
|
648
|
+
refPods.map((pod) => {
|
|
631
649
|
const { stateColor, stateDisplay } = pod;
|
|
632
650
|
|
|
633
651
|
if (out[stateDisplay]) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rancher/shell",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.12-rc.2",
|
|
4
4
|
"description": "Rancher Dashboard Shell",
|
|
5
5
|
"repository": "https://github.com/rancher/dashboard",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
"@aws-sdk/client-eks": "3.879.0",
|
|
33
33
|
"@aws-sdk/client-iam": "3.863.0",
|
|
34
34
|
"@aws-sdk/client-kms": "3.863.0",
|
|
35
|
-
"@smithy/fetch-http-handler": "5.1.1",
|
|
36
35
|
"@babel/plugin-proposal-optional-chaining": "7.21.0",
|
|
37
36
|
"@babel/plugin-proposal-private-methods": "7.18.6",
|
|
38
37
|
"@babel/plugin-proposal-private-property-in-object": "7.14.5",
|
|
@@ -40,6 +39,7 @@
|
|
|
40
39
|
"@novnc/novnc": "1.2.0",
|
|
41
40
|
"@popperjs/core": "2.11.8",
|
|
42
41
|
"@rancher/icons": "2.0.55",
|
|
42
|
+
"@smithy/fetch-http-handler": "5.1.1",
|
|
43
43
|
"@types/is-url": "1.2.30",
|
|
44
44
|
"@types/node": "25.3.3",
|
|
45
45
|
"@types/semver": "^7.5.8",
|
|
@@ -52,19 +52,19 @@
|
|
|
52
52
|
"@vue/vue3-jest": "27.0.0",
|
|
53
53
|
"add": "2.0.6",
|
|
54
54
|
"ansi_up": "5.0.0",
|
|
55
|
+
"axios": "1.15.0",
|
|
55
56
|
"axios-retry": "3.1.9",
|
|
56
|
-
"axios": "1.13.5",
|
|
57
57
|
"babel-eslint": "10.1.0",
|
|
58
58
|
"babel-plugin-module-resolver": "5.0.2",
|
|
59
59
|
"babel-preset-vue": "2.0.2",
|
|
60
60
|
"cache-loader": "4.1.0",
|
|
61
61
|
"chart.js": "4.5.1",
|
|
62
62
|
"clipboard-polyfill": "4.0.1",
|
|
63
|
-
"codemirror-editor-vue3": "2.8.0",
|
|
64
63
|
"codemirror": ">=5.64.0 <6",
|
|
64
|
+
"codemirror-editor-vue3": "2.8.0",
|
|
65
65
|
"color": "5.0.3",
|
|
66
|
-
"cookie-universal": "2.2.2",
|
|
67
66
|
"cookie": "0.7.0",
|
|
67
|
+
"cookie-universal": "2.2.2",
|
|
68
68
|
"core-js": "3.48.0",
|
|
69
69
|
"cron-validator": "1.4.0",
|
|
70
70
|
"cronstrue": "3.9.0",
|
|
@@ -73,14 +73,14 @@
|
|
|
73
73
|
"csv-loader": "3.0.3",
|
|
74
74
|
"custom-event-polyfill": "^1.0.7",
|
|
75
75
|
"cypress": "11.1.0",
|
|
76
|
-
"d3-selection": "3.0.0",
|
|
77
76
|
"d3": "7.3.0",
|
|
77
|
+
"d3-selection": "3.0.0",
|
|
78
78
|
"dayjs": "1.11.18",
|
|
79
|
-
"defu": "6.1.4",
|
|
80
79
|
"diff2html": "3.4.24",
|
|
81
80
|
"dompurify": "3.2.5",
|
|
82
81
|
"element-matches": "^0.1.2",
|
|
83
82
|
"entities": "4.5.0",
|
|
83
|
+
"eslint": "7.32.0",
|
|
84
84
|
"eslint-config-standard": "16.0.3",
|
|
85
85
|
"eslint-import-resolver-node": "0.3.9",
|
|
86
86
|
"eslint-module-utils": "2.6.1",
|
|
@@ -89,7 +89,6 @@
|
|
|
89
89
|
"eslint-plugin-jest": "24.4.0",
|
|
90
90
|
"eslint-plugin-n": "15.2.0",
|
|
91
91
|
"eslint-plugin-vue": "9.32.0",
|
|
92
|
-
"eslint": "7.32.0",
|
|
93
92
|
"event-target-shim": "5.0.1",
|
|
94
93
|
"express": "4.17.1",
|
|
95
94
|
"file-saver": "2.0.2",
|
|
@@ -100,13 +99,13 @@
|
|
|
100
99
|
"intl-messageformat": "7.8.4",
|
|
101
100
|
"ip": "2.0.1",
|
|
102
101
|
"is-url": "1.2.4",
|
|
102
|
+
"jest": "27.5.1",
|
|
103
103
|
"jest-serializer-vue": "2.0.2",
|
|
104
104
|
"jexl": "2.3.0",
|
|
105
|
-
"jest": "27.5.1",
|
|
106
105
|
"jquery": "3.5.1",
|
|
107
106
|
"js-cookie": "3.0.5",
|
|
108
|
-
"js-yaml-loader": "1.2.2",
|
|
109
107
|
"js-yaml": "4.1.1",
|
|
108
|
+
"js-yaml-loader": "1.2.2",
|
|
110
109
|
"jsdiff": "1.1.1",
|
|
111
110
|
"jsonpath-plus": "10.3.0",
|
|
112
111
|
"jsrsasign": "11.1.1",
|
|
@@ -118,8 +117,8 @@
|
|
|
118
117
|
"nyc": "17.1.0",
|
|
119
118
|
"papaparse": "5.3.0",
|
|
120
119
|
"portal-vue": "~3.0.0",
|
|
121
|
-
"sass-loader": "12.6.0",
|
|
122
120
|
"sass": "1.97.3",
|
|
121
|
+
"sass-loader": "12.6.0",
|
|
123
122
|
"serve-static": "1.14.1",
|
|
124
123
|
"set-cookie-parser": "2.4.6",
|
|
125
124
|
"shell-quote": "1.7.3",
|
|
@@ -131,10 +130,11 @@
|
|
|
131
130
|
"ufo": "0.7.11",
|
|
132
131
|
"unfetch": "4.2.0",
|
|
133
132
|
"url-parse": "1.5.10",
|
|
133
|
+
"vee-validate": "^4.15.1",
|
|
134
|
+
"vue": "3.5.29",
|
|
134
135
|
"vue-router": "4.6.4",
|
|
135
136
|
"vue-select": "4.0.0-beta.6",
|
|
136
137
|
"vue-server-renderer": "2.7.16",
|
|
137
|
-
"vue": "3.5.29",
|
|
138
138
|
"vue3-resize": "0.2.0",
|
|
139
139
|
"vue3-virtual-scroll-list": "0.2.1",
|
|
140
140
|
"vuedraggable": "4.1.0",
|
|
@@ -142,12 +142,12 @@
|
|
|
142
142
|
"webpack-bundle-analyzer": "4.10.2",
|
|
143
143
|
"webpack-virtual-modules": "0.6.2",
|
|
144
144
|
"worker-loader": "3.0.8",
|
|
145
|
+
"xterm": "5.2.1",
|
|
145
146
|
"xterm-addon-canvas": "0.5.0",
|
|
146
147
|
"xterm-addon-fit": "0.8.0",
|
|
147
148
|
"xterm-addon-search": "0.13.0",
|
|
148
149
|
"xterm-addon-web-links": "0.9.0",
|
|
149
150
|
"xterm-addon-webgl": "0.16.0",
|
|
150
|
-
"xterm": "5.2.1",
|
|
151
151
|
"yarn": "1.22.22"
|
|
152
152
|
},
|
|
153
153
|
"resolutions": {
|