diff --git a/backend/internal/features/notifiers/enums.go b/backend/internal/features/notifiers/enums.go index a9adce7..8e960bb 100644 --- a/backend/internal/features/notifiers/enums.go +++ b/backend/internal/features/notifiers/enums.go @@ -6,4 +6,5 @@ const ( NotifierTypeEmail NotifierType = "EMAIL" NotifierTypeTelegram NotifierType = "TELEGRAM" NotifierTypeWebhook NotifierType = "WEBHOOK" + NotifierTypeSlack NotifierType = "SLACK" ) diff --git a/backend/internal/features/notifiers/model.go b/backend/internal/features/notifiers/model.go index b083885..6d75c13 100644 --- a/backend/internal/features/notifiers/model.go +++ b/backend/internal/features/notifiers/model.go @@ -4,6 +4,7 @@ import ( "errors" "log/slog" "postgresus-backend/internal/features/notifiers/models/email_notifier" + slack_notifier "postgresus-backend/internal/features/notifiers/models/slack" telegram_notifier "postgresus-backend/internal/features/notifiers/models/telegram" webhook_notifier "postgresus-backend/internal/features/notifiers/models/webhook" @@ -21,6 +22,7 @@ type Notifier struct { TelegramNotifier *telegram_notifier.TelegramNotifier `json:"telegramNotifier" gorm:"foreignKey:NotifierID"` EmailNotifier *email_notifier.EmailNotifier `json:"emailNotifier" gorm:"foreignKey:NotifierID"` WebhookNotifier *webhook_notifier.WebhookNotifier `json:"webhookNotifier" gorm:"foreignKey:NotifierID"` + SlackNotifier *slack_notifier.SlackNotifier `json:"slackNotifier" gorm:"foreignKey:NotifierID"` } func (n *Notifier) TableName() string { @@ -56,6 +58,8 @@ func (n *Notifier) getSpecificNotifier() NotificationSender { return n.EmailNotifier case NotifierTypeWebhook: return n.WebhookNotifier + case NotifierTypeSlack: + return n.SlackNotifier default: panic("unknown notifier type: " + string(n.NotifierType)) } diff --git a/backend/internal/features/notifiers/models/slack/model.go b/backend/internal/features/notifiers/models/slack/model.go new file mode 100644 index 0000000..a743a7b --- /dev/null +++ b/backend/internal/features/notifiers/models/slack/model.go @@ -0,0 +1,134 @@ +package slack_notifier + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "strconv" + "strings" + "time" + + "github.com/google/uuid" +) + +type SlackNotifier struct { + NotifierID uuid.UUID `json:"notifierId" gorm:"primaryKey;column:notifier_id"` + BotToken string `json:"botToken" gorm:"not null;column:bot_token"` + TargetChatID string `json:"targetChatId" gorm:"not null;column:target_chat_id"` +} + +func (s *SlackNotifier) TableName() string { return "slack_notifiers" } + +func (s *SlackNotifier) Validate() error { + if s.BotToken == "" { + return errors.New("bot token is required") + } + + if s.TargetChatID == "" { + return errors.New("target channel ID is required") + } + + if !strings.HasPrefix(s.TargetChatID, "C") && !strings.HasPrefix(s.TargetChatID, "G") && + !strings.HasPrefix(s.TargetChatID, "D") && + !strings.HasPrefix(s.TargetChatID, "U") { + return errors.New( + "target channel ID must be a valid Slack channel ID (starts with C, G, D) or User ID (starts with U)", + ) + } + + return nil +} + +func (s *SlackNotifier) Send(logger *slog.Logger, heading, message string) error { + full := fmt.Sprintf("*%s*", heading) + + if message != "" { + full = fmt.Sprintf("%s\n\n%s", full, message) + } + + payload, _ := json.Marshal(map[string]any{ + "channel": s.TargetChatID, + "text": full, + "mrkdwn": true, + }) + + const ( + maxAttempts = 5 + defaultBackoff = 2 * time.Second // when Retry-After header missing + backoffMultiplier = 1.5 // use exponential growth + ) + + var ( + backoff = defaultBackoff + attempts = 0 + ) + + for { + attempts++ + + req, err := http.NewRequest( + "POST", + "https://slack.com/api/chat.postMessage", + bytes.NewReader(payload), + ) + if err != nil { + return fmt.Errorf("create request: %w", err) + } + + req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Header.Set("Authorization", "Bearer "+s.BotToken) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("send slack message: %w", err) + } + + defer func() { + if err := resp.Body.Close(); err != nil { + logger.Warn("Failed to close response body", "error", err) + } + }() + + if resp.StatusCode == http.StatusTooManyRequests { // 429 + retryAfter := backoff + if h := resp.Header.Get("Retry-After"); h != "" { + if seconds, _ := strconv.Atoi(h); seconds > 0 { + retryAfter = time.Duration(seconds) * time.Second + } + } + + if attempts >= maxAttempts { + return fmt.Errorf("rate-limited after %d attempts, giving up", attempts) + } + + logger.Warn("Slack rate-limited, retrying", "after", retryAfter, "attempt", attempts) + time.Sleep(retryAfter) + backoff = time.Duration(float64(backoff) * backoffMultiplier) + + continue + } + + // Slack always returns 200 for logical errors, so decode body + var respBody struct { + OK bool `json:"ok"` + Error string `json:"error,omitempty"` + } + + if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil { + raw, _ := io.ReadAll(resp.Body) + return fmt.Errorf("decode response: %v – raw: %s", err, raw) + } + + if !respBody.OK { + return fmt.Errorf("slack API error: %s", respBody.Error) + } + + logger.Info("Slack message sent", "channel", s.TargetChatID, "attempts", attempts) + + return nil + } +} diff --git a/backend/internal/features/notifiers/repository.go b/backend/internal/features/notifiers/repository.go index a1b0672..9040de8 100644 --- a/backend/internal/features/notifiers/repository.go +++ b/backend/internal/features/notifiers/repository.go @@ -26,17 +26,21 @@ func (r *NotifierRepository) Save(notifier *Notifier) error { if notifier.WebhookNotifier != nil { notifier.WebhookNotifier.NotifierID = notifier.ID } + case NotifierTypeSlack: + if notifier.SlackNotifier != nil { + notifier.SlackNotifier.NotifierID = notifier.ID + } } if notifier.ID == uuid.Nil { if err := tx.Create(notifier). - Omit("TelegramNotifier", "EmailNotifier", "WebhookNotifier"). + Omit("TelegramNotifier", "EmailNotifier", "WebhookNotifier", "SlackNotifier"). Error; err != nil { return err } } else { if err := tx.Save(notifier). - Omit("TelegramNotifier", "EmailNotifier", "WebhookNotifier"). + Omit("TelegramNotifier", "EmailNotifier", "WebhookNotifier", "SlackNotifier"). Error; err != nil { return err } @@ -64,6 +68,13 @@ func (r *NotifierRepository) Save(notifier *Notifier) error { return err } } + case NotifierTypeSlack: + if notifier.SlackNotifier != nil { + notifier.SlackNotifier.NotifierID = notifier.ID // Ensure ID is set + if err := tx.Save(notifier.SlackNotifier).Error; err != nil { + return err + } + } } return nil @@ -78,6 +89,7 @@ func (r *NotifierRepository) FindByID(id uuid.UUID) (*Notifier, error) { Preload("TelegramNotifier"). Preload("EmailNotifier"). Preload("WebhookNotifier"). + Preload("SlackNotifier"). Where("id = ?", id). First(¬ifier).Error; err != nil { return nil, err @@ -94,6 +106,7 @@ func (r *NotifierRepository) FindByUserID(userID uuid.UUID) ([]*Notifier, error) Preload("TelegramNotifier"). Preload("EmailNotifier"). Preload("WebhookNotifier"). + Preload("SlackNotifier"). Where("user_id = ?", userID). Find(¬ifiers).Error; err != nil { return nil, err @@ -124,6 +137,12 @@ func (r *NotifierRepository) Delete(notifier *Notifier) error { return err } } + case NotifierTypeSlack: + if notifier.SlackNotifier != nil { + if err := tx.Delete(notifier.SlackNotifier).Error; err != nil { + return err + } + } } // Delete the main notifier diff --git a/backend/migrations/20250624065518_add_slack_notifier.sql b/backend/migrations/20250624065518_add_slack_notifier.sql new file mode 100644 index 0000000..47fca12 --- /dev/null +++ b/backend/migrations/20250624065518_add_slack_notifier.sql @@ -0,0 +1,24 @@ +-- +goose Up +-- +goose StatementBegin + +-- Create slack notifiers table +CREATE TABLE slack_notifiers ( + notifier_id UUID PRIMARY KEY, + bot_token TEXT NOT NULL, + target_chat_id TEXT NOT NULL +); + +ALTER TABLE slack_notifiers + ADD CONSTRAINT fk_slack_notifiers_notifier + FOREIGN KEY (notifier_id) + REFERENCES notifiers (id) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +DROP TABLE IF EXISTS slack_notifiers; + +-- +goose StatementEnd diff --git a/frontend/public/icons/notifiers/slack.svg b/frontend/public/icons/notifiers/slack.svg new file mode 100644 index 0000000..519730c --- /dev/null +++ b/frontend/public/icons/notifiers/slack.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/entity/notifiers/index.ts b/frontend/src/entity/notifiers/index.ts index f348efd..c5c66b4 100644 --- a/frontend/src/entity/notifiers/index.ts +++ b/frontend/src/entity/notifiers/index.ts @@ -1,7 +1,16 @@ export { notifierApi } from './api/notifierApi'; export type { Notifier } from './models/Notifier'; -export type { EmailNotifier } from './models/EmailNotifier'; -export type { TelegramNotifier } from './models/TelegramNotifier'; -export type { WebhookNotifier } from './models/WebhookNotifier'; -export { WebhookMethod } from './models/WebhookMethod'; export { NotifierType } from './models/NotifierType'; + +export type { EmailNotifier } from './models/email/EmailNotifier'; +export { validateEmailNotifier } from './models/email/validateEmailNotifier'; + +export type { TelegramNotifier } from './models/telegram/TelegramNotifier'; +export { validateTelegramNotifier } from './models/telegram/validateTelegramNotifier'; + +export type { WebhookNotifier } from './models/webhook/WebhookNotifier'; +export { validateWebhookNotifier } from './models/webhook/validateWebhookNotifier'; +export { WebhookMethod } from './models/webhook/WebhookMethod'; + +export type { SlackNotifier } from './models/slack/SlackNotifier'; +export { validateSlackNotifier } from './models/slack/validateSlackNotifier'; diff --git a/frontend/src/entity/notifiers/models/Notifier.ts b/frontend/src/entity/notifiers/models/Notifier.ts index c8a72ed..e99f80c 100644 --- a/frontend/src/entity/notifiers/models/Notifier.ts +++ b/frontend/src/entity/notifiers/models/Notifier.ts @@ -1,7 +1,8 @@ -import type { EmailNotifier } from './EmailNotifier'; import type { NotifierType } from './NotifierType'; -import type { TelegramNotifier } from './TelegramNotifier'; -import type { WebhookNotifier } from './WebhookNotifier'; +import type { SlackNotifier } from './slack/SlackNotifier'; +import type { EmailNotifier } from './email/EmailNotifier'; +import type { TelegramNotifier } from './telegram/TelegramNotifier'; +import type { WebhookNotifier } from './webhook/WebhookNotifier'; export interface Notifier { id: string; @@ -13,4 +14,5 @@ export interface Notifier { telegramNotifier?: TelegramNotifier; emailNotifier?: EmailNotifier; webhookNotifier?: WebhookNotifier; + slackNotifier?: SlackNotifier; } diff --git a/frontend/src/entity/notifiers/models/NotifierType.ts b/frontend/src/entity/notifiers/models/NotifierType.ts index 627bd82..f842904 100644 --- a/frontend/src/entity/notifiers/models/NotifierType.ts +++ b/frontend/src/entity/notifiers/models/NotifierType.ts @@ -2,4 +2,5 @@ export enum NotifierType { EMAIL = 'EMAIL', TELEGRAM = 'TELEGRAM', WEBHOOK = 'WEBHOOK', + SLACK = 'SLACK', } diff --git a/frontend/src/entity/notifiers/models/EmailNotifier.ts b/frontend/src/entity/notifiers/models/email/EmailNotifier.ts similarity index 100% rename from frontend/src/entity/notifiers/models/EmailNotifier.ts rename to frontend/src/entity/notifiers/models/email/EmailNotifier.ts diff --git a/frontend/src/entity/notifiers/models/email/validateEmailNotifier.ts b/frontend/src/entity/notifiers/models/email/validateEmailNotifier.ts new file mode 100644 index 0000000..8cdb4ba --- /dev/null +++ b/frontend/src/entity/notifiers/models/email/validateEmailNotifier.ts @@ -0,0 +1,25 @@ +import type { EmailNotifier } from './EmailNotifier'; + +export const validateEmailNotifier = (notifier: EmailNotifier): boolean => { + if (!notifier.targetEmail) { + return false; + } + + if (!notifier.smtpHost) { + return false; + } + + if (!notifier.smtpPort) { + return false; + } + + if (!notifier.smtpUser) { + return false; + } + + if (!notifier.smtpPassword) { + return false; + } + + return true; +}; diff --git a/frontend/src/entity/notifiers/models/getNotifierLogoFromType.ts b/frontend/src/entity/notifiers/models/getNotifierLogoFromType.ts index 326fb13..7690c7c 100644 --- a/frontend/src/entity/notifiers/models/getNotifierLogoFromType.ts +++ b/frontend/src/entity/notifiers/models/getNotifierLogoFromType.ts @@ -8,6 +8,8 @@ export const getNotifierLogoFromType = (type: NotifierType) => { return '/icons/notifiers/telegram.svg'; case NotifierType.WEBHOOK: return '/icons/notifiers/webhook.svg'; + case NotifierType.SLACK: + return '/icons/notifiers/slack.svg'; default: return ''; } diff --git a/frontend/src/entity/notifiers/models/getNotifierNameFromType.ts b/frontend/src/entity/notifiers/models/getNotifierNameFromType.ts index 7dac91e..4597f50 100644 --- a/frontend/src/entity/notifiers/models/getNotifierNameFromType.ts +++ b/frontend/src/entity/notifiers/models/getNotifierNameFromType.ts @@ -8,6 +8,8 @@ export const getNotifierNameFromType = (type: NotifierType) => { return 'Telegram'; case NotifierType.WEBHOOK: return 'Webhook'; + case NotifierType.SLACK: + return 'Slack'; default: return ''; } diff --git a/frontend/src/entity/notifiers/models/slack/SlackNotifier.ts b/frontend/src/entity/notifiers/models/slack/SlackNotifier.ts new file mode 100644 index 0000000..a658761 --- /dev/null +++ b/frontend/src/entity/notifiers/models/slack/SlackNotifier.ts @@ -0,0 +1,4 @@ +export interface SlackNotifier { + botToken: string; + targetChatId: string; +} diff --git a/frontend/src/entity/notifiers/models/slack/validateSlackNotifier.ts b/frontend/src/entity/notifiers/models/slack/validateSlackNotifier.ts new file mode 100644 index 0000000..63665d8 --- /dev/null +++ b/frontend/src/entity/notifiers/models/slack/validateSlackNotifier.ts @@ -0,0 +1,13 @@ +import type { SlackNotifier } from './SlackNotifier'; + +export const validateSlackNotifier = (notifier: SlackNotifier): boolean => { + if (!notifier.botToken) { + return false; + } + + if (!notifier.targetChatId) { + return false; + } + + return true; +}; diff --git a/frontend/src/entity/notifiers/models/TelegramNotifier.ts b/frontend/src/entity/notifiers/models/telegram/TelegramNotifier.ts similarity index 100% rename from frontend/src/entity/notifiers/models/TelegramNotifier.ts rename to frontend/src/entity/notifiers/models/telegram/TelegramNotifier.ts diff --git a/frontend/src/entity/notifiers/models/telegram/validateTelegramNotifier.ts b/frontend/src/entity/notifiers/models/telegram/validateTelegramNotifier.ts new file mode 100644 index 0000000..36fe2eb --- /dev/null +++ b/frontend/src/entity/notifiers/models/telegram/validateTelegramNotifier.ts @@ -0,0 +1,13 @@ +import type { TelegramNotifier } from './TelegramNotifier'; + +export const validateTelegramNotifier = (notifier: TelegramNotifier): boolean => { + if (!notifier.botToken) { + return false; + } + + if (!notifier.targetChatId) { + return false; + } + + return true; +}; diff --git a/frontend/src/entity/notifiers/models/WebhookMethod.ts b/frontend/src/entity/notifiers/models/webhook/WebhookMethod.ts similarity index 100% rename from frontend/src/entity/notifiers/models/WebhookMethod.ts rename to frontend/src/entity/notifiers/models/webhook/WebhookMethod.ts diff --git a/frontend/src/entity/notifiers/models/WebhookNotifier.ts b/frontend/src/entity/notifiers/models/webhook/WebhookNotifier.ts similarity index 100% rename from frontend/src/entity/notifiers/models/WebhookNotifier.ts rename to frontend/src/entity/notifiers/models/webhook/WebhookNotifier.ts diff --git a/frontend/src/entity/notifiers/models/webhook/validateWebhookNotifier.ts b/frontend/src/entity/notifiers/models/webhook/validateWebhookNotifier.ts new file mode 100644 index 0000000..5d71f74 --- /dev/null +++ b/frontend/src/entity/notifiers/models/webhook/validateWebhookNotifier.ts @@ -0,0 +1,9 @@ +import type { WebhookNotifier } from './WebhookNotifier'; + +export const validateWebhookNotifier = (notifier: WebhookNotifier): boolean => { + if (!notifier.webhookUrl) { + return false; + } + + return true; +}; diff --git a/frontend/src/features/notifiers/ui/edit/EditNotifierComponent.tsx b/frontend/src/features/notifiers/ui/edit/EditNotifierComponent.tsx index 94962a3..63bb6c0 100644 --- a/frontend/src/features/notifiers/ui/edit/EditNotifierComponent.tsx +++ b/frontend/src/features/notifiers/ui/edit/EditNotifierComponent.tsx @@ -6,10 +6,15 @@ import { NotifierType, WebhookMethod, notifierApi, + validateEmailNotifier, + validateSlackNotifier, + validateTelegramNotifier, + validateWebhookNotifier, } from '../../../../entity/notifiers'; import { getNotifierLogoFromType } from '../../../../entity/notifiers/models/getNotifierLogoFromType'; import { ToastHelper } from '../../../../shared/toast'; import { EditEmailNotifierComponent } from './notifiers/EditEmailNotifierComponent'; +import { EditSlackNotifierComponent } from './notifiers/EditSlackNotifierComponent'; import { EditTelegramNotifierComponent } from './notifiers/EditTelegramNotifierComponent'; import { EditWebhookNotifierComponent } from './notifiers/EditWebhookNotifierComponent'; @@ -67,6 +72,9 @@ export function EditNotifierComponent({ }); } catch (e) { alert((e as Error).message); + alert( + 'Make sure channel is public or bot is added to the private channel (via @invite) or group. For direct messages use User ID from Slack profile.', + ); } setIsSendingTestNotification(false); @@ -102,6 +110,13 @@ export function EditNotifierComponent({ }; } + if (type === NotifierType.SLACK) { + notifier.slackNotifier = { + botToken: '', + targetChatId: '', + }; + } + setNotifier( JSON.parse( JSON.stringify({ @@ -129,27 +144,28 @@ export function EditNotifierComponent({ ); }, [editingNotifier]); + useEffect(() => { + setIsTestNotificationSuccess(false); + }, [notifier]); + const isAllDataFilled = () => { if (!notifier) return false; - if (!notifier.name) return false; - if (notifier.notifierType === NotifierType.TELEGRAM) { - return notifier.telegramNotifier?.botToken && notifier.telegramNotifier?.targetChatId; + if (notifier.notifierType === NotifierType.TELEGRAM && notifier.telegramNotifier) { + return validateTelegramNotifier(notifier.telegramNotifier); } - if (notifier.notifierType === NotifierType.EMAIL) { - return ( - notifier.emailNotifier?.targetEmail && - notifier.emailNotifier?.smtpHost && - notifier.emailNotifier?.smtpPort && - notifier.emailNotifier?.smtpUser && - notifier.emailNotifier?.smtpPassword - ); + if (notifier.notifierType === NotifierType.EMAIL && notifier.emailNotifier) { + return validateEmailNotifier(notifier.emailNotifier); } - if (notifier.notifierType === NotifierType.WEBHOOK) { - return notifier.webhookNotifier?.webhookUrl; + if (notifier.notifierType === NotifierType.WEBHOOK && notifier.webhookNotifier) { + return validateWebhookNotifier(notifier.webhookNotifier); + } + + if (notifier.notifierType === NotifierType.SLACK && notifier.slackNotifier) { + return validateSlackNotifier(notifier.slackNotifier); } return false; @@ -185,6 +201,7 @@ export function EditNotifierComponent({ { label: 'Telegram', value: NotifierType.TELEGRAM }, { label: 'Email', value: NotifierType.EMAIL }, { label: 'Webhook', value: NotifierType.WEBHOOK }, + { label: 'Slack', value: NotifierType.SLACK }, ]} onChange={(value) => { setNotifierType(value); @@ -223,6 +240,14 @@ export function EditNotifierComponent({ setIsUnsaved={setIsUnsaved} /> )} + + {notifier?.notifierType === NotifierType.SLACK && ( + + )}
diff --git a/frontend/src/features/notifiers/ui/edit/notifiers/EditSlackNotifierComponent.tsx b/frontend/src/features/notifiers/ui/edit/notifiers/EditSlackNotifierComponent.tsx new file mode 100644 index 0000000..7ed2608 --- /dev/null +++ b/frontend/src/features/notifiers/ui/edit/notifiers/EditSlackNotifierComponent.tsx @@ -0,0 +1,76 @@ +import { Input } from 'antd'; + +import type { Notifier } from '../../../../../entity/notifiers'; + +interface Props { + notifier: Notifier; + setNotifier: (notifier: Notifier) => void; + setIsUnsaved: (isUnsaved: boolean) => void; +} + +export function EditSlackNotifierComponent({ notifier, setNotifier, setIsUnsaved }: Props) { + return ( + <> +
+ + How to connect Slack (how to get bot token and chat ID)? + +
+ +
+
Bot token
+ +
+ { + if (!notifier?.slackNotifier) return; + + setNotifier({ + ...notifier, + slackNotifier: { + ...notifier.slackNotifier, + botToken: e.target.value.trim(), + }, + }); + setIsUnsaved(true); + }} + size="small" + className="w-full" + placeholder="xoxb-..." + /> +
+
+ +
+
Target chat ID
+ +
+ { + if (!notifier?.slackNotifier) return; + + setNotifier({ + ...notifier, + slackNotifier: { + ...notifier.slackNotifier, + targetChatId: e.target.value.trim(), + }, + }); + setIsUnsaved(true); + }} + size="small" + className="w-full" + placeholder="C1234567890" + /> +
+
+ + ); +} diff --git a/frontend/src/features/notifiers/ui/edit/notifiers/EditWebhookNotifierComponent.tsx b/frontend/src/features/notifiers/ui/edit/notifiers/EditWebhookNotifierComponent.tsx index 0ed4133..d67a30d 100644 --- a/frontend/src/features/notifiers/ui/edit/notifiers/EditWebhookNotifierComponent.tsx +++ b/frontend/src/features/notifiers/ui/edit/notifiers/EditWebhookNotifierComponent.tsx @@ -2,7 +2,7 @@ import { InfoCircleOutlined } from '@ant-design/icons'; import { Input, Select, Tooltip } from 'antd'; import type { Notifier } from '../../../../../entity/notifiers'; -import { WebhookMethod } from '../../../../../entity/notifiers/models/WebhookMethod'; +import { WebhookMethod } from '../../../../../entity/notifiers/models/webhook/WebhookMethod'; interface Props { notifier: Notifier; diff --git a/frontend/src/features/notifiers/ui/show/ShowNotifierComponent.tsx b/frontend/src/features/notifiers/ui/show/ShowNotifierComponent.tsx index 1eb3bbc..15fa46d 100644 --- a/frontend/src/features/notifiers/ui/show/ShowNotifierComponent.tsx +++ b/frontend/src/features/notifiers/ui/show/ShowNotifierComponent.tsx @@ -2,6 +2,7 @@ import { type Notifier, NotifierType } from '../../../../entity/notifiers'; import { getNotifierLogoFromType } from '../../../../entity/notifiers/models/getNotifierLogoFromType'; import { getNotifierNameFromType } from '../../../../entity/notifiers/models/getNotifierNameFromType'; import { ShowEmailNotifierComponent } from './notifier/ShowEmailNotifierComponent'; +import { ShowSlackNotifierComponent } from './notifier/ShowSlackNotifierComponent'; import { ShowTelegramNotifierComponent } from './notifier/ShowTelegramNotifierComponent'; import { ShowWebhookNotifierComponent } from './notifier/ShowWebhookNotifierComponent'; @@ -31,6 +32,10 @@ export function ShowNotifierComponent({ notifier }: Props) { {notifier?.notifierType === NotifierType.WEBHOOK && ( )} + + {notifier?.notifierType === NotifierType.SLACK && ( + + )}
); diff --git a/frontend/src/features/notifiers/ui/show/notifier/ShowSlackNotifierComponent.tsx b/frontend/src/features/notifiers/ui/show/notifier/ShowSlackNotifierComponent.tsx new file mode 100644 index 0000000..ba20759 --- /dev/null +++ b/frontend/src/features/notifiers/ui/show/notifier/ShowSlackNotifierComponent.tsx @@ -0,0 +1,22 @@ +import type { Notifier } from '../../../../../entity/notifiers'; + +interface Props { + notifier: Notifier; +} + +export function ShowSlackNotifierComponent({ notifier }: Props) { + return ( + <> +
+
Bot token
+ +
*********
+
+ +
+
Target chat ID
+ {notifier?.slackNotifier?.targetChatId} +
+ + ); +}