Initial Monitoring V2 Custom Chart UI

rancher/dashboard#908
This commit is contained in:
Westly Wright 2020-08-04 16:35:58 -07:00
parent 4511f0f06f
commit 687ce496b2
No known key found for this signature in database
GPG Key ID: 4FAB3D8673DC54A3
8 changed files with 931 additions and 16 deletions

View File

@ -11,6 +11,8 @@ generic:
create: Create
created: Created
customize: Customize
disabled: Disabled
enabled: Enabled
na: n/a
name: Name
remove: Remove
@ -64,7 +66,7 @@ suffix:
# Components & Pages
##############################
asyncButton:
default:
default:
action: Action
waiting: Waiting
success: Success
@ -376,7 +378,7 @@ ingress:
internalExternalIP:
none: None
istio:
istio:
cni: Enabled CNI
customOverlayFile:
label: Custom Overlay File
@ -401,14 +403,72 @@ logging:
clusterName: Cluster Name
user: User
password: Password
clientCert:
clientCert:
label: Client Cert
placeholder: Paste in the CA certificate
clientKey:
clientKey:
label: Client Key
placeholder: Paste in the client key
clientKeyPass: Client Key Pass
monitoring:
accessModes:
many: ReadWriteMany
once: ReadWriteOnce
readOnlyMany: ReadOnlyMany
clusterType:
k3s: 'K3s,'
kubeAdmin: KubeADM
label: Cluster Type
managed: 'Managed Cluster (EKS, GKE, AKS, etc.)'
other: Other
placeholder: Select cluster type
rke: RKE
grafana:
storage:
annotations: PVC Annotations
className: Storage Class Name
existingClaim: Use Existing Claim
finalizers: PVC Finalizers
label: Persistent Storage for Grafana
mode: Access Mode
selector: Selector
size: Size
subpath: Use Subpath
type: Persistent Storage Types
types:
existing: Enable With Existing PVC
statefulset: Enable with StatefulSet Template
template: Enable with PVC Template
volumeMode: Volume Mode
volumeName: Volume Name
title: Grafana Config
prometheus:
config:
adminApi: Admin API
evaluation: Evaluation Interval
limits:
cpu: CPU Limit
memory: Memory Limit
requests:
cpu: Requested CPU
memory: Requested Memory
retention: Retention
retentionSize: Retention Size
scrape: Scrape Interval
storage:
className: Storage Class Name
label: Persistent Storage for Prometheus
mode: Access Mode
selector: Selector
size: Size
volumeMode: Volume Mode
volumeName: Volume Name
title: Prometheus Config
volume:
modes:
block: Block
file: Filesystem
node:
detail:

View File

@ -0,0 +1,149 @@
<script>
import isEmpty from 'lodash/isEmpty';
import LabeledSelect from '@/components/form/LabeledSelect';
const CLUSTER_TYPES = [
{
id: 'RKE',
label: 'monitoring.clusterType.rke',
},
{
id: 'K3s',
label: 'monitoring.clusterType.k3s',
},
{
id: 'KubeADM',
label: 'monitoring.clusterType.kubeAdmin',
},
{
id: 'Managed Cluster (EKS, GKE, AKS, etc.)',
label: 'monitoring.clusterType.managed',
},
{
id: 'Other',
label: 'monitoring.clusterType.other',
},
];
const CONFIG_KEYS = {
rke: ['rkeControllerManager', 'rkeScheduler', 'rkeProxy', 'rkeEtcd'],
k3s: ['k3sControllerManager', 'k3sScheduler', 'k3sProxy'],
kubeadm: [
'kubeAdmControllerManager',
'kubeAdmScheduler',
'kubeAdmProxy',
'kubeAdmEtcd',
],
managed: [
'kubeControllerManager',
'kubeScheduler',
'kubeEtcd',
'prometheusOperator.hostNetwork',
],
other: ['kubeControllerManager', 'kubeScheduler', 'kubeEtcd'],
};
export default {
components: { LabeledSelect },
props: {
value: {
type: Object,
default: () => {
return {};
},
},
},
data() {
return {
clusterType: null,
clusterTypes: CLUSTER_TYPES,
configKeys: CONFIG_KEYS,
};
},
watch: {
// This method is not that disimilar to persistentStorageType in Grafan config
// The reason for the divergence is that Grafna has a subkey on the chart
// where these keys are at the root of the chart. Vue complains about calling
// this.$set(this, 'value', obj) as we need to do here to reset the values in bulk.
// So rather than call each set on each line individually I give you this.
clusterType(clusterType, oldClusterType) {
if (isEmpty(clusterType)) {
return;
}
let resetOut = [];
let setNewOut = [];
// reset old values
switch (oldClusterType) {
case 'RKE':
resetOut = [this.configKeys.rke, false];
break;
case 'K3s':
resetOut = [this.configKeys.k3s, false];
break;
case 'KubeADM':
resetOut = [this.configKeys.kubeadm, false];
break;
case 'Managed Cluster (EKS, GKE, AKS, etc.)':
resetOut = [this.configKeys.managed, false];
break;
case 'Other':
resetOut = [this.configKeys.other, false];
break;
default:
break;
}
// set new values
switch (clusterType) {
case 'RKE':
setNewOut = [this.configKeys.rke, true];
break;
case 'K3s':
setNewOut = [this.configKeys.k3s, true];
break;
case 'KubeADM':
setNewOut = [this.configKeys.kubeadm, true];
break;
case 'Managed Cluster (EKS, GKE, AKS, etc.)':
setNewOut = [this.configKeys.managed, false];
this.setClusterTypeEnabledValues(
['prometheusOperator.hostNetwork'],
true
);
break;
case 'Other':
setNewOut = [this.configKeys.other, true];
break;
default:
break;
}
this.setClusterTypeEnabledValues(resetOut);
this.setClusterTypeEnabledValues(setNewOut);
},
},
methods: {
setClusterTypeEnabledValues([keyNames = [], valueToSet = null]) {
const { value } = this;
keyNames.forEach((kn) => {
this.$set(value[kn], 'enabled', valueToSet);
});
},
},
};
</script>
<template>
<LabeledSelect
:label="t('monitoring.clusterType.label')"
:placeholder="t('monitoring.clusterType.placeholder')"
:localized-label="true"
:options="clusterTypes"
:value="clusterType"
@input="({id}) => clusterType = id"
/>
</template>

View File

@ -0,0 +1,45 @@
<script>
import LabeledSelect from '@/components/form/LabeledSelect';
export default {
components: { LabeledSelect },
props: {
options: {
type: Array,
default: () => {
return [];
},
},
value: {
type: String,
default: '',
},
},
methods: {
createNewStorageClassName(newOption) {
newOption = { metadata: { name: newOption } };
return newOption;
},
updateName({ metadata: { name } }) {
this.$emit('updateName', name);
},
},
};
</script>
<template>
<LabeledSelect
v-bind="$attrs"
:value="value"
option-key="metadata.name"
option-label="metadata.name"
:create-option="createNewStorageClassName"
:localized-label="false"
:options="options"
:push-tags="true"
:taggable="true"
@input="updateName"
/>
</template>

View File

@ -0,0 +1,298 @@
<script>
import RadioGroup from '@/components/form/RadioGroup';
import LabeledInput from '@/components/form/LabeledInput';
import LabeledSelect from '@/components/form/LabeledSelect';
import KeyValue from '@/components/form/KeyValue';
import ArrayList from '@/components/form/ArrayList';
import StorageClassSelector from '@/chart/monitoring/StorageClassSelector';
export default {
components: {
ArrayList,
KeyValue,
LabeledInput,
LabeledSelect,
RadioGroup,
StorageClassSelector,
},
props: {
accessModes: {
type: Array,
required: true,
},
mode: {
type: String,
default: 'create',
},
storageClasses: {
type: Array,
default: () => {
return [];
},
},
pvcs: {
type: Array,
default: () => {
return [];
},
},
value: {
type: Object,
default: () => {
return {};
},
},
},
data() {
return {
persistantStorageTypes: ['disabled', 'existing', 'pvc', 'statefulset'],
persistentStorageType: 'disabled',
persistentStorageTypeLabels: [
this.t('generic.disabled'),
this.t('monitoring.grafana.storage.types.existing'),
this.t('monitoring.grafana.storage.types.template'),
this.t('monitoring.grafana.storage.types.statefulset'),
],
};
},
watch: {
persistentStorageType(newType, oldType) {
let newValsOut;
let resetValsOut;
switch (oldType) {
case 'existing':
resetValsOut = {
existingClaim: null,
subPath: null,
type: null,
};
break;
case 'pvc':
resetValsOut = {
accessModes: null,
storageClassName: null,
size: null,
subPath: null,
annotations: null,
finalizers: null,
};
break;
case 'statefulset':
resetValsOut = {
accessModes: null,
storageClassName: null,
size: null,
subPath: null,
};
break;
default:
break;
}
switch (newType) {
case 'existing':
newValsOut = {
existingClaim: null,
subPath: null,
type: null,
};
break;
case 'pvc':
newValsOut = {
accessModes: null,
storageClassName: null,
size: null,
subPath: null,
type: 'pvc',
annotations: null,
finalizers: null,
};
break;
case 'statefulset':
newValsOut = {
accessModes: null,
storageClassName: null,
size: null,
subPath: null,
type: 'statefulset',
};
break;
default:
this.$delete(this.value.grafana, 'persistence');
break;
}
this.$set(this.value.grafana, 'persistence', resetValsOut);
this.$set(this.value.grafana, 'persistence', newValsOut);
},
},
};
</script>
<template>
<div>
<div class="title">
<h3>{{ t('monitoring.grafana.title') }}</h3>
</div>
<div class="grafana-config">
<div class="row pt-10 pb-10">
<div class="col span-12 persistent-storage-config">
<RadioGroup
v-model="persistentStorageType"
:label="t('monitoring.grafana.storage.label')"
:labels="persistentStorageTypeLabels"
:mode="mode"
:options="persistantStorageTypes"
/>
</div>
</div>
<template v-if="persistentStorageType === 'existing'">
<div class="row">
<div class="col span-6">
<StorageClassSelector
:value="value.grafana.persistence.existingClaim"
:mode="mode"
:options="pvcs"
:label="t('monitoring.grafana.storage.existingClaim')"
@updateName="(name) => $set(value.grafana.persistence, 'existingClaim', name)"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model="value.grafana.persistence.subPath"
:label="t('monitoring.grafana.storage.subpath')"
:mode="mode"
/>
</div>
</div>
</template>
<template v-else-if="persistentStorageType === 'pvc'">
<div class="row">
<div class="col span-6">
<LabeledSelect
v-model="value.grafana.persistence.accessModes"
:label="t('monitoring.grafana.storage.mode')"
:localized-label="true"
:mode="mode"
:options="accessModes"
:reduce="({id})=> id"
/>
</div>
<div class="col span-6">
<StorageClassSelector
:value="value.grafana.persistence.storageClassName"
:mode="mode"
:options="storageClasses"
:label="t('monitoring.prometheus.storage.className')"
@updateName="(name) => $set(value.grafana.persistence, 'storageClassName', name)"
/>
</div>
</div>
<div class="row">
<div class="col span-6">
<LabeledInput
v-model="value.grafana.persistence.size"
:label="t('monitoring.grafana.storage.size')"
:mode="mode"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model="value.grafana.persistence.subPath"
:label="t('monitoring.grafana.storage.subpath')"
:mode="mode"
/>
</div>
</div>
</template>
<template v-else-if="persistentStorageType === 'statefulset'">
<div class="row">
<div class="col span-6">
<LabeledSelect
v-model="value.grafana.persistence.accessModes"
:label="t('monitoring.grafana.storage.mode')"
:localized-label="true"
:mode="mode"
:options="accessModes"
:reduce="({id})=> id"
/>
</div>
<div class="col span-6">
<StorageClassSelector
:value="value.grafana.persistence.storageClassName"
:mode="mode"
:options="storageClasses"
:label="t('monitoring.prometheus.storage.className')"
@updateName="(name) => $set(value.grafana.persistence, 'storageClassName', name)"
/>
</div>
</div>
<div class="row">
<div class="col span-6">
<LabeledInput
v-model="value.grafana.persistence.size"
:label="t('monitoring.grafana.storage.size')"
:mode="mode"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model="value.grafana.persistence.subPath"
:label="t('monitoring.grafana.storage.subpath')"
:mode="mode"
/>
</div>
</div>
<div class="mt-20">
<div class="mb-5 mt-5">
<label class="text-label mb-10">{{ t('monitoring.grafana.storage.annotations') }}</label>
</div>
<div class="row">
<div class="col span-12">
<KeyValue
v-model="value.grafana.persistence.annotations"
:mode="mode"
:pad-left="false"
:protip="true"
:read-allowed="false"
/>
</div>
</div>
</div>
<div class="row mt-20">
<div class="col span-12">
<ArrayList
v-model="value.grafana.persistence.finalizers"
table-class="fixed"
:mode="mode"
:pad-left="false"
:protip="true"
:title="t('monitoring.grafana.storage.finalizers')"
/>
</div>
</div>
</template>
</div>
</div>
</template>
<style lang="scss">
.grafana-config {
& > * {
margin-top: 10px;
}
// .persistent-storage-config {
// .radio-group {
// &:not(.text-label) {
// display: flex;
// justify-content: space-between;
// }
// }
// }
}
</style>

116
chart/monitoring/index.vue Normal file
View File

@ -0,0 +1,116 @@
<script>
import { STORAGE_CLASS, PVC } from '@/config/types';
import merge from 'lodash/merge';
import ClusterSelector from './ClusterSelector';
import Prometheus from './prometheus';
import Grafana from './grafana';
export default {
components: {
ClusterSelector,
Prometheus,
Grafana,
},
props: {
mode: {
type: String,
default: 'create',
},
value: {
type: Object,
default: () => {
return {};
},
},
},
data() {
return {
accessModes: [
{
id: 'ReadWriteOnce',
label: 'monitoring.accessModes.once',
},
{
id: 'ReadOnlyMany',
label: 'monitoring.accessModes.readOnlyMany',
},
{
id: 'ReadWriteMany',
label: 'monitoring.accessModes.many',
},
],
storageClasses: [],
pvcs: [],
};
},
mounted() {
if (this.mode === 'create') {
const defaultPrometheusSpec = {
scrapeInterval: '1m',
evaluationInterval: '1m',
retention: '10d',
retentionSize: '50Gib',
enableAdminAPI: false,
};
merge(this.value.prometheus.prometheusSpec, defaultPrometheusSpec);
}
this.fetchDeps();
},
methods: {
async fetchDeps() {
const storageClasses = await this.$store.dispatch('cluster/findAll', { type: STORAGE_CLASS });
const pvcs = await this.$store.dispatch('cluster/findAll', { type: PVC });
if (storageClasses) {
this.storageClasses = storageClasses;
}
if (pvcs) {
this.pvcs = pvcs;
}
},
},
};
</script>
<template>
<div class="config-monitoring-container">
<section class="config-cluster-general">
<ClusterSelector :value="value" :mode="mode" />
</section>
<section class="config-prometheus-container">
<Prometheus
v-model="value"
:access-modes="accessModes"
:mode="mode"
:storage-classes="storageClasses"
/>
</section>
<section class="config-grafana-container">
<Grafana
v-model="value"
:access-modes="accessModes"
:mode="mode"
:pvcs="pvcs"
:storage-classes="storageClasses"
/>
</section>
</div>
</template>
<style lang="scss">
.config-monitoring-container {
> section {
margin-bottom: 20px;
.title {
border-bottom: 1px solid var(--border);
}
}
}
</style>

View File

@ -0,0 +1,242 @@
<script>
import LabeledInput from '@/components/form/LabeledInput';
import LabeledSelect from '@/components/form/LabeledSelect';
import RadioGroup from '@/components/form/RadioGroup';
import KeyValue from '@/components/form/KeyValue';
import StorageClassSelector from '@/chart/monitoring/StorageClassSelector';
export default {
components: {
KeyValue,
LabeledInput,
LabeledSelect,
RadioGroup,
StorageClassSelector,
},
props: {
accessModes: {
type: Array,
required: true,
},
mode: {
type: String,
default: 'create',
},
storageClasses: {
type: Array,
default: () => {
return [];
},
},
value: {
type: Object,
default: () => {
return {};
},
},
},
data() {
return {
volumeModes: [
{
id: 'Filesystem',
label: 'monitoring.volume.modes.file',
},
{
id: 'Block',
label: 'monitoring.volume.modes.block',
},
],
enablePersistantStorage: false,
};
},
watch: {
enablePersistantStorage(enabled) {
if (!!enabled) {
this.$set(
this.value.prometheus.prometheusSpec.storageSpec,
'volumeClaimTemplate',
{ spec: { resources: { requests: { storage: '50Gi' } } } }
);
} else {
this.$delete(
this.value.prometheus.prometheusSpec.storageSpec,
'volumeClaimTemplate'
);
}
},
},
};
</script>
<template>
<div>
<div class="title">
<h3>{{ t('monitoring.prometheus.title') }}</h3>
</div>
<div class="prometheus-config">
<div class="row">
<div class="col span-6">
<LabeledInput
v-model="value.prometheus.prometheusSpec.scrapeInterval"
:label="t('monitoring.prometheus.config.scrape')"
:mode="mode"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model="value.prometheus.prometheusSpec.evaluationInterval"
:label="t('monitoring.prometheus.config.evaluation')"
:mode="mode"
/>
</div>
</div>
<div class="row">
<div class="col span-6">
<LabeledInput
v-model="value.prometheus.prometheusSpec.retention"
:label="t('monitoring.prometheus.config.retention')"
:mode="mode"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model="value.prometheus.prometheusSpec.retentionSize"
:label="t('monitoring.prometheus.config.retentionSize')"
:mode="mode"
/>
</div>
</div>
<div class="row">
<div class="col span-6">
<LabeledInput
v-model="value.prometheus.prometheusSpec.resources.requests.cpu"
:label="t('monitoring.prometheus.config.requests.cpu')"
:mode="mode"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model="value.prometheus.prometheusSpec.resources.requests.memory"
:label="t('monitoring.prometheus.config.requests.memory')"
:mode="mode"
/>
</div>
</div>
<div class="row">
<div class="col span-6">
<LabeledInput
v-model="value.prometheus.prometheusSpec.resources.limits.memory"
:label="t('monitoring.prometheus.config.limits.memory')"
:mode="mode"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model="value.prometheus.prometheusSpec.resources.limits.cpu"
:label="t('monitoring.prometheus.config.limits.cpu')"
:mode="mode"
/>
</div>
</div>
<div class="row pt-10 pb-10">
<div class="col span-6">
<RadioGroup
v-model="value.prometheus.prometheusSpec.enableAdminAPI"
:label="t('monitoring.prometheus.config.adminApi')"
:labels="[t('generic.disabled'), t('generic.enabled')]"
:mode="mode"
:options="[false, true]"
/>
</div>
<div class="col span-6">
<RadioGroup
v-model="enablePersistantStorage"
:label="t('monitoring.prometheus.storage.label')"
:labels="[t('generic.disabled'), t('generic.enabled')]"
:mode="mode"
:options="[false, true]"
/>
</div>
</div>
<template v-if="enablePersistantStorage">
<div class="row">
<div class="col span-6">
<LabeledSelect
v-model="value.prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.accessModes"
:label="t('monitoring.prometheus.storage.mode')"
:localized-label="true"
:mode="mode"
:options="accessModes"
:reduce="({id})=> id"
/>
</div>
<div class="col span-6">
<StorageClassSelector
:mode="mode"
:options="storageClasses"
:value="value.prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.storageClassName"
:label="t('monitoring.prometheus.storage.className')"
@updateName="(name) => $set(value.prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec, 'storageClassName', name)"
/>
</div>
</div>
<div class="row">
<div class="col span-6">
<LabeledSelect
v-model="value.prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.volumeMode"
:label="t('monitoring.prometheus.storage.volumeMode')"
:localized-label="true"
:mode="mode"
:options="volumeModes"
:reduce="({id})=> id"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model="value.prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.volumeName"
:label="t('monitoring.prometheus.storage.volumeName')"
:mode="mode"
/>
</div>
</div>
<div class="row">
<div class="col span-6">
<LabeledInput
v-model="value.prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage"
:label="t('monitoring.prometheus.storage.size')"
:mode="mode"
/>
</div>
</div>
<div class="row">
<div class="col span-12">
<div class="mb-5 mt-5">
<label class="text-label mb-10">{{ t('monitoring.prometheus.storage.selector') }}</label>
</div>
<KeyValue
v-model="value.prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.selector"
:mode="mode"
:pad-left="false"
:protip="false"
:read-allowed="false"
/>
</div>
</div>
</template>
</div>
</div>
</template>
<style lang="scss">
.prometheus-config {
& > * {
margin-top: 10px;
}
}
</style>

View File

@ -93,6 +93,10 @@ export default {
defaultAddValue: {
type: [String, Number, Object, Array],
default: ''
},
tableClass: {
type: [String, Object, Array],
default: 'fixed zebra-table'
}
},
@ -185,7 +189,7 @@ export default {
<label :style="{'color': 'var(--input-label)', 'font-size':'14px'}">{{ title }} <i v-if="protip" v-tooltip="protip" class="icon icon-info" style="font-size: 12px" /></label>
</div>
<table v-if="rows.length" class="fixed zebra-table">
<table v-if="rows.length" :class="tableClass">
<thead v-if="showHeader">
<tr>
<th v-if="padLeft" class="left"></th>

View File

@ -171,21 +171,22 @@ export default {
<v-select
v-if="!isView"
ref="input"
:value="value ? value : ' '"
class="inline"
:class="{'no-label':!(label||'').length}"
:disabled="isView || disabled"
:options="options"
:get-option-label="opt=>getOptionLabel(opt)"
:get-option-key="opt=>optionKey ? get(opt, optionKey) : getOptionLabel(opt)"
:label="optionLabel"
:reduce="x => reduce(x)"
v-bind="$attrs"
class="inline"
:append-to-body="!!placement"
:calculate-position="placement ? withPopper : undefined"
@search:focus="onFocus"
@search:blur="onBlur"
:class="{'no-label':!(label||'').length}"
:disabled="isView || disabled"
:get-option-key="opt=>optionKey ? get(opt, optionKey) : getOptionLabel(opt)"
:get-option-label="opt=>getOptionLabel(opt)"
:label="optionLabel"
:options="options"
:placeholder="placeholder"
:reduce="x => reduce(x)"
:value="value ? value : ' '"
@input="e=>$emit('input', e)"
@search:blur="onBlur"
@search:focus="onFocus"
>
<template v-if="!$attrs.multiple" v-slot:selected-option-container>
<span style="display: none"></span>