diff --git a/backend/pkg/api/data_access/notifications.go b/backend/pkg/api/data_access/notifications.go index 1d3427307..064faa8ce 100644 --- a/backend/pkg/api/data_access/notifications.go +++ b/backend/pkg/api/data_access/notifications.go @@ -83,7 +83,7 @@ const ( DiscordWebhookFormat string = "discord" - GroupOfflineThresholdDefault float64 = 0.1 + GroupEfficiencyBelowThresholdDefault float64 = 0.95 MaxCollateralThresholdDefault float64 = 1.0 MinCollateralThresholdDefault float64 = 0.2 ERC20TokenTransfersValueThresholdDefault float64 = 0.1 @@ -559,19 +559,6 @@ func (d *DataAccessService) GetValidatorDashboardNotificationDetails(ctx context continue } notificationDetails.ValidatorBackOnline = append(notificationDetails.ValidatorBackOnline, t.NotificationEventValidatorBackOnline{Index: curNotification.ValidatorIndex, EpochCount: curNotification.Epoch}) - case types.ValidatorGroupIsOfflineEventName: - // TODO type / collection not present yet, skipping - /*curNotification, ok := not.(*notification.validatorGroupIsOfflineNotification) - if !ok { - return nil, fmt.Errorf("failed to cast notification to validatorGroupIsOfflineNotification") - } - if curNotification.Status == 0 { - notificationDetails.GroupOffline = ... - notificationDetails.GroupOfflineReminder = ... - } else { - notificationDetails.GroupBackOnline = ... - } - */ case types.ValidatorReceivedWithdrawalEventName: curNotification, ok := notification.(*n.ValidatorWithdrawalNotification) if !ok { @@ -1447,7 +1434,7 @@ func (d *DataAccessService) GetNotificationSettings(ctx context.Context, userId func (d *DataAccessService) GetNotificationSettingsDefaultValues(ctx context.Context) (*t.NotificationSettingsDefaultValues, error) { return &t.NotificationSettingsDefaultValues{ - GroupOfflineThreshold: GroupOfflineThresholdDefault, + GroupEfficiencyBelowThreshold: GroupEfficiencyBelowThresholdDefault, MaxCollateralThreshold: MaxCollateralThresholdDefault, MinCollateralThreshold: MinCollateralThresholdDefault, ERC20TokenTransfersValueThreshold: ERC20TokenTransfersValueThresholdDefault, @@ -1763,7 +1750,6 @@ func (d *DataAccessService) GetNotificationSettingsDashboards(ctx context.Contex Network uint64 `db:"network"` WebhookUrl sql.NullString `db:"webhook_target"` IsWebhookDiscordEnabled sql.NullBool `db:"discord_webhook"` - IsRealTimeModeEnabled sql.NullBool `db:"realtime_notifications"` }{} wg.Go(func() error { err := d.alloyReader.SelectContext(ctx, &valDashboards, ` @@ -1774,8 +1760,7 @@ func (d *DataAccessService) GetNotificationSettingsDashboards(ctx context.Contex g.name AS group_name, d.network, g.webhook_target, - (g.webhook_format = $1) AS discord_webhook, - g.realtime_notifications + (g.webhook_format = $1) AS discord_webhook FROM users_val_dashboards d INNER JOIN users_val_dashboards_groups g ON d.id = g.dashboard_id WHERE d.user_id = $2`, DiscordWebhookFormat, userId) @@ -1850,9 +1835,9 @@ func (d *DataAccessService) GetNotificationSettingsDashboards(ctx context.Contex if dashboardType == ValidatorDashboardEventPrefix { resultMap[event.Filter] = &t.NotificationSettingsDashboardsTableRow{ Settings: t.NotificationSettingsValidatorDashboard{ - GroupOfflineThreshold: GroupOfflineThresholdDefault, - MaxCollateralThreshold: MaxCollateralThresholdDefault, - MinCollateralThreshold: MinCollateralThresholdDefault, + GroupEfficiencyBelowThreshold: GroupEfficiencyBelowThresholdDefault, + MaxCollateralThreshold: MaxCollateralThresholdDefault, + MinCollateralThreshold: MinCollateralThresholdDefault, }, } } else if dashboardType == AccountDashboardEventPrefix { @@ -1869,9 +1854,9 @@ func (d *DataAccessService) GetNotificationSettingsDashboards(ctx context.Contex switch eventName { case types.ValidatorIsOfflineEventName: settings.IsValidatorOfflineSubscribed = true - case types.GroupIsOfflineEventName: - settings.IsGroupOfflineSubscribed = true - settings.GroupOfflineThreshold = event.Threshold + case types.ValidatorGroupEfficiencyEventName: + settings.IsGroupEfficiencyBelowSubscribed = true + settings.GroupEfficiencyBelowThreshold = event.Threshold case types.ValidatorMissedAttestationEventName: settings.IsAttestationsMissedSubscribed = true case types.ValidatorMissedProposalEventName, types.ValidatorExecutedProposalEventName: @@ -1917,9 +1902,9 @@ func (d *DataAccessService) GetNotificationSettingsDashboards(ctx context.Contex if _, ok := resultMap[key]; !ok { resultMap[key] = &t.NotificationSettingsDashboardsTableRow{ Settings: t.NotificationSettingsValidatorDashboard{ - GroupOfflineThreshold: GroupOfflineThresholdDefault, - MaxCollateralThreshold: MaxCollateralThresholdDefault, - MinCollateralThreshold: MinCollateralThresholdDefault, + GroupEfficiencyBelowThreshold: GroupEfficiencyBelowThresholdDefault, + MaxCollateralThreshold: MaxCollateralThresholdDefault, + MinCollateralThreshold: MinCollateralThresholdDefault, }, } } @@ -1936,7 +1921,6 @@ func (d *DataAccessService) GetNotificationSettingsDashboards(ctx context.Contex if valSettings, ok := resultMap[key].Settings.(*t.NotificationSettingsValidatorDashboard); ok { valSettings.WebhookUrl = valDashboard.WebhookUrl.String valSettings.IsWebhookDiscordEnabled = valDashboard.IsWebhookDiscordEnabled.Bool - valSettings.IsRealTimeModeEnabled = valDashboard.IsRealTimeModeEnabled.Bool } } @@ -2101,7 +2085,7 @@ func (d *DataAccessService) UpdateNotificationSettingsValidatorDashboard(ctx con eventFilter := fmt.Sprintf("%s:%d:%d", ValidatorDashboardEventPrefix, dashboardId, groupId) d.AddOrRemoveEvent(&eventsToInsert, &eventsToDelete, settings.IsValidatorOfflineSubscribed, userId, types.ValidatorIsOfflineEventName, networkName, eventFilter, epoch, 0) - d.AddOrRemoveEvent(&eventsToInsert, &eventsToDelete, settings.IsGroupOfflineSubscribed, userId, types.GroupIsOfflineEventName, networkName, eventFilter, epoch, settings.GroupOfflineThreshold) + d.AddOrRemoveEvent(&eventsToInsert, &eventsToDelete, settings.IsGroupEfficiencyBelowSubscribed, userId, types.ValidatorGroupEfficiencyEventName, networkName, eventFilter, epoch, settings.GroupEfficiencyBelowThreshold) d.AddOrRemoveEvent(&eventsToInsert, &eventsToDelete, settings.IsAttestationsMissedSubscribed, userId, types.ValidatorMissedAttestationEventName, networkName, eventFilter, epoch, 0) d.AddOrRemoveEvent(&eventsToInsert, &eventsToDelete, settings.IsUpcomingBlockProposalSubscribed, userId, types.ValidatorUpcomingProposalEventName, networkName, eventFilter, epoch, 0) d.AddOrRemoveEvent(&eventsToInsert, &eventsToDelete, settings.IsSyncSubscribed, userId, types.SyncCommitteeSoon, networkName, eventFilter, epoch, 0) @@ -2162,9 +2146,8 @@ func (d *DataAccessService) UpdateNotificationSettingsValidatorDashboard(ctx con UPDATE users_val_dashboards_groups SET webhook_target = NULLIF($1, ''), - webhook_format = CASE WHEN $2 THEN $3 ELSE NULL END, - realtime_notifications = CASE WHEN $4 THEN TRUE ELSE NULL END - WHERE dashboard_id = $5 AND id = $6`, settings.WebhookUrl, settings.IsWebhookDiscordEnabled, DiscordWebhookFormat, settings.IsRealTimeModeEnabled, dashboardId, groupId) + webhook_format = CASE WHEN $2 THEN $3 ELSE NULL END + WHERE dashboard_id = $4 AND id = $5`, settings.WebhookUrl, settings.IsWebhookDiscordEnabled, DiscordWebhookFormat, dashboardId, groupId) if err != nil { return err } diff --git a/backend/pkg/api/handlers/public.go b/backend/pkg/api/handlers/public.go index 98d1b8b55..128506d8b 100644 --- a/backend/pkg/api/handlers/public.go +++ b/backend/pkg/api/handlers/public.go @@ -2495,12 +2495,9 @@ func (h *HandlerService) PublicGetUserNotificationSettingsDashboards(w http.Resp handleErr(w, r, errors.New("invalid settings type")) return } - if !userInfo.PremiumPerks.NotificationsValidatorDashboardGroupOffline && settings.IsGroupOfflineSubscribed { - settings.IsGroupOfflineSubscribed = false - settings.GroupOfflineThreshold = defaultSettings.GroupOfflineThreshold - } - if !userInfo.PremiumPerks.NotificationsValidatorDashboardRealTimeMode && settings.IsRealTimeModeEnabled { - settings.IsRealTimeModeEnabled = false + if !userInfo.PremiumPerks.NotificationsValidatorDashboardGroupEfficiency && settings.IsGroupEfficiencyBelowSubscribed { + settings.IsGroupEfficiencyBelowSubscribed = false + settings.GroupEfficiencyBelowThreshold = defaultSettings.GroupEfficiencyBelowThreshold } data[i].Settings = settings } @@ -2537,7 +2534,7 @@ func (h *HandlerService) PublicPutUserNotificationSettingsValidatorDashboard(w h handleErr(w, r, err) return } - checkMinMax(&v, req.GroupOfflineThreshold, 0, 1, "group_offline_threshold") + checkMinMax(&v, req.GroupEfficiencyBelowThreshold, 0, 1, "group_offline_threshold") vars := mux.Vars(r) dashboardId := v.checkPrimaryDashboardId(vars["dashboard_id"]) groupId := v.checkExistingGroupId(vars["group_id"]) @@ -2553,12 +2550,8 @@ func (h *HandlerService) PublicPutUserNotificationSettingsValidatorDashboard(w h handleErr(w, r, err) return } - if !userInfo.PremiumPerks.NotificationsValidatorDashboardGroupOffline && req.IsGroupOfflineSubscribed { - returnForbidden(w, r, errors.New("user does not have premium perks to subscribe group offline")) - return - } - if !userInfo.PremiumPerks.NotificationsValidatorDashboardRealTimeMode && req.IsRealTimeModeEnabled { - returnForbidden(w, r, errors.New("user does not have premium perks to subscribe real time mode")) + if !userInfo.PremiumPerks.NotificationsValidatorDashboardGroupEfficiency && req.IsGroupEfficiencyBelowSubscribed { + returnForbidden(w, r, errors.New("user does not have premium perks to subscribe group efficiency event")) return } diff --git a/backend/pkg/api/types/data_access.go b/backend/pkg/api/types/data_access.go index 66e4b5fb8..6a2859f6f 100644 --- a/backend/pkg/api/types/data_access.go +++ b/backend/pkg/api/types/data_access.go @@ -309,7 +309,7 @@ type MobileAppBundleStats struct { // Notification structs type NotificationSettingsDefaultValues struct { - GroupOfflineThreshold float64 + GroupEfficiencyBelowThreshold float64 MaxCollateralThreshold float64 MinCollateralThreshold float64 ERC20TokenTransfersValueThreshold float64 diff --git a/backend/pkg/api/types/notifications.go b/backend/pkg/api/types/notifications.go index 36ca1b1b6..2b9e1bc24 100644 --- a/backend/pkg/api/types/notifications.go +++ b/backend/pkg/api/types/notifications.go @@ -40,7 +40,7 @@ type NotificationDashboardsTableRow struct { GroupId uint64 `db:"group_id" json:"group_id"` GroupName string `db:"group_name" json:"group_name"` EntityCount uint64 `db:"entity_count" json:"entity_count"` - EventTypes pq.StringArray `db:"event_types" json:"event_types" tstype:"('validator_online' | 'validator_offline' | 'group_online' | 'group_offline' | 'attestation_missed' | 'proposal_success' | 'proposal_missed' | 'proposal_upcoming' | 'max_collateral' | 'min_collateral' | 'sync' | 'withdrawal' | 'validator_got_slashed' | 'validator_has_slashed' | 'incoming_tx' | 'outgoing_tx' | 'transfer_erc20' | 'transfer_erc721' | 'transfer_erc1155')[]" faker:"slice_len=2, oneof: validator_online, validator_offline, group_online, group_offline, attestation_missed, proposal_success, proposal_missed, proposal_upcoming, max_collateral, min_collateral, sync, withdrawal, validator_got_slashed, validator_has_slashed, incoming_tx, outgoing_tx, transfer_erc20, transfer_erc721, transfer_erc1155"` + EventTypes pq.StringArray `db:"event_types" json:"event_types" tstype:"('validator_online' | 'validator_offline' | 'group_efficiency_below' | 'attestation_missed' | 'proposal_success' | 'proposal_missed' | 'proposal_upcoming' | 'max_collateral' | 'min_collateral' | 'sync' | 'withdrawal' | 'validator_got_slashed' | 'validator_has_slashed' | 'incoming_tx' | 'outgoing_tx' | 'transfer_erc20' | 'transfer_erc721' | 'transfer_erc1155')[]" faker:"slice_len=2, oneof: validator_online, validator_offline, group_efficiency_below, attestation_missed, proposal_success, proposal_missed, proposal_upcoming, max_collateral, min_collateral, sync, withdrawal, validator_got_slashed, validator_has_slashed, incoming_tx, outgoing_tx, transfer_erc20, transfer_erc721, transfer_erc1155"` } type InternalGetUserNotificationDashboardsResponse ApiPagingResponse[NotificationDashboardsTableRow] @@ -62,8 +62,8 @@ type NotificationEventWithdrawal struct { type NotificationValidatorDashboardDetail struct { DashboardName string `db:"dashboard_name" json:"dashboard_name"` GroupName string `db:"group_name" json:"group_name"` - ValidatorOffline []uint64 `json:"validator_offline"` // validator indices - GroupOffline bool `json:"group_offline"` // TODO not filled yet + ValidatorOffline []uint64 `json:"validator_offline"` // validator indices + GroupEfficiencyBelow float64 `json:"group_efficiency_below,omitempty"` // fill with the `group_efficiency_below` threshold if event is present ProposalMissed []IndexSlots `json:"proposal_missed"` ProposalDone []IndexBlocks `json:"proposal_done"` UpcomingProposals []IndexSlots `json:"upcoming_proposals"` @@ -72,9 +72,7 @@ type NotificationValidatorDashboardDetail struct { AttestationMissed []IndexEpoch `json:"attestation_missed"` // index (epoch) Withdrawal []NotificationEventWithdrawal `json:"withdrawal"` ValidatorOfflineReminder []uint64 `json:"validator_offline_reminder"` // validator indices; TODO not filled yet - GroupOfflineReminder bool `json:"group_offline_reminder"` // TODO not filled yet ValidatorBackOnline []NotificationEventValidatorBackOnline `json:"validator_back_online"` - GroupBackOnline uint64 `json:"group_back_online"` // TODO not filled yet MinimumCollateralReached []Address `json:"min_collateral_reached"` // node addresses MaximumCollateralReached []Address `json:"max_collateral_reached"` // node addresses } @@ -202,11 +200,10 @@ type InternalGetUserNotificationSettingsResponse ApiDataResponse[NotificationSet type NotificationSettingsValidatorDashboard struct { WebhookUrl string `json:"webhook_url" faker:"url"` IsWebhookDiscordEnabled bool `json:"is_webhook_discord_enabled"` - IsRealTimeModeEnabled bool `json:"is_real_time_mode_enabled"` IsValidatorOfflineSubscribed bool `json:"is_validator_offline_subscribed"` - IsGroupOfflineSubscribed bool `json:"is_group_offline_subscribed"` - GroupOfflineThreshold float64 `json:"group_offline_threshold" faker:"boundary_start=0, boundary_end=1"` + IsGroupEfficiencyBelowSubscribed bool `json:"is_group_efficiency_below_subscribed"` + GroupEfficiencyBelowThreshold float64 `json:"group_efficiency_below_threshold" faker:"boundary_start=0, boundary_end=1"` IsAttestationsMissedSubscribed bool `json:"is_attestations_missed_subscribed"` IsBlockProposalSubscribed bool `json:"is_block_proposal_subscribed"` IsUpcomingBlockProposalSubscribed bool `json:"is_upcoming_block_proposal_subscribed"` diff --git a/backend/pkg/api/types/user.go b/backend/pkg/api/types/user.go index d31ac8e0e..8e4a30dc7 100644 --- a/backend/pkg/api/types/user.go +++ b/backend/pkg/api/types/user.go @@ -117,25 +117,24 @@ type ExtraDashboardValidatorsPremiumAddon struct { } type PremiumPerks struct { - AdFree bool `json:"ad_free"` // note that this is somhow redunant, since there is already ApiPerks.NoAds - ValidatorDashboards uint64 `json:"validator_dashboards"` - ValidatorsPerDashboard uint64 `json:"validators_per_dashboard"` - ValidatorGroupsPerDashboard uint64 `json:"validator_groups_per_dashboard"` - ShareCustomDashboards bool `json:"share_custom_dashboards"` - ManageDashboardViaApi bool `json:"manage_dashboard_via_api"` - BulkAdding bool `json:"bulk_adding"` - ChartHistorySeconds ChartHistorySeconds `json:"chart_history_seconds"` - EmailNotificationsPerDay uint64 `json:"email_notifications_per_day"` - ConfigureNotificationsViaApi bool `json:"configure_notifications_via_api"` - ValidatorGroupNotifications uint64 `json:"validator_group_notifications"` - WebhookEndpoints uint64 `json:"webhook_endpoints"` - MobileAppCustomThemes bool `json:"mobile_app_custom_themes"` - MobileAppWidget bool `json:"mobile_app_widget"` - MonitorMachines uint64 `json:"monitor_machines"` - MachineMonitoringHistorySeconds uint64 `json:"machine_monitoring_history_seconds"` - NotificationsMachineCustomThreshold bool `json:"notifications_machine_custom_threshold"` - NotificationsValidatorDashboardRealTimeMode bool `json:"notifications_validator_dashboard_real_time_mode"` - NotificationsValidatorDashboardGroupOffline bool `json:"notifications_validator_dashboard_group_offline"` + AdFree bool `json:"ad_free"` // note that this is somhow redunant, since there is already ApiPerks.NoAds + ValidatorDashboards uint64 `json:"validator_dashboards"` + ValidatorsPerDashboard uint64 `json:"validators_per_dashboard"` + ValidatorGroupsPerDashboard uint64 `json:"validator_groups_per_dashboard"` + ShareCustomDashboards bool `json:"share_custom_dashboards"` + ManageDashboardViaApi bool `json:"manage_dashboard_via_api"` + BulkAdding bool `json:"bulk_adding"` + ChartHistorySeconds ChartHistorySeconds `json:"chart_history_seconds"` + EmailNotificationsPerDay uint64 `json:"email_notifications_per_day"` + ConfigureNotificationsViaApi bool `json:"configure_notifications_via_api"` + ValidatorGroupNotifications uint64 `json:"validator_group_notifications"` + WebhookEndpoints uint64 `json:"webhook_endpoints"` + MobileAppCustomThemes bool `json:"mobile_app_custom_themes"` + MobileAppWidget bool `json:"mobile_app_widget"` + MonitorMachines uint64 `json:"monitor_machines"` + MachineMonitoringHistorySeconds uint64 `json:"machine_monitoring_history_seconds"` + NotificationsMachineCustomThreshold bool `json:"notifications_machine_custom_threshold"` + NotificationsValidatorDashboardGroupEfficiency bool `json:"notifications_validator_dashboard_group_efficiency"` } // TODO @patrick post-beta StripeCreateCheckoutSession and StripeCustomerPortal are currently served from v1 (loadbalanced), Once V1 is not affected by this anymore, consider wrapping this with ApiDataResponse diff --git a/backend/pkg/commons/db/user.go b/backend/pkg/commons/db/user.go index fd7712bfa..8ad275f8b 100644 --- a/backend/pkg/commons/db/user.go +++ b/backend/pkg/commons/db/user.go @@ -37,17 +37,16 @@ var freeTierProduct t.PremiumProduct = t.PremiumProduct{ Daily: 0, Weekly: 0, }, - EmailNotificationsPerDay: 10, - ConfigureNotificationsViaApi: false, - ValidatorGroupNotifications: 1, - WebhookEndpoints: 1, - MobileAppCustomThemes: false, - MobileAppWidget: false, - MonitorMachines: 1, - MachineMonitoringHistorySeconds: 3600 * 3, - NotificationsMachineCustomThreshold: false, - NotificationsValidatorDashboardRealTimeMode: false, - NotificationsValidatorDashboardGroupOffline: false, + EmailNotificationsPerDay: 10, + ConfigureNotificationsViaApi: false, + ValidatorGroupNotifications: 1, + WebhookEndpoints: 1, + MobileAppCustomThemes: false, + MobileAppWidget: false, + MonitorMachines: 1, + MachineMonitoringHistorySeconds: 3600 * 3, + NotificationsMachineCustomThreshold: false, + NotificationsValidatorDashboardGroupEfficiency: false, }, PricePerMonthEur: 0, PricePerYearEur: 0, @@ -69,17 +68,16 @@ var adminPerks = t.PremiumPerks{ Daily: maxJsInt, Weekly: maxJsInt, }, - EmailNotificationsPerDay: maxJsInt, - ConfigureNotificationsViaApi: true, - ValidatorGroupNotifications: maxJsInt, - WebhookEndpoints: maxJsInt, - MobileAppCustomThemes: true, - MobileAppWidget: true, - MonitorMachines: maxJsInt, - MachineMonitoringHistorySeconds: maxJsInt, - NotificationsMachineCustomThreshold: true, - NotificationsValidatorDashboardRealTimeMode: true, - NotificationsValidatorDashboardGroupOffline: true, + EmailNotificationsPerDay: maxJsInt, + ConfigureNotificationsViaApi: true, + ValidatorGroupNotifications: maxJsInt, + WebhookEndpoints: maxJsInt, + MobileAppCustomThemes: true, + MobileAppWidget: true, + MonitorMachines: maxJsInt, + MachineMonitoringHistorySeconds: maxJsInt, + NotificationsMachineCustomThreshold: true, + NotificationsValidatorDashboardGroupEfficiency: true, } func GetUserInfo(ctx context.Context, userId uint64, userDbReader *sqlx.DB) (*t.UserInfo, error) { @@ -350,17 +348,16 @@ func GetProductSummary(ctx context.Context) (*t.ProductSummary, error) { // TODO Daily: month, Weekly: 0, }, - EmailNotificationsPerDay: 15, - ConfigureNotificationsViaApi: false, - ValidatorGroupNotifications: 3, - WebhookEndpoints: 3, - MobileAppCustomThemes: true, - MobileAppWidget: true, - MonitorMachines: 2, - MachineMonitoringHistorySeconds: 3600 * 24 * 30, - NotificationsMachineCustomThreshold: true, - NotificationsValidatorDashboardRealTimeMode: true, - NotificationsValidatorDashboardGroupOffline: true, + EmailNotificationsPerDay: 15, + ConfigureNotificationsViaApi: false, + ValidatorGroupNotifications: 3, + WebhookEndpoints: 3, + MobileAppCustomThemes: true, + MobileAppWidget: true, + MonitorMachines: 2, + MachineMonitoringHistorySeconds: 3600 * 24 * 30, + NotificationsMachineCustomThreshold: true, + NotificationsValidatorDashboardGroupEfficiency: true, }, PricePerMonthEur: 9.99, PricePerYearEur: 107.88, @@ -385,17 +382,16 @@ func GetProductSummary(ctx context.Context) (*t.ProductSummary, error) { // TODO Daily: 2 * month, Weekly: 8 * week, }, - EmailNotificationsPerDay: 20, - ConfigureNotificationsViaApi: false, - ValidatorGroupNotifications: 10, - WebhookEndpoints: 10, - MobileAppCustomThemes: true, - MobileAppWidget: true, - MonitorMachines: 10, - MachineMonitoringHistorySeconds: 3600 * 24 * 30, - NotificationsMachineCustomThreshold: true, - NotificationsValidatorDashboardRealTimeMode: true, - NotificationsValidatorDashboardGroupOffline: true, + EmailNotificationsPerDay: 20, + ConfigureNotificationsViaApi: false, + ValidatorGroupNotifications: 10, + WebhookEndpoints: 10, + MobileAppCustomThemes: true, + MobileAppWidget: true, + MonitorMachines: 10, + MachineMonitoringHistorySeconds: 3600 * 24 * 30, + NotificationsMachineCustomThreshold: true, + NotificationsValidatorDashboardGroupEfficiency: true, }, PricePerMonthEur: 29.99, PricePerYearEur: 311.88, @@ -420,17 +416,16 @@ func GetProductSummary(ctx context.Context) (*t.ProductSummary, error) { // TODO Daily: 12 * month, Weekly: maxJsInt, }, - EmailNotificationsPerDay: 50, - ConfigureNotificationsViaApi: true, - ValidatorGroupNotifications: 60, - WebhookEndpoints: 30, - MobileAppCustomThemes: true, - MobileAppWidget: true, - MonitorMachines: 10, - MachineMonitoringHistorySeconds: 3600 * 24 * 30, - NotificationsMachineCustomThreshold: true, - NotificationsValidatorDashboardRealTimeMode: true, - NotificationsValidatorDashboardGroupOffline: true, + EmailNotificationsPerDay: 50, + ConfigureNotificationsViaApi: true, + ValidatorGroupNotifications: 60, + WebhookEndpoints: 30, + MobileAppCustomThemes: true, + MobileAppWidget: true, + MonitorMachines: 10, + MachineMonitoringHistorySeconds: 3600 * 24 * 30, + NotificationsMachineCustomThreshold: true, + NotificationsValidatorDashboardGroupEfficiency: true, }, PricePerMonthEur: 49.99, PricePerYearEur: 479.88, diff --git a/backend/pkg/commons/types/frontend.go b/backend/pkg/commons/types/frontend.go index b45f1b06d..7fafe28be 100644 --- a/backend/pkg/commons/types/frontend.go +++ b/backend/pkg/commons/types/frontend.go @@ -68,8 +68,7 @@ const ( ValidatorMissedProposalEventName EventName = "validator_proposal_missed" ValidatorExecutedProposalEventName EventName = "validator_proposal_submitted" - ValidatorDidSlashEventName EventName = "validator_did_slash" - ValidatorGroupIsOfflineEventName EventName = "validator_group_is_offline" + ValidatorDidSlashEventName EventName = "validator_did_slash" ValidatorReceivedDepositEventName EventName = "validator_received_deposit" NetworkSlashingEventName EventName = "network_slashing" @@ -86,7 +85,7 @@ const ( // Validator dashboard events ValidatorIsOfflineEventName EventName = "validator_is_offline" ValidatorIsOnlineEventName EventName = "validator_is_online" - GroupIsOfflineEventName EventName = "group_is_offline" + ValidatorGroupEfficiencyEventName EventName = "validator_group_efficiency" ValidatorMissedAttestationEventName EventName = "validator_attestation_missed" ValidatorProposalEventName EventName = "validator_proposal" ValidatorUpcomingProposalEventName EventName = "validator_proposal_upcoming" diff --git a/frontend/components/bc/BcAccordion.vue b/frontend/components/bc/BcAccordion.vue index aed38db72..a6c8dfb4b 100644 --- a/frontend/components/bc/BcAccordion.vue +++ b/frontend/components/bc/BcAccordion.vue @@ -4,6 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' const props = defineProps<{ infoCopy?: string, + item?: T, items?: T[], open?: boolean, }>() @@ -56,17 +57,22 @@ const copyText = async () => { + + + + + + user.value?.premium_perks.notifications_validator_dashboard_group_efficiency, +) function closeDialog(): void { dialogRef?.value.close() } @@ -17,10 +21,9 @@ function closeDialog(): void { const checkboxes = ref({ is_attestations_missed_subscribed: props.value?.is_attestations_missed_subscribed ?? false, is_block_proposal_subscribed: props.value?.is_block_proposal_subscribed ?? false, - is_group_offline_subscribed: props.value?.is_group_offline_subscribed ?? false, + is_group_efficiency_below_subscribed: props.value?.is_group_efficiency_below_subscribed ?? false, is_max_collateral_subscribed: props.value?.is_max_collateral_subscribed ?? false, is_min_collateral_subscribed: props.value?.is_min_collateral_subscribed ?? false, - is_real_time_mode_enabled: props.value?.is_real_time_mode_enabled ?? false, is_slashed_subscribed: props.value?.is_slashed_subscribed ?? false, is_sync_subscribed: props.value?.is_sync_subscribed ?? false, is_upcoming_block_proposal_subscribed: props.value?.is_upcoming_block_proposal_subscribed ?? false, @@ -28,7 +31,7 @@ const checkboxes = ref({ is_withdrawal_processed_subscribed: props.value?.is_withdrawal_processed_subscribed ?? false, }) const thresholds = ref({ - group_offline_threshold: formatFraction(props.value?.group_offline_threshold ?? 0), + group_efficiency_below_threshold: formatFraction(props.value?.group_efficiency_below_threshold ?? 0), max_collateral_threshold: formatFraction(props.value?.max_collateral_threshold ?? 0), min_collateral_threshold: formatFraction(props.value?.min_collateral_threshold ?? 0), }) @@ -43,7 +46,7 @@ watchDebounced([ ], () => { emit('change-settings', { ...checkboxes.value, - group_offline_threshold: Number(formatToFraction(thresholds.value.group_offline_threshold)), + group_efficiency_below_threshold: Number(formatToFraction(thresholds.value.group_efficiency_below_threshold)), max_collateral_threshold: Number(formatToFraction(thresholds.value.max_collateral_threshold)), min_collateral_threshold: Number(formatToFraction(thresholds.value.min_collateral_threshold)), }) @@ -118,6 +121,14 @@ watch(hasAllEvents, () => { v-model:checkbox="checkboxes.is_slashed_subscribed" :label="$t('notifications.subscriptions.validators.validator_got_slashed.label')" /> + { } const validatorSub: NotificationSettingsValidatorDashboard = { - group_offline_threshold: 0, // means "deactivated/unchecked" + group_efficiency_below_threshold: 0, is_attestations_missed_subscribed: true, is_block_proposal_subscribed: true, - is_group_offline_subscribed: true, + is_group_efficiency_below_subscribed: true, is_max_collateral_subscribed: false, is_min_collateral_subscribed: false, - is_real_time_mode_enabled: false, is_slashed_subscribed: false, is_sync_subscribed: true, is_upcoming_block_proposal_subscribed: false, diff --git a/frontend/composables/notifications/useNotificationsManagementDashboards.ts b/frontend/composables/notifications/useNotificationsManagementDashboards.ts index a27fbf32d..cb4b0b799 100644 --- a/frontend/composables/notifications/useNotificationsManagementDashboards.ts +++ b/frontend/composables/notifications/useNotificationsManagementDashboards.ts @@ -83,13 +83,12 @@ export function useNotificationsManagementDashboards() { return } const accountDashboarSettings = settings as NotificationSettingsValidatorDashboard - accountDashboarSettings.group_offline_threshold = 0 + accountDashboarSettings.group_efficiency_below_threshold = 0 accountDashboarSettings.is_attestations_missed_subscribed = false accountDashboarSettings.is_block_proposal_subscribed = false - accountDashboarSettings.is_group_offline_subscribed = false + accountDashboarSettings.is_group_efficiency_below_subscribed = false accountDashboarSettings.is_max_collateral_subscribed = false accountDashboarSettings.is_min_collateral_subscribed = false - accountDashboarSettings.is_real_time_mode_enabled = false accountDashboarSettings.is_slashed_subscribed = false accountDashboarSettings.is_sync_subscribed = false accountDashboarSettings.is_upcoming_block_proposal_subscribed = false diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 66a5f0ec2..f2256ef24 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -616,6 +616,8 @@ }, "entity": { "attestation_missed":"Attestation missed", + "group_efficiency": "Group efficiency ", + "group_efficiency_text": "efficiency below {percentage}", "max_collateral":"Maximum collateral reached", "min_collateral":"Minimum collateral reached", "proposal_done": "Proposal done", @@ -650,8 +652,7 @@ }, "event_type": { "attestation_missed": "Attestation missed", - "group_offline": "Group offline", - "group_online": "Group online", + "group_efficiency_below": "Low group efficiency", "incoming_tx": "Incoming Tx.", "max_collateral": "Max. collateral", "min_collateral": "Min. collateral", @@ -814,6 +815,10 @@ "erc1155_token_transfers": { "label": "ERC1155 token transfers" }, + "group_efficiency": { + "info": "efficiency below {percentage}", + "label": "Group efficiency" + }, "ignore_spam_transactions": { "hint": [ "Ignores", @@ -844,6 +849,10 @@ "label": "Block proposal (missed & success)" }, "explanation": "All notifications are sent after network finality (~20min).", + "group_efficiency": { + "info": "Notifies you if your group's efficiency falls below {percentage}% in any given epoch", + "label": "Group efficiency below" + }, "max_collateral_reached": { "label": "Max collateral reached" }, diff --git a/frontend/types/api/notifications.ts b/frontend/types/api/notifications.ts index 2dc1c96ee..067fa6778 100644 --- a/frontend/types/api/notifications.ts +++ b/frontend/types/api/notifications.ts @@ -43,7 +43,7 @@ export interface NotificationDashboardsTableRow { group_id: number /* uint64 */; group_name: string; entity_count: number /* uint64 */; - event_types: ('validator_online' | 'validator_offline' | 'group_online' | 'group_offline' | 'attestation_missed' | 'proposal_success' | 'proposal_missed' | 'proposal_upcoming' | 'max_collateral' | 'min_collateral' | 'sync' | 'withdrawal' | 'validator_got_slashed' | 'validator_has_slashed' | 'incoming_tx' | 'outgoing_tx' | 'transfer_erc20' | 'transfer_erc721' | 'transfer_erc1155')[]; + event_types: ('validator_online' | 'validator_offline' | 'group_efficiency_below' | 'attestation_missed' | 'proposal_success' | 'proposal_missed' | 'proposal_upcoming' | 'max_collateral' | 'min_collateral' | 'sync' | 'withdrawal' | 'validator_got_slashed' | 'validator_has_slashed' | 'incoming_tx' | 'outgoing_tx' | 'transfer_erc20' | 'transfer_erc721' | 'transfer_erc1155')[]; } export type InternalGetUserNotificationDashboardsResponse = ApiPagingResponse; export interface NotificationEventValidatorBackOnline { @@ -59,7 +59,7 @@ export interface NotificationValidatorDashboardDetail { dashboard_name: string; group_name: string; validator_offline: number /* uint64 */[]; // validator indices - group_offline: boolean; // TODO not filled yet + group_efficiency_below?: number /* float64 */; // fill with the `group_efficiency_below` threshold if event is present proposal_missed: IndexSlots[]; proposal_done: IndexBlocks[]; upcoming_proposals: IndexSlots[]; @@ -68,9 +68,7 @@ export interface NotificationValidatorDashboardDetail { attestation_missed: IndexEpoch[]; // index (epoch) withdrawal: NotificationEventWithdrawal[]; validator_offline_reminder: number /* uint64 */[]; // validator indices; TODO not filled yet - group_offline_reminder: boolean; // TODO not filled yet validator_back_online: NotificationEventValidatorBackOnline[]; - group_back_online: number /* uint64 */; // TODO not filled yet min_collateral_reached: Address[]; // node addresses max_collateral_reached: Address[]; // node addresses } @@ -189,10 +187,9 @@ export type InternalGetUserNotificationSettingsResponse = ApiDataResponse