Monitoring UI Polish - Overview & Create Route

rancher/dashboard#1986

update receiver tabs and labeled tooltips

fix styling on receiver responder rows
This commit is contained in:
Westly Wright 2021-01-04 10:38:49 -07:00
parent a4aaf62034
commit 534e0a982b
No known key found for this signature in database
GPG Key ID: 4FAB3D8673DC54A3
13 changed files with 542 additions and 496 deletions

View File

@ -25,6 +25,7 @@
@import "./global/tooltip"; @import "./global/tooltip";
@import "./global/table"; @import "./global/table";
@import "./global/select"; @import "./global/select";
@import "./global/resource";
@import "./vendor/vue-select"; @import "./vendor/vue-select";
@import "./vendor/vue-js-modal"; @import "./vendor/vue-js-modal";

View File

@ -0,0 +1,92 @@
.create-resource-container {
.subtypes-container {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.subtype-content {
width: 100%;
}
.subtype-banner {
border-left: 5px solid var(--primary);
border-radius: var(--border-radius);
display: flex;
flex-basis: 40%;
margin: 10px;
min-height: 100px;
padding: 10px;
box-shadow: 0 0 20px var(--shadow);
&.disabled {
cursor: not-allowed !important;
background-color: var(--disabled-bg);
}
&.selected {
background-color: var(--accent-btn);
}
&.top {
background-image: linear-gradient(
-90deg,
var(--body-bg),
var(--accent-btn)
);
h2 {
margin: 0px;
}
}
.title {
align-items: center;
display: flex;
width: 100%;
h5 {
margin: 0;
}
.flex-right {
margin-left: auto;
}
}
.description {
color: var(--input-label);
display: flex;
flex-direction: column;
justify-content: center;
}
&:not(.top) {
align-items: top;
flex-direction: row;
justify-content: start;
&:hover {
cursor: pointer;
box-shadow: 0px 0px 1px var(--outline-width) var(--outline);
}
}
.round-image {
border-radius: 50%;
height: 50px;
margin-right: 10px;
width: 50px;
overflow: hidden;
}
.banner-abbrv {
align-items: center;
background-color: var(--primary);
color: white;
display: flex;
font-size: 2.5em;
height: 100%;
justify-content: center;
width: 100%;
}
}
}

View File

@ -1119,6 +1119,32 @@ monitoring:
block: Block block: Block
file: Filesystem file: Filesystem
monitoringReceiver:
addButton: Add {type}
custom:
label: Custom
title: Custom Config
info: The YAML provided here will be directly appended to your receiver within the Alertmanager Config Secret.
email:
label: Email
title: Email Config
opsgenie:
label: Opsgenie
title: Opsgenie Config
pagerduty:
label: PagerDuty
title: PagerDuty Config
info: "You can find additional info on creating an Integration Key for PagerDuty <a href='https://www.pagerduty.com/docs/guides/prometheus-integration-guide/' target='_blank' rel='noopener nofollow' class='flex-right'>here</a>."
slack:
label: Slack
title: Slack Config
info: "You can find additional info on creating Incoming Webhooks for Slack <a href='https://rancher.slack.com/apps/A0F7XDUAZ-incoming-webhooks' target='_blank' rel='noopener noreferrer nofollow'>here</a> ."
webhook:
label: Webhook
title: Webhook Config
monitoringRoute: monitoringRoute:
groups: groups:
label: Group By label: Group By

View File

@ -359,7 +359,7 @@ export default {
</section> </section>
</template> </template>
<style lang='scss'> <style lang='scss' scoped>
.cru-resource-yaml-container { .cru-resource-yaml-container {
.resource-yaml { .resource-yaml {
.yaml-editor { .yaml-editor {
@ -367,96 +367,12 @@ export default {
} }
} }
} }
.subtypes-container { .create-resource-container {
display: flex; .subtype-banner {
flex-wrap: wrap; .round-image {
width: 100%; background-color: var(--primary);
}
}
} }
.subtype-content {
width: 100%;
}
.subtype-banner {
border-left: 5px solid var(--primary);
border-radius: var(--border-radius);
display: flex;
flex-basis: 40%;
margin: 10px;
min-height: 100px;
padding: 10px;
box-shadow: 0 0 20px var(--shadow);
&.selected {
background-color: var(--accent-btn);
}
&.top {
background-image: linear-gradient(
-90deg,
var(--body-bg),
var(--accent-btn)
);
H2 {
margin: 0px;
}
}
.title {
align-items: center;
display: flex;
width: 100%;
// flex-basis: 10%;
h5 {
margin: 0;
}
.flex-right {
margin-left: auto;
}
}
.description {
color: var(--input-label);
display: flex;
flex-direction: column;
justify-content: center;
}
.description {
color: var(--input-label);
}
&:not(.top) {
align-items: top;
flex-direction: row;
justify-content: start;
&:hover {
cursor: pointer;
box-shadow: 0px 0px 1px var(--outline-width) var(--outline);
}
}
.round-image {
background-color: var(--primary);
border-radius: 50%;
height: 50px;
margin-right: 10px;
width: 50px;
overflow: hidden;
}
.banner-abbrv {
align-items: center;
background-color: var(--primary);
color: white;
display: flex;
font-size: 2.5em;
height: 100%;
justify-content: center;
width: 100%;
}
}
</style> </style>

View File

@ -22,8 +22,8 @@ export default {
}, },
tooltip: { tooltip: {
type: String,
default: null, default: null,
type: [String, Object]
}, },
hoverTooltip: { hoverTooltip: {

View File

@ -72,7 +72,7 @@ export default {
}, },
tooltip: { tooltip: {
default: null, default: null,
type: String type: [String, Object]
}, },
value: { value: {
default: null, default: null,

View File

@ -2,7 +2,7 @@
export default { export default {
props: { props: {
value: { value: {
type: String, type: [String, Object],
default: null default: null
}, },
@ -22,7 +22,7 @@ export default {
<template> <template>
<div ref="container" class="labeled-tooltip" :class="{[status]: true, hoverable: hover}"> <div ref="container" class="labeled-tooltip" :class="{[status]: true, hoverable: hover}">
<template v-if="hover"> <template v-if="hover">
<i v-tooltip="{content: value, classes: [`tooltip-${status}`]}" :class="{'hover':!value}" class="icon icon-info status-icon" /> <i v-tooltip="value.content ? { ...{content: value.content, classes: [`tooltip-${status}`]}, ...value } : value" :class="{'hover':!value}" class="icon icon-info status-icon" />
</template> </template>
<template v-else> <template v-else>
<i :class="{'hover':!value}" class="icon icon-info status-icon" /> <i :class="{'hover':!value}" class="icon icon-info status-icon" />

View File

@ -165,7 +165,7 @@ export default {
<img :src="receiverType.logo" /> <img :src="receiverType.logo" />
</div> </div>
<h4 class="name ml-10"> <h4 class="name ml-10">
{{ receiverType.label }} <t :k="receiverType.label" />
</h4> </h4>
</div> </div>
<div v-if="receiverType.name !== 'custom'" class="right"> <div v-if="receiverType.name !== 'custom'" class="right">
@ -175,14 +175,13 @@ export default {
</GradientBox> </GradientBox>
</div> </div>
</Tab> </Tab>
<Tab v-for="(receiverType, i) in receiverTypes" :key="i" :label="receiverType.label" :name="receiverType.name" :weight="receiverTypes.length - i"> <Tab
<Banner v-if="receiverType.name === 'slack'" color="info"> v-for="(receiverType, i) in receiverTypes"
Here's how you create <a href="https://rancher.slack.com/apps/A0F7XDUAZ-incoming-webhooks" target="_blank" rel="noopener noreferrer nofollow">Incoming Webhooks</a> for Slack. :key="i"
</Banner> :label="t(receiverType.label)"
<Banner v-if="!isView && receiverType.name === 'custom'" color="info" label="The YAML provided here will be directly appended to your receiver within the Alertmanager Config Secret" /> :name="receiverType.name"
<Banner v-if="!isView && receiverType.name === 'pagerduty'" color="info"> :weight="receiverTypes.length - i"
Here's how you create an <a href="https://www.pagerduty.com/docs/guides/prometheus-integration-guide/" target="_blank" rel="noopener nofollow" class="flex-right">Integration Key</a> for PagerDuty >
</Banner>
<YamlEditor <YamlEditor
v-if="receiverType.name === 'custom'" v-if="receiverType.name === 'custom'"
ref="customEditor" ref="customEditor"
@ -196,8 +195,7 @@ export default {
class="namespace-list" class="namespace-list"
:mode="mode" :mode="mode"
:default-add-value="{}" :default-add-value="{}"
:disabled="disabled" :add-label="t('monitoringReceiver.addButton', { type: t(receiverType.label) })"
:add-label="'Add ' + receiverType.label"
> >
<template #default="props"> <template #default="props">
<div :class="{'pt-30': !isView}"> <div :class="{'pt-30': !isView}">

View File

@ -1,191 +1,198 @@
<script> <script>
import ArrayList from '@/components/form/ArrayList'; import ArrayList from '@/components/form/ArrayList';
import LabeledInput from '@/components/form/LabeledInput'; import LabeledInput from '@/components/form/LabeledInput';
import LabeledSelect from '@/components/form/LabeledSelect'; import Select from '@/components/form/Select';
import Checkbox from '@/components/form/Checkbox'; import Checkbox from '@/components/form/Checkbox';
import InputWithSelect from '@/components/form/InputWithSelect'; import InputWithSelect from '@/components/form/InputWithSelect';
import { _VIEW } from '@/config/query-params'; import { _VIEW } from '@/config/query-params';
export const TARGETS = [ export const TARGETS = [
{ {
label: 'Id', label: 'Id',
value: 'id' value: 'id'
}, },
{ {
label: 'Name', label: 'Name',
value: 'name' value: 'name'
}, },
{ {
label: 'Username', label: 'Username',
value: 'username' value: 'username'
} }
]; ];
export const TYPES = [ export const TYPES = [
{ {
label: 'Team', label: 'Team',
value: 'team' value: 'team'
}, },
{ {
label: 'User', label: 'User',
value: 'user' value: 'user'
}, },
{ {
label: 'Escalation', label: 'Escalation',
value: 'escalation' value: 'escalation'
}, },
{ {
label: 'Schedule', label: 'Schedule',
value: 'schedule' value: 'schedule'
} }
]; ];
export default { export default {
components: { components: {
ArrayList, Checkbox, InputWithSelect, LabeledInput, LabeledSelect ArrayList, Checkbox, InputWithSelect, LabeledInput, Select
}, },
props: { props: {
mode: { mode: {
type: String, type: String,
required: true, required: true,
}, },
value: { value: {
type: Object, type: Object,
required: true required: true
} }
}, },
data() { data() {
this.$set(this.value, 'http_config', this.value.http_config || {}); this.$set(this.value, 'http_config', this.value.http_config || {});
this.$set(this.value, 'send_resolved', typeof this.value.send_resolved === 'boolean' ? this.value.send_resolved : true); this.$set(this.value, 'send_resolved', typeof this.value.send_resolved === 'boolean' ? this.value.send_resolved : true);
this.$set(this.value, 'responders', this.value.responders || []); this.$set(this.value, 'responders', this.value.responders || []);
const responders = this.value.responders.map((responder) => { const responders = this.value.responders.map((responder) => {
const target = TARGETS.find(target => responder[target.value]); const target = TARGETS.find(target => responder[target.value]);
return { return {
type: responder.type, type: responder.type,
target: target.value, target: target.value,
value: responder[target.value] value: responder[target.value]
}; };
}); });
return { return {
defaultResponder: { defaultResponder: {
type: TYPES[0].value, type: TYPES[0].value,
target: TARGETS[0].value, target: TARGETS[0].value,
value: '' value: ''
}, },
responders, responders,
TARGETS, TARGETS,
TYPES TYPES
}; };
}, },
computed: { computed: {
isView() { isView() {
return this.mode === _VIEW; return this.mode === _VIEW;
} }
}, },
watch: { watch: {
responders: { responders: {
deep: true, deep: true,
handler() { handler() {
const responders = this.responders.map((responder) => { const responders = this.responders.map((responder) => {
return { return {
type: responder.type, type: responder.type,
[responder.target]: responder.value [responder.target]: responder.value
}; };
}); });
this.$set(this.value, 'responders', responders); this.$set(this.value, 'responders', responders);
} }
} }
}, },
methods: { methods: {
updateResponder({ selected, text }, row) { updateResponder({ selected, text }, row) {
row.target = selected; row.target = selected;
row.value = text; row.value = text;
}, },
typeLabel(type) { typeLabel(type) {
return TYPES.find(t => t.value === type).label; return TYPES.find(t => t.value === type).label;
}, },
targetLabel(target) { targetLabel(target) {
return TARGETS.find(t => t.value === target).label; return TARGETS.find(t => t.value === target).label;
} }
} }
}; };
</script> </script>
<template> <template>
<div> <div>
<div class="row mb-20"> <div class="row mb-20">
<div class="col span-12"> <div class="col span-12">
<LabeledInput v-model="value.api_key" :mode="mode" label="API Key" /> <LabeledInput v-model="value.api_key" :mode="mode" label="API Key" />
</div> </div>
</div> </div>
<div class="row mb-20"> <div class="row mb-20">
<div class="col span-12"> <div class="col span-12">
<LabeledInput v-model="value.http_config.proxy_url" :mode="mode" label="Proxy URL" placeholder="e.g. http://my-proxy/" /> <LabeledInput v-model="value.http_config.proxy_url" :mode="mode" label="Proxy URL" placeholder="e.g. http://my-proxy/" />
</div> </div>
</div> </div>
<div class="row mb-20"> <div class="row mb-20">
<Checkbox v-model="value.send_resolved" :mode="mode" label="Enable send resolved alerts" /> <Checkbox v-model="value.send_resolved" :mode="mode" label="Enable send resolved alerts" />
</div> </div>
<div class="row"> <div class="row">
<div class="col span-12"> <div class="col span-12">
<h3>Responders</h3> <h3>Responders</h3>
<ArrayList v-model="responders" :mode="mode" :default-add-value="defaultResponder" :show-header="true"> <ArrayList v-model="responders" :mode="mode" :default-add-value="defaultResponder" :show-header="true">
<template v-slot:column-headers> <template v-slot:column-headers>
<div class="row" :class="{'mb-15': isView}"> <div class="row mb-10">
<div class="col span-6"> <div class="col span-6">
<span class="text-label">Type</span> <span class="text-label">Type</span>
</div> </div>
<div class="col span-6"> <div class="col span-6 send-to">
<span class="text-label">Send To</span> <span class="text-label">Send To</span>
</div> </div>
</div> </div>
</template> </template>
<template v-slot:columns="scope"> <template v-slot:columns="scope">
<div class="row responder"> <div class="row responder">
<div class="col span-6"> <div class="col span-6">
<span v-if="isView">{{ typeLabel(scope.row.value.type) }}</span> <span v-if="isView">{{ typeLabel(scope.row.value.type) }}</span>
<LabeledSelect v-else v-model="scope.row.value.type" :mode="mode" label="Type" :options="TYPES" /> <Select v-else v-model="scope.row.value.type" :mode="mode" label="Type" :options="TYPES" />
</div> </div>
<div class="col-span-6 target"> <div class="col-span-6 target">
<span v-if="isView">{{ targetLabel(scope.row.value.target) }}: {{ scope.row.value.value }}</span> <span v-if="isView">{{ targetLabel(scope.row.value.target) }}: {{ scope.row.value.value }}</span>
<InputWithSelect <InputWithSelect
v-else v-else
:mode="mode" :mode="mode"
:options="TARGETS" :options="TARGETS"
:select-value="scope.row.value.target" :select-value="scope.row.value.target"
:text-value="scope.row.value.value" :text-value="scope.row.value.value"
@input="updateResponder($event, scope.row.value)" @input="updateResponder($event, scope.row.value)"
/> />
</div> </div>
</div> </div>
</template> </template>
</ArrayList> </ArrayList>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.responder { .responder {
&, .target { &, .target {
width: 100%; width: 100%;
} }
.send-to {
.target ::v-deep { margin-left: -35px;
& .input-container { }
height: $input-height;
} .unlabeled-select ::v-deep {
height: $input-height;
& .unlabeled-select { }
min-width: 35%;
} .target ::v-deep {
} & .input-container {
} height: $input-height;
</style> }
& .unlabeled-select {
min-width: 35%;
}
}
}
</style>

View File

@ -1,66 +1,88 @@
<script> <script>
import LabeledInput from '@/components/form/LabeledInput'; import LabeledInput from '@/components/form/LabeledInput';
import LabeledSelect from '@/components/form/LabeledSelect'; import LabeledSelect from '@/components/form/LabeledSelect';
import Checkbox from '@/components/form/Checkbox'; import Checkbox from '@/components/form/Checkbox';
export default { export default {
components: { components: {
Checkbox, LabeledInput, LabeledSelect Checkbox,
}, LabeledInput,
props: { LabeledSelect,
mode: { },
type: String, props: {
required: true, mode: {
}, type: String,
value: { required: true,
type: Object, },
required: true value: {
} type: Object,
}, required: true,
data() { },
this.$set(this.value, 'http_config', this.value.http_config || {}); },
this.$set(this.value, 'send_resolved', typeof this.value.send_resolved === 'boolean' ? this.value.send_resolved : true); data() {
this.$set(this.value, 'http_config', this.value.http_config || {});
const integrationMapping = { this.$set(this.value, 'send_resolved', typeof this.value.send_resolved === 'boolean' ? this.value.send_resolved : true);
'Events API v2': 'routing_key',
Prometheus: 'service_key' const integrationMapping = {
}; 'Events API v2': 'routing_key',
Prometheus: 'service_key',
const integrationTypeOptions = Object.keys(integrationMapping); };
return { const integrationTypeOptions = Object.keys(integrationMapping);
integrationMapping,
integrationTypeOptions, return {
integrationType: this.value.routing_key ? integrationTypeOptions[0] : integrationTypeOptions[1] integrationMapping,
}; integrationTypeOptions,
}, integrationType: this.value.routing_key ? integrationTypeOptions[0] : integrationTypeOptions[1]
watch: { };
integrationType() { },
this.integrationTypeOptions.forEach((option) => { watch: {
this.value[this.integrationMapping[option]] = null; integrationType() {
}); this.integrationTypeOptions.forEach((option) => {
} this.value[this.integrationMapping[option]] = null;
} });
}; },
</script> },
};
<template> </script>
<div>
<div class="row mb-20"> <template>
<div class="col span-6"> <div>
<LabeledSelect v-model="integrationType" :options="integrationTypeOptions" :mode="mode" label="Integration Type" /> <div class="row mb-20">
</div> <div class="col span-6">
<div class="col span-6"> <LabeledSelect
<LabeledInput v-model="value[integrationMapping[integrationType]]" :mode="mode" label="Default Integration Key" /> v-model="integrationType"
</div> :options="integrationTypeOptions"
</div> :mode="mode"
<div class="row mb-20"> :tooltip="{ content: t('monitoringReceiver.pagerduty.info', {}, raw=true), autoHide: false}"
<div class="col span-12"> :hover-tooltip="true"
<LabeledInput v-model="value.http_config.proxy_url" :mode="mode" label="Proxy URL" placeholder="e.g. http://my-proxy/" /> label="Integration Type"
</div> />
</div> </div>
<div class="row"> <div class="col span-6">
<Checkbox v-model="value.send_resolved" :mode="mode" label="Enable send resolved alerts" /> <LabeledInput
</div> v-model="value[integrationMapping[integrationType]]"
</div> :mode="mode"
</template> label="Default Integration Key"
/>
</div>
</div>
<div class="row mb-20">
<div class="col span-12">
<LabeledInput
v-model="value.http_config.proxy_url"
:mode="mode"
label="Proxy URL"
placeholder="e.g. http://my-proxy/"
/>
</div>
</div>
<div class="row">
<Checkbox
v-model="value.send_resolved"
:mode="mode"
label="Enable send resolved alerts"
/>
</div>
</div>
</template>

View File

@ -12,15 +12,19 @@ export default {
}, },
value: { value: {
type: Object, type: Object,
required: true required: true,
} },
}, },
data() { data() {
this.$set(this.value, 'http_config', this.value.http_config || {}); this.$set(this.value, 'http_config', this.value.http_config || {});
this.$set(this.value, 'send_resolved', this.value.send_resolved || false); this.$set(this.value, 'send_resolved', this.value.send_resolved || false);
if (this.mode === _CREATE) { if (this.mode === _CREATE) {
this.$set(this.value, 'text', this.value.text || '{{ template "slack.rancher.text" . }}'); this.$set(
this.value,
'text',
this.value.text || '{{ template "slack.rancher.text" . }}'
);
} }
return {}; return {};
@ -32,19 +36,39 @@ export default {
<div> <div>
<div class="row mb-20"> <div class="row mb-20">
<div class="col span-12"> <div class="col span-12">
<LabeledInput v-model="value.api_url" :mode="mode" label="URL" placeholder="e.g. https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" /> <LabeledInput
v-model="value.api_url"
:mode="mode"
label="Webhook URL"
:tooltip="{ content: t('monitoringReceiver.slack.info', {}, raw=true), autoHide: false}"
placeholder="e.g. https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
/>
</div> </div>
</div> </div>
<div class="row mb-20"> <div class="row mb-20">
<div class="col span-6"> <div class="col span-6">
<LabeledInput v-model="value.channel" :mode="mode" label="Default Channel" placeholder="e.g. #example" /> <LabeledInput
v-model="value.channel"
:mode="mode"
label="Default Channel"
placeholder="e.g. #example"
/>
</div> </div>
<div class="col span-6"> <div class="col span-6">
<LabeledInput v-model="value.http_config.proxy_url" :mode="mode" label="Proxy URL" placeholder="e.g. http://my-proxy/" /> <LabeledInput
v-model="value.http_config.proxy_url"
:mode="mode"
label="Proxy URL"
placeholder="e.g. http://my-proxy/"
/>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<Checkbox v-model="value.send_resolved" :mode="mode" label="Enable send resolved alerts" /> <Checkbox
v-model="value.send_resolved"
:mode="mode"
label="Enable send resolved alerts"
/>
</div> </div>
</div> </div>
</template> </template>

View File

@ -6,43 +6,46 @@ import jsyaml from 'js-yaml';
export const RECEIVERS_TYPES = [ export const RECEIVERS_TYPES = [
{ {
name: 'slack', name: 'slack',
label: 'Slack', label: 'monitoringReceiver.slack.label',
title: 'Slack Config', title: 'monitoringReceiver.slack.title',
info: 'monitoringReceiver.slack.info',
key: 'slack_configs', key: 'slack_configs',
logo: require(`~/assets/images/vendor/slack.svg`) logo: require(`~/assets/images/vendor/slack.svg`)
}, },
{ {
name: 'email', name: 'email',
label: 'Email', label: 'monitoringReceiver.email.label',
title: 'Email Config', title: 'monitoringReceiver.email.title',
key: 'email_configs', key: 'email_configs',
logo: require(`~/assets/images/vendor/email.svg`) logo: require(`~/assets/images/vendor/email.svg`)
}, },
{ {
name: 'pagerduty', name: 'pagerduty',
label: 'PagerDuty', label: 'monitoringReceiver.pagerduty.label',
title: 'PagerDuty Config', title: 'monitoringReceiver.pagerduty.title',
info: 'monitoringReceiver.pagerduty.info',
key: 'pagerduty_configs', key: 'pagerduty_configs',
logo: require(`~/assets/images/vendor/pagerduty.svg`) logo: require(`~/assets/images/vendor/pagerduty.svg`)
}, },
{ {
name: 'opsgenie', name: 'opsgenie',
label: 'Opsgenie', label: 'monitoringReceiver.opsgenie.label',
title: 'Opsgenie Config', title: 'monitoringReceiver.opsgenie.title',
key: 'opsgenie_configs', key: 'opsgenie_configs',
logo: require(`~/assets/images/vendor/email.svg`) logo: require(`~/assets/images/vendor/email.svg`)
}, },
{ {
name: 'webhook', name: 'webhook',
label: 'Webhook', label: 'monitoringReceiver.webhook.label',
title: 'Webhook Config', title: 'monitoringReceiver.webhook.title',
key: 'webhook_configs', key: 'webhook_configs',
logo: require(`~/assets/images/vendor/webhook.svg`) logo: require(`~/assets/images/vendor/webhook.svg`)
}, },
{ {
name: 'custom', name: 'custom',
label: 'Custom', label: 'monitoringReceiver.custom.label',
title: 'Custom Config', title: 'monitoringReceiver.custom.title',
info: 'monitoringReceiver.custom.info',
key: 'webhook_configs', key: 'webhook_configs',
logo: require(`~/assets/images/vendor/custom.svg`) logo: require(`~/assets/images/vendor/custom.svg`)
}, },

View File

@ -59,24 +59,27 @@ export default {
group: 'prometheus', group: 'prometheus',
iconSrc: this.prometheusSrc, iconSrc: this.prometheusSrc,
label: 'monitoring.overview.linkedList.prometheusPromQl.label', label: 'monitoring.overview.linkedList.prometheusPromQl.label',
description: 'monitoring.overview.linkedList.prometheusPromQl.description', description:
link: `/k8s/clusters/${ this.currentCluster.id }/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-prometheus:9090/proxy/graph`, 'monitoring.overview.linkedList.prometheusPromQl.description',
link: `/k8s/clusters/${ this.currentCluster.id }/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-prometheus:9090/proxy/graph`,
}, },
{ {
enabled: false, enabled: false,
group: 'prometheus', group: 'prometheus',
iconSrc: this.prometheusSrc, iconSrc: this.prometheusSrc,
label: 'monitoring.overview.linkedList.prometheusRules.label', label: 'monitoring.overview.linkedList.prometheusRules.label',
description: 'monitoring.overview.linkedList.prometheusRules.description', description:
link: `/k8s/clusters/${ this.currentCluster.id }/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-prometheus:9090/proxy/rules`, 'monitoring.overview.linkedList.prometheusRules.description',
link: `/k8s/clusters/${ this.currentCluster.id }/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-prometheus:9090/proxy/rules`,
}, },
{ {
enabled: false, enabled: false,
group: 'prometheus', group: 'prometheus',
iconSrc: this.prometheusSrc, iconSrc: this.prometheusSrc,
label: 'monitoring.overview.linkedList.prometheusTargets.label', label: 'monitoring.overview.linkedList.prometheusTargets.label',
description: 'monitoring.overview.linkedList.prometheusTargets.description', description:
link: `/k8s/clusters/${ this.currentCluster.id }/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-prometheus:9090/proxy/targets`, 'monitoring.overview.linkedList.prometheusTargets.description',
link: `/k8s/clusters/${ this.currentCluster.id }/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-prometheus:9090/proxy/targets`,
}, },
]; ];
@ -87,13 +90,17 @@ export default {
async fetchDeps() { async fetchDeps() {
const { $store, externalLinks } = this; const { $store, externalLinks } = this;
const workloads = await Promise.all(Object.values(WORKLOAD_TYPES).map(type => this.$store.dispatch('cluster/findAll', { type }))); const workloads = await Promise.all(
Object.values(WORKLOAD_TYPES).map(type => this.$store.dispatch('cluster/findAll', { type })
)
);
workloads.flat().forEach((workload) => { workloads.flat().forEach((workload) => {
if ( if (
!isEmpty(workload?.spec?.template?.spec?.containers) && !isEmpty(workload?.spec?.template?.spec?.containers) &&
(workload.spec.template.spec.containers.find(c => c.image.includes('quay.io/coreos/prometheus-operator') || workload.spec.template.spec.containers.find(
c.image.includes('rancher/coreos-prometheus-operator')) c => c.image.includes('quay.io/coreos/prometheus-operator') ||
c.image.includes('rancher/coreos-prometheus-operator')
) && ) &&
workload?.metadata?.namespace !== CATTLE_MONITORING_NAMESPACE workload?.metadata?.namespace !== CATTLE_MONITORING_NAMESPACE
) { ) {
@ -108,10 +115,24 @@ export default {
if (!isEmpty(hash.endpoints)) { if (!isEmpty(hash.endpoints)) {
const amMatch = findBy(externalLinks, 'group', 'alertmanager'); const amMatch = findBy(externalLinks, 'group', 'alertmanager');
const grafanaMatch = findBy(externalLinks, 'group', 'grafana'); const grafanaMatch = findBy(externalLinks, 'group', 'grafana');
const promeMatch = externalLinks.filter(el => el.group === 'prometheus'); const promeMatch = externalLinks.filter(
const alertmanager = findBy(hash.endpoints, 'id', `${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-alertmanager`); el => el.group === 'prometheus'
const grafana = findBy(hash.endpoints, 'id', `${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-grafana`); );
const prometheus = findBy(hash.endpoints, 'id', `${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-prometheus`); const alertmanager = findBy(
hash.endpoints,
'id',
`${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-alertmanager`
);
const grafana = findBy(
hash.endpoints,
'id',
`${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-grafana`
);
const prometheus = findBy(
hash.endpoints,
'id',
`${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-prometheus`
);
if (!isEmpty(alertmanager) && !isEmpty(alertmanager.subsets)) { if (!isEmpty(alertmanager) && !isEmpty(alertmanager.subsets)) {
amMatch.enabled = true; amMatch.enabled = true;
@ -126,7 +147,7 @@ export default {
} }
} }
}, },
} },
}; };
</script> </script>
@ -142,112 +163,48 @@ export default {
</div> </div>
</div> </div>
</header> </header>
<div class="links"> <div>
<Banner v-if="v1Installed" color="warning"> <Banner v-if="v1Installed" color="warning">
<template #default> <template #default>
<t k="monitoring.v1Warning" :raw="true" /> <t k="monitoring.v1Warning" :raw="true" />
</template> </template>
</Banner> </Banner>
<div v-for="fel in externalLinks" :key="fel.label" class="link-container"> <div class="create-resource-container">
<a v-if="fel.enabled" :href="fel.link" target="_blank" rel="noopener noreferrer"> <div class="subtypes-container">
<div class="link-logo"> <a
<LazyImage :src="fel.iconSrc" /> v-for="fel in externalLinks"
</div> :key="fel.label"
<div class="link-content"> v-tooltip="!fel.enabled ? t('monitoring.overview.linkedList.na') : undefined"
<t :k="fel.label" /> :href="fel.enabled ? fel.link : (void 0)"
<i class="icon icon-external-link pull-right" /> :disabled="!fel.enabled"
<hr /> target="_blank"
<div class="description"><t :k="fel.description" /></div> rel="noopener noreferrer"
</div> :class="{ 'subtype-banner': true, disabled: !fel.enabled}"
</a> >
<a v-else v-tooltip="t('monitoring.overview.linkedList.na')" href="javascript:void(0)" :disabled="!fel.enabled"> <div class="subtype-content">
<div class="link-logo"> <div class="title">
<LazyImage :src="fel.iconSrc" /> <div class="subtype-logo round-image">
</div> <LazyImage :src="fel.iconSrc" />
<div class="link-content"> </div>
<t :k="fel.label" /> <h5>
<i class="icon icon-external-link pull-right" /> <span>
<hr /> <t :k="fel.label" />
<div class="description"><t :k="fel.description" /></div> </span>
</div> </h5>
</a> <div class="flex-right">
<i class="icon icon-external-link" />
</div>
</div>
<hr />
<div class="description">
<span>
<t :k="fel.description" />
</span>
</div>
</div>
</a>
</div>
</div> </div>
</div> </div>
</section> </section>
</template> </template>
<style lang="scss">
.links {
display: flex;
flex-wrap: wrap;
width: 100%;
.link-container {
background-color: var(--input-bg);
border-radius: var(--border-radius);
border: solid 1px var(--input-border);
display: flex;
flex-basis: 40%;
margin: 0 10px 10px 0;
max-width: 325px;
min-height: 100px;
border-left: solid 10px var(--primary);
a[disabled] {
cursor: not-allowed;
background-color: var(---disabled-bg);
}
&:hover {
box-shadow: 0px 0px 1px var(--outline-width) var(--outline);
}
> a {
align-items: center;
display: flex;
flex: 1 0;
padding: 10px;
.link-logo,
.link-content {
display: inline-block;
}
.link-logo {
text-align: center;
// position: absolute;
// left: 25px;
// top: 25px;
width: 60px;
height: 60px;
border-radius: calc(2 * var(--border-radius));
background-color: white;
img {
width: 56px;
height: 56px;
-o-object-fit: contain;
object-fit: contain;
position: relative;
top: 2px;
}
}
.link-content {
width: 100%;
margin-left: 10px;
}
.description {
margin-top: 10px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
text-overflow: ellipsis;
color: var(--secondary);
}
}
}
}
</style>