@rancher/shell 0.3.0 → 0.3.1
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/styles/global/_button.scss +5 -1
- package/assets/styles/global/_columns.scss +4 -0
- package/assets/styles/global/_layout.scss +1 -2
- package/assets/styles/global/_select.scss +1 -4
- package/assets/styles/themes/_dark.scss +4 -4
- package/assets/styles/themes/_light.scss +4 -3
- package/assets/styles/themes/_suse.scss +1 -1
- package/assets/styles/vendor/vue-select.scss +4 -3
- package/assets/translations/en-us.yaml +669 -73
- package/assets/translations/zh-hans.yaml +547 -165
- package/chart/monitoring/steps/uninstall-v1.vue +2 -2
- package/cloud-credential/azure.vue +23 -0
- package/cloud-credential/harvester.vue +25 -62
- package/cloud-credential/pnap.vue +80 -0
- package/components/.DS_Store +0 -0
- package/components/AdvancedSection.vue +9 -2
- package/components/Alert.vue +2 -2
- package/components/ButtonDropdown.vue +0 -2
- package/components/ButtonGroup.vue +1 -0
- package/components/CollapsibleCard.vue +0 -1
- package/components/CruResource.vue +41 -4
- package/components/DetailTop.vue +58 -3
- package/components/DisableAuthProviderModal.vue +106 -0
- package/{rancher-components/components/Utils/DraggableZone → components}/DraggableZone.vue +0 -0
- package/components/ExplorerMembers.vue +253 -30
- package/components/ExplorerProjectsNamespaces.vue +77 -33
- package/components/GrowlManager.vue +3 -3
- package/components/IconOrSvg.vue +149 -0
- package/components/LogItem.vue +69 -0
- package/components/PodSecurityAdmission.vue +302 -0
- package/components/PromptModal.vue +1 -0
- package/components/ResourceDetail/Masthead.vue +54 -2
- package/components/ResourceDetail/index.vue +12 -5
- package/components/ResourceList/Masthead.vue +11 -1
- package/components/ResourceList/ResourceLoadingIndicator.vue +12 -2
- package/components/ResourceList/index.vue +53 -12
- package/components/ResourceList/resource-list.config.js +7 -0
- package/components/ResourceTable.vue +31 -6
- package/components/SimpleBox.vue +1 -1
- package/components/SortableTable/THead.vue +15 -5
- package/components/SortableTable/index.vue +21 -10
- package/components/Tabbed/index.vue +20 -15
- package/components/__tests__/.DS_Store +0 -0
- package/components/__tests__/AsyncButton.test.ts +140 -0
- package/components/__tests__/BackLink.test.ts +33 -0
- package/components/__tests__/ButtonGroup.test.ts +124 -0
- package/components/__tests__/ClusterBadge.test.ts +32 -0
- package/components/__tests__/CollapsibleCard.test.ts +64 -0
- package/components/__tests__/ConsumptionGauge.test.ts +88 -0
- package/components/__tests__/CruResource.test.ts +3 -2
- package/components/__tests__/FixedBanner.test.ts +129 -0
- package/components/__tests__/GrowlManager.test.ts +147 -0
- package/components/__tests__/NamespaceFilter.test.ts +33 -25
- package/components/__tests__/PercentageBar.test.ts +32 -0
- package/components/__tests__/PodSecurityAdmission.test.ts +398 -0
- package/components/auth/AuthBanner.vue +20 -10
- package/components/auth/RoleDetailEdit.vue +26 -17
- package/components/auth/SelectPrincipal.vue +36 -5
- package/components/form/ArrayList.vue +3 -35
- package/components/form/ArrayListGrouped.vue +13 -4
- package/components/form/ArrayListSelect.vue +5 -5
- package/components/form/Error.vue +8 -0
- package/components/form/KeyValue.vue +39 -7
- package/components/form/LabeledSelect.vue +5 -2
- package/components/form/Labels.vue +46 -16
- package/components/form/Members/ClusterPermissionsEditor.vue +17 -17
- package/components/form/Members/MembershipEditor.vue +12 -12
- package/components/form/NameNsDescription.vue +1 -1
- package/components/form/NodeScheduling.vue +1 -1
- package/components/form/Probe.vue +3 -3
- package/components/form/ResourceQuota/Project.vue +6 -6
- package/components/form/ResourceTabs/index.vue +1 -6
- package/components/form/Security.vue +7 -6
- package/components/form/Select.vue +3 -2
- package/components/form/SelectOrCreateAuthSecret.vue +22 -29
- package/components/form/ServicePorts.vue +8 -0
- package/components/form/WorkloadPorts.vue +7 -1
- package/components/form/__tests__/ArrayList.test.ts +74 -0
- package/components/form/__tests__/ArrayListGrouped.test.ts +6 -4
- package/components/formatter/Checked.vue +1 -1
- package/components/formatter/ClusterLink.vue +5 -0
- package/components/formatter/IconIsDefault.vue +2 -2
- package/components/formatter/InternalExternalIP.vue +11 -8
- package/components/formatter/LiveDuration.vue +78 -0
- package/components/formatter/WorkloadHealthScale.vue +5 -3
- package/components/nav/Header.vue +6 -3
- package/components/nav/NamespaceFilter.vue +146 -63
- package/components/nav/TopLevelMenu.vue +22 -19
- package/components/nav/WindowManager/ContainerLogs.vue +83 -126
- package/components/nav/WindowManager/ContainerShell.vue +9 -7
- package/components/nav/WindowManager/Window.vue +2 -0
- package/components/nav/WindowManager/index.vue +10 -0
- package/config/elemental-types.js +9 -0
- package/config/features.js +2 -0
- package/config/home-links.js +4 -1
- package/config/pod-security-admission.ts +82 -0
- package/config/product/apps.js +1 -1
- package/config/product/auth.js +6 -5
- package/config/product/explorer.js +6 -6
- package/config/product/fleet.js +1 -1
- package/config/product/manager.js +6 -2
- package/config/secret.js +0 -1
- package/config/settings.ts +26 -9
- package/config/table-headers.js +22 -11
- package/config/types.js +4 -1
- package/content/docs/zh-hans/getting-started.md +113 -137
- package/content/docs/zh-hans/whats-new.md +8 -46
- package/creators/pkg/package-lock.json +37 -0
- package/creators/pkg/package.json +1 -1
- package/detail/catalog.cattle.io.app.vue +1 -1
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +35 -9
- package/detail/service.vue +2 -9
- package/detail/workload/index.vue +0 -1
- package/dialog/AddClusterMemberDialog.vue +22 -28
- package/dialog/AddProjectMemberDialog.vue +53 -9
- package/dialog/DiagnosticTimingsDialog.vue +8 -7
- package/dialog/DrainNode.vue +44 -48
- package/dialog/ForceMachineRemoveDialog.vue +5 -7
- package/dialog/GenericPrompt.vue +15 -20
- package/dialog/RollbackWorkloadDialog.vue +15 -46
- package/dialog/RotateCertificatesDialog.vue +5 -7
- package/dialog/RotateEncryptionKeyDialog.vue +5 -9
- package/dialog/SaveAsRKETemplateDialog.vue +5 -13
- package/dialog/ScaleMachineDownDialog.vue +1 -1
- package/dialog/ScalePoolDownDialog.vue +121 -0
- package/edit/__tests__/management.cattle.io.setting.test.ts +3 -3
- package/edit/auth/azuread.vue +16 -16
- package/edit/auth/github.vue +8 -0
- package/edit/auth/googleoauth.vue +10 -1
- package/edit/auth/ldap/index.vue +10 -0
- package/edit/auth/oidc.vue +10 -0
- package/edit/auth/saml.vue +10 -0
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +1 -1
- package/edit/cloudcredential.vue +3 -7
- package/edit/logging-flow/Match.vue +39 -8
- package/edit/logging-flow/index.vue +27 -4
- package/edit/management.cattle.io.podsecurityadmissionconfigurationtemplate.vue +107 -0
- package/edit/management.cattle.io.project.vue +8 -1
- package/edit/management.cattle.io.setting.vue +5 -2
- package/edit/management.cattle.io.user.vue +7 -1
- package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +23 -7
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/email.vue +2 -2
- package/edit/monitoring.coreos.com.prometheusrule/GroupRules.vue +14 -6
- package/edit/namespace.vue +18 -4
- package/edit/networking.k8s.io.ingress/Certificate.vue +1 -0
- package/edit/networking.k8s.io.ingress/IngressClass.vue +8 -6
- package/edit/networking.k8s.io.ingress/RulePath.vue +12 -6
- package/edit/networking.k8s.io.ingress/index.vue +8 -6
- package/edit/persistentvolume/index.vue +30 -27
- package/edit/persistentvolume/plugins/cephfs.vue +29 -29
- package/edit/persistentvolume/plugins/csi.vue +102 -62
- package/edit/persistentvolume/plugins/fc.vue +19 -19
- package/edit/persistentvolume/plugins/iscsi.vue +45 -45
- package/edit/persistentvolume/plugins/rbd.vue +39 -39
- package/edit/persistentvolumeclaim.vue +78 -75
- package/edit/provisioning.cattle.io.cluster/MachinePool.vue +11 -7
- package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +10 -1
- package/edit/provisioning.cattle.io.cluster/RegistryMirrors.vue +87 -27
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -6
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +93 -0
- package/edit/provisioning.cattle.io.cluster/import.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/index.vue +29 -6
- package/edit/provisioning.cattle.io.cluster/rke2.vue +440 -152
- package/edit/secret/index.vue +3 -7
- package/edit/service.vue +3 -1
- package/edit/storage.k8s.io.storageclass/index.vue +100 -16
- package/edit/storage.k8s.io.storageclass/provisioners/driver.harvesterhci.io.vue +114 -0
- package/edit/workload/__tests__/index.test.ts +98 -0
- package/edit/workload/index.vue +58 -8
- package/edit/workload/mixins/workload.js +107 -70
- package/edit/workload/storage/ContainerMountPaths.vue +0 -10
- package/edit/workload/storage/emptyDir.vue +88 -0
- package/edit/workload/storage/ephemeralVolume/index.vue +1 -1
- package/edit/workload/storage/index.vue +8 -0
- package/edit/workload/storage/persistentVolumeClaim/index.vue +1 -1
- package/layouts/default.vue +57 -44
- package/list/__tests__/workload.test.ts +5 -2
- package/list/catalog.cattle.io.app.vue +1 -0
- package/list/cis.cattle.io.clusterscan.vue +1 -0
- package/list/fleet.cattle.io.bundle.vue +5 -6
- package/list/fleet.cattle.io.cluster.vue +6 -3
- package/list/fleet.cattle.io.clusterregistrationtoken.vue +5 -6
- package/list/fleet.cattle.io.gitrepo.vue +4 -9
- package/list/helm.cattle.io.projecthelmchart.vue +1 -5
- package/list/logging.banzaicloud.io.clusterflow.vue +4 -1
- package/list/logging.banzaicloud.io.flow.vue +6 -5
- package/list/management.cattle.io.cluster.vue +1 -0
- package/list/management.cattle.io.feature.vue +3 -4
- package/list/management.cattle.io.podsecurityadmissionconfigurationtemplate.vue +47 -0
- package/list/management.cattle.io.setting.vue +2 -2
- package/list/management.cattle.io.user.vue +4 -10
- package/list/monitoring.coreos.com.alertmanagerconfig.vue +2 -7
- package/list/node.vue +8 -5
- package/list/persistentvolume.vue +3 -3
- package/list/persistentvolumeclaim.vue +3 -4
- package/list/provisioning.cattle.io.cluster.vue +18 -19
- package/list/service.vue +6 -14
- package/list/workload.vue +43 -38
- package/machine-config/azure.vue +429 -60
- package/machine-config/pnap.vue +288 -0
- package/mixins/auth-config.js +1 -3
- package/mixins/browser-tab-visibility.js +8 -14
- package/mixins/chart.js +1 -1
- package/mixins/create-edit-view/impl.js +4 -0
- package/mixins/create-edit-view/index.js +4 -2
- package/mixins/resource-fetch-namespaced.js +98 -0
- package/mixins/resource-fetch.js +79 -45
- package/mixins/resource-manager.js +1 -23
- package/models/apps.controllerrevision.js +7 -0
- package/models/apps.daemonset.js +18 -0
- package/models/apps.deployment.js +44 -0
- package/models/apps.replicaset.js +7 -0
- package/models/apps.statefulset.js +18 -0
- package/models/batch.job.js +7 -14
- package/models/cluster/node.js +10 -2
- package/models/cluster.x-k8s.io.machine.js +26 -4
- package/models/cluster.x-k8s.io.machinedeployment.js +12 -2
- package/models/event.js +7 -0
- package/models/logging.banzaicloud.io.flow.js +4 -0
- package/models/management.cattle.io.cluster.js +1 -1
- package/models/management.cattle.io.clusterroletemplatebinding.js +1 -1
- package/models/management.cattle.io.globalrole.js +2 -2
- package/models/management.cattle.io.node.js +37 -2
- package/models/management.cattle.io.podsecurityadmissionconfigurationtemplate.ts +4 -0
- package/models/management.cattle.io.project.js +30 -11
- package/models/management.cattle.io.setting.js +1 -1
- package/models/management.cattle.io.user.js +37 -1
- package/models/namespace.js +42 -5
- package/models/persistentvolume.js +14 -2
- package/models/pod.js +15 -0
- package/models/projectroletemplatebinding.js +7 -0
- package/models/provisioning.cattle.io.cluster.js +61 -10
- package/models/rke-machine.cattle.io.pnapmachinetemplate.js +15 -0
- package/models/service.js +14 -13
- package/models/storage.k8s.io.storageclass.js +33 -18
- package/models/workload.js +38 -7
- package/nuxt.config.js +27 -17
- package/package.json +7 -7
- package/pages/about.vue +14 -2
- package/pages/c/_cluster/apps/charts/index.vue +4 -3
- package/pages/c/_cluster/apps/charts/install.vue +59 -22
- package/pages/c/_cluster/auth/config/_id.vue +6 -0
- package/pages/c/_cluster/auth/config/index.vue +8 -6
- package/pages/c/_cluster/auth/group.principal/assign-edit.vue +1 -1
- package/pages/c/_cluster/auth/roles/index.vue +1 -1
- package/pages/c/_cluster/explorer/index.vue +12 -6
- package/pages/c/_cluster/longhorn/index.vue +1 -1
- package/pages/c/_cluster/monitoring/alertmanagerconfig/_alertmanagerconfigid/receiver.vue +15 -4
- package/pages/c/_cluster/monitoring/index.vue +1 -1
- package/pages/c/_cluster/neuvector/index.vue +1 -1
- package/pages/c/_cluster/settings/performance.vue +48 -2
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +34 -1
- package/pages/c/_cluster/uiplugins/index.vue +28 -2
- package/pages/diagnostic.vue +5 -4
- package/pages/home.vue +105 -30
- package/pages/prefs.vue +23 -12
- package/pages/rio/mesh.vue +1 -1
- package/pkg/dynamic-importer.lib.js +8 -0
- package/pkg/vue.config.js +4 -0
- package/plugins/dashboard-store/__tests__/mutations.spec.js +406 -0
- package/plugins/dashboard-store/actions.js +32 -25
- package/plugins/dashboard-store/getters.js +50 -33
- package/plugins/dashboard-store/mutations.js +134 -28
- package/plugins/dashboard-store/resource-class.js +21 -41
- package/plugins/steve/actions.js +30 -0
- package/plugins/steve/caches/resourceCache.js +60 -0
- package/plugins/steve/getters.js +44 -1
- package/plugins/steve/mutations.js +97 -36
- package/plugins/steve/resourceWatcher.js +277 -0
- package/plugins/steve/schema.utils.js +25 -0
- package/plugins/steve/subscribe.js +288 -115
- package/plugins/steve/worker/index.js +17 -0
- package/plugins/steve/worker/web-worker.advanced.js +302 -0
- package/plugins/steve/{web-worker.steve-sub-worker.js → worker/web-worker.basic.js} +3 -44
- package/rancher-components/Card/Card.vue +3 -3
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +1 -0
- package/rancher-components/StringList/StringList.test.ts +45 -420
- package/rancher-components/StringList/StringList.vue +1 -10
- package/rancher-components/components/Banner/Banner.test.ts +44 -0
- package/rancher-components/components/Banner/Banner.vue +129 -61
- package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +13 -22
- package/rancher-components/components/Form/Checkbox/Checkbox.vue +8 -6
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +9 -9
- package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +0 -1
- package/rancher-components/components/StringList/StringList.test.ts +7 -7
- package/rancher-components/components/StringList/StringList.vue +21 -15
- package/scripts/test-plugins-build.sh +8 -0
- package/static/loading-indicator.html +1 -1
- package/store/index.js +54 -3
- package/store/plugins.js +0 -17
- package/store/pnap.js +128 -0
- package/store/prefs.js +4 -2
- package/store/type-map.js +55 -13
- package/types/pod-security-admission.ts +36 -0
- package/types/shell/index.d.ts +496 -396
- package/utils/__tests__/object.test.ts +17 -1
- package/utils/__tests__/pod-security-admission.test.ts +61 -0
- package/utils/async.ts +36 -0
- package/utils/color.js +45 -0
- package/utils/crypto/browserHashUtils.js +18 -0
- package/utils/dynamic-importer.js +8 -0
- package/utils/install-redirect.js +1 -1
- package/utils/object.js +24 -0
- package/utils/pod-security-admission.ts +39 -0
- package/utils/socket.js +61 -24
- package/utils/string.js +2 -0
- package/utils/svg-filter.js +301 -0
- package/utils/time.js +49 -0
- package/utils/validators/cidr.js +4 -0
- package/utils/validators/formRules/__tests__/index.test.ts +23 -3
- package/utils/validators/formRules/index.ts +14 -0
- package/config/product/harvester-manager.js +0 -162
- package/edit/harvesterhci.io.management.cluster.vue +0 -153
- package/list/harvesterhci.io.management.cluster.vue +0 -241
- package/machine-config/harvester.vue +0 -693
- package/models/harvesterhci.io.management.cluster.js +0 -228
- package/pages/c/_cluster/harvesterManager/index.vue +0 -24
- package/rancher-components/Card/Card.test.ts +0 -39
- package/rancher-components/Utils/DraggableZone/DraggableZone.vue +0 -181
- package/rancher-components/Utils/DraggableZone/index.ts +0 -1
- package/rancher-components/components/Utils/DraggableZone/index.ts +0 -1
package/models/pod.js
CHANGED
|
@@ -181,6 +181,21 @@ export default class Pod extends WorkloadService {
|
|
|
181
181
|
return 0;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
processSaveResponse(res) {
|
|
185
|
+
if (res._headers && res._headers.warning) {
|
|
186
|
+
const warnings = res._headers.warning.split('299') || [];
|
|
187
|
+
const hasPsaWarnings = warnings.filter(warning => warning.includes('violate PodSecurity')).length;
|
|
188
|
+
|
|
189
|
+
if (hasPsaWarnings) {
|
|
190
|
+
this.$dispatch('growl/warning', {
|
|
191
|
+
title: this.$rootGetters['i18n/t']('growl.podSecurity.title'),
|
|
192
|
+
message: this.$rootGetters['i18n/t']('growl.podSecurity.message'),
|
|
193
|
+
timeout: 5000,
|
|
194
|
+
}, { root: true });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
184
199
|
save() {
|
|
185
200
|
const prev = { ...this };
|
|
186
201
|
|
|
@@ -17,4 +17,11 @@ export default class PRTB extends NormanModel {
|
|
|
17
17
|
get roleTemplate() {
|
|
18
18
|
return this.$rootGetters['management/byId'](MANAGEMENT.ROLE_TEMPLATE, this.roleTemplateId);
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
get steve() {
|
|
22
|
+
return this.$dispatch(`management/find`, {
|
|
23
|
+
type: MANAGEMENT.PROJECT_ROLE_TEMPLATE_BINDING,
|
|
24
|
+
id: this.id?.replace(':', '/')
|
|
25
|
+
}, { root: true });
|
|
26
|
+
}
|
|
20
27
|
}
|
|
@@ -71,6 +71,17 @@ export default class ProvCluster extends SteveModel {
|
|
|
71
71
|
return super.creationTimestamp;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// Models can specify a single action that will be shown as a button in the details masthead
|
|
75
|
+
get detailsAction() {
|
|
76
|
+
const canExplore = this.mgmt?.isReady && !this.hasError;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
action: 'explore',
|
|
80
|
+
label: this.$rootGetters['i18n/t']('cluster.explore'),
|
|
81
|
+
enabled: canExplore,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
74
85
|
get _availableActions() {
|
|
75
86
|
const out = super._availableActions;
|
|
76
87
|
const isLocal = this.mgmt?.isLocal;
|
|
@@ -114,12 +125,12 @@ export default class ProvCluster extends SteveModel {
|
|
|
114
125
|
label: this.$rootGetters['i18n/t']('nav.kubeconfig.download'),
|
|
115
126
|
icon: 'icon icon-download',
|
|
116
127
|
bulkable: true,
|
|
117
|
-
enabled: this.mgmt?.hasAction('generateKubeconfig')
|
|
128
|
+
enabled: this.mgmt?.hasAction('generateKubeconfig'),
|
|
118
129
|
}, {
|
|
119
130
|
action: 'copyKubeConfig',
|
|
120
131
|
label: this.t('cluster.copyConfig'),
|
|
121
132
|
bulkable: false,
|
|
122
|
-
enabled: this.mgmt?.hasAction('generateKubeconfig')
|
|
133
|
+
enabled: this.mgmt?.hasAction('generateKubeconfig'),
|
|
123
134
|
icon: 'icon icon-copy',
|
|
124
135
|
}, {
|
|
125
136
|
action: 'snapshotAction',
|
|
@@ -165,6 +176,15 @@ export default class ProvCluster extends SteveModel {
|
|
|
165
176
|
return out;
|
|
166
177
|
}
|
|
167
178
|
|
|
179
|
+
explore() {
|
|
180
|
+
const location = {
|
|
181
|
+
name: 'c-cluster',
|
|
182
|
+
params: { cluster: this.mgmt.id }
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
this.currentRouter().push(location);
|
|
186
|
+
}
|
|
187
|
+
|
|
168
188
|
goToViewYaml() {
|
|
169
189
|
let location;
|
|
170
190
|
|
|
@@ -423,6 +443,36 @@ export default class ProvCluster extends SteveModel {
|
|
|
423
443
|
return this.pools.reduce((acc, pool) => acc + (pool.unavailable || 0), 0);
|
|
424
444
|
}
|
|
425
445
|
|
|
446
|
+
get unavailableMachines() {
|
|
447
|
+
if (this.isReady) {
|
|
448
|
+
if (this.isRke1) {
|
|
449
|
+
const names = this.nodes.filter((node) => {
|
|
450
|
+
return node.status.conditions.find(c => c.error && c.type === 'Ready');
|
|
451
|
+
}).map((node) => {
|
|
452
|
+
const name = node.status.nodeName || node.metadata.name;
|
|
453
|
+
|
|
454
|
+
return this.t('cluster.availabilityWarnings.node', { name });
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
return names.join('<br>');
|
|
458
|
+
} else {
|
|
459
|
+
const names = this.machines.filter((machine) => {
|
|
460
|
+
return machine.status.conditions.find(c => c.error && c.type === 'NodeHealthy');
|
|
461
|
+
}).map((machine) => {
|
|
462
|
+
if (machine.status.nodeRef?.name) {
|
|
463
|
+
return this.t('cluster.availabilityWarnings.node', { name: machine.status.nodeRef.name });
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return this.t('cluster.availabilityWarnings.machine', { name: machine.metadata.name });
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
return names.join('<br>');
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return '';
|
|
474
|
+
}
|
|
475
|
+
|
|
426
476
|
get stateParts() {
|
|
427
477
|
const out = [
|
|
428
478
|
{
|
|
@@ -580,24 +630,25 @@ export default class ProvCluster extends SteveModel {
|
|
|
580
630
|
this.$dispatch('promptRestore', [resource]);
|
|
581
631
|
}
|
|
582
632
|
|
|
583
|
-
saveAsRKETemplate(
|
|
633
|
+
saveAsRKETemplate(cluster = this) {
|
|
584
634
|
this.$dispatch('promptModal', {
|
|
585
|
-
|
|
586
|
-
component:
|
|
635
|
+
componentProps: { cluster },
|
|
636
|
+
component: 'SaveAsRKETemplateDialog'
|
|
587
637
|
});
|
|
588
638
|
}
|
|
589
639
|
|
|
590
|
-
rotateCertificates(
|
|
640
|
+
rotateCertificates(cluster = this) {
|
|
591
641
|
this.$dispatch('promptModal', {
|
|
592
|
-
|
|
642
|
+
componentProps: { cluster },
|
|
643
|
+
|
|
593
644
|
component: 'RotateCertificatesDialog'
|
|
594
645
|
});
|
|
595
646
|
}
|
|
596
647
|
|
|
597
|
-
rotateEncryptionKey(
|
|
648
|
+
rotateEncryptionKey(cluster = this) {
|
|
598
649
|
this.$dispatch('promptModal', {
|
|
599
|
-
|
|
600
|
-
component:
|
|
650
|
+
componentProps: { cluster },
|
|
651
|
+
component: 'RotateEncryptionKeyDialog'
|
|
601
652
|
});
|
|
602
653
|
}
|
|
603
654
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import MachineTemplate from './rke-machine.cattle.io.machinetemplate';
|
|
2
|
+
|
|
3
|
+
export default class PnapMachineTemplate extends MachineTemplate {
|
|
4
|
+
get provider() {
|
|
5
|
+
return 'pnap';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get providerLocation() {
|
|
9
|
+
return this.spec.template.spec.serverLocation;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get providerSize() {
|
|
13
|
+
return this.spec.template.spec.serverType;
|
|
14
|
+
}
|
|
15
|
+
}
|
package/models/service.js
CHANGED
|
@@ -128,23 +128,24 @@ export default class extends SteveModel {
|
|
|
128
128
|
return out;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
get
|
|
131
|
+
get podRelationship() {
|
|
132
132
|
const { metadata:{ relationships = [] } } = this;
|
|
133
133
|
|
|
134
|
-
return
|
|
135
|
-
|
|
136
|
-
let pods = [];
|
|
134
|
+
return (relationships || []).filter(relationship => relationship.toType === POD)[0];
|
|
135
|
+
}
|
|
137
136
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
137
|
+
async fetchPods() {
|
|
138
|
+
if (this.podRelationship) {
|
|
139
|
+
await this.$dispatch('cluster/findMatching', {
|
|
140
|
+
type: POD,
|
|
141
|
+
selector: this.podRelationship.selector,
|
|
142
|
+
namespace: this.namespace
|
|
143
|
+
}, { root: true });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
145
146
|
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
get pods() {
|
|
148
|
+
return this.podRelationship ? this.$getters.matching( POD, this.podRelationship.selector, this.namespace ) : [];
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
get serviceType() {
|
|
@@ -2,21 +2,26 @@ import { STORAGE } from '@shell/config/labels-annotations';
|
|
|
2
2
|
import { STORAGE_CLASS } from '@shell/config/types';
|
|
3
3
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
4
4
|
|
|
5
|
+
// These are storage class drivers w/ custom components
|
|
6
|
+
// all but longhorn are in-tree plugins
|
|
5
7
|
export const PROVISIONER_OPTIONS = [
|
|
6
8
|
{
|
|
7
|
-
labelKey:
|
|
8
|
-
value:
|
|
9
|
-
supported:
|
|
9
|
+
labelKey: 'storageClass.aws-ebs.title',
|
|
10
|
+
value: 'kubernetes.io/aws-ebs',
|
|
11
|
+
supported: true,
|
|
12
|
+
deprecated: true,
|
|
10
13
|
},
|
|
11
14
|
{
|
|
12
|
-
labelKey:
|
|
13
|
-
value:
|
|
14
|
-
supported:
|
|
15
|
+
labelKey: 'storageClass.azure-disk.title',
|
|
16
|
+
value: 'kubernetes.io/azure-disk',
|
|
17
|
+
supported: true,
|
|
18
|
+
deprecated: true
|
|
15
19
|
},
|
|
16
20
|
{
|
|
17
|
-
labelKey:
|
|
18
|
-
value:
|
|
19
|
-
supported:
|
|
21
|
+
labelKey: 'storageClass.azure-file.title',
|
|
22
|
+
value: 'kubernetes.io/azure-file',
|
|
23
|
+
supported: true,
|
|
24
|
+
deprecated: true,
|
|
20
25
|
},
|
|
21
26
|
{
|
|
22
27
|
labelKey: 'storageClass.rbd.title',
|
|
@@ -27,9 +32,10 @@ export const PROVISIONER_OPTIONS = [
|
|
|
27
32
|
value: 'kubernetes.io/glusterfs',
|
|
28
33
|
},
|
|
29
34
|
{
|
|
30
|
-
labelKey:
|
|
31
|
-
value:
|
|
32
|
-
supported:
|
|
35
|
+
labelKey: 'storageClass.gce-pd.title',
|
|
36
|
+
value: 'kubernetes.io/gce-pd',
|
|
37
|
+
supported: true,
|
|
38
|
+
deprecated: true,
|
|
33
39
|
},
|
|
34
40
|
{
|
|
35
41
|
labelKey: 'storageClass.no-provisioner.title',
|
|
@@ -41,8 +47,9 @@ export const PROVISIONER_OPTIONS = [
|
|
|
41
47
|
supported: true
|
|
42
48
|
},
|
|
43
49
|
{
|
|
44
|
-
labelKey:
|
|
45
|
-
value:
|
|
50
|
+
labelKey: 'storageClass.cinder.title',
|
|
51
|
+
value: 'kubernetes.io/cinder',
|
|
52
|
+
deprecated: true,
|
|
46
53
|
},
|
|
47
54
|
{
|
|
48
55
|
labelKey: 'storageClass.portworx-volume.title',
|
|
@@ -61,17 +68,25 @@ export const PROVISIONER_OPTIONS = [
|
|
|
61
68
|
value: 'kubernetes.io/storageos',
|
|
62
69
|
},
|
|
63
70
|
{
|
|
64
|
-
labelKey:
|
|
65
|
-
value:
|
|
66
|
-
supported:
|
|
71
|
+
labelKey: 'storageClass.vsphere-volume.title',
|
|
72
|
+
value: 'kubernetes.io/vsphere-volume',
|
|
73
|
+
supported: true,
|
|
74
|
+
deprecated: true
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
labelKey: 'storageClass.harvesterhci.title',
|
|
78
|
+
value: 'driver.harvesterhci.io',
|
|
79
|
+
supported: true,
|
|
80
|
+
hideCustomize: true,
|
|
67
81
|
}
|
|
68
82
|
];
|
|
69
83
|
|
|
70
84
|
export default class extends SteveModel {
|
|
71
85
|
get provisionerDisplay() {
|
|
72
86
|
const option = PROVISIONER_OPTIONS.find(o => o.value === this.provisioner);
|
|
87
|
+
const fallback = `${ this.provisioner } ${ this.t('persistentVolume.csi.drivers.suffix') }`;
|
|
73
88
|
|
|
74
|
-
return option ? this.t(option.labelKey) : this.provisioner;
|
|
89
|
+
return option ? this.t(option.labelKey) : this.$rootGetters['i18n/withFallback'](`persistentVolume.csi.drivers.${ this.provisioner.replaceAll('.', '-') }`, null, fallback);
|
|
75
90
|
}
|
|
76
91
|
|
|
77
92
|
get isDefault() {
|
package/models/workload.js
CHANGED
|
@@ -7,6 +7,16 @@ import { convertSelectorObj, matching, matches } from '@shell/utils/selector';
|
|
|
7
7
|
import { SEPARATOR } from '@shell/components/DetailTop';
|
|
8
8
|
import WorkloadService from '@shell/models/workload.service';
|
|
9
9
|
|
|
10
|
+
export const defaultContainer = {
|
|
11
|
+
imagePullPolicy: 'Always',
|
|
12
|
+
name: 'container-0',
|
|
13
|
+
securityContext: {
|
|
14
|
+
runAsNonRoot: false,
|
|
15
|
+
readOnlyRootFilesystem: false,
|
|
16
|
+
privileged: false,
|
|
17
|
+
allowPrivilegeEscalation: false,
|
|
18
|
+
}
|
|
19
|
+
};
|
|
10
20
|
export default class Workload extends WorkloadService {
|
|
11
21
|
// remove clone as yaml/edit as yaml until API supported
|
|
12
22
|
get _availableActions() {
|
|
@@ -98,7 +108,9 @@ export default class Workload extends WorkloadService {
|
|
|
98
108
|
if (!spec.template) {
|
|
99
109
|
spec.template = {
|
|
100
110
|
spec: {
|
|
101
|
-
restartPolicy:
|
|
111
|
+
restartPolicy: this.type === WORKLOAD_TYPES.JOB ? 'Never' : 'Always',
|
|
112
|
+
containers: [{ ...defaultContainer }],
|
|
113
|
+
initContainers: []
|
|
102
114
|
}
|
|
103
115
|
};
|
|
104
116
|
}
|
|
@@ -109,14 +121,14 @@ export default class Workload extends WorkloadService {
|
|
|
109
121
|
vm.$set(this, 'spec', spec);
|
|
110
122
|
}
|
|
111
123
|
|
|
112
|
-
toggleRollbackModal(
|
|
124
|
+
toggleRollbackModal( workload = this ) {
|
|
113
125
|
this.$dispatch('promptModal', {
|
|
114
|
-
|
|
115
|
-
component:
|
|
126
|
+
componentProps: { workload },
|
|
127
|
+
component: 'RollbackWorkloadDialog'
|
|
116
128
|
});
|
|
117
129
|
}
|
|
118
130
|
|
|
119
|
-
async rollBackWorkload( cluster, workload, rollbackRequestData ) {
|
|
131
|
+
async rollBackWorkload( cluster, workload, type, rollbackRequestData ) {
|
|
120
132
|
const rollbackRequestBody = JSON.stringify(rollbackRequestData);
|
|
121
133
|
|
|
122
134
|
if ( Array.isArray( workload ) ) {
|
|
@@ -125,8 +137,15 @@ export default class Workload extends WorkloadService {
|
|
|
125
137
|
const namespace = workload.metadata.namespace;
|
|
126
138
|
const workloadName = workload.metadata.name;
|
|
127
139
|
|
|
128
|
-
|
|
129
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Ensure we go out to the correct cluster
|
|
142
|
+
*
|
|
143
|
+
* Build the request body in the same format that kubectl
|
|
144
|
+
* uses to call the Kubernetes API to roll back a workload.
|
|
145
|
+
* To see an example request body, run:
|
|
146
|
+
* kubectl rollout undo deployment/[deployment name] --to-revision=[revision number] -v=8
|
|
147
|
+
*/
|
|
148
|
+
await this.patch(rollbackRequestBody, { url: `/k8s/clusters/${ cluster.id }/apis/apps/v1/namespaces/${ namespace }/${ type }/${ workloadName }` });
|
|
130
149
|
}
|
|
131
150
|
|
|
132
151
|
pause() {
|
|
@@ -610,6 +629,18 @@ export default class Workload extends WorkloadService {
|
|
|
610
629
|
return out;
|
|
611
630
|
}
|
|
612
631
|
|
|
632
|
+
get currentRevisionNumber() {
|
|
633
|
+
if (this.ownedByWorkload || this.kind === 'Job' || this.kind === 'CronJob') {
|
|
634
|
+
return undefined;
|
|
635
|
+
}
|
|
636
|
+
if (this.kind === 'Deployment') {
|
|
637
|
+
return this.metadata.annotations['deployment.kubernetes.io/revision'];
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// 'DaemonSet', 'StatefulSet'
|
|
641
|
+
return this.metadata.generation;
|
|
642
|
+
}
|
|
643
|
+
|
|
613
644
|
async matchingPods() {
|
|
614
645
|
const all = await this.$dispatch('findAll', { type: POD });
|
|
615
646
|
const allInNamespace = all.filter(pod => pod.metadata.namespace === this.metadata.namespace);
|
package/nuxt.config.js
CHANGED
|
@@ -22,16 +22,15 @@ export const API_PATH = api;
|
|
|
22
22
|
|
|
23
23
|
const dev = (process.env.NODE_ENV !== 'production');
|
|
24
24
|
const devPorts = dev || process.env.DEV_PORTS === 'true';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
require('./package.json').version;
|
|
25
|
+
|
|
26
|
+
// human readable version used on rancher dashboard about page
|
|
27
|
+
const dashboardVersion = process.env.DASHBOARD_VERSION;
|
|
29
28
|
|
|
30
29
|
const prime = process.env.PRIME;
|
|
31
30
|
|
|
32
31
|
const pl = process.env.PL || STANDARD;
|
|
33
|
-
const commit = process.env.COMMIT || 'head';
|
|
34
32
|
const perfTest = (process.env.PERF_TEST === 'true'); // Enable performance testing when in dev
|
|
33
|
+
const instrumentCode = (process.env.TEST_INSTRUMENT === 'true'); // Instrument code for code coverage in e2e tests
|
|
35
34
|
|
|
36
35
|
// Allow skipping of eslint check
|
|
37
36
|
// 0 = Skip browser and console checks
|
|
@@ -84,8 +83,21 @@ export default function(dir, _appConfig) {
|
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
85
|
|
|
86
|
+
// Instrument code for tests
|
|
87
|
+
const babelPlugins = [
|
|
88
|
+
// TODO: Browser support
|
|
89
|
+
// ['@babel/plugin-transform-modules-commonjs'],
|
|
90
|
+
['@babel/plugin-proposal-private-property-in-object', { loose: true }]
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
if (instrumentCode) {
|
|
94
|
+
babelPlugins.push('babel-plugin-istanbul');
|
|
95
|
+
|
|
96
|
+
console.warn('Instrumenting code for coverage'); // eslint-disable-line no-console
|
|
97
|
+
}
|
|
98
|
+
|
|
87
99
|
// ===============================================================================================
|
|
88
|
-
// Functions for the UI
|
|
100
|
+
// Functions for the UI Plugins
|
|
89
101
|
// ===============================================================================================
|
|
90
102
|
|
|
91
103
|
const appConfig = _appConfig || {};
|
|
@@ -225,7 +237,11 @@ export default function(dir, _appConfig) {
|
|
|
225
237
|
|
|
226
238
|
if (index !== -1 && (index + 1) < ctx.length) {
|
|
227
239
|
const pkg = ctx[index + 1];
|
|
228
|
-
|
|
240
|
+
let p = path.resolve(dir, 'pkg', pkg, resource.request.substr(5));
|
|
241
|
+
|
|
242
|
+
if (resource.request.startsWith(`@pkg/${ pkg }`)) {
|
|
243
|
+
p = path.resolve(dir, 'pkg', resource.request.substr(5));
|
|
244
|
+
}
|
|
229
245
|
|
|
230
246
|
resource.request = p;
|
|
231
247
|
}
|
|
@@ -266,7 +282,7 @@ export default function(dir, _appConfig) {
|
|
|
266
282
|
console.log(`Build: ${ dev ? 'Development' : 'Production' }`); // eslint-disable-line no-console
|
|
267
283
|
|
|
268
284
|
if ( !dev ) {
|
|
269
|
-
console.log(`Version: ${
|
|
285
|
+
console.log(`Version: ${ dashboardVersion }`); // eslint-disable-line no-console
|
|
270
286
|
}
|
|
271
287
|
|
|
272
288
|
if ( resourceBase ) {
|
|
@@ -305,8 +321,6 @@ export default function(dir, _appConfig) {
|
|
|
305
321
|
|
|
306
322
|
// Configuration visible to the client, https://nuxtjs.org/api/configuration-env
|
|
307
323
|
env: {
|
|
308
|
-
commit,
|
|
309
|
-
version,
|
|
310
324
|
dev,
|
|
311
325
|
pl,
|
|
312
326
|
perfTest,
|
|
@@ -315,7 +329,8 @@ export default function(dir, _appConfig) {
|
|
|
315
329
|
api
|
|
316
330
|
},
|
|
317
331
|
|
|
318
|
-
|
|
332
|
+
// vars accessible via this.$config https://nuxtjs.org/docs/configuration-glossary/configuration-runtime-config/
|
|
333
|
+
publicRuntimeConfig: { rancherEnv, dashboardVersion },
|
|
319
334
|
|
|
320
335
|
buildDir: dev ? '.nuxt' : '.nuxt-prod',
|
|
321
336
|
|
|
@@ -538,12 +553,7 @@ export default function(dir, _appConfig) {
|
|
|
538
553
|
'@babel/preset-typescript',
|
|
539
554
|
];
|
|
540
555
|
},
|
|
541
|
-
plugins:
|
|
542
|
-
// TODO: Browser support
|
|
543
|
-
// ['@babel/plugin-transform-modules-commonjs'],
|
|
544
|
-
['@babel/plugin-proposal-private-property-in-object', { loose: true }],
|
|
545
|
-
'babel-plugin-istanbul'
|
|
546
|
-
],
|
|
556
|
+
plugins: babelPlugins
|
|
547
557
|
}
|
|
548
558
|
},
|
|
549
559
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rancher/shell",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Rancher Dashboard Shell",
|
|
5
5
|
"repository": "https://github.com/rancherlabs/dashboard",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"nyc": "15.1.0",
|
|
114
114
|
"papaparse": "5.3.0",
|
|
115
115
|
"portal-vue": "2.1.7",
|
|
116
|
-
"rancher-icons": "rancher/icons#v2.0.
|
|
116
|
+
"rancher-icons": "rancher/icons#v2.0.13",
|
|
117
117
|
"require-extension-hooks": "0.3.3",
|
|
118
118
|
"require-extension-hooks-babel": "1.0.0",
|
|
119
119
|
"require-extension-hooks-vue": "3.0.0",
|
|
@@ -143,11 +143,11 @@
|
|
|
143
143
|
"vuex": "3.6.2",
|
|
144
144
|
"webpack-bundle-analyzer": "4.5.0",
|
|
145
145
|
"webpack-virtual-modules": "0.4.3",
|
|
146
|
-
"xterm": "
|
|
147
|
-
"xterm-addon-fit": "0.
|
|
148
|
-
"xterm-addon-search": "0.
|
|
149
|
-
"xterm-addon-web-links": "0.
|
|
150
|
-
"xterm-addon-webgl": "0.
|
|
146
|
+
"xterm": "5.0.0",
|
|
147
|
+
"xterm-addon-fit": "0.6.0",
|
|
148
|
+
"xterm-addon-search": "0.10.0",
|
|
149
|
+
"xterm-addon-web-links": "0.7.0",
|
|
150
|
+
"xterm-addon-webgl": "0.13.0",
|
|
151
151
|
"worker-loader": "3.0.8",
|
|
152
152
|
"yarn": "1.22.18"
|
|
153
153
|
},
|
package/pages/about.vue
CHANGED
|
@@ -16,7 +16,8 @@ export default {
|
|
|
16
16
|
},
|
|
17
17
|
data() {
|
|
18
18
|
return {
|
|
19
|
-
|
|
19
|
+
dashboardVersion: this.$config.dashboardVersion,
|
|
20
|
+
settings: null,
|
|
20
21
|
SETTING
|
|
21
22
|
};
|
|
22
23
|
},
|
|
@@ -117,10 +118,21 @@ export default {
|
|
|
117
118
|
target="_blank"
|
|
118
119
|
rel="nofollow noopener noreferrer"
|
|
119
120
|
>
|
|
120
|
-
{{
|
|
121
|
+
{{ t("about.versions.rancher") }}
|
|
121
122
|
</a>
|
|
122
123
|
</td><td>{{ rancherVersion.value }}</td>
|
|
123
124
|
</tr>
|
|
125
|
+
<tr v-if="dashboardVersion">
|
|
126
|
+
<td>
|
|
127
|
+
<a
|
|
128
|
+
href="https://github.com/rancher/dashboard"
|
|
129
|
+
target="_blank"
|
|
130
|
+
rel="nofollow noopener noreferrer"
|
|
131
|
+
>
|
|
132
|
+
{{ t("generic.dashboard") }}
|
|
133
|
+
</a>
|
|
134
|
+
</td><td>{{ dashboardVersion }}</td>
|
|
135
|
+
</tr>
|
|
124
136
|
<tr v-if="cliVersion">
|
|
125
137
|
<td>
|
|
126
138
|
<a
|
|
@@ -34,7 +34,7 @@ export default {
|
|
|
34
34
|
},
|
|
35
35
|
|
|
36
36
|
async fetch() {
|
|
37
|
-
await this.$store.dispatch('catalog/load');
|
|
37
|
+
await this.$store.dispatch('catalog/load', { force: true, reset: true });
|
|
38
38
|
|
|
39
39
|
const query = this.$route.query;
|
|
40
40
|
|
|
@@ -53,7 +53,6 @@ export default {
|
|
|
53
53
|
searchQuery: null,
|
|
54
54
|
showDeprecated: null,
|
|
55
55
|
showHidden: null,
|
|
56
|
-
chartMode: this.$store.getters['prefs/get'](SHOW_CHART_MODE),
|
|
57
56
|
chartOptions: [
|
|
58
57
|
{
|
|
59
58
|
label: 'Browse',
|
|
@@ -71,6 +70,8 @@ export default {
|
|
|
71
70
|
...mapGetters(['currentCluster']),
|
|
72
71
|
...mapGetters({ allCharts: 'catalog/charts', loadingErrors: 'catalog/errors' }),
|
|
73
72
|
|
|
73
|
+
chartMode: mapPref(SHOW_CHART_MODE),
|
|
74
|
+
|
|
74
75
|
hideRepos: mapPref(HIDE_REPOS),
|
|
75
76
|
|
|
76
77
|
repoOptions() {
|
|
@@ -339,7 +340,7 @@ export default {
|
|
|
339
340
|
<template>
|
|
340
341
|
<Loading v-if="$fetchState.pending" />
|
|
341
342
|
<div v-else>
|
|
342
|
-
<header
|
|
343
|
+
<header>
|
|
343
344
|
<div class="title">
|
|
344
345
|
<h1 class="m-0">
|
|
345
346
|
{{ t('catalog.charts.header') }}
|