dashboard/shell/chart/monitoring/index.vue

367 lines
11 KiB
Vue

<script>
import isEmpty from 'lodash/isEmpty';
import merge from 'lodash/merge';
import { mapGetters } from 'vuex';
import Alerting from '@shell/chart/monitoring/alerting';
import { Checkbox } from '@components/Form/Checkbox';
import ClusterSelector from '@shell/chart/monitoring/ClusterSelector';
import Grafana from '@shell/chart/monitoring/grafana';
import { LabeledInput } from '@components/Form/LabeledInput';
import Loading from '@shell/components/Loading';
import Prometheus from '@shell/chart/monitoring/prometheus';
import Tab from '@shell/components/Tabbed/Tab';
import Tabbed from '@shell/components/Tabbed';
import { allHash } from '@shell/utils/promise';
import { STORAGE_CLASS, PVC, SECRET, WORKLOAD_TYPES } from '@shell/config/types';
import { CATTLE_MONITORING_NAMESPACE } from '@shell/utils/monitoring';
export default {
emits: ['register-before-hook', 'input'],
components: {
Alerting,
Checkbox,
ClusterSelector,
Grafana,
LabeledInput,
Loading,
Prometheus,
Tab,
Tabbed
},
props: {
chart: {
type: Object,
default: () => ({}),
},
mode: {
type: String,
default: 'create',
},
value: {
type: Object,
default: () => {
return {};
},
},
},
async fetch() {
const { $store } = this;
// Fetch all the resources required for all the tabs asynchronously up front
const hashPromises = {
namespaces: $store.getters['namespaces'](),
pvcs: $store.dispatch('cluster/findAll', { type: PVC }),
// Used in Alerting tab
monitoringSecrets: $store.dispatch('cluster/findAll', {
type: SECRET,
opt: { namespaced: CATTLE_MONITORING_NAMESPACE }
}),
storageClasses: $store.dispatch('cluster/findAll', { type: STORAGE_CLASS }),
};
// Are we editing an existing chart?
// (ported from shell/chart/monitoring/prometheus/index.vue)
const { existing = false } = this.$attrs;
// If needed, fetch all the workloads that have prometheus operator like containers
const { CRON_JOB, ...validWorkloads } = WORKLOAD_TYPES;
this.workloadTypes = !existing ? Object.values(validWorkloads) : [];
this.workloadTypes.forEach((type) => {
// We'll use a filter to fetch the results. Atm there's no neat way to differentiate between ALL results and JUST filtered
// So to avoid calls to all getting these filtered (and vice-versa) forget type before and after
$store.dispatch('cluster/forgetType', type);
hashPromises[type] = $store.dispatch('cluster/findAll', {
type,
opt: {
watch: false,
// We're only interested in images with operator like names (note: these will match partial strings)
filter: { 'spec.template.spec.containers.image': ['quay.io/coreos/prometheus-operator', 'rancher/coreos-prometheus-operator'] }
}
});
});
const hash = await allHash(hashPromises);
this.targetNamespace = hash.namespaces[this.chart.targetNamespace] || false;
if (!isEmpty(hash.storageClasses)) {
this.storageClasses = hash.storageClasses;
}
if (!isEmpty(hash.pvcs)) {
this.pvcs = hash.pvcs;
}
if (!isEmpty(hash.monitoringSecrets)) {
this.monitoringSecrets = hash.monitoringSecrets;
}
this.workloadTypes.forEach((type) => {
if (hash[type]) {
this.filteredWorkloads.push(...hash[type]);
}
});
},
beforeUnmount() {
this.workloadTypes.forEach((type) => {
this.$store.dispatch('cluster/forgetType', type);
});
},
data() {
return {
accessModes: [
{
id: 'ReadWriteOnce',
label: 'monitoring.accessModes.once',
},
{
id: 'ReadOnlyMany',
label: 'monitoring.accessModes.readOnlyMany',
},
{
id: 'ReadWriteMany',
label: 'monitoring.accessModes.many',
},
],
clusterType: {},
disableAggregateRoles: false,
prometheusResources: [],
pvcs: [],
monitoringSecrets: [],
storageClasses: [],
targetNamespace: null,
filteredWorkloads: [],
workloadTypes: []
};
},
computed: {
...mapGetters(['currentCluster']),
provider() {
return this.currentCluster.status.provider.toLowerCase();
},
},
watch: {
'value.global.rbac.userRoles.create'(createUserRoles) {
if (createUserRoles) {
this.disableAggregateRoles = false;
} else {
this.value.global.rbac.userRoles.aggregateToDefaultRoles = false;
this.disableAggregateRoles = true;
}
},
},
created() {
if (this.mode === 'create') {
// merge here doesn't work (existing values are lost when going from form to yaml and back again) so instead supply some better default values
// any changes here need to respect the order of properties (reflected in the yaml diff)
const extendedDefaults = {
global: {
rbac: {
userRoles: {
create: this.mergeValue(this.value?.global?.rbac?.userRoles?.create, true),
aggregateToDefaultRoles: this.mergeValue(this.value?.global?.rbac?.userRoles?.aggregateToDefaultRoles, true),
},
},
},
prometheus: {
prometheusSpec: {
scrapeInterval: this.mergeValue(this.value?.prometheus?.prometheusSpec?.scrapeInterval, '1m'),
evaluationInterval: this.mergeValue(this.value?.prometheus?.prometheusSpec?.evaluationInterval, '1m'),
retention: this.mergeValue(this.value?.prometheus?.prometheusSpec?.retention, '10d'),
retentionSize: this.mergeValue(this.value?.prometheus?.prometheusSpec?.retentionSize, '50GiB'),
enableAdminAPI: this.mergeValue(this.value?.prometheus?.prometheusSpec?.enableAdminAPI, false),
},
},
};
merge(this.value, extendedDefaults);
if (this.provider.startsWith('rke2')) {
this.value.rke2IngressNginx['enabled'] = true;
this.value.rke2Etcd['enabled'] = true;
this.value.rkeEtcd['enabled'] = false;
} else if (this.provider.startsWith('rke')) {
this.value['ingressNginx'] = this.value.ingressNginx || {};
this.value.ingressNginx['enabled'] = true;
} else {
this.value.rkeEtcd['enabled'] = false;
}
}
this.$emit('register-before-hook', this.willSave, 'willSave');
},
methods: {
willSave() {
const { prometheusSpec } = this.value.prometheus;
const selector =
prometheusSpec?.storageSpec?.volumeClaimTemplate?.spec?.selector;
// This works for UI editor installation
// However, it doesn't work for yaml editor installation
// Global values later merged again in charts/install.vue addGlobalValuesTo()
// We still need to remove the global values from charts/install.vue addGlobalValuesTo()
if (
selector &&
isEmpty(selector.matchExpressions) &&
isEmpty(selector.matchLabels)
) {
delete this.value.prometheus.prometheusSpec.storageSpec
.volumeClaimTemplate.spec.selector;
}
},
mergeValue(value, defaultValue) {
return value === undefined || (typeof value === 'string' && !value.length) ? defaultValue : value;
},
onTabChanged() {
window.scrollTop = 0;
}
},
};
</script>
<template>
<Loading
v-if="$fetchState.pending"
mode="relative"
/>
<div
v-else
class="config-monitoring-container"
>
<Tabbed
:side-tabs="true"
:hide-single-tab="true"
class="step__values__content with-name"
@changed="onTabChanged"
>
<Tab
name="general"
:label="t('monitoring.tabs.general')"
:weight="99"
>
<div>
<div class="row mb-20">
<div class="col span-6">
<ClusterSelector
:value="value"
:mode="mode"
@onClusterTypeChanged="clusterType = $event"
/>
</div>
</div>
<div
v-if="clusterType.group === 'managed'"
class="row mb-20"
>
<Checkbox
v-model:value="value.prometheusOperator.hostNetwork"
label-key="monitoring.hostNetwork.label"
:tooltip="t('monitoring.hostNetwork.tip', {}, true)"
/>
</div>
<div class="row">
<div class="col span-6">
<Checkbox
v-model:value="value.global.rbac.userRoles.create"
label-key="monitoring.createDefaultRoles.label"
:tooltip="t('monitoring.createDefaultRoles.tip', {}, true)"
/>
</div>
<div class="col span-6">
<Checkbox
v-model:value="value.global.rbac.userRoles.aggregateToDefaultRoles"
label-key="monitoring.aggregateDefaultRoles.label"
:tooltip="{
content: t('monitoring.aggregateDefaultRoles.tip', {}, true),
autoHide: false,
}"
:disabled="disableAggregateRoles"
/>
</div>
</div>
<div
v-if="provider === 'rke' && value.rkeEtcd"
class="row mt-20"
>
<div class="col span-6">
<LabeledInput
v-model:value="value.rkeEtcd.clients.https.certDir"
:label="t('monitoring.etcdNodeDirectory.label')"
:tooltip="t('monitoring.etcdNodeDirectory.tooltip', {}, true)"
:hover-tooltip="true"
:mode="mode"
/>
</div>
</div>
</div>
</Tab>
<Tab
name="prometheus"
:label="t('monitoring.tabs.prometheus')"
:weight="98"
>
<div>
<Prometheus
:value="value"
v-bind="$attrs"
:access-modes="accessModes"
:mode="mode"
:storage-classes="storageClasses"
:prometheus-pods="prometheusResources"
:filteredWorkloads="filteredWorkloads"
@update:value="$emit('input', $event)"
/>
</div>
</Tab>
<Tab
name="alerting"
:label="t('monitoring.tabs.alerting')"
:weight="97"
>
<div>
<Alerting
:value="value"
:mode="mode"
:monitoringSecrets="monitoringSecrets"
@update:value="$emit('input', $event)"
/>
</div>
</Tab>
<Tab
name="grafana"
:label="t('monitoring.tabs.grafana')"
:weight="96"
>
<div>
<Grafana
:value="value"
:access-modes="accessModes"
:mode="mode"
:pvcs="pvcs"
:storage-classes="storageClasses"
@update:value="$emit('input', $event)"
/>
</div>
</Tab>
</Tabbed>
</div>
</template>