feat: improve Dozzle Cloud discoverability#4609
Conversation
…upport Introduces a dedicated cloud.yaml config file separate from notifications.yml. Adds CloudConfig struct with YAML encode/decode helpers, a migration function that promotes legacy cloud dispatchers out of notifications.yml, and CloudConfig getter/setter/remove methods on MultiHostService. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add CloudConfig/SetCloudConfig/RemoveCloudConfig to HostService interface - Add GET /api/cloud/config and DELETE /api/cloud/config routes - Update cloudCallback to save cloud config, handle `from` param for redirect - Update cloudStatus to read from cloud config first, fall back to dispatchers - Add stub implementations of CloudConfig methods to K8sClusterService Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a header cloud icon that surfaces Dozzle Cloud discoverability in three states: not linked (grey, CTA buttons), linked/healthy (blue with green dot, usage/plan info), and linked/error (red with re-link prompt). Fetches config on mount and status lazily on popover open. Handles #cloudLinked hash after OAuth return. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Show two side-by-side cards (Dozzle Cloud and HTTP Webhook) when no destinations are configured, guiding users toward their first destination with cloud OAuth or webhook drawer pre-selected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows cloud linking CTA when not linked, and displays plan, usage progress, and unlink option when linked. Integrated into settings.vue after the About section. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Critical:
Minor:
Looks good:
|
| m.cloudConfig = &cc | ||
| log.Info().Msg("Migrated cloud config from dispatchers") | ||
|
|
||
| // Save dedicated cloud.yml |
There was a problem hiding this comment.
Where do the notifications get updated to cloud? Is that already done?
| defer file.Close() | ||
|
|
||
| if err := notification.WriteCloudConfig(file, *m.cloudConfig); err != nil { | ||
| log.Error().Err(err).Msg("Could not write cloud config") |
There was a problem hiding this comment.
Doesn't this need to broadcast to all agents?
internal/web/cloud.go
Outdated
| apiKey = cc.APIKey | ||
| } else { | ||
| // Fall back to iterating dispatchers for backward compat | ||
| for _, d := range h.hostService.Dispatchers() { |
There was a problem hiding this comment.
If this is migrated then why is it needed?
…provements Add CloudSettingsCard component, cloud config handlers, CloudPopover header icon, empty state for notification destinations, and cloud config composable for improved Dozzle Cloud discoverability. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Critical:
Bugs:
Performance:
Minor:
|
- Include cloud dispatcher in Dispatchers() API for alert dropdowns - Add prefix field to DispatcherResponse for cloud destination cards - Shared reactive cloud config state across all components (useCloudConfig) - Fetch cloud status on page load for immediate icon state - Hide delete button on cloud destination cards - Cloud-specific title/description in destination edit drawer - Use existing toast system for cloud link success notification - Friendly cloud link success message - Broadcast notification config to agents on unlink and reconnect - Exclude cloud from WriteConfig (persisted separately in cloud.yml) - Filter cloud from agent broadcast dispatchers (sent via CloudConfig) - Silence interrupt signal log noise in log listener - Add migration package to split old notifications.yml cloud config into dedicated cloud.yml with subscription dispatcher ID remapping Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Review: feat: improve Dozzle Cloud discoverability Bugs
Issues
Minor
|
internal/agent/client.go
Outdated
| } | ||
|
|
||
| func (c *Client) UpdateNotificationConfig(ctx context.Context, subscriptions []types.SubscriptionConfig, dispatchers []types.DispatcherConfig) error { | ||
| func (c *Client) UpdateNotificationConfig(ctx context.Context, subscriptions []types.SubscriptionConfig, dispatchers []types.DispatcherConfig, cloudConfig *types.CloudConfig) error { |
There was a problem hiding this comment.
Hmm it feels weird that this part of notification to update cloud? Should cloud be it's own standalone method?
internal/agent/server.go
Outdated
| if err != nil { | ||
| log.Error().Err(err).Msg("Failed to create cloud dispatcher from broadcast config") | ||
| } else { | ||
| s.notificationConfigHandler.SetCloudDispatcher(d) |
There was a problem hiding this comment.
Yea, similar question I wonder if this should outside of notification manager?
| } | ||
|
|
||
| // Rewrite notifications.yml | ||
| if err := writeYAML(notificationsPath, config); err != nil { |
There was a problem hiding this comment.
Does it ever get broadcasted?
| ) | ||
|
|
||
| // CloudConfig holds the cloud dispatcher credentials and metadata. | ||
| type CloudConfig struct { |
There was a problem hiding this comment.
Oh good, it doest have it's own class. So above there is just some mix with notification?
Cloud config is no longer piggybacked on UpdateNotificationConfig. Instead, it has its own UpdateCloudConfig RPC, broadcast method (broadcastCloudConfig), and is sent/received independently. Addresses PR review feedback about cloud config being mixed into the notification config flow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ReviewMigration partial-write risk (internal/migration/cloud_config.go:86-102) Dead prop path (assets/components/Notification/DestinationCard.vue:44-58) Double fetchCloudConfig on settings page (assets/composable/cloudConfig.ts) Unguarded write in StartNotificationManager (internal/support/docker/multi_host_service.go:225) Minor: cloudConfig handler returns linked: true whenever the config exists, even if APIKey is empty (e.g. corrupt YAML decoded without error). Consider gating on cc.APIKey != empty string. |
Add empty-state, cloud-link-success, and cloud section translations to all 16 non-English locale files. Update cloud-exists text. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ReviewMigration atomicity (internal/migration/cloud_config.go): If the first writeYAML (cloud.yml) succeeds but the second (notifications.yml) fails, both files exist with the cloud dispatcher in notifications.yml. On next startup migration is skipped (cloud.yml exists), leaving a stale cloud dispatcher entry until the next WriteConfig call. Low probability but a comment or TODO would help. Broadcast scope on new host (multi_host_service.go): When a new agent connects, broadcastCloudConfig() and broadcastNotificationConfig() send to all agents, not just the new one. For deployments with many agents this causes N-1 redundant RPCs per connection event. Host channel buffer (multi_host_service.go): hostCh has buffer 1. Two agents coming online near-simultaneously could drop one event, leaving that agent without its initial config broadcast. No migration tests: MigrateCloudConfig has no unit tests for the subscription remap logic or partial-failure paths. Given it mutates config files at startup, basic table tests would be worth adding. linked field always true (internal/web/cloud.go cloudConfigResponse): The Linked field is hardcoded to true whenever a config exists — the frontend checks cloudConfig?.linked but this can never be false. Either remove the field or document the invariant. Minor: double blank line after ClearCloudDispatcher in manager.go. |
|
Review: feat: improve Dozzle Cloud discoverability Bugs / Issues
Non-critical
|
Agents now write cloud.yml to disk when receiving cloud config via gRPC, and load it on startup. Previously cloud config was only held in memory and lost on agent restart. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Issues:
|
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Review: feat/cloud-discoverability Architecture is clean — separating cloud config from notifications.yml is the right call. Issues:
Minor nits:
|
Add debug/warn logs to trace cloud dispatcher type assertion and file write success on agents, helping diagnose broadcast issues. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Security — API key file permissions
Migration atomicity (
Pointer-to-interface (
Minor
|
Summary
cloud.yamlas first-class config, separate from notification dispatchers — with automatic migration from existingnotifications.ymlfromquery param in OAuth callback to route users back to where they started (cloud popup vs notifications page)Test plan
notifications.yml— verify migration createscloud.ymland strips inline API keycloud.yml— verify grey cloud icon appears, popover shows value propfrom=cloud, return to/#cloudLinkedfrom=notifications, return to notificationscloud.ymland cloud dispatchers🤖 Generated with Claude Code