@rancher/shell 3.0.8-rc.1 → 3.0.8-rc.12
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/brand/suse/banner.svg +1 -0
- package/assets/brand/suse/dark/banner.svg +1 -0
- package/assets/brand/suse/dark/login-landscape.svg +1 -0
- package/assets/brand/suse/dark/rancher-logo.svg +1 -1
- package/assets/brand/suse/favicon.png +0 -0
- package/assets/brand/suse/login-landscape.svg +1 -0
- package/assets/brand/suse/metadata.json +11 -1
- package/assets/brand/suse/rancher-logo.svg +1 -1
- package/assets/fonts/suse/suse-v2-latin-300.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-300.woff2 +0 -0
- package/assets/fonts/suse/suse-v2-latin-600.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-600.woff2 +0 -0
- package/assets/fonts/suse/suse-v2-latin-700.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-700.woff2 +0 -0
- package/assets/fonts/suse/suse-v2-latin-800.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-800.woff2 +0 -0
- package/assets/fonts/suse/suse-v2-latin-regular.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-regular.woff2 +0 -0
- package/assets/images/content/README.md +5 -0
- package/assets/images/content/cloud-native.svg +84 -0
- package/assets/images/content/dark/cloud-native.svg +21 -0
- package/assets/images/content/dark/shield.svg +59 -0
- package/assets/images/content/dark/suse.svg +10 -0
- package/assets/images/content/shield.svg +59 -0
- package/assets/images/content/suse.svg +10 -0
- package/assets/styles/base/_typography.scss +1 -0
- package/assets/styles/fonts/_fontstack.scss +53 -1
- package/assets/styles/global/_cards.scss +0 -3
- package/assets/styles/global/_layout.scss +21 -35
- package/assets/styles/themes/_dark.scss +1 -1
- package/assets/styles/themes/_light.scss +1 -1
- package/assets/styles/themes/_modern.scss +11 -3
- package/assets/styles/themes/_suse.scss +116 -24
- package/assets/translations/en-us.yaml +94 -10
- package/components/AutoscalerCard.vue +113 -0
- package/components/AutoscalerTab.vue +94 -0
- package/components/BackLink.vue +8 -0
- package/components/BannerGraphic.vue +36 -21
- package/components/BrandImage.vue +17 -6
- package/components/ClusterIconMenu.vue +1 -1
- package/components/ClusterProviderIcon.vue +1 -1
- package/components/Cron/CronExpressionEditor.vue +1 -1
- package/components/Cron/CronExpressionEditorModal.vue +1 -1
- package/components/Drawer/Chrome.vue +2 -6
- package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +4 -9
- package/components/Drawer/ResourceDetailDrawer/YamlTab.vue +3 -8
- package/components/Drawer/ResourceDetailDrawer/composables.ts +3 -4
- package/components/Drawer/ResourceDetailDrawer/index.vue +4 -9
- package/components/Drawer/ResourceDetailDrawer/types.ts +17 -0
- package/components/Drawer/types.ts +3 -0
- package/components/DynamicContent/DynamicContentBanner.vue +102 -0
- package/components/DynamicContent/DynamicContentCloseButton.vue +42 -0
- package/components/DynamicContent/DynamicContentIcon.vue +132 -0
- package/components/DynamicContent/DynamicContentPanel.vue +112 -0
- package/components/DynamicContent/content.ts +78 -0
- package/components/EmberPage.vue +1 -1
- package/components/IconOrSvg.vue +2 -2
- package/components/PaginatedResourceTable.vue +2 -6
- package/components/PopoverCard.vue +192 -0
- package/components/Questions/__tests__/index.test.ts +159 -0
- package/components/Resource/Detail/CopyToClipboard.vue +4 -1
- package/components/Resource/Detail/FetchLoader/composables.ts +18 -4
- package/components/Resource/Detail/Metadata/Annotations/index.vue +2 -2
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +1 -1
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +4 -0
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +1 -1
- package/components/Resource/Detail/Metadata/Labels/index.vue +2 -2
- package/components/Resource/Detail/Metadata/composables.ts +9 -9
- package/components/Resource/Detail/Metadata/index.vue +3 -3
- package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -19
- package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +0 -29
- package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +132 -150
- package/components/Resource/Detail/ResourcePopover/index.vue +54 -159
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +0 -2
- package/components/Resource/Detail/TitleBar/composables.ts +2 -1
- package/components/Resource/Detail/TitleBar/index.vue +10 -6
- package/components/Resource/Detail/composables.ts +12 -0
- package/components/ResourceDetail/Masthead/latest.vue +29 -0
- package/components/ResourceDetail/index.vue +4 -1
- package/components/ResourceList/Masthead.vue +1 -1
- package/components/ResourceTable.vue +1 -1
- package/components/SortableTable/index.vue +2 -1
- package/components/Tabbed/__tests__/index.test.ts +86 -0
- package/components/{nav/WindowManager → Window}/ContainerLogs.vue +1 -1
- package/components/{nav/WindowManager → Window}/ContainerLogsActions.vue +1 -0
- package/components/{nav/WindowManager → Window}/__tests__/ContainerLogs.test.ts +1 -1
- package/components/{nav/WindowManager → Window}/__tests__/ContainerShell.test.ts +2 -2
- package/components/__tests__/AutoscalerCard.test.ts +154 -0
- package/components/__tests__/AutoscalerTab.test.ts +125 -0
- package/components/__tests__/PopoverCard.test.ts +204 -0
- package/components/auth/SelectPrincipal.vue +24 -6
- package/components/auth/__tests__/SelectPrincipal.test.ts +119 -0
- package/components/auth/login/ldap.vue +3 -3
- package/components/form/NodeScheduling.vue +2 -2
- package/components/formatter/Autoscaler.vue +97 -0
- package/components/formatter/InternalExternalIP.vue +198 -24
- package/components/formatter/__tests__/Autoscaler.test.ts +156 -0
- package/components/formatter/__tests__/InternalExternalIP.test.ts +133 -0
- package/components/google/util/__tests__/formatter.test.ts +47 -0
- package/components/google/util/formatter.ts +5 -2
- package/components/nav/Group.vue +21 -5
- package/components/nav/Header.vue +37 -17
- package/components/nav/NamespaceFilter.vue +13 -1
- package/components/nav/NotificationCenter/index.vue +2 -1
- package/components/nav/TopLevelMenu.helper.ts +16 -6
- package/components/nav/TopLevelMenu.vue +4 -2
- package/components/nav/Type.vue +8 -3
- package/components/{DraggableZone.vue → nav/WindowManager/PinArea.vue} +47 -80
- package/components/nav/WindowManager/composables/useComponentsMount.ts +70 -0
- package/components/nav/WindowManager/composables/useDimensionsHandler.ts +105 -0
- package/components/nav/WindowManager/composables/useDragHandler.ts +99 -0
- package/components/nav/WindowManager/composables/usePanelHandler.ts +72 -0
- package/components/nav/WindowManager/composables/usePanelsHandler.ts +14 -0
- package/components/nav/WindowManager/composables/useResizeHandler.ts +167 -0
- package/components/nav/WindowManager/composables/useTabsHandler.ts +51 -0
- package/components/nav/WindowManager/constants.ts +23 -0
- package/components/nav/WindowManager/index.vue +61 -575
- package/components/nav/WindowManager/panels/HorizontalPanel.vue +265 -0
- package/components/nav/WindowManager/panels/TabBodyContainer.vue +39 -0
- package/components/nav/WindowManager/panels/VerticalPanel.vue +308 -0
- package/components/nav/__tests__/Type.test.ts +59 -0
- package/components/templates/default.vue +4 -40
- package/components/templates/home.vue +31 -5
- package/components/templates/plain.vue +30 -4
- package/components/templates/standalone.vue +1 -1
- package/composables/useI18n.ts +10 -1
- package/composables/useInterval.ts +15 -0
- package/config/__test__/uiplugins.test.ts +309 -0
- package/config/labels-annotations.js +9 -1
- package/config/product/explorer.js +3 -1
- package/config/product/manager.js +20 -9
- package/config/router/navigation-guards/clusters.js +3 -3
- package/config/router/navigation-guards/products.js +1 -1
- package/config/router/routes.js +10 -2
- package/config/settings.ts +2 -1
- package/config/store.js +4 -2
- package/config/table-headers.js +8 -0
- package/config/types.js +9 -0
- package/config/uiplugins.js +46 -2
- package/config/version.js +1 -1
- package/core/__test__/extension-manager-impl.test.js +236 -0
- package/core/extension-manager-impl.js +21 -4
- package/core/plugin-helpers.ts +4 -2
- package/core/plugins-loader.js +2 -2
- package/core/types-provisioning.ts +8 -1
- package/detail/pod.vue +1 -0
- package/detail/provisioning.cattle.io.cluster.vue +19 -7
- package/dialog/DeveloperLoadExtensionDialog.vue +13 -4
- package/dialog/RollbackWorkloadDialog.vue +2 -5
- package/dialog/SearchDialog.vue +1 -0
- package/directives/ui-context.ts +103 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +2 -2
- package/edit/auth/__tests__/oidc.test.ts +26 -0
- package/edit/auth/github.vue +5 -0
- package/edit/auth/oidc.vue +5 -1
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +1 -0
- package/edit/cloudcredential.vue +1 -1
- package/edit/configmap.vue +1 -0
- package/edit/constraints.gatekeeper.sh.constraint/index.vue +1 -0
- package/edit/fleet.cattle.io.gitrepo.vue +0 -10
- package/edit/fleet.cattle.io.helmop.vue +6 -6
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +1 -0
- package/edit/logging-flow/index.vue +1 -0
- package/edit/logging.banzaicloud.io.output/index.vue +1 -0
- package/edit/management.cattle.io.fleetworkspace.vue +1 -1
- package/edit/management.cattle.io.project.vue +1 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +4 -1
- package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +2 -1
- package/edit/monitoring.coreos.com.prometheusrule/index.vue +1 -0
- package/edit/monitoring.coreos.com.receiver/index.vue +2 -1
- package/edit/monitoring.coreos.com.route.vue +1 -1
- package/edit/namespace.vue +1 -0
- package/edit/networking.istio.io.destinationrule/index.vue +1 -0
- package/edit/networking.k8s.io.ingress/index.vue +1 -0
- package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -0
- package/edit/networking.k8s.io.networkpolicy/index.vue +1 -0
- package/edit/node.vue +1 -0
- package/edit/persistentvolume/index.vue +27 -22
- package/edit/persistentvolume/plugins/awsElasticBlockStore.vue +13 -14
- package/edit/persistentvolume/plugins/azureDisk.vue +49 -48
- package/edit/persistentvolume/plugins/azureFile.vue +15 -14
- package/edit/persistentvolume/plugins/cephfs.vue +15 -14
- package/edit/persistentvolume/plugins/cinder.vue +15 -14
- package/edit/persistentvolume/plugins/csi.vue +18 -16
- package/edit/persistentvolume/plugins/fc.vue +13 -14
- package/edit/persistentvolume/plugins/flexVolume.vue +15 -14
- package/edit/persistentvolume/plugins/flocker.vue +1 -3
- package/edit/persistentvolume/plugins/gcePersistentDisk.vue +13 -14
- package/edit/persistentvolume/plugins/glusterfs.vue +15 -14
- package/edit/persistentvolume/plugins/hostPath.vue +40 -39
- package/edit/persistentvolume/plugins/iscsi.vue +13 -14
- package/edit/persistentvolume/plugins/local.vue +1 -3
- package/edit/persistentvolume/plugins/longhorn.vue +23 -22
- package/edit/persistentvolume/plugins/nfs.vue +15 -14
- package/edit/persistentvolume/plugins/photonPersistentDisk.vue +1 -14
- package/edit/persistentvolume/plugins/portworxVolume.vue +15 -14
- package/edit/persistentvolume/plugins/quobyte.vue +15 -14
- package/edit/persistentvolume/plugins/rbd.vue +15 -14
- package/edit/persistentvolume/plugins/scaleIO.vue +15 -14
- package/edit/persistentvolume/plugins/storageos.vue +15 -14
- package/edit/persistentvolume/plugins/vsphereVolume.vue +1 -3
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +32 -5
- package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.test.ts +35 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +155 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
- package/edit/provisioning.cattle.io.cluster/index.vue +28 -18
- package/edit/provisioning.cattle.io.cluster/rke2.vue +50 -16
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +107 -5
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +92 -4
- package/edit/secret/index.vue +1 -1
- package/edit/service.vue +9 -4
- package/edit/serviceaccount.vue +1 -0
- package/edit/storage.k8s.io.storageclass/index.vue +1 -0
- package/edit/workload/index.vue +2 -1
- package/edit/workload/mixins/workload.js +1 -1
- package/initialize/App.vue +4 -4
- package/initialize/install-directives.js +2 -0
- package/initialize/install-plugins.js +19 -2
- package/list/provisioning.cattle.io.cluster.vue +15 -2
- package/machine-config/amazonec2.vue +42 -135
- package/machine-config/components/EC2Networking.vue +490 -0
- package/machine-config/components/__tests__/EC2Networking.test.ts +148 -0
- package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +294 -0
- package/machine-config/digitalocean.vue +11 -0
- package/machine-config/google.vue +1 -1
- package/mixins/__tests__/brand.spec.ts +2 -2
- package/mixins/__tests__/chart.test.ts +21 -0
- package/mixins/brand.js +1 -7
- package/mixins/chart.js +7 -1
- package/mixins/create-edit-view/index.js +5 -0
- package/models/__tests__/chart.test.ts +33 -4
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +112 -5
- package/models/chart.js +25 -13
- package/models/cluster/node.js +13 -6
- package/models/cluster.x-k8s.io.machine.js +10 -20
- package/models/cluster.x-k8s.io.machinedeployment.js +5 -1
- package/models/management.cattle.io.cluster.js +21 -3
- package/models/management.cattle.io.kontainerdriver.js +1 -0
- package/models/provisioning.cattle.io.cluster.js +249 -33
- package/package.json +8 -7
- package/pages/auth/login.vue +41 -5
- package/pages/auth/setup.vue +1 -1
- package/pages/auth/verify.vue +3 -3
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +135 -0
- package/pages/c/_cluster/apps/charts/chart.vue +33 -15
- package/pages/c/_cluster/apps/charts/index.vue +11 -13
- package/pages/c/_cluster/apps/charts/install.vue +1 -1
- package/pages/c/_cluster/explorer/index.vue +8 -6
- package/pages/c/_cluster/manager/hostedprovider/index.vue +220 -0
- package/pages/c/_cluster/settings/brand.vue +1 -1
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +7 -0
- package/pages/c/_cluster/uiplugins/catalogs.vue +147 -0
- package/pages/c/_cluster/uiplugins/index.vue +126 -184
- package/pages/home.vue +14 -4
- package/pkg/auto-import.js +3 -3
- package/pkg/dynamic-importer.lib.js +5 -1
- package/pkg/import.js +1 -1
- package/plugins/dashboard-client-init.js +3 -0
- package/plugins/dashboard-store/getters.js +19 -2
- package/plugins/dashboard-store/model-loader.js +1 -1
- package/plugins/dashboard-store/resource-class.js +10 -6
- package/plugins/dynamic-content.js +13 -0
- package/plugins/i18n.js +8 -0
- package/plugins/plugin.js +2 -2
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +333 -0
- package/plugins/steve/steve-class.js +1 -1
- package/plugins/steve/steve-pagination-utils.ts +39 -20
- package/plugins/steve/subscribe.js +17 -9
- package/plugins/subscribe-events.ts +4 -2
- package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +6 -34
- package/rancher-components/Pill/RcStatusBadge/index.ts +0 -1
- package/rancher-components/Pill/RcStatusBadge/types.ts +1 -1
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +5 -28
- package/rancher-components/Pill/RcStatusIndicator/types.ts +2 -1
- package/rancher-components/Pill/types.ts +0 -1
- package/rancher-components/RcDropdown/RcDropdownItem.vue +1 -0
- package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +5 -1
- package/rancher-components/RcIcon/RcIcon.test.ts +51 -0
- package/rancher-components/RcIcon/RcIcon.vue +46 -0
- package/rancher-components/RcIcon/index.ts +1 -0
- package/rancher-components/RcIcon/types.ts +160 -0
- package/rancher-components/utils/status.test.ts +67 -0
- package/rancher-components/utils/status.ts +77 -0
- package/scripts/typegen.sh +1 -0
- package/store/__tests__/catalog.test.ts +1 -1
- package/store/action-menu.js +8 -0
- package/store/auth.js +4 -4
- package/store/catalog.js +6 -0
- package/store/features.js +1 -0
- package/store/i18n.js +3 -3
- package/store/index.js +40 -19
- package/store/notifications.ts +51 -4
- package/store/plugins.js +7 -3
- package/store/prefs.js +6 -6
- package/store/type-map.js +7 -7
- package/store/ui-context.ts +86 -0
- package/store/wm.ts +244 -0
- package/types/notifications/index.ts +27 -3
- package/types/shell/index.d.ts +80 -4
- package/types/store/__tests__/pagination.types.spec.ts +137 -0
- package/types/store/pagination.types.ts +157 -9
- package/types/store/subscribe-events.types.ts +8 -1
- package/types/store/subscribe.types.ts +1 -0
- package/types/window-manager.ts +24 -0
- package/utils/__tests__/object.test.ts +19 -0
- package/utils/__tests__/provider.test.ts +98 -0
- package/utils/__tests__/selector-typed.test.ts +263 -0
- package/utils/__tests__/version.test.ts +19 -1
- package/utils/autoscaler-utils.ts +7 -0
- package/utils/back-off.ts +3 -3
- package/utils/brand.ts +29 -0
- package/utils/chart.js +18 -0
- package/utils/color.js +1 -1
- package/utils/dynamic-content/__tests__/announcement.test.ts +498 -0
- package/utils/dynamic-content/__tests__/info.test.ts +21 -9
- package/utils/dynamic-content/announcement.ts +142 -0
- package/utils/dynamic-content/example.json +40 -0
- package/utils/dynamic-content/index.ts +6 -2
- package/utils/dynamic-content/info.ts +44 -2
- package/utils/dynamic-content/new-release.ts +1 -1
- package/utils/dynamic-content/notification-handler.ts +48 -0
- package/utils/dynamic-content/types.d.ts +53 -1
- package/utils/dynamic-importer.js +2 -2
- package/utils/favicon.js +4 -4
- package/utils/object.js +20 -2
- package/utils/pagination-utils.ts +2 -2
- package/utils/pagination-wrapper.ts +13 -9
- package/utils/provider.ts +14 -0
- package/utils/scroll.js +7 -0
- package/utils/selector-typed.ts +6 -2
- package/utils/settings.ts +15 -0
- package/utils/unit-tests/pagination-utils.spec.ts +8 -8
- package/utils/validators/machine-pool.ts +13 -3
- package/utils/version.js +15 -0
- package/vue.config.js +3 -3
- package/assets/images/icons/document.svg +0 -3
- package/plugins/nuxt-client-init.js +0 -3
- package/store/wm.js +0 -95
- /package/components/{nav/WindowManager → Window}/ChartReadme.vue +0 -0
- /package/components/{nav/WindowManager → Window}/ContainerShell.vue +0 -0
- /package/components/{nav/WindowManager → Window}/KubectlShell.vue +0 -0
- /package/components/{nav/WindowManager → Window}/MachineSsh.vue +0 -0
- /package/components/{nav/WindowManager → Window}/Window.vue +0 -0
package/store/i18n.js
CHANGED
|
@@ -285,8 +285,8 @@ export const actions = {
|
|
|
285
285
|
return;
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
const lastLoad = rootState.$
|
|
289
|
-
const i18nExt = rootState.$
|
|
288
|
+
const lastLoad = rootState.$extension?.lastLoad;
|
|
289
|
+
const i18nExt = rootState.$extension?.getDynamic('l10n', locale);
|
|
290
290
|
const reload = lastLoaded < lastLoad;
|
|
291
291
|
|
|
292
292
|
lastLoaded = lastLoad;
|
|
@@ -314,7 +314,7 @@ export const actions = {
|
|
|
314
314
|
|
|
315
315
|
// load all of the default locales from the plugins for fallback
|
|
316
316
|
if (locale !== DEFAULT_LOCALE) {
|
|
317
|
-
const defaultI18nExt = rootState.$
|
|
317
|
+
const defaultI18nExt = rootState.$extension?.getDynamic('l10n', DEFAULT_LOCALE);
|
|
318
318
|
|
|
319
319
|
if (defaultI18nExt && defaultI18nExt.length) {
|
|
320
320
|
defaultI18nExt.forEach((fn) => {
|
package/store/index.js
CHANGED
|
@@ -233,7 +233,7 @@ const updateActiveNamespaceCache = (state, activeNamespaceCache) => {
|
|
|
233
233
|
* Are we in the vai enabled world where mgmt clusters are paginated?
|
|
234
234
|
*/
|
|
235
235
|
const paginateClusters = ({ rootGetters, state }) => {
|
|
236
|
-
return paginationUtils.isEnabled({ rootGetters, $
|
|
236
|
+
return paginationUtils.isEnabled({ rootGetters, $extension: state.$extension }, { store: 'management', resource: { id: MANAGEMENT.CLUSTER, context: 'side-bar' } });
|
|
237
237
|
};
|
|
238
238
|
|
|
239
239
|
export const state = () => {
|
|
@@ -262,8 +262,9 @@ export const state = () => {
|
|
|
262
262
|
$router: markRaw({}),
|
|
263
263
|
$route: markRaw({}),
|
|
264
264
|
$plugin: markRaw({}),
|
|
265
|
+
$extension: markRaw({}),
|
|
265
266
|
showWorkspaceSwitcher: true,
|
|
266
|
-
|
|
267
|
+
localCluster: null,
|
|
267
268
|
};
|
|
268
269
|
};
|
|
269
270
|
|
|
@@ -272,10 +273,20 @@ export const getters = {
|
|
|
272
273
|
return state.clusterReady === true;
|
|
273
274
|
},
|
|
274
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Cache of the mgmt cluster fetched at start up
|
|
278
|
+
*
|
|
279
|
+
* We cannot rely on the store to cache this as the store may contain a page without the local cluster
|
|
280
|
+
*/
|
|
281
|
+
localCluster(state) {
|
|
282
|
+
return state.localCluster;
|
|
283
|
+
},
|
|
284
|
+
|
|
275
285
|
isMultiCluster(state, getters) {
|
|
276
|
-
const
|
|
286
|
+
const clusterCount = getters['management/all'](COUNT)?.[0]?.counts?.[MANAGEMENT.CLUSTER]?.summary?.count || 0;
|
|
287
|
+
const localCluster = getters['localCluster'];
|
|
277
288
|
|
|
278
|
-
if (
|
|
289
|
+
if (clusterCount === 1 && !!localCluster) {
|
|
279
290
|
return false;
|
|
280
291
|
} else {
|
|
281
292
|
return true;
|
|
@@ -592,10 +603,9 @@ export const getters = {
|
|
|
592
603
|
},
|
|
593
604
|
|
|
594
605
|
isStandaloneHarvester(state, getters) {
|
|
595
|
-
const
|
|
596
|
-
const cluster = clusters.find((c) => c.id === 'local') || {};
|
|
606
|
+
const localCluster = getters['localCluster'];
|
|
597
607
|
|
|
598
|
-
return getters['isSingleProduct'] &&
|
|
608
|
+
return getters['isSingleProduct'] && localCluster?.isHarvester && !getters['isRancherInHarvester'];
|
|
599
609
|
},
|
|
600
610
|
|
|
601
611
|
showTopLevelMenu(getters) {
|
|
@@ -640,9 +650,10 @@ export const mutations = {
|
|
|
640
650
|
clearPageActionHandler(state) {
|
|
641
651
|
state.pageActionHandler = null;
|
|
642
652
|
},
|
|
643
|
-
managementChanged(state, { ready, isRancher }) {
|
|
653
|
+
managementChanged(state, { ready, isRancher, localCluster }) {
|
|
644
654
|
state.managementReady = ready;
|
|
645
655
|
state.isRancher = isRancher;
|
|
656
|
+
state.localCluster = localCluster;
|
|
646
657
|
},
|
|
647
658
|
clusterReady(state, ready) {
|
|
648
659
|
state.clusterReady = ready;
|
|
@@ -765,6 +776,7 @@ export const mutations = {
|
|
|
765
776
|
},
|
|
766
777
|
|
|
767
778
|
setPlugin(state, pluginDefinition) {
|
|
779
|
+
state.$extension = markRaw(pluginDefinition || {});
|
|
768
780
|
state.$plugin = markRaw(pluginDefinition || {});
|
|
769
781
|
},
|
|
770
782
|
|
|
@@ -846,11 +858,21 @@ export const actions = {
|
|
|
846
858
|
|
|
847
859
|
res = await allHash(promises);
|
|
848
860
|
|
|
861
|
+
let localCluster = null;
|
|
862
|
+
|
|
849
863
|
if (!res[MANAGEMENT.SETTING] || !paginateClusters({ rootGetters, state })) {
|
|
850
864
|
// This introduces a synchronous request, however we need settings to determine if SSP is enabled
|
|
851
|
-
|
|
852
|
-
res[MANAGEMENT.CLUSTER] = await dispatch('management/findAll', { type: MANAGEMENT.CLUSTER, opt: { watch: false } });
|
|
865
|
+
await dispatch('management/findAll', { type: MANAGEMENT.CLUSTER, opt: { watch: false } });
|
|
853
866
|
toWatch.push(MANAGEMENT.CLUSTER);
|
|
867
|
+
|
|
868
|
+
localCluster = getters['management/byId'](MANAGEMENT.CLUSTER, 'local');
|
|
869
|
+
} else {
|
|
870
|
+
try {
|
|
871
|
+
localCluster = await dispatch('management/find', {
|
|
872
|
+
type: MANAGEMENT.CLUSTER, id: 'local', opt: { watch: false }
|
|
873
|
+
});
|
|
874
|
+
} catch (e) { // we don't care about errors, specifically 404s
|
|
875
|
+
}
|
|
854
876
|
}
|
|
855
877
|
|
|
856
878
|
// See comment above. Now that we have feature flags we can watch resources
|
|
@@ -858,11 +880,7 @@ export const actions = {
|
|
|
858
880
|
dispatch('management/watch', { type });
|
|
859
881
|
});
|
|
860
882
|
|
|
861
|
-
const isMultiCluster = getters['isMultiCluster'];
|
|
862
|
-
|
|
863
883
|
// If the local cluster is a Harvester cluster and 'rancher-manager-support' is true, it means that the embedded Rancher is being used.
|
|
864
|
-
const localCluster = res[MANAGEMENT.CLUSTER]?.find((c) => c.id === 'local');
|
|
865
|
-
|
|
866
884
|
if (localCluster?.isHarvester) {
|
|
867
885
|
const harvesterSetting = await dispatch('cluster/findAll', { type: HCI.SETTING, opt: { url: `/v1/harvester/${ HCI.SETTING }s` } });
|
|
868
886
|
const rancherManagerSupport = harvesterSetting.find((setting) => setting.id === 'rancher-manager-support');
|
|
@@ -904,6 +922,7 @@ export const actions = {
|
|
|
904
922
|
commit('managementChanged', {
|
|
905
923
|
ready: true,
|
|
906
924
|
isRancher,
|
|
925
|
+
localCluster
|
|
907
926
|
});
|
|
908
927
|
|
|
909
928
|
if ( res[FLEET.WORKSPACE] ) {
|
|
@@ -914,6 +933,8 @@ export const actions = {
|
|
|
914
933
|
});
|
|
915
934
|
}
|
|
916
935
|
|
|
936
|
+
const isMultiCluster = getters['isMultiCluster'];
|
|
937
|
+
|
|
917
938
|
console.log(`Done loading management; isRancher=${ isRancher }; isMultiCluster=${ isMultiCluster }`); // eslint-disable-line no-console
|
|
918
939
|
},
|
|
919
940
|
|
|
@@ -1176,7 +1197,7 @@ export const actions = {
|
|
|
1176
1197
|
|
|
1177
1198
|
store.dispatch('gcStopIntervals');
|
|
1178
1199
|
|
|
1179
|
-
Object.values(this.$
|
|
1200
|
+
Object.values(this.$extension.getPlugins()).forEach((p) => {
|
|
1180
1201
|
if (p.onLogOut) {
|
|
1181
1202
|
p.onLogOut(store);
|
|
1182
1203
|
}
|
|
@@ -1232,10 +1253,10 @@ export const actions = {
|
|
|
1232
1253
|
}
|
|
1233
1254
|
},
|
|
1234
1255
|
|
|
1235
|
-
|
|
1236
|
-
commit('setRouter',
|
|
1237
|
-
commit('setRoute',
|
|
1238
|
-
commit('setPlugin',
|
|
1256
|
+
dashboardClientInit({ dispatch, commit, rootState }, context) {
|
|
1257
|
+
commit('setRouter', context.app.router);
|
|
1258
|
+
commit('setRoute', context.route);
|
|
1259
|
+
commit('setPlugin', context.app.$extension);
|
|
1239
1260
|
|
|
1240
1261
|
dispatch('management/rehydrateSubscribe');
|
|
1241
1262
|
dispatch('cluster/rehydrateSubscribe');
|
package/store/notifications.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { md5 } from '@shell/utils/crypto';
|
|
2
2
|
import { randomStr } from '@shell/utils/string';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
EncryptedNotification,
|
|
5
|
+
Notification,
|
|
6
|
+
NotificationLevel,
|
|
7
|
+
NotificationHandlerExtensionName,
|
|
8
|
+
StoredNotification
|
|
9
|
+
} from '@shell/types/notifications';
|
|
4
10
|
import { encrypt, decrypt, deriveKey } from '@shell/utils/crypto/encryption';
|
|
5
11
|
|
|
6
12
|
/**
|
|
@@ -65,6 +71,9 @@ async function saveEncryptedNotification(getters: any, notification: Notificatio
|
|
|
65
71
|
level: notification.level,
|
|
66
72
|
primaryAction: notification.primaryAction,
|
|
67
73
|
secondaryAction: notification.secondaryAction,
|
|
74
|
+
preference: notification.preference,
|
|
75
|
+
handlerName: notification.handlerName,
|
|
76
|
+
data: notification.data,
|
|
68
77
|
};
|
|
69
78
|
|
|
70
79
|
const localStorageKey = getters['localStorageKey'];
|
|
@@ -108,15 +117,23 @@ export const getters = {
|
|
|
108
117
|
return state.notifications;
|
|
109
118
|
},
|
|
110
119
|
|
|
120
|
+
visible: (state: NotificationsStore) => {
|
|
121
|
+
return state.notifications.filter((n) => n.level !== NotificationLevel.Hidden);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
hidden: (state: NotificationsStore) => {
|
|
125
|
+
return state.notifications.filter((n) => n.level === NotificationLevel.Hidden);
|
|
126
|
+
},
|
|
127
|
+
|
|
111
128
|
item: (state: NotificationsStore) => {
|
|
112
129
|
return (id: string) => {
|
|
113
130
|
return state.notifications.find((i) => i.id === id);
|
|
114
131
|
};
|
|
115
132
|
},
|
|
116
133
|
|
|
117
|
-
// Count of unread notifications
|
|
134
|
+
// Count of unread notifications - only considers visible notifications
|
|
118
135
|
unreadCount: (state: NotificationsStore) => {
|
|
119
|
-
return state.notifications.filter((n) => !n.read).length;
|
|
136
|
+
return state.notifications.filter((n) => !n.read && n.level !== NotificationLevel.Hidden).length;
|
|
120
137
|
},
|
|
121
138
|
|
|
122
139
|
/**
|
|
@@ -192,9 +209,10 @@ export const mutations = {
|
|
|
192
209
|
syncIndex(state);
|
|
193
210
|
},
|
|
194
211
|
|
|
212
|
+
// Only mark visible notifications as read via mark all
|
|
195
213
|
markAllRead(state: NotificationsStore) {
|
|
196
214
|
state.notifications.forEach((notification) => {
|
|
197
|
-
if (!notification.read) {
|
|
215
|
+
if (!notification.read && notification.level !== NotificationLevel.Hidden) {
|
|
198
216
|
notification.read = true;
|
|
199
217
|
}
|
|
200
218
|
});
|
|
@@ -252,6 +270,20 @@ export const mutations = {
|
|
|
252
270
|
},
|
|
253
271
|
};
|
|
254
272
|
|
|
273
|
+
async function callNotifyHandler({ $extension }: any, notification: Notification, read: boolean) {
|
|
274
|
+
if (notification?.handlerName) {
|
|
275
|
+
const handler = $extension.getDynamic(NotificationHandlerExtensionName, notification.handlerName);
|
|
276
|
+
|
|
277
|
+
if (handler) {
|
|
278
|
+
try {
|
|
279
|
+
await handler.onReadUpdated(notification, read);
|
|
280
|
+
} catch (e) {
|
|
281
|
+
console.error('Error invoking notification handler', e); // eslint-disable-line no-console
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
255
287
|
export const actions = {
|
|
256
288
|
async add( { commit, dispatch, getters }: any, notification: Notification) {
|
|
257
289
|
// We encrypt the notification on add - this is the only time we will encrypt it
|
|
@@ -295,6 +327,10 @@ export const actions = {
|
|
|
295
327
|
if (notification?.preference) {
|
|
296
328
|
await dispatch('prefs/set', notification.preference, { root: true });
|
|
297
329
|
}
|
|
330
|
+
|
|
331
|
+
if (notification?.handlerName) {
|
|
332
|
+
await callNotifyHandler({ $extension: (this as any).$extension }, notification, true);
|
|
333
|
+
}
|
|
298
334
|
},
|
|
299
335
|
|
|
300
336
|
async markUnread({ commit, dispatch, getters }: any, id: string) {
|
|
@@ -309,6 +345,10 @@ export const actions = {
|
|
|
309
345
|
value: notification.preference.unsetValue || '',
|
|
310
346
|
}, { root: true });
|
|
311
347
|
}
|
|
348
|
+
|
|
349
|
+
if (notification?.handlerName) {
|
|
350
|
+
await callNotifyHandler({ $extension: (this as any).$extension }, notification, false);
|
|
351
|
+
}
|
|
312
352
|
},
|
|
313
353
|
|
|
314
354
|
async markAllRead({ commit, dispatch, getters }: any) {
|
|
@@ -321,6 +361,13 @@ export const actions = {
|
|
|
321
361
|
for (let i = 0; i < withPreference.length; i++) {
|
|
322
362
|
await dispatch('prefs/set', withPreference[i].preference, { root: true });
|
|
323
363
|
}
|
|
364
|
+
|
|
365
|
+
// For all notifications that have a handler, call the handler
|
|
366
|
+
const withHandler = getters.all.filter((n: Notification) => !!n.handlerName);
|
|
367
|
+
|
|
368
|
+
for (let i = 0; i < withHandler.length; i++) {
|
|
369
|
+
await callNotifyHandler({ $extension: (this as any).$extension }, withHandler[i], true);
|
|
370
|
+
}
|
|
324
371
|
},
|
|
325
372
|
|
|
326
373
|
remove({ commit, getters }: any, id: string) {
|
package/store/plugins.js
CHANGED
|
@@ -43,7 +43,6 @@ export const rke1Supports = [
|
|
|
43
43
|
// Map a credential driver name to a component name
|
|
44
44
|
// e.g. ec2 and eks both use the 'aws' driver to share the same pool of creds.
|
|
45
45
|
const driverMap = {
|
|
46
|
-
aks: 'azure',
|
|
47
46
|
amazonec2: 'aws',
|
|
48
47
|
amazoneks: 'aws',
|
|
49
48
|
amazonelasticcontainerservice: 'aws',
|
|
@@ -62,6 +61,9 @@ const driverToFieldMap = {
|
|
|
62
61
|
aws: 'amazonec2',
|
|
63
62
|
gcp: 'google',
|
|
64
63
|
oracle: 'oci',
|
|
64
|
+
aks: 'azure',
|
|
65
|
+
eks: 'amazonec2',
|
|
66
|
+
gke: 'google'
|
|
65
67
|
};
|
|
66
68
|
|
|
67
69
|
// Machine driver fields that are probably a credential field
|
|
@@ -107,8 +109,10 @@ const driverToCloudProviderMap = {
|
|
|
107
109
|
linode: '', // Show restricted options
|
|
108
110
|
vmwarevsphere: 'rancher-vsphere',
|
|
109
111
|
ovhcloudpubliccloud: '',
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
aks: 'azure',
|
|
113
|
+
eks: 'aws',
|
|
114
|
+
gke: 'google',
|
|
115
|
+
custom: undefined // Show all options
|
|
112
116
|
};
|
|
113
117
|
|
|
114
118
|
// Dynamically loaded drivers can call this eventually to register their options
|
package/store/prefs.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SETTING } from '@shell/config/settings';
|
|
2
2
|
import { MANAGEMENT, STEVE } from '@shell/config/types';
|
|
3
3
|
import { clone } from '@shell/utils/object';
|
|
4
|
+
import { getBrandMeta } from '@shell/utils/brand';
|
|
4
5
|
|
|
5
6
|
const definitions = {};
|
|
6
7
|
/**
|
|
@@ -119,6 +120,7 @@ export const SCALE_POOL_PROMPT = create('scale-pool-prompt', null, { parseJSON }
|
|
|
119
120
|
export const READ_NEW_RELEASE = create('read-new-release', '', { parseJSON });
|
|
120
121
|
export const READ_SUPPORT_NOTICE = create('read-support-notice', '', { parseJSON });
|
|
121
122
|
export const READ_UPCOMING_SUPPORT_NOTICE = create('read-upcoming-support-notice', '', { parseJSON });
|
|
123
|
+
export const READ_ANNOUNCEMENTS = create('read-announcements', '', { parseJSON });
|
|
122
124
|
|
|
123
125
|
// --------------------
|
|
124
126
|
|
|
@@ -521,16 +523,14 @@ export const actions = {
|
|
|
521
523
|
setBrandStyle({ rootState, rootGetters }, dark = false) {
|
|
522
524
|
if (rootState.managementReady) {
|
|
523
525
|
try {
|
|
524
|
-
const brandSetting = rootGetters['management/
|
|
526
|
+
const brandSetting = rootGetters['management/brand'];
|
|
525
527
|
|
|
526
|
-
if (brandSetting
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
const brandMeta = require(`~shell/assets/brand/${ brand }/metadata.json`);
|
|
528
|
+
if (brandSetting !== '') {
|
|
529
|
+
const brandMeta = getBrandMeta(brandSetting);
|
|
530
530
|
const hasStylesheet = brandMeta.hasStylesheet === 'true';
|
|
531
531
|
|
|
532
532
|
if (hasStylesheet) {
|
|
533
|
-
document.body.classList.add(
|
|
533
|
+
document.body.classList.add(brandMeta);
|
|
534
534
|
} else {
|
|
535
535
|
// TODO option apply color at runtime
|
|
536
536
|
}
|
package/store/type-map.js
CHANGED
|
@@ -365,7 +365,7 @@ export function DSL(store, product, module = 'type-map') {
|
|
|
365
365
|
|
|
366
366
|
let called = false;
|
|
367
367
|
|
|
368
|
-
export async function applyProducts(store, $
|
|
368
|
+
export async function applyProducts(store, $extension) {
|
|
369
369
|
if (called) {
|
|
370
370
|
return;
|
|
371
371
|
}
|
|
@@ -379,7 +379,7 @@ export async function applyProducts(store, $plugin) {
|
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
// Load the products from all plugins
|
|
382
|
-
$
|
|
382
|
+
$extension.loadProducts();
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
export function productsLoaded() {
|
|
@@ -1895,7 +1895,7 @@ function ifHave(getters, option) {
|
|
|
1895
1895
|
case IF_HAVE.NOT_V1_ISTIO: {
|
|
1896
1896
|
return !isV1Istio(getters);
|
|
1897
1897
|
}
|
|
1898
|
-
case IF_HAVE.MULTI_CLUSTER: {
|
|
1898
|
+
case IF_HAVE.MULTI_CLUSTER: { // Used by harvester extension
|
|
1899
1899
|
return getters.isMultiCluster;
|
|
1900
1900
|
}
|
|
1901
1901
|
case IF_HAVE.NEUVECTOR_NAMESPACE: {
|
|
@@ -1904,10 +1904,10 @@ function ifHave(getters, option) {
|
|
|
1904
1904
|
case IF_HAVE.ADMIN: {
|
|
1905
1905
|
return isAdminUser(getters);
|
|
1906
1906
|
}
|
|
1907
|
-
case IF_HAVE.MCM_DISABLED: {
|
|
1907
|
+
case IF_HAVE.MCM_DISABLED: { // There's a general MCM ff, this is conflating it with a harvester concept
|
|
1908
1908
|
return !getters['isRancherInHarvester'];
|
|
1909
1909
|
}
|
|
1910
|
-
case IF_HAVE.NOT_STANDALONE_HARVESTER: {
|
|
1910
|
+
case IF_HAVE.NOT_STANDALONE_HARVESTER: { // Not used by harvester extension...
|
|
1911
1911
|
return !getters['isStandaloneHarvester'];
|
|
1912
1912
|
}
|
|
1913
1913
|
default:
|
|
@@ -2026,7 +2026,7 @@ function hasCustom(state, rootState, kind, key, fallback) {
|
|
|
2026
2026
|
}
|
|
2027
2027
|
|
|
2028
2028
|
// Check to see if the custom kind is provided by a plugin (ignore booleans)
|
|
2029
|
-
const pluginComponent = rootState.$
|
|
2029
|
+
const pluginComponent = rootState.$extension.getDynamic(kind, key);
|
|
2030
2030
|
|
|
2031
2031
|
if (typeof pluginComponent !== 'boolean' && !!pluginComponent) {
|
|
2032
2032
|
cache[key] = true;
|
|
@@ -2046,7 +2046,7 @@ function hasCustom(state, rootState, kind, key, fallback) {
|
|
|
2046
2046
|
}
|
|
2047
2047
|
|
|
2048
2048
|
function loadExtension(rootState, kind, key, fallback) {
|
|
2049
|
-
const ext = rootState.$
|
|
2049
|
+
const ext = rootState.$extension.getDynamic(kind, key);
|
|
2050
2050
|
|
|
2051
2051
|
if (ext) {
|
|
2052
2052
|
if (typeof ext === 'function') {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
interface Context {
|
|
2
|
+
tag: string;
|
|
3
|
+
value: any;
|
|
4
|
+
hookId?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Element {
|
|
10
|
+
id: number;
|
|
11
|
+
context: Context
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface State {
|
|
15
|
+
idCounter: number;
|
|
16
|
+
elements: Record<string, Element>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const state = function(): State {
|
|
20
|
+
return {
|
|
21
|
+
idCounter: 0,
|
|
22
|
+
elements: {}
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const getters = {
|
|
27
|
+
all: (state: State) => {
|
|
28
|
+
return Object.values(state.elements)
|
|
29
|
+
.map((e) => e.context)
|
|
30
|
+
.sort((a, b) => (a.tag || '').localeCompare(b.tag || '') || 0);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const mutations = {
|
|
35
|
+
add(state: State, element: Element) {
|
|
36
|
+
state.elements[element.id] = element;
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
update(state: State, element: Element) {
|
|
40
|
+
const existingElement = state.elements[element.id];
|
|
41
|
+
|
|
42
|
+
if (existingElement) {
|
|
43
|
+
existingElement.context = element.context;
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
remove(state: State, element: Element) {
|
|
48
|
+
delete state.elements[element.id];
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
let id = null;
|
|
53
|
+
|
|
54
|
+
export const actions = {
|
|
55
|
+
add({ commit, state }: { commit: Function, state: State }, context: Context) {
|
|
56
|
+
if (context?.value === undefined || !context?.tag) {
|
|
57
|
+
throw new Error(`[ui-context] context {{${ JSON.stringify(context) }}} is not valid`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
id = `ctx-${ state.idCounter++ }`;
|
|
61
|
+
|
|
62
|
+
commit('add', { id, context });
|
|
63
|
+
|
|
64
|
+
return id;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
update({ commit, state }: { commit: Function, state: State }, element: Element) {
|
|
68
|
+
const old = state.elements[element.id];
|
|
69
|
+
|
|
70
|
+
if (!old) {
|
|
71
|
+
throw new Error(`[ui-context] element with id {{${ element.id }}} not found`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
commit('update', element);
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
remove({ commit, state }: { commit: Function, state: State }, id: number) {
|
|
78
|
+
const element = state.elements[id];
|
|
79
|
+
|
|
80
|
+
if (!element) {
|
|
81
|
+
throw new Error(`[ui-context] element with id {{${ id }}} not found`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
commit('remove', element);
|
|
85
|
+
}
|
|
86
|
+
};
|