From 19adebc4061e753c21614a2113fb6e2b7ae27ffc Mon Sep 17 00:00:00 2001 From: Kevin Biju <52661649+heavycrystal@users.noreply.github.com> Date: Wed, 11 Sep 2024 00:25:20 +0530 Subject: [PATCH 1/9] per mirror alerting (#2063) --- flow/activities/flowable.go | 6 +- flow/alerting/alerting.go | 69 +++++++++++++------ flow/cmd/alerts.go | 32 ++++++--- flow/connectors/core.go | 3 +- flow/connectors/postgres/postgres.go | 27 ++++---- flow/otel_metrics/peerdb_gauges/attributes.go | 1 + .../V38__alerting_config_mirror_names.sql | 1 + protos/route.proto | 6 +- ui/app/alert-config/new.tsx | 37 ++++++++-- ui/app/alert-config/page.tsx | 19 +++-- ui/app/alert-config/validation.ts | 1 + 11 files changed, 142 insertions(+), 60 deletions(-) create mode 100644 nexus/catalog/migrations/V38__alerting_config_mirror_names.sql diff --git a/flow/activities/flowable.go b/flow/activities/flowable.go index 392b5c7671..5ff48b7c7e 100644 --- a/flow/activities/flowable.go +++ b/flow/activities/flowable.go @@ -702,7 +702,11 @@ func (a *FlowableActivity) RecordSlotSizes(ctx context.Context) error { slotMetricGauges.OpenReplicationConnectionsGauge = openReplicationConnectionsGauge } - if err := srcConn.HandleSlotInfo(ctx, a.Alerter, a.CatalogPool, slotName, peerName, slotMetricGauges); err != nil { + if err := srcConn.HandleSlotInfo(ctx, a.Alerter, a.CatalogPool, &alerting.AlertKeys{ + FlowName: config.FlowJobName, + PeerName: peerName, + SlotName: slotName, + }, slotMetricGauges); err != nil { logger.Error("Failed to handle slot info", slog.Any("error", err)) } }() diff --git a/flow/alerting/alerting.go b/flow/alerting/alerting.go index f75059c6ec..7c08c00fe1 100644 --- a/flow/alerting/alerting.go +++ b/flow/alerting/alerting.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "log/slog" + "slices" "strings" "time" @@ -25,44 +26,59 @@ type Alerter struct { } type AlertSenderConfig struct { - Sender AlertSender - Id int64 + Sender AlertSender + AlertForMirrors []string + Id int64 +} + +type AlertKeys struct { + FlowName string + PeerName string + SlotName string } func (a *Alerter) registerSendersFromPool(ctx context.Context) ([]AlertSenderConfig, error) { rows, err := a.catalogPool.Query(ctx, - "SELECT id,service_type,service_config,enc_key_id FROM peerdb_stats.alerting_config") + `SELECT + id, + service_type, + service_config, + enc_key_id, + alert_for_mirrors + FROM peerdb_stats.alerting_config`) if err != nil { return nil, fmt.Errorf("failed to read alerter config from catalog: %w", err) } keys := peerdbenv.PeerDBEncKeys() return pgx.CollectRows(rows, func(row pgx.CollectableRow) (AlertSenderConfig, error) { - var id int64 + var alertSenderConfig AlertSenderConfig var serviceType ServiceType var serviceConfigEnc []byte var encKeyId string - if err := row.Scan(&id, &serviceType, &serviceConfigEnc, &encKeyId); err != nil { - return AlertSenderConfig{}, err + if err := row.Scan(&alertSenderConfig.Id, &serviceType, &serviceConfigEnc, &encKeyId, + &alertSenderConfig.AlertForMirrors); err != nil { + return alertSenderConfig, err } key, err := keys.Get(encKeyId) if err != nil { - return AlertSenderConfig{}, err + return alertSenderConfig, err } serviceConfig, err := key.Decrypt(serviceConfigEnc) if err != nil { - return AlertSenderConfig{}, err + return alertSenderConfig, err } switch serviceType { case SLACK: var slackServiceConfig slackAlertConfig if err := json.Unmarshal(serviceConfig, &slackServiceConfig); err != nil { - return AlertSenderConfig{}, fmt.Errorf("failed to unmarshal %s service config: %w", serviceType, err) + return alertSenderConfig, fmt.Errorf("failed to unmarshal %s service config: %w", serviceType, err) } - return AlertSenderConfig{Id: id, Sender: newSlackAlertSender(&slackServiceConfig)}, nil + alertSenderConfig.Sender = newSlackAlertSender(&slackServiceConfig) + return alertSenderConfig, nil case EMAIL: var replyToAddresses []string if replyToEnvString := strings.TrimSpace( @@ -75,10 +91,10 @@ func (a *Alerter) registerSendersFromPool(ctx context.Context) ([]AlertSenderCon replyToAddresses: replyToAddresses, } if emailServiceConfig.sourceEmail == "" { - return AlertSenderConfig{}, errors.New("missing sourceEmail for Email alerting service") + return alertSenderConfig, errors.New("missing sourceEmail for Email alerting service") } if err := json.Unmarshal(serviceConfig, &emailServiceConfig); err != nil { - return AlertSenderConfig{}, fmt.Errorf("failed to unmarshal %s service config: %w", serviceType, err) + return alertSenderConfig, fmt.Errorf("failed to unmarshal %s service config: %w", serviceType, err) } var region *string if envRegion := peerdbenv.PeerDBAlertingEmailSenderRegion(); envRegion != "" { @@ -89,9 +105,11 @@ func (a *Alerter) registerSendersFromPool(ctx context.Context) ([]AlertSenderCon if alertSenderErr != nil { return AlertSenderConfig{}, fmt.Errorf("failed to initialize email alerter: %w", alertSenderErr) } - return AlertSenderConfig{Id: id, Sender: alertSender}, nil + alertSenderConfig.Sender = alertSender + + return alertSenderConfig, nil default: - return AlertSenderConfig{}, fmt.Errorf("unknown service type: %s", serviceType) + return alertSenderConfig, fmt.Errorf("unknown service type: %s", serviceType) } }) } @@ -119,7 +137,7 @@ func NewAlerter(ctx context.Context, catalogPool *pgxpool.Pool) *Alerter { } } -func (a *Alerter) AlertIfSlotLag(ctx context.Context, peerName string, slotInfo *protos.SlotInfo) { +func (a *Alerter) AlertIfSlotLag(ctx context.Context, alertKeys *AlertKeys, slotInfo *protos.SlotInfo) { alertSenderConfigs, err := a.registerSendersFromPool(ctx) if err != nil { logger.LoggerFromCtx(ctx).Warn("failed to set alert senders", slog.Any("error", err)) @@ -144,12 +162,16 @@ func (a *Alerter) AlertIfSlotLag(ctx context.Context, peerName string, slotInfo } } - alertKey := fmt.Sprintf("%s Slot Lag Threshold Exceeded for Peer %s", deploymentUIDPrefix, peerName) + alertKey := fmt.Sprintf("%s Slot Lag Threshold Exceeded for Peer %s", deploymentUIDPrefix, alertKeys.PeerName) alertMessageTemplate := fmt.Sprintf("%sSlot `%s` on peer `%s` has exceeded threshold size of %%dMB, "+ - `currently at %.2fMB!`, deploymentUIDPrefix, slotInfo.SlotName, peerName, slotInfo.LagInMb) + `currently at %.2fMB!`, deploymentUIDPrefix, slotInfo.SlotName, alertKeys.PeerName, slotInfo.LagInMb) if slotInfo.LagInMb > float32(lowestSlotLagMBAlertThreshold) { for _, alertSenderConfig := range alertSenderConfigs { + if len(alertSenderConfig.AlertForMirrors) > 0 && + !slices.Contains(alertSenderConfig.AlertForMirrors, alertKeys.FlowName) { + continue + } if a.checkAndAddAlertToCatalog(ctx, alertSenderConfig.Id, alertKey, fmt.Sprintf(alertMessageTemplate, lowestSlotLagMBAlertThreshold)) { if alertSenderConfig.Sender.getSlotLagMBAlertThreshold() > 0 { @@ -168,7 +190,7 @@ func (a *Alerter) AlertIfSlotLag(ctx context.Context, peerName string, slotInfo } } -func (a *Alerter) AlertIfOpenConnections(ctx context.Context, peerName string, +func (a *Alerter) AlertIfOpenConnections(ctx context.Context, alertKeys *AlertKeys, openConnections *protos.GetOpenConnectionsForUserResult, ) { alertSenderConfigs, err := a.registerSendersFromPool(ctx) @@ -191,17 +213,22 @@ func (a *Alerter) AlertIfOpenConnections(ctx context.Context, peerName string, lowestOpenConnectionsThreshold := defaultOpenConnectionsThreshold for _, alertSender := range alertSenderConfigs { if alertSender.Sender.getOpenConnectionsAlertThreshold() > 0 { - lowestOpenConnectionsThreshold = min(lowestOpenConnectionsThreshold, alertSender.Sender.getOpenConnectionsAlertThreshold()) + lowestOpenConnectionsThreshold = min(lowestOpenConnectionsThreshold, + alertSender.Sender.getOpenConnectionsAlertThreshold()) } } - alertKey := fmt.Sprintf("%s Max Open Connections Threshold Exceeded for Peer %s", deploymentUIDPrefix, peerName) + alertKey := fmt.Sprintf("%s Max Open Connections Threshold Exceeded for Peer %s", deploymentUIDPrefix, alertKeys.PeerName) alertMessageTemplate := fmt.Sprintf("%sOpen connections from PeerDB user `%s` on peer `%s`"+ ` has exceeded threshold size of %%d connections, currently at %d connections!`, - deploymentUIDPrefix, openConnections.UserName, peerName, openConnections.CurrentOpenConnections) + deploymentUIDPrefix, openConnections.UserName, alertKeys.PeerName, openConnections.CurrentOpenConnections) if openConnections.CurrentOpenConnections > int64(lowestOpenConnectionsThreshold) { for _, alertSenderConfig := range alertSenderConfigs { + if len(alertSenderConfig.AlertForMirrors) > 0 && + !slices.Contains(alertSenderConfig.AlertForMirrors, alertKeys.FlowName) { + continue + } if a.checkAndAddAlertToCatalog(ctx, alertSenderConfig.Id, alertKey, fmt.Sprintf(alertMessageTemplate, lowestOpenConnectionsThreshold)) { if alertSenderConfig.Sender.getOpenConnectionsAlertThreshold() > 0 { diff --git a/flow/cmd/alerts.go b/flow/cmd/alerts.go index d3ea2b1c72..863aeab794 100644 --- a/flow/cmd/alerts.go +++ b/flow/cmd/alerts.go @@ -11,7 +11,7 @@ import ( ) func (h *FlowRequestHandler) GetAlertConfigs(ctx context.Context, req *protos.GetAlertConfigsRequest) (*protos.GetAlertConfigsResponse, error) { - rows, err := h.pool.Query(ctx, "select id, service_type, service_config, enc_key_id from peerdb_stats.alerting_config") + rows, err := h.pool.Query(ctx, "SELECT id,service_type,service_config,enc_key_id,alert_for_mirrors from peerdb_stats.alerting_config") if err != nil { return nil, err } @@ -20,7 +20,7 @@ func (h *FlowRequestHandler) GetAlertConfigs(ctx context.Context, req *protos.Ge var serviceConfigPayload []byte var encKeyID string config := &protos.AlertConfig{} - if err := row.Scan(&config.Id, &config.ServiceType, &serviceConfigPayload, &encKeyID); err != nil { + if err := row.Scan(&config.Id, &config.ServiceType, &serviceConfigPayload, &encKeyID, &config.AlertForMirrors); err != nil { return nil, err } serviceConfig, err := peerdbenv.Decrypt(encKeyID, serviceConfigPayload) @@ -42,34 +42,46 @@ func (h *FlowRequestHandler) PostAlertConfig(ctx context.Context, req *protos.Po if err != nil { return nil, err } - serviceConfig, err := key.Encrypt(shared.UnsafeFastStringToReadOnlyBytes(req.ServiceConfig)) + serviceConfig, err := key.Encrypt(shared.UnsafeFastStringToReadOnlyBytes(req.Config.ServiceConfig)) if err != nil { return nil, err } - if req.Id == -1 { + if req.Config.Id == -1 { var id int32 if err := h.pool.QueryRow( ctx, - "insert into peerdb_stats.alerting_config (service_type, service_config, enc_key_id) values ($1, $2, $3) returning id", - req.ServiceType, + `INSERT INTO peerdb_stats.alerting_config ( + service_type, + service_config, + enc_key_id, + alert_for_mirrors + ) VALUES ( + $1, + $2, + $3, + $4 + ) RETURNING id`, + req.Config.ServiceType, serviceConfig, key.ID, + req.Config.AlertForMirrors, ).Scan(&id); err != nil { return nil, err } return &protos.PostAlertConfigResponse{Id: id}, nil } else if _, err := h.pool.Exec( ctx, - "update peerdb_stats.alerting_config set service_type = $1, service_config = $2, enc_key_id = $3 where id = $4", - req.ServiceType, + "update peerdb_stats.alerting_config set service_type = $1, service_config = $2, enc_key_id = $3, alert_for_mirrors = $4 where id = $5", + req.Config.ServiceType, serviceConfig, key.ID, - req.Id, + req.Config.AlertForMirrors, + req.Config.Id, ); err != nil { return nil, err } - return &protos.PostAlertConfigResponse{Id: req.Id}, nil + return &protos.PostAlertConfigResponse{Id: req.Config.Id}, nil } func (h *FlowRequestHandler) DeleteAlertConfig( diff --git a/flow/connectors/core.go b/flow/connectors/core.go index 0b7a35b1c2..e19693c7d4 100644 --- a/flow/connectors/core.go +++ b/flow/connectors/core.go @@ -79,8 +79,7 @@ type CDCPullConnectorCore interface { ctx context.Context, alerter *alerting.Alerter, catalogPool *pgxpool.Pool, - slotName string, - peerName string, + alertKeys *alerting.AlertKeys, slotMetricGauges peerdb_gauges.SlotMetricGauges, ) error diff --git a/flow/connectors/postgres/postgres.go b/flow/connectors/postgres/postgres.go index b08d97837b..c54ad06024 100644 --- a/flow/connectors/postgres/postgres.go +++ b/flow/connectors/postgres/postgres.go @@ -1161,28 +1161,29 @@ func (c *PostgresConnector) HandleSlotInfo( ctx context.Context, alerter *alerting.Alerter, catalogPool *pgxpool.Pool, - slotName string, - peerName string, + alertKeys *alerting.AlertKeys, slotMetricGauges peerdb_gauges.SlotMetricGauges, ) error { logger := logger.LoggerFromCtx(ctx) - slotInfo, err := getSlotInfo(ctx, c.conn, slotName, c.config.Database) + slotInfo, err := getSlotInfo(ctx, c.conn, alertKeys.SlotName, c.config.Database) if err != nil { logger.Warn("warning: failed to get slot info", "error", err) return err } if len(slotInfo) == 0 { - logger.Warn("warning: unable to get slot info", "slotName", slotName) + logger.Warn("warning: unable to get slot info", slog.String("slotName", alertKeys.SlotName)) return nil } - logger.Info(fmt.Sprintf("Checking %s lag for %s", slotName, peerName), slog.Float64("LagInMB", float64(slotInfo[0].LagInMb))) - alerter.AlertIfSlotLag(ctx, peerName, slotInfo[0]) + logger.Info(fmt.Sprintf("Checking %s lag for %s", alertKeys.SlotName, alertKeys.PeerName), + slog.Float64("LagInMB", float64(slotInfo[0].LagInMb))) + alerter.AlertIfSlotLag(ctx, alertKeys, slotInfo[0]) slotMetricGauges.SlotLagGauge.Set(float64(slotInfo[0].LagInMb), attribute.NewSet( - attribute.String(peerdb_gauges.PeerNameKey, peerName), - attribute.String(peerdb_gauges.SlotNameKey, slotName), + attribute.String(peerdb_gauges.FlowNameKey, alertKeys.FlowName), + attribute.String(peerdb_gauges.PeerNameKey, alertKeys.PeerName), + attribute.String(peerdb_gauges.SlotNameKey, alertKeys.SlotName), attribute.String(peerdb_gauges.DeploymentUidKey, peerdbenv.PeerDBDeploymentUID()))) // Also handles alerts for PeerDB user connections exceeding a given limit here @@ -1191,9 +1192,10 @@ func (c *PostgresConnector) HandleSlotInfo( logger.Warn("warning: failed to get current open connections", "error", err) return err } - alerter.AlertIfOpenConnections(ctx, peerName, res) + alerter.AlertIfOpenConnections(ctx, alertKeys, res) slotMetricGauges.OpenConnectionsGauge.Set(res.CurrentOpenConnections, attribute.NewSet( - attribute.String(peerdb_gauges.PeerNameKey, peerName), + attribute.String(peerdb_gauges.FlowNameKey, alertKeys.FlowName), + attribute.String(peerdb_gauges.PeerNameKey, alertKeys.PeerName), attribute.String(peerdb_gauges.DeploymentUidKey, peerdbenv.PeerDBDeploymentUID()))) replicationRes, err := getOpenReplicationConnectionsForUser(ctx, c.conn, c.config.User) @@ -1203,10 +1205,11 @@ func (c *PostgresConnector) HandleSlotInfo( } slotMetricGauges.OpenReplicationConnectionsGauge.Set(replicationRes.CurrentOpenConnections, attribute.NewSet( - attribute.String(peerdb_gauges.PeerNameKey, peerName), + attribute.String(peerdb_gauges.FlowNameKey, alertKeys.FlowName), + attribute.String(peerdb_gauges.PeerNameKey, alertKeys.PeerName), attribute.String(peerdb_gauges.DeploymentUidKey, peerdbenv.PeerDBDeploymentUID()))) - return monitoring.AppendSlotSizeInfo(ctx, catalogPool, peerName, slotInfo[0]) + return monitoring.AppendSlotSizeInfo(ctx, catalogPool, alertKeys.PeerName, slotInfo[0]) } func getOpenConnectionsForUser(ctx context.Context, conn *pgx.Conn, user string) (*protos.GetOpenConnectionsForUserResult, error) { diff --git a/flow/otel_metrics/peerdb_gauges/attributes.go b/flow/otel_metrics/peerdb_gauges/attributes.go index 70296a51d5..78b54b6119 100644 --- a/flow/otel_metrics/peerdb_gauges/attributes.go +++ b/flow/otel_metrics/peerdb_gauges/attributes.go @@ -3,5 +3,6 @@ package peerdb_gauges const ( PeerNameKey string = "peerName" SlotNameKey string = "slotName" + FlowNameKey string = "flowName" DeploymentUidKey string = "deploymentUID" ) diff --git a/nexus/catalog/migrations/V38__alerting_config_mirror_names.sql b/nexus/catalog/migrations/V38__alerting_config_mirror_names.sql new file mode 100644 index 0000000000..ea878a3fc3 --- /dev/null +++ b/nexus/catalog/migrations/V38__alerting_config_mirror_names.sql @@ -0,0 +1 @@ +ALTER TABLE peerdb_stats.alerting_config ADD COLUMN IF NOT EXISTS alert_for_mirrors TEXT[]; diff --git a/protos/route.proto b/protos/route.proto index ad755ebcd3..fb30bbb423 100644 --- a/protos/route.proto +++ b/protos/route.proto @@ -41,13 +41,13 @@ message AlertConfig { int32 id = 1; string service_type = 2; string service_config = 3; + repeated string alert_for_mirrors = 4; } message GetAlertConfigsRequest { } + message PostAlertConfigRequest { - int32 id = 1; - string service_type = 2; - string service_config = 3; + AlertConfig config = 1; } message DeleteAlertConfigRequest { int32 id = 1; diff --git a/ui/app/alert-config/new.tsx b/ui/app/alert-config/new.tsx index b188c1862a..f399b7af16 100644 --- a/ui/app/alert-config/new.tsx +++ b/ui/app/alert-config/new.tsx @@ -1,3 +1,4 @@ +import { PostAlertConfigRequest } from '@/grpc_generated/route'; import { Button } from '@/lib/Button'; import { Label } from '@/lib/Label/Label'; import { TextField } from '@/lib/TextField'; @@ -26,6 +27,7 @@ export interface AlertConfigProps { serviceType: ServiceType; alertConfig: serviceConfigType; forEdit?: boolean; + alertForMirrors?: string[]; } function ConfigLabel(data: { label: string; value: string }) { @@ -161,6 +163,10 @@ export function NewConfig(alertProps: AlertConfigProps) { alertProps.alertConfig ); + const [alertForMirrors, setAlertForMirrors] = useState( + alertProps.alertForMirrors || [] + ); + const [loading, setLoading] = useState(false); const handleAdd = async () => { @@ -185,6 +191,7 @@ export function NewConfig(alertProps: AlertConfigProps) { id: Number(alertProps.id || -1), serviceType, serviceConfig, + alertForMirrors, }; const alertReqValidity = alertConfigReqSchema.safeParse(alertConfigReq); @@ -192,14 +199,23 @@ export function NewConfig(alertProps: AlertConfigProps) { notifyErr(alertReqValidity.error.issues[0].message); return; } - (alertConfigReq as any).serviceConfig = JSON.stringify( - alertConfigReq.serviceConfig - ); + + const alertConfigProtoReq: PostAlertConfigRequest = { + config: { + id: alertConfigReq.id || -1, + serviceType: alertConfigReq.serviceType, + serviceConfig: JSON.stringify(alertConfigReq.serviceConfig), + alertForMirrors: + alertConfigReq.alertForMirrors?.filter( + (mirror) => mirror && mirror.trim() !== '' + ) || [], + }, + }; setLoading(true); const createRes = await fetch('/api/v1/alerts/config', { method: 'POST', - body: JSON.stringify(alertConfigReq), + body: JSON.stringify(alertConfigProtoReq), }); setLoading(false); @@ -289,6 +305,19 @@ export function NewConfig(alertProps: AlertConfigProps) { } /> +
+

+ Alert only for these mirrors (leave empty to alert for all mirrors) +

+ setAlertForMirrors(e.target.value.split(','))} + /> +
{ServiceFields} + ); +}; + +export default ProjectCard; diff --git a/ui/app/supabase/styles.tsx b/ui/app/supabase/styles.tsx new file mode 100644 index 0000000000..aa9b52517f --- /dev/null +++ b/ui/app/supabase/styles.tsx @@ -0,0 +1,42 @@ +export const ProjectsContainerStyle: React.CSSProperties = { + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + rowGap: '1rem', +}; + +export const ProjectListStyle: React.CSSProperties = { + display: 'flex', + flexDirection: 'column', + rowGap: '1rem', + maxHeight: '40%', + overflowY: 'auto', + width: '30%', +}; + +export const ProjectCardStyle: React.CSSProperties = { + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + fontSize: 13, + padding: '0.5rem', + borderRadius: '1rem', + backgroundColor: 'white', + boxShadow: '0px 0px 2px rgba(0, 0, 0, 0.1)', + border: '1px solid rgba(0, 0, 0, 0.1)', + cursor: 'pointer', + width: '100%', +}; + +export const ProjectNameStyle: React.CSSProperties = { + width: '70%', + padding: '4px 8px', + alignItems: 'start', + textAlign: 'left', + overflowX: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}; diff --git a/ui/components/PeerComponent.tsx b/ui/components/PeerComponent.tsx index 6454575e84..aeef4926cf 100644 --- a/ui/components/PeerComponent.tsx +++ b/ui/components/PeerComponent.tsx @@ -20,6 +20,8 @@ export const DBTypeToImageMapping = (peerType: DBType | string) => { return '/images/crunchy.png'; case 'NEON': return '/images/neon.png'; + case 'SUPABASE': + return '/svgs/supabase-logo-icon.svg'; case DBType.POSTGRES: case 'POSTGRES': return '/svgs/pg.svg'; @@ -57,13 +59,13 @@ export const DBTypeToImageMapping = (peerType: DBType | string) => { } }; -const PeerButton = ({ +export default function PeerButton({ peerName, peerType, }: { peerName: string; peerType: DBType; -}) => { +}) { const router = useRouter(); const [isLoading, setIsLoading] = useState(false); @@ -94,6 +96,4 @@ const PeerButton = ({ ); -}; - -export default PeerButton; +} diff --git a/ui/components/PeerForms/PostgresForm.tsx b/ui/components/PeerForms/PostgresForm.tsx index 1f69ff90a5..d030c2b89b 100644 --- a/ui/components/PeerForms/PostgresForm.tsx +++ b/ui/components/PeerForms/PostgresForm.tsx @@ -1,5 +1,5 @@ 'use client'; -import { PeerSetter } from '@/app/dto/PeersDTO'; +import { PeerConfig, PeerSetter } from '@/app/dto/PeersDTO'; import { PeerSetting } from '@/app/peers/create/[peerType]/helpers/common'; import { SSHSetting, @@ -13,15 +13,23 @@ import { RowWithTextField } from '@/lib/Layout'; import { Switch } from '@/lib/Switch'; import { TextField } from '@/lib/TextField'; import { Tooltip } from '@/lib/Tooltip'; +import { useSearchParams } from 'next/navigation'; import { useEffect, useState } from 'react'; import { InfoPopover } from '../InfoPopover'; interface ConfigProps { settings: PeerSetting[]; setter: PeerSetter; + config: PeerConfig; type: string; } -export default function PostgresForm({ settings, setter, type }: ConfigProps) { +export default function PostgresForm({ + settings, + config, + setter, + type, +}: ConfigProps) { + const searchParams = useSearchParams(); const [showSSH, setShowSSH] = useState(false); const [sshConfig, setSSHConfig] = useState(blankSSHConfig); const handleFile = ( @@ -45,13 +53,6 @@ export default function PostgresForm({ settings, setter, type }: ConfigProps) { } }; - const handleChange = ( - e: React.ChangeEvent, - setting: PeerSetting - ) => { - setting.stateHandler(e.target.value, setter); - }; - const handleSSHParam = ( e: React.ChangeEvent, setting: SSHSetting @@ -64,12 +65,17 @@ export default function PostgresForm({ settings, setter, type }: ConfigProps) { }; useEffect(() => { - setter((prev) => { - return { - ...prev, - sshConfig: showSSH ? sshConfig : undefined, - }; - }); + const host = searchParams.get('host'); + if (host) setter((curr) => ({ ...curr, host })); + const database = searchParams.get('db'); + if (database) setter((curr) => ({ ...curr, database })); + }, [setter, searchParams]); + + useEffect(() => { + setter((prev) => ({ + ...prev, + sshConfig: showSSH ? sshConfig : undefined, + })); }, [sshConfig, setter, showSSH]); return ( @@ -105,8 +111,11 @@ export default function PostgresForm({ settings, setter, type }: ConfigProps) { variant='simple' type={setting.type} defaultValue={setting.default} + value={ + setting.field && config[setting.field as keyof PeerConfig] + } onChange={(e: React.ChangeEvent) => - handleChange(e, setting) + setting.stateHandler(e.target.value, setter) } /> {setting.tips && ( diff --git a/ui/components/SelectSource.tsx b/ui/components/SelectSource.tsx index 951eabfd2f..7df186a543 100644 --- a/ui/components/SelectSource.tsx +++ b/ui/components/SelectSource.tsx @@ -9,12 +9,12 @@ import useSWR from 'swr'; import { DBTypeToImageMapping } from './PeerComponent'; // label corresponds to PeerType -function SourceLabel({ label }: { label: string }) { +function SourceLabel({ label, url }: { label: string; url?: string }) { const peerLogo = DBTypeToImageMapping(label); return (