From 400aafa3b3232200e4fe201679e1864dd2271b61 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 9 Sep 2020 12:46:19 +0200 Subject: [PATCH] Migration: Edit notification channel (#25980) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * implement edit page * connectWithCleanup * remove angular related code * use loadingindicator * use the correct loading component * handle secureFields * fixed implementation of secure fields * Keep secureFields after rerendering the form * CollapsableSection and Page refactor * use checkbox instead of switch * fix comment * add cursor to section * Fixed issues after PR review * Fix issue with some settings being undefined * new reducer and start with test * algorithm to migrate secure fields * UX: Minor UI Tweaks * Added field around checkboxes, and missing required field * fixed test * tests for util * minor tweaks and changes * define as records * fix typ error * forward invalid to textarea and inputcontrol * merge formdata and redux data in test * fix issue with creating channel * do not figure out securefields in migration Co-authored-by: Torkel Ödegaard --- .../Collapse/CollapsableSection.mdx | 10 + .../Collapse/CollapsableSection.story.tsx | 21 ++ .../Collapse/CollapsableSection.tsx | 38 ++++ .../src/components/Forms/Checkbox.tsx | 2 +- packages/grafana-ui/src/components/index.ts | 1 + pkg/services/alerting/notifier.go | 20 +- .../alerting/notifiers/alertmanager.go | 31 +-- pkg/services/alerting/notifiers/dingding.go | 22 +- pkg/services/alerting/notifiers/discord.go | 18 -- pkg/services/alerting/notifiers/email.go | 23 +- pkg/services/alerting/notifiers/googlechat.go | 7 - pkg/services/alerting/notifiers/hipchat.go | 19 -- pkg/services/alerting/notifiers/kafka.go | 11 - pkg/services/alerting/notifiers/line.go | 20 +- pkg/services/alerting/notifiers/opsgenie.go | 47 +--- pkg/services/alerting/notifiers/pagerduty.go | 55 +---- pkg/services/alerting/notifiers/pushover.go | 97 +------- pkg/services/alerting/notifiers/sensu.go | 36 +-- pkg/services/alerting/notifiers/slack.go | 123 +---------- pkg/services/alerting/notifiers/teams.go | 8 +- pkg/services/alerting/notifiers/telegram.go | 32 +-- pkg/services/alerting/notifiers/threema.go | 50 +---- pkg/services/alerting/notifiers/victorops.go | 25 +-- pkg/services/alerting/notifiers/webhook.go | 34 +-- .../alerting/EditNotificationChannelPage.tsx | 150 +++++++++++++ .../alerting/NewAlertNotificationPage.tsx | 132 ----------- .../alerting/NewNotificationChannelPage.tsx | 96 ++++++++ .../alerting/components/BasicSettings.tsx | 44 ++++ .../alerting/components/ChannelSettings.tsx | 36 +++ .../components/NewNotificationChannelForm.tsx | 125 ----------- .../components/NotificationChannelForm.tsx | 100 +++++++++ .../components/NotificationChannelOptions.tsx | 44 +++- .../components/NotificationSettings.tsx | 59 +++++ .../alerting/components/OptionElement.tsx | 31 +-- .../alerting/partials/notification_edit.html | 89 -------- public/app/features/alerting/state/actions.ts | 47 ++-- .../features/alerting/state/reducers.test.ts | 200 ++++++++++++++++- .../app/features/alerting/state/reducers.ts | 114 +++++++++- .../app/features/alerting/state/selectors.ts | 10 +- .../utils/notificationChannel.test.ts | 209 ++++++++++++++++++ .../alerting/utils/notificationChannels.ts | 63 ++++++ public/app/features/all.ts | 1 - public/app/routes/routes.ts | 19 +- public/app/types/alerting.ts | 32 ++- public/app/types/store.ts | 3 +- 45 files changed, 1296 insertions(+), 1058 deletions(-) create mode 100644 packages/grafana-ui/src/components/Collapse/CollapsableSection.mdx create mode 100644 packages/grafana-ui/src/components/Collapse/CollapsableSection.story.tsx create mode 100644 packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx create mode 100644 public/app/features/alerting/EditNotificationChannelPage.tsx delete mode 100644 public/app/features/alerting/NewAlertNotificationPage.tsx create mode 100644 public/app/features/alerting/NewNotificationChannelPage.tsx create mode 100644 public/app/features/alerting/components/BasicSettings.tsx create mode 100644 public/app/features/alerting/components/ChannelSettings.tsx delete mode 100644 public/app/features/alerting/components/NewNotificationChannelForm.tsx create mode 100644 public/app/features/alerting/components/NotificationChannelForm.tsx create mode 100644 public/app/features/alerting/components/NotificationSettings.tsx delete mode 100644 public/app/features/alerting/partials/notification_edit.html create mode 100644 public/app/features/alerting/utils/notificationChannel.test.ts create mode 100644 public/app/features/alerting/utils/notificationChannels.ts diff --git a/packages/grafana-ui/src/components/Collapse/CollapsableSection.mdx b/packages/grafana-ui/src/components/Collapse/CollapsableSection.mdx new file mode 100644 index 00000000000..4cdd68991ee --- /dev/null +++ b/packages/grafana-ui/src/components/Collapse/CollapsableSection.mdx @@ -0,0 +1,10 @@ +import { Meta, Props } from '@storybook/addon-docs/blocks'; +import { CollapsableSection } from './CollapsableSection'; + + + +# Collapsable Section +A simple container for enabling collapsing/expanding of content. + + + diff --git a/packages/grafana-ui/src/components/Collapse/CollapsableSection.story.tsx b/packages/grafana-ui/src/components/Collapse/CollapsableSection.story.tsx new file mode 100644 index 00000000000..b0a5f6f869b --- /dev/null +++ b/packages/grafana-ui/src/components/Collapse/CollapsableSection.story.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CollapsableSection } from './CollapsableSection'; +import mdx from './CollapsableSection.mdx'; + +export default { + title: 'Layout/CollapsableSection', + component: CollapsableSection, + parameters: { + docs: { + page: mdx, + }, + }, +}; + +export const simple = () => { + return ( + +
Here's some content
+
+ ); +}; diff --git a/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx b/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx new file mode 100644 index 00000000000..e11a7f57657 --- /dev/null +++ b/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx @@ -0,0 +1,38 @@ +import React, { FC, ReactNode, useState } from 'react'; +import { css } from 'emotion'; +import { GrafanaTheme } from '@grafana/data'; +import { useStyles } from '../../themes'; +import { Icon } from '..'; + +export interface Props { + label: string; + isOpen: boolean; + children: ReactNode; +} + +export const CollapsableSection: FC = ({ label, isOpen, children }) => { + const [open, toggleOpen] = useState(isOpen); + const styles = useStyles(collapsableSectionStyles); + + return ( +
+
toggleOpen(!open)} className={styles.header}> + + {label} +
+
{open && children}
+
+ ); +}; + +const collapsableSectionStyles = (theme: GrafanaTheme) => { + return { + header: css` + font-size: ${theme.typography.size.lg}; + cursor: pointer; + `, + content: css` + padding: ${theme.spacing.md} 0 ${theme.spacing.md} ${theme.spacing.md}; + `, + }; +}; diff --git a/packages/grafana-ui/src/components/Forms/Checkbox.tsx b/packages/grafana-ui/src/components/Forms/Checkbox.tsx index aa75364c1d3..92d42833428 100644 --- a/packages/grafana-ui/src/components/Forms/Checkbox.tsx +++ b/packages/grafana-ui/src/components/Forms/Checkbox.tsx @@ -80,7 +80,7 @@ export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme) => { background: ${theme.colors.formInputBg}; border: 1px solid ${theme.colors.formInputBorder}; position: absolute; - top: 1px; + top: 2px; left: 0; &:hover { diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 39553882e46..a75963c3715 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -93,6 +93,7 @@ export { export { Alert, AlertVariant } from './Alert/Alert'; export { GraphSeriesToggler, GraphSeriesTogglerAPI } from './Graph/GraphSeriesToggler'; export { Collapse, ControlledCollapse } from './Collapse/Collapse'; +export { CollapsableSection } from './Collapse/CollapsableSection'; export { LogLabels } from './Logs/LogLabels'; export { LogRows } from './Logs/LogRows'; export { getLogRowStyles } from './Logs/getLogRowStyles'; diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index 533d7866777..8603aa232c4 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -23,14 +23,13 @@ var newImageUploaderProvider = func() (imguploader.ImageUploader, error) { // NotifierPlugin holds meta information about a notifier. type NotifierPlugin struct { - Type string `json:"type"` - Name string `json:"name"` - Heading string `json:"heading"` - Description string `json:"description"` - Info string `json:"info"` - OptionsTemplate string `json:"optionsTemplate"` - Factory NotifierFactory `json:"-"` - Options []NotifierOption `json:"options"` + Type string `json:"type"` + Name string `json:"name"` + Heading string `json:"heading"` + Description string `json:"description"` + Info string `json:"info"` + Factory NotifierFactory `json:"-"` + Options []NotifierOption `json:"options"` } // NotifierOption holds information about options specific for the NotifierPlugin. @@ -45,6 +44,7 @@ type NotifierOption struct { ShowWhen ShowWhen `json:"showWhen"` Required bool `json:"required"` ValidationRule string `json:"validationRule"` + Secure bool `json:"secure"` } // InputType is the type of input that can be rendered in the frontend. @@ -65,8 +65,8 @@ const ( ElementTypeInput = "input" // ElementTypeSelect will render a select ElementTypeSelect = "select" - // ElementTypeSwitch will render a switch - ElementTypeSwitch = "switch" + // ElementTypeCheckbox will render a checkbox + ElementTypeCheckbox = "checkbox" // ElementTypeTextArea will render a textarea ElementTypeTextArea = "textarea" ) diff --git a/pkg/services/alerting/notifiers/alertmanager.go b/pkg/services/alerting/notifiers/alertmanager.go index fea254e3159..bc0b9ea6f25 100644 --- a/pkg/services/alerting/notifiers/alertmanager.go +++ b/pkg/services/alerting/notifiers/alertmanager.go @@ -20,36 +20,6 @@ func init() { Description: "Sends alert to Prometheus Alertmanager", Heading: "Alertmanager settings", Factory: NewAlertmanagerNotifier, - OptionsTemplate: ` -

Alertmanager settings

-
- Url(s) - - - As specified in Alertmanager documentation, do not specify a load balancer here. Enter all your Alertmanager URLs comma-separated. - -
-
- Basic Auth User - -
-
- Basic Auth Password -
- - -
-
- - reset -
-
- - `, Options: []alerting.NotifierOption{ { Label: "Url", @@ -71,6 +41,7 @@ func init() { Element: alerting.ElementTypeInput, InputType: alerting.InputTypePassword, PropertyName: "basicAuthPassword", + Secure: true, }, }, }) diff --git a/pkg/services/alerting/notifiers/dingding.go b/pkg/services/alerting/notifiers/dingding.go index 9149f3a6665..0070cfdacd7 100644 --- a/pkg/services/alerting/notifiers/dingding.go +++ b/pkg/services/alerting/notifiers/dingding.go @@ -12,26 +12,14 @@ import ( ) const defaultDingdingMsgType = "link" -const dingdingOptionsTemplate = ` -

DingDing settings

-
- Url - -
-
- MessageType - -
-` func init() { alerting.RegisterNotifier(&alerting.NotifierPlugin{ - Type: "dingding", - Name: "DingDing", - Description: "Sends HTTP POST request to DingDing", - Heading: "DingDing settings", - Factory: newDingDingNotifier, - OptionsTemplate: dingdingOptionsTemplate, + Type: "dingding", + Name: "DingDing", + Description: "Sends HTTP POST request to DingDing", + Heading: "DingDing settings", + Factory: newDingDingNotifier, Options: []alerting.NotifierOption{ { Label: "Url", diff --git a/pkg/services/alerting/notifiers/discord.go b/pkg/services/alerting/notifiers/discord.go index dbcb22609ea..b3a40612b12 100644 --- a/pkg/services/alerting/notifiers/discord.go +++ b/pkg/services/alerting/notifiers/discord.go @@ -23,24 +23,6 @@ func init() { Description: "Sends notifications to Discord", Factory: newDiscordNotifier, Heading: "Discord settings", - OptionsTemplate: ` -

Discord settings

-
- Message Content - - - - Mention a group using @ or a user using <@ID> when notifying in a channel - -
-
- Webhook URL - -
- `, Options: []alerting.NotifierOption{ { Label: "Message Content", diff --git a/pkg/services/alerting/notifiers/email.go b/pkg/services/alerting/notifiers/email.go index 81b29770dd1..fadeaf0e3b0 100644 --- a/pkg/services/alerting/notifiers/email.go +++ b/pkg/services/alerting/notifiers/email.go @@ -19,32 +19,11 @@ func init() { Description: "Sends notifications using Grafana server configured SMTP settings", Factory: NewEmailNotifier, Heading: "Email settings", - OptionsTemplate: ` -

Email settings

-
- - -
-
- - -
-
- You can enter multiple email addresses using a ";" separator -
- `, Options: []alerting.NotifierOption{ { Label: "Single email", Description: "Send a single email to all recipients", - Element: alerting.ElementTypeSwitch, + Element: alerting.ElementTypeCheckbox, PropertyName: "singleEmail", }, { diff --git a/pkg/services/alerting/notifiers/googlechat.go b/pkg/services/alerting/notifiers/googlechat.go index ddb469bd253..123df9a7c5e 100644 --- a/pkg/services/alerting/notifiers/googlechat.go +++ b/pkg/services/alerting/notifiers/googlechat.go @@ -19,13 +19,6 @@ func init() { Description: "Sends notifications to Google Hangouts Chat via webhooks based on the official JSON message format", Factory: newGoogleChatNotifier, Heading: "Google Hangouts Chat settings", - OptionsTemplate: ` -

Google Hangouts Chat settings

-
- Url - -
- `, Options: []alerting.NotifierOption{ { Label: "Url", diff --git a/pkg/services/alerting/notifiers/hipchat.go b/pkg/services/alerting/notifiers/hipchat.go index 8c76e7045b0..ae29fe4e64e 100644 --- a/pkg/services/alerting/notifiers/hipchat.go +++ b/pkg/services/alerting/notifiers/hipchat.go @@ -20,25 +20,6 @@ func init() { Description: "Sends notifications uto a HipChat Room", Heading: "HipChat settings", Factory: NewHipChatNotifier, - OptionsTemplate: ` -

HipChat settings

-
- Hip Chat Url - -
-
- API Key - -
-
- Room ID - - -
- `, Options: []alerting.NotifierOption{ { Label: "Hip Chat Url", diff --git a/pkg/services/alerting/notifiers/kafka.go b/pkg/services/alerting/notifiers/kafka.go index 8783620f6ed..bba43017bd3 100644 --- a/pkg/services/alerting/notifiers/kafka.go +++ b/pkg/services/alerting/notifiers/kafka.go @@ -19,17 +19,6 @@ func init() { Description: "Sends notifications to Kafka Rest Proxy", Heading: "Kafka settings", Factory: NewKafkaNotifier, - OptionsTemplate: ` -

Kafka settings

-
- Kafka REST Proxy - -
-
- Topic - -
- `, Options: []alerting.NotifierOption{ { Label: "Kafka REST Proxy", diff --git a/pkg/services/alerting/notifiers/line.go b/pkg/services/alerting/notifiers/line.go index f61c9d605ea..85eca74bc54 100644 --- a/pkg/services/alerting/notifiers/line.go +++ b/pkg/services/alerting/notifiers/line.go @@ -17,25 +17,6 @@ func init() { Description: "Send notifications to LINE notify", Heading: "LINE notify settings", Factory: NewLINENotifier, - OptionsTemplate: ` -

LINE notify settings

-
- -
- - -
-
- - reset -
-
-`, Options: []alerting.NotifierOption{ { Label: "Token", @@ -44,6 +25,7 @@ func init() { Placeholder: "LINE notify token key", PropertyName: "token", Required: true, + Secure: true, }}, }) } diff --git a/pkg/services/alerting/notifiers/opsgenie.go b/pkg/services/alerting/notifiers/opsgenie.go index e7a3158fee9..8a9d058dc40 100644 --- a/pkg/services/alerting/notifiers/opsgenie.go +++ b/pkg/services/alerting/notifiers/opsgenie.go @@ -18,48 +18,6 @@ func init() { Description: "Sends notifications to OpsGenie", Heading: "OpsGenie settings", Factory: NewOpsGenieNotifier, - OptionsTemplate: ` -

OpsGenie settings

-
- -
- - -
-
- - reset -
-
-
- Alert API Url - -
-
- - -
-
- - -
-`, Options: []alerting.NotifierOption{ { Label: "API Key", @@ -68,6 +26,7 @@ func init() { Placeholder: "OpsGenie API Key", PropertyName: "apiKey", Required: true, + Secure: true, }, { Label: "Alert API Url", @@ -79,12 +38,12 @@ func init() { }, { Label: "Auto close incidents", - Element: alerting.ElementTypeSwitch, + Element: alerting.ElementTypeCheckbox, Description: "Automatically close alerts in OpsGenie once the alert goes back to ok.", PropertyName: "autoClose", }, { Label: "Override priority", - Element: alerting.ElementTypeSwitch, + Element: alerting.ElementTypeCheckbox, Description: "Allow the alert priority to be set using the og_priority tag", PropertyName: "overridePriority", }, diff --git a/pkg/services/alerting/notifiers/pagerduty.go b/pkg/services/alerting/notifiers/pagerduty.go index 7712bcf544a..b9b2813b447 100644 --- a/pkg/services/alerting/notifiers/pagerduty.go +++ b/pkg/services/alerting/notifiers/pagerduty.go @@ -20,53 +20,6 @@ func init() { Description: "Sends notifications to PagerDuty", Heading: "PagerDuty settings", Factory: NewPagerdutyNotifier, - OptionsTemplate: ` -

PagerDuty settings

-
- Integration Key -
- - -
-
- - reset -
-
-
- Severity -
- -
-
-
- - -
-
- - -
- `, Options: []alerting.NotifierOption{ { Label: "Integration Key", @@ -101,10 +54,16 @@ func init() { }, { Label: "Auto resolve incidents", - Element: alerting.ElementTypeSwitch, + Element: alerting.ElementTypeCheckbox, Description: "Resolve incidents in pagerduty once the alert goes back to ok.", PropertyName: "autoResolve", }, + { + Label: "Include message in details", + Element: alerting.ElementTypeCheckbox, + Description: "Move the alert message from the PD summary into the custom details. This changes the custom details object and may break event rules you have configured", + PropertyName: "messageInDetails", + }, }, }) } diff --git a/pkg/services/alerting/notifiers/pushover.go b/pkg/services/alerting/notifiers/pushover.go index 8b7fa0b19a4..79cc97520cc 100644 --- a/pkg/services/alerting/notifiers/pushover.go +++ b/pkg/services/alerting/notifiers/pushover.go @@ -17,31 +17,6 @@ import ( const pushoverEndpoint = "https://api.pushover.net/1/messages.json" func init() { - sounds := ` - 'default', - 'pushover', - 'bike', - 'bugle', - 'cashregister', - 'classical', - 'cosmic', - 'falling', - 'gamelan', - 'incoming', - 'intermission', - 'magic', - 'mechanical', - 'pianobar', - 'siren', - 'spacealarm', - 'tugboat', - 'alien', - 'climb', - 'persistent', - 'echo', - 'updown', - 'none'` - soundOptions := []alerting.SelectOption{ { Value: "default", @@ -122,76 +97,6 @@ func init() { Description: "Sends HTTP POST request to the Pushover API", Heading: "Pushover settings", Factory: NewPushoverNotifier, - OptionsTemplate: ` -

Pushover settings

-
- -
- - -
-
- - reset -
-
-
- -
- - -
-
- - reset -
-
-
- Device(s) (optional) - -
-
- Priority - -
-
- Retry - - Expire - -
-
- Alerting sound - -
-
- OK sound - -
- `, Options: []alerting.NotifierOption{ { Label: "API Token", @@ -200,6 +105,7 @@ func init() { Placeholder: "Application token", PropertyName: "apiToken", Required: true, + Secure: true, }, { Label: "User key(s)", @@ -208,6 +114,7 @@ func init() { Placeholder: "comma-separated list", PropertyName: "userKey", Required: true, + Secure: true, }, { Label: "Device(s) (optional)", diff --git a/pkg/services/alerting/notifiers/sensu.go b/pkg/services/alerting/notifiers/sensu.go index 6397d15581e..69e392a7aa8 100644 --- a/pkg/services/alerting/notifiers/sensu.go +++ b/pkg/services/alerting/notifiers/sensu.go @@ -18,41 +18,6 @@ func init() { Description: "Sends HTTP POST request to a Sensu API", Heading: "Sensu settings", Factory: NewSensuNotifier, - OptionsTemplate: ` -

Sensu settings

-
- Url - -
-
- Source - -
-
- Handler - -
-
- Username - -
-
- -
- - -
-
- - reset -
-
- `, Options: []alerting.NotifierOption{ { Label: "Url", @@ -87,6 +52,7 @@ func init() { Element: alerting.ElementTypeInput, InputType: alerting.InputTypePassword, PropertyName: "passsword ", + Secure: true, }, }, }) diff --git a/pkg/services/alerting/notifiers/slack.go b/pkg/services/alerting/notifiers/slack.go index bd13e5a96f1..88674de9fb2 100644 --- a/pkg/services/alerting/notifiers/slack.go +++ b/pkg/services/alerting/notifiers/slack.go @@ -27,123 +27,6 @@ func init() { Description: "Sends notifications to Slack via Slack Webhooks", Heading: "Slack settings", Factory: NewSlackNotifier, - OptionsTemplate: ` -

Slack settings

-
- Url -
- - -
-
- - reset -
-
-
- Recipient - - - - Override default channel or user, use #channel-name, @username (has to be all lowercase, no whitespace), or user/channel Slack ID - -
-
- Username - - - - Set the username for the bot's message - -
-
- Icon emoji - - - - Provide an emoji to use as the icon for the bot's message. Overrides the icon URL - -
-
- Icon URL - - - - Provide a URL to an image to use as the icon for the bot's message - -
-
- Mention Users - - - - Mention one or more users (comma separated) when notifying in a channel, by ID (you can copy this from the user's Slack profile) - -
-
- Mention Groups - - - - Mention one or more groups (comma separated) when notifying in a channel (you can copy this from the group's Slack profile URL) - -
-
- Mention Channel - - - Mention whole channel or just active members when notifying - -
-
-
-
- - - - Provide a bot token to use the Slack file.upload API (starts with "xoxb"). Specify Recipient for this to work - -
-
- - reset -
-
- `, Options: []alerting.NotifierOption{ { Label: "Url", @@ -152,6 +35,7 @@ func init() { Placeholder: "Slack incoming webhook url", PropertyName: "url", Required: true, + Secure: true, }, { Label: "Recipient", @@ -172,14 +56,14 @@ func init() { Element: alerting.ElementTypeInput, InputType: alerting.InputTypeText, Description: "Provide an emoji to use as the icon for the bot's message. Overrides the icon URL.", - PropertyName: "icon_emoji", + PropertyName: "iconEmoji", }, { Label: "Icon URL", Element: alerting.ElementTypeInput, InputType: alerting.InputTypeText, Description: "Provide a URL to an image to use as the icon for the bot's message", - PropertyName: "icon_url", + PropertyName: "iconUrl", }, { Label: "Mention Users", @@ -221,6 +105,7 @@ func init() { InputType: alerting.InputTypeText, Description: "Provide a bot token to use the Slack file.upload API (starts with \"xoxb\"). Specify Recipient for this to work", PropertyName: "token", + Secure: true, }, }, }) diff --git a/pkg/services/alerting/notifiers/teams.go b/pkg/services/alerting/notifiers/teams.go index e30a10bcbfc..e31a095e192 100644 --- a/pkg/services/alerting/notifiers/teams.go +++ b/pkg/services/alerting/notifiers/teams.go @@ -16,13 +16,6 @@ func init() { Description: "Sends notifications using Incoming Webhook connector to Microsoft Teams", Heading: "Teams settings", Factory: NewTeamsNotifier, - OptionsTemplate: ` -

Teams settings

-
- Url - -
- `, Options: []alerting.NotifierOption{ { Label: "URL", @@ -30,6 +23,7 @@ func init() { InputType: alerting.InputTypeText, Placeholder: "Teams incoming webhook url", PropertyName: "url", + Required: true, }, }, }) diff --git a/pkg/services/alerting/notifiers/telegram.go b/pkg/services/alerting/notifiers/telegram.go index 86fdbaeb4d8..ede1ccb1487 100644 --- a/pkg/services/alerting/notifiers/telegram.go +++ b/pkg/services/alerting/notifiers/telegram.go @@ -28,37 +28,6 @@ func init() { Description: "Sends notifications to Telegram", Heading: "Telegram API settings", Factory: NewTelegramNotifier, - OptionsTemplate: ` -

Telegram API settings

-
- -
- - -
-
- - reset -
-
-
- - - - Integer Telegram Chat Identifier -
- `, Options: []alerting.NotifierOption{ { Label: "BOT API Token", @@ -67,6 +36,7 @@ func init() { Placeholder: "Telegram BOT API Token", PropertyName: "bottoken", Required: true, + Secure: true, }, { Label: "Chat ID", diff --git a/pkg/services/alerting/notifiers/threema.go b/pkg/services/alerting/notifiers/threema.go index 9a08fd67102..0ad1342e1a5 100644 --- a/pkg/services/alerting/notifiers/threema.go +++ b/pkg/services/alerting/notifiers/threema.go @@ -24,55 +24,6 @@ func init() { Info: "Notifications can be configured for any Threema Gateway ID of type \"Basic\". End-to-End IDs are not currently supported." + "The Threema Gateway ID can be set up at https://gateway.threema.ch/.", Factory: NewThreemaNotifier, - OptionsTemplate: ` -

Threema Gateway settings

-

- Notifications can be configured for any Threema Gateway ID of type - "Basic". End-to-End IDs are not currently supported. -

-

- The Threema Gateway ID can be set up at - https://gateway.threema.ch/. -

-
- Gateway ID - - - - Your 8 character Threema Gateway ID (starting with a *) - -
-
- Recipient ID - - - - The 8 character Threema ID that should receive the alerts - -
-
- -
- - -
-
- - reset -
-
- `, Options: []alerting.NotifierOption{ { Label: "Gateway ID", @@ -101,6 +52,7 @@ func init() { Description: "Your Threema Gateway API secret.", PropertyName: "api_secret", Required: true, + Secure: true, }, }, }) diff --git a/pkg/services/alerting/notifiers/victorops.go b/pkg/services/alerting/notifiers/victorops.go index e0eb0092291..ae87150406c 100644 --- a/pkg/services/alerting/notifiers/victorops.go +++ b/pkg/services/alerting/notifiers/victorops.go @@ -25,29 +25,6 @@ func init() { Description: "Sends notifications to VictorOps", Heading: "VictorOps settings", Factory: NewVictoropsNotifier, - OptionsTemplate: ` -

VictorOps settings

-
- Url - -
-
- No Data Alert Type -
- -
-
-
- - -
- `, Options: []alerting.NotifierOption{ { Label: "Url", @@ -60,7 +37,7 @@ func init() { { Label: "Auto resolve incidents", Description: "Resolve incidents in VictorOps once the alert goes back to ok.", - Element: alerting.ElementTypeSwitch, + Element: alerting.ElementTypeCheckbox, PropertyName: "autoResolve", }, }, diff --git a/pkg/services/alerting/notifiers/webhook.go b/pkg/services/alerting/notifiers/webhook.go index e9894c7b076..b0b842efdba 100644 --- a/pkg/services/alerting/notifiers/webhook.go +++ b/pkg/services/alerting/notifiers/webhook.go @@ -15,39 +15,6 @@ func init() { Description: "Sends HTTP POST request to a URL", Heading: "Webhook settings", Factory: NewWebHookNotifier, - OptionsTemplate: ` -

Webhook settings

-
- Url - -
-
- Http Method -
- -
-
-
- Username - -
-
-
-
- - -
-
- - reset -
-
- `, Options: []alerting.NotifierOption{ { Label: "Url", @@ -82,6 +49,7 @@ func init() { Element: alerting.ElementTypeInput, InputType: alerting.InputTypePassword, PropertyName: "password", + Secure: true, }, }, }) diff --git a/public/app/features/alerting/EditNotificationChannelPage.tsx b/public/app/features/alerting/EditNotificationChannelPage.tsx new file mode 100644 index 00000000000..8d1bfa15710 --- /dev/null +++ b/public/app/features/alerting/EditNotificationChannelPage.tsx @@ -0,0 +1,150 @@ +import React, { PureComponent } from 'react'; +import { MapDispatchToProps, MapStateToProps } from 'react-redux'; +import { NavModel } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { Form, Spinner } from '@grafana/ui'; +import Page from 'app/core/components/Page/Page'; +import { connectWithCleanUp } from 'app/core/components/connectWithCleanUp'; +import { NotificationChannelForm } from './components/NotificationChannelForm'; +import { + loadNotificationChannel, + loadNotificationTypes, + testNotificationChannel, + updateNotificationChannel, +} from './state/actions'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { getRouteParamsId } from 'app/core/selectors/location'; +import { mapChannelsToSelectableValue, transformSubmitData, transformTestData } from './utils/notificationChannels'; +import { NotificationChannelType, NotificationChannelDTO, StoreState } from 'app/types'; +import { resetSecureField } from './state/reducers'; + +interface OwnProps {} + +interface ConnectedProps { + navModel: NavModel; + channelId: number; + notificationChannel: any; + notificationChannelTypes: NotificationChannelType[]; +} + +interface DispatchProps { + loadNotificationTypes: typeof loadNotificationTypes; + loadNotificationChannel: typeof loadNotificationChannel; + testNotificationChannel: typeof testNotificationChannel; + updateNotificationChannel: typeof updateNotificationChannel; + resetSecureField: typeof resetSecureField; +} + +type Props = OwnProps & ConnectedProps & DispatchProps; + +export class EditNotificationChannelPage extends PureComponent { + componentDidMount() { + const { channelId } = this.props; + + this.props.loadNotificationTypes(); + this.props.loadNotificationChannel(channelId); + } + + onSubmit = (formData: NotificationChannelDTO) => { + const { notificationChannel } = this.props; + + this.props.updateNotificationChannel({ + /* + Some settings which lives in a collapsed section will not be registered since + the section will not be rendered if a user doesn't expand it. Therefore we need to + merge the initialData with any changes from the form. + */ + ...transformSubmitData({ + ...notificationChannel, + ...formData, + settings: { ...notificationChannel.settings, ...formData.settings }, + }), + id: notificationChannel.id, + }); + }; + + onTestChannel = (formData: NotificationChannelDTO) => { + const { notificationChannel } = this.props; + /* + Same as submit + */ + this.props.testNotificationChannel( + transformTestData({ + ...notificationChannel, + ...formData, + settings: { ...notificationChannel.settings, ...formData.settings }, + }) + ); + }; + + render() { + const { navModel, notificationChannel, notificationChannelTypes } = this.props; + + return ( + + +

Edit notification channel

+ {notificationChannel && notificationChannel.id > 0 ? ( +
n.value === notificationChannel.type), + }} + > + {({ control, errors, getValues, register, watch }) => { + const selectedChannel = notificationChannelTypes.find(c => c.value === getValues().type.value); + + return ( + + ); + }} + + ) : ( +
+ Loading notification channel + +
+ )} +
+
+ ); + } +} + +const mapStateToProps: MapStateToProps = state => { + const channelId = getRouteParamsId(state.location) as number; + return { + navModel: getNavModel(state.navIndex, 'channels'), + channelId, + notificationChannel: state.notificationChannel.notificationChannel, + notificationChannelTypes: state.notificationChannel.notificationChannelTypes, + }; +}; + +const mapDispatchToProps: MapDispatchToProps = { + loadNotificationTypes, + loadNotificationChannel, + testNotificationChannel, + updateNotificationChannel, + resetSecureField, +}; + +export default connectWithCleanUp( + mapStateToProps, + mapDispatchToProps, + state => state.notificationChannel +)(EditNotificationChannelPage); diff --git a/public/app/features/alerting/NewAlertNotificationPage.tsx b/public/app/features/alerting/NewAlertNotificationPage.tsx deleted file mode 100644 index 527533cdf44..00000000000 --- a/public/app/features/alerting/NewAlertNotificationPage.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React, { PureComponent } from 'react'; -import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; -import { NavModel, SelectableValue } from '@grafana/data'; -import { config } from '@grafana/runtime'; -import { Form } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; -import { NewNotificationChannelForm } from './components/NewNotificationChannelForm'; -import { getNavModel } from 'app/core/selectors/navModel'; -import { createNotificationChannel, loadNotificationTypes, testNotificationChannel } from './state/actions'; -import { NotificationChannel, NotificationChannelDTO, StoreState } from '../../types'; - -interface OwnProps {} - -interface ConnectedProps { - navModel: NavModel; - notificationChannels: NotificationChannel[]; -} - -interface DispatchProps { - createNotificationChannel: typeof createNotificationChannel; - loadNotificationTypes: typeof loadNotificationTypes; - testNotificationChannel: typeof testNotificationChannel; -} - -type Props = OwnProps & ConnectedProps & DispatchProps; - -const defaultValues: NotificationChannelDTO = { - name: '', - type: { value: 'email', label: 'Email' }, - sendReminder: false, - disableResolveMessage: false, - frequency: '15m', - settings: { - uploadImage: config.rendererAvailable, - autoResolve: true, - httpMethod: 'POST', - severity: 'critical', - }, - isDefault: false, -}; - -class NewAlertNotificationPage extends PureComponent { - componentDidMount() { - this.props.loadNotificationTypes(); - } - - onSubmit = (data: NotificationChannelDTO) => { - /* - Some settings can be options in a select, in order to not save a SelectableValue - we need to use check if it is a SelectableValue and use its value. - */ - const settings = Object.fromEntries( - Object.entries(data.settings).map(([key, value]) => { - return [key, value.hasOwnProperty('value') ? value.value : value]; - }) - ); - - this.props.createNotificationChannel({ - ...defaultValues, - ...data, - type: data.type.value, - settings: { ...defaultValues.settings, ...settings }, - }); - }; - - onTestChannel = (data: NotificationChannelDTO) => { - this.props.testNotificationChannel({ - name: data.name, - type: data.type.value, - frequency: data.frequency ?? defaultValues.frequency, - settings: { ...Object.assign(defaultValues.settings, data.settings) }, - }); - }; - - render() { - const { navModel, notificationChannels } = this.props; - - /* - Need to transform these as we have options on notificationChannels, - this will render a dropdown within the select. - - TODO: Memoize? - */ - const selectableChannels: Array> = notificationChannels.map(channel => ({ - value: channel.value, - label: channel.label, - description: channel.description, - })); - - return ( - - -

New Notification Channel

-
- {({ register, errors, control, getValues, watch }) => { - const selectedChannel = notificationChannels.find(c => c.value === getValues().type.value); - - return ( - - ); - }} - -
-
- ); - } -} - -const mapStateToProps: MapStateToProps = state => { - return { - navModel: getNavModel(state.navIndex, 'channels'), - notificationChannels: state.alertRules.notificationChannels, - }; -}; - -const mapDispatchToProps: MapDispatchToProps = { - createNotificationChannel, - loadNotificationTypes, - testNotificationChannel, -}; - -export default connect(mapStateToProps, mapDispatchToProps)(NewAlertNotificationPage); diff --git a/public/app/features/alerting/NewNotificationChannelPage.tsx b/public/app/features/alerting/NewNotificationChannelPage.tsx new file mode 100644 index 00000000000..7d934010631 --- /dev/null +++ b/public/app/features/alerting/NewNotificationChannelPage.tsx @@ -0,0 +1,96 @@ +import React, { PureComponent } from 'react'; +import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; +import { NavModel } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { Form } from '@grafana/ui'; +import Page from 'app/core/components/Page/Page'; +import { NotificationChannelForm } from './components/NotificationChannelForm'; +import { + defaultValues, + mapChannelsToSelectableValue, + transformSubmitData, + transformTestData, +} from './utils/notificationChannels'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { createNotificationChannel, loadNotificationTypes, testNotificationChannel } from './state/actions'; +import { NotificationChannelType, NotificationChannelDTO, StoreState } from '../../types'; +import { resetSecureField } from './state/reducers'; + +interface OwnProps {} + +interface ConnectedProps { + navModel: NavModel; + notificationChannelTypes: NotificationChannelType[]; +} + +interface DispatchProps { + createNotificationChannel: typeof createNotificationChannel; + loadNotificationTypes: typeof loadNotificationTypes; + testNotificationChannel: typeof testNotificationChannel; + resetSecureField: typeof resetSecureField; +} + +type Props = OwnProps & ConnectedProps & DispatchProps; + +class NewNotificationChannelPage extends PureComponent { + componentDidMount() { + this.props.loadNotificationTypes(); + } + + onSubmit = (data: NotificationChannelDTO) => { + this.props.createNotificationChannel(transformSubmitData({ ...defaultValues, ...data })); + }; + + onTestChannel = (data: NotificationChannelDTO) => { + this.props.testNotificationChannel(transformTestData({ ...defaultValues, ...data })); + }; + + render() { + const { navModel, notificationChannelTypes } = this.props; + + return ( + + +

New notification channel

+
+ {({ register, errors, control, getValues, watch }) => { + const selectedChannel = notificationChannelTypes.find(c => c.value === getValues().type.value); + + return ( + + ); + }} + +
+
+ ); + } +} + +const mapStateToProps: MapStateToProps = state => { + return { + navModel: getNavModel(state.navIndex, 'channels'), + notificationChannelTypes: state.notificationChannel.notificationChannelTypes, + }; +}; + +const mapDispatchToProps: MapDispatchToProps = { + createNotificationChannel, + loadNotificationTypes, + testNotificationChannel, + resetSecureField, +}; + +export default connect(mapStateToProps, mapDispatchToProps)(NewNotificationChannelPage); diff --git a/public/app/features/alerting/components/BasicSettings.tsx b/public/app/features/alerting/components/BasicSettings.tsx new file mode 100644 index 00000000000..9f686f541f4 --- /dev/null +++ b/public/app/features/alerting/components/BasicSettings.tsx @@ -0,0 +1,44 @@ +import React, { FC } from 'react'; +import { SelectableValue } from '@grafana/data'; +import { CollapsableSection, Field, Input, InputControl, Select } from '@grafana/ui'; +import { NotificationChannelOptions } from './NotificationChannelOptions'; +import { NotificationSettingsProps } from './NotificationChannelForm'; +import { NotificationChannelSecureFields, NotificationChannelType } from '../../../types'; + +interface Props extends NotificationSettingsProps { + selectedChannel: NotificationChannelType; + channels: Array>; + secureFields: NotificationChannelSecureFields; + resetSecureField: (key: string) => void; +} + +export const BasicSettings: FC = ({ + control, + currentFormValues, + errors, + secureFields, + selectedChannel, + channels, + register, + resetSecureField, +}) => { + return ( + + + + + + + + o.required)} + currentFormValues={currentFormValues} + secureFields={secureFields} + onResetSecureField={resetSecureField} + register={register} + errors={errors} + control={control} + /> + + ); +}; diff --git a/public/app/features/alerting/components/ChannelSettings.tsx b/public/app/features/alerting/components/ChannelSettings.tsx new file mode 100644 index 00000000000..79cd233ea72 --- /dev/null +++ b/public/app/features/alerting/components/ChannelSettings.tsx @@ -0,0 +1,36 @@ +import React, { FC } from 'react'; +import { CollapsableSection, InfoBox } from '@grafana/ui'; +import { NotificationChannelOptions } from './NotificationChannelOptions'; +import { NotificationSettingsProps } from './NotificationChannelForm'; +import { NotificationChannelSecureFields, NotificationChannelType } from '../../../types'; + +interface Props extends NotificationSettingsProps { + selectedChannel: NotificationChannelType; + secureFields: NotificationChannelSecureFields; + resetSecureField: (key: string) => void; +} + +export const ChannelSettings: FC = ({ + control, + currentFormValues, + errors, + selectedChannel, + secureFields, + register, + resetSecureField, +}) => { + return ( + + {selectedChannel.info !== '' && {selectedChannel.info}} + !o.required)} + currentFormValues={currentFormValues} + register={register} + errors={errors} + control={control} + onResetSecureField={resetSecureField} + secureFields={secureFields} + /> + + ); +}; diff --git a/public/app/features/alerting/components/NewNotificationChannelForm.tsx b/public/app/features/alerting/components/NewNotificationChannelForm.tsx deleted file mode 100644 index e0a7670c9e6..00000000000 --- a/public/app/features/alerting/components/NewNotificationChannelForm.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import React, { FC, useEffect } from 'react'; -import { css } from 'emotion'; -import { GrafanaTheme, SelectableValue } from '@grafana/data'; -import { - Button, - Field, - FormAPI, - HorizontalGroup, - InfoBox, - Input, - InputControl, - Select, - stylesFactory, - Switch, - useTheme, -} from '@grafana/ui'; -import { NotificationChannel, NotificationChannelDTO } from '../../../types'; -import { NotificationChannelOptions } from './NotificationChannelOptions'; - -interface Props extends Omit, 'formState'> { - selectableChannels: Array>; - selectedChannel?: NotificationChannel; - imageRendererAvailable: boolean; - - onTestChannel: (data: NotificationChannelDTO) => void; -} - -export const NewNotificationChannelForm: FC = ({ - control, - errors, - selectedChannel, - selectableChannels, - register, - watch, - getValues, - imageRendererAvailable, - onTestChannel, -}) => { - const styles = getStyles(useTheme()); - - useEffect(() => { - watch(['type', 'settings.priority', 'sendReminder', 'uploadImage']); - }, []); - - const currentFormValues = getValues(); - return ( - <> -
- - - - - - - - - - - - - {currentFormValues.uploadImage && !imageRendererAvailable && ( - - Grafana cannot find an image renderer to capture an image for the notification. Please make sure the Grafana - Image Renderer plugin is installed. Please contact your Grafana administrator to install the plugin. - - )} - - - - - - - {currentFormValues.sendReminder && ( - <> - - - - - Alert reminders are sent after rules are evaluated. Therefore a reminder can never be sent more frequently - than a configured alert rule evaluation interval. - - - )} -
- {selectedChannel && ( - - )} - - - - - - - ); -}; - -const getStyles = stylesFactory((theme: GrafanaTheme) => { - return { - basicSettings: css` - margin-bottom: ${theme.spacing.xl}; - `, - }; -}); diff --git a/public/app/features/alerting/components/NotificationChannelForm.tsx b/public/app/features/alerting/components/NotificationChannelForm.tsx new file mode 100644 index 00000000000..29fd9afc0ac --- /dev/null +++ b/public/app/features/alerting/components/NotificationChannelForm.tsx @@ -0,0 +1,100 @@ +import React, { FC, useEffect } from 'react'; +import { css } from 'emotion'; +import { GrafanaTheme, SelectableValue } from '@grafana/data'; +import { Button, FormAPI, HorizontalGroup, stylesFactory, useTheme, Spinner } from '@grafana/ui'; +import { NotificationChannelType, NotificationChannelDTO, NotificationChannelSecureFields } from '../../../types'; +import { NotificationSettings } from './NotificationSettings'; +import { BasicSettings } from './BasicSettings'; +import { ChannelSettings } from './ChannelSettings'; + +interface Props extends Omit, 'formState'> { + selectableChannels: Array>; + selectedChannel?: NotificationChannelType; + imageRendererAvailable: boolean; + secureFields: NotificationChannelSecureFields; + resetSecureField: (key: string) => void; + onTestChannel: (data: NotificationChannelDTO) => void; +} + +export interface NotificationSettingsProps + extends Omit, 'formState' | 'watch' | 'getValues'> { + currentFormValues: NotificationChannelDTO; +} + +export const NotificationChannelForm: FC = ({ + control, + errors, + selectedChannel, + selectableChannels, + register, + watch, + getValues, + imageRendererAvailable, + onTestChannel, + resetSecureField, + secureFields, +}) => { + const styles = getStyles(useTheme()); + + useEffect(() => { + watch(['type', 'settings.priority', 'sendReminder', 'uploadImage']); + }, []); + + const currentFormValues = getValues(); + return selectedChannel ? ( + <> +
+ + {/* If there are no non-required fields, don't render this section*/} + {selectedChannel.options.filter(o => !o.required).length > 0 && ( + + )} + +
+ + + + + + + + + ) : ( + + ); +}; + +const getStyles = stylesFactory((theme: GrafanaTheme) => { + return { + basicSettings: css` + margin-bottom: ${theme.spacing.xl}; + `, + }; +}); diff --git a/public/app/features/alerting/components/NotificationChannelOptions.tsx b/public/app/features/alerting/components/NotificationChannelOptions.tsx index 9fa7968a017..52fb6eaa9f9 100644 --- a/public/app/features/alerting/components/NotificationChannelOptions.tsx +++ b/public/app/features/alerting/components/NotificationChannelOptions.tsx @@ -1,28 +1,30 @@ import React, { FC } from 'react'; import { SelectableValue } from '@grafana/data'; -import { Field, FormAPI, InfoBox } from '@grafana/ui'; +import { Button, Checkbox, Field, FormAPI, Input } from '@grafana/ui'; import { OptionElement } from './OptionElement'; -import { NotificationChannel, NotificationChannelDTO, Option } from '../../../types'; +import { NotificationChannelDTO, NotificationChannelOption, NotificationChannelSecureFields } from '../../../types'; interface Props extends Omit, 'formState' | 'getValues' | 'watch'> { - selectedChannel: NotificationChannel; + selectedChannelOptions: NotificationChannelOption[]; currentFormValues: NotificationChannelDTO; + secureFields: NotificationChannelSecureFields; + + onResetSecureField: (key: string) => void; } export const NotificationChannelOptions: FC = ({ control, currentFormValues, errors, - selectedChannel, + selectedChannelOptions, register, + onResetSecureField, + secureFields, }) => { return ( <> -

{selectedChannel.heading}

- {selectedChannel.info !== '' && {selectedChannel.info}} - {selectedChannel.options.map((option: Option, index: number) => { + {selectedChannelOptions.map((option: NotificationChannelOption, index: number) => { const key = `${option.label}-${index}`; - // Some options can be dependent on other options, this determines what is selected in the dependency options // I think this needs more thought. const selectedOptionValue = @@ -33,6 +35,18 @@ export const NotificationChannelOptions: FC = ({ return null; } + if (option.element === 'checkbox') { + return ( + + + + ); + } return ( = ({ invalid={errors.settings && !!errors.settings[option.propertyName]} error={errors.settings && errors.settings[option.propertyName]?.message} > - + {secureFields && secureFields[option.propertyName] ? ( + onResetSecureField(option.propertyName)} variant="secondary" type="button"> + Reset + + } + /> + ) : ( + + )} ); })} diff --git a/public/app/features/alerting/components/NotificationSettings.tsx b/public/app/features/alerting/components/NotificationSettings.tsx new file mode 100644 index 00000000000..56529bc90b3 --- /dev/null +++ b/public/app/features/alerting/components/NotificationSettings.tsx @@ -0,0 +1,59 @@ +import React, { FC } from 'react'; +import { Checkbox, CollapsableSection, Field, InfoBox, Input } from '@grafana/ui'; +import { NotificationSettingsProps } from './NotificationChannelForm'; + +interface Props extends NotificationSettingsProps { + imageRendererAvailable: boolean; +} + +export const NotificationSettings: FC = ({ currentFormValues, imageRendererAvailable, register }) => { + return ( + + + + + + + + {currentFormValues.uploadImage && !imageRendererAvailable && ( + + Grafana cannot find an image renderer to capture an image for the notification. Please make sure the Grafana + Image Renderer plugin is installed. Please contact your Grafana administrator to install the plugin. + + )} + + + + + + + {currentFormValues.sendReminder && ( + <> + + + + + )} + + ); +}; diff --git a/public/app/features/alerting/components/OptionElement.tsx b/public/app/features/alerting/components/OptionElement.tsx index 4082770e35e..1f3f4ab3e73 100644 --- a/public/app/features/alerting/components/OptionElement.tsx +++ b/public/app/features/alerting/components/OptionElement.tsx @@ -1,17 +1,19 @@ import React, { FC } from 'react'; -import { FormAPI, Input, InputControl, Select, Switch, TextArea } from '@grafana/ui'; -import { Option } from '../../../types'; +import { FormAPI, Input, InputControl, Select, TextArea } from '@grafana/ui'; +import { NotificationChannelOption } from '../../../types'; interface Props extends Pick, 'register' | 'control'> { - option: Option; + option: NotificationChannelOption; + invalid?: boolean; } -export const OptionElement: FC = ({ control, option, register }) => { - const modelValue = `settings.${option.propertyName}`; +export const OptionElement: FC = ({ control, option, register, invalid }) => { + const modelValue = option.secure ? `secureSettings.${option.propertyName}` : `settings.${option.propertyName}`; switch (option.element) { case 'input': return ( = ({ control, option, register }) => { ); case 'select': - return ; - - case 'textarea': return ( -