Pulse/internal/cloudcp/portal/bootstrap.go
2026-07-07 20:37:18 +01:00

203 lines
8.7 KiB
Go

package portal
import (
"encoding/json"
"html/template"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/cloudcp/registry"
)
type BootstrapWorkspace struct {
ID string `json:"id"`
DisplayName string `json:"display_name"`
State string `json:"state"`
Healthy bool `json:"healthy"`
HealthStatus string `json:"health_status"`
SetupStatus string `json:"setup_status,omitempty"`
AgentCount *int `json:"agent_count,omitempty"`
AgentTokenCount *int `json:"agent_token_count,omitempty"`
UnusedAgentTokenCount *int `json:"unused_agent_token_count,omitempty"`
LastAgentSeenAt string `json:"last_agent_seen_at,omitempty"`
AlertRouteCount *int `json:"alert_route_count,omitempty"`
DisabledAlertRouteCount *int `json:"disabled_alert_route_count,omitempty"`
ActiveCriticalAlertCount *int `json:"active_critical_alert_count,omitempty"`
ActiveWarningAlertCount *int `json:"active_warning_alert_count,omitempty"`
ActiveAlertsUpdatedAt string `json:"active_alerts_updated_at,omitempty"`
ReportScheduleCount *int `json:"report_schedule_count,omitempty"`
DisabledReportScheduleCount *int `json:"disabled_report_schedule_count,omitempty"`
LastHealthCheck string `json:"last_health_check,omitempty"`
CreatedAt string `json:"created_at"`
}
type BootstrapMember struct {
SubjectID string `json:"subject_id"`
UserID string `json:"user_id,omitempty"`
Email string `json:"email"`
Role string `json:"role"`
State string `json:"state"`
CreatedAt string `json:"created_at,omitempty"`
}
type BootstrapSetupTemplate struct {
ID string `json:"id"`
Title string `json:"title"`
AgentNaming string `json:"agent_naming"`
AlertRouting string `json:"alert_routing"`
Reporting string `json:"reporting"`
Access string `json:"access"`
}
type BootstrapAccount struct {
ID string `json:"id"`
Kind string `json:"kind"`
KindLabel string `json:"kind_label"`
Name string `json:"name"`
Role string `json:"role"`
CanManage bool `json:"can_manage"`
HasBilling bool `json:"has_billing"`
Workspaces []BootstrapWorkspace `json:"workspaces"`
Members []BootstrapMember `json:"members"`
SetupTemplates []BootstrapSetupTemplate `json:"setup_templates,omitempty"`
}
type BootstrapData struct {
Authenticated bool `json:"authenticated"`
Email string `json:"email"`
HasSelfHostedCommercial bool `json:"has_self_hosted_commercial"`
PublicSiteURL string `json:"public_site_url"`
SupportEmail string `json:"support_email"`
CommercialAPIBaseURL string `json:"commercial_api_base_url"`
CommercialAPIBasePath string `json:"commercial_api_base_path"`
PortalPath string `json:"portal_path"`
BootstrapPath string `json:"bootstrap_path"`
MagicLinkRequestPath string `json:"magic_link_request_path"`
SignupPath string `json:"signup_path"`
LogoutPath string `json:"logout_path"`
AccountAPIBasePath string `json:"account_api_base_path"`
PortalAPIBasePath string `json:"portal_api_base_path"`
Accounts []BootstrapAccount `json:"accounts"`
}
func MarshalBootstrapJSON(data BootstrapData) (template.JS, error) {
payload, err := json.Marshal(data)
if err != nil {
return "", err
}
return template.JS(payload), nil
}
func BuildBootstrapDataWithSignupPath(authenticated bool, email string, accounts []portalPageAccount, hasSelfHostedCommercial bool, signupPath string) BootstrapData {
bootstrapAccounts := make([]BootstrapAccount, 0, len(accounts))
for _, account := range accounts {
workspaces := make([]BootstrapWorkspace, 0, len(account.Workspaces))
for _, workspace := range account.Workspaces {
lastHealthCheck := ""
if workspace.LastHealthCheck != nil {
lastHealthCheck = workspace.LastHealthCheck.UTC().Format(time.RFC3339)
}
lastAgentSeenAt := ""
if workspace.LastAgentSeenAt != nil {
lastAgentSeenAt = workspace.LastAgentSeenAt.UTC().Format(time.RFC3339)
}
activeAlertsUpdatedAt := ""
if workspace.ActiveAlertsUpdatedAt != nil {
activeAlertsUpdatedAt = workspace.ActiveAlertsUpdatedAt.UTC().Format(time.RFC3339)
}
workspaces = append(workspaces, BootstrapWorkspace{
ID: workspace.ID,
DisplayName: workspace.DisplayName,
State: workspace.State,
Healthy: workspace.Healthy,
HealthStatus: workspace.HealthStatus,
SetupStatus: workspace.SetupStatus,
AgentCount: workspace.AgentCount,
AgentTokenCount: workspace.AgentTokenCount,
UnusedAgentTokenCount: workspace.UnusedAgentTokenCount,
LastAgentSeenAt: lastAgentSeenAt,
AlertRouteCount: workspace.AlertRouteCount,
DisabledAlertRouteCount: workspace.DisabledAlertRouteCount,
ActiveCriticalAlertCount: workspace.ActiveCriticalAlertCount,
ActiveWarningAlertCount: workspace.ActiveWarningAlertCount,
ActiveAlertsUpdatedAt: activeAlertsUpdatedAt,
ReportScheduleCount: workspace.ReportScheduleCount,
DisabledReportScheduleCount: workspace.DisabledReportScheduleCount,
LastHealthCheck: lastHealthCheck,
CreatedAt: workspace.CreatedAt.UTC().Format(time.RFC3339),
})
}
members := make([]BootstrapMember, 0, len(account.Members))
for _, member := range account.Members {
createdAt := ""
if !member.CreatedAt.IsZero() {
createdAt = member.CreatedAt.UTC().Format(time.RFC3339)
}
members = append(members, BootstrapMember{
SubjectID: member.SubjectID,
UserID: member.UserID,
Email: member.Email,
Role: string(member.Role),
State: string(member.State),
CreatedAt: createdAt,
})
}
bootstrapAccounts = append(bootstrapAccounts, BootstrapAccount{
ID: account.ID,
Kind: account.Kind,
KindLabel: account.KindLabel,
Name: account.Name,
Role: account.Role,
CanManage: account.CanManage,
HasBilling: account.HasBilling,
Workspaces: workspaces,
Members: members,
SetupTemplates: defaultSetupTemplatesForAccount(account),
})
}
return BootstrapData{
Authenticated: authenticated,
Email: email,
HasSelfHostedCommercial: hasSelfHostedCommercial,
PublicSiteURL: defaultPublicSiteURL,
SupportEmail: defaultSupportEmail,
CommercialAPIBaseURL: defaultCommercialAPIBaseURL,
CommercialAPIBasePath: PortalCommercialAPIBasePath,
PortalPath: defaultPortalPath,
BootstrapPath: PortalBootstrapPath,
MagicLinkRequestPath: PortalMagicLinkRequestPath,
SignupPath: signupPath,
LogoutPath: defaultLogoutPath,
AccountAPIBasePath: defaultAccountAPIBasePath,
PortalAPIBasePath: defaultPortalAPIBasePath,
Accounts: bootstrapAccounts,
}
}
func defaultSetupTemplatesForAccount(account portalPageAccount) []BootstrapSetupTemplate {
if account.Kind != string(registry.AccountKindMSP) {
return nil
}
return []BootstrapSetupTemplate{
{
ID: "standard-client-onboarding",
Title: "Standard client onboarding",
AgentNaming: "Each client workspace opens an isolated Pulse runtime; repeated hostnames are expected across clients.",
AlertRouting: "Create at least one enabled alert route inside each client runtime.",
Reporting: "Schedule at least one client performance report, with client branding if white_label is granted, before the workspace is marked ready.",
Access: "Invite provider staff from Access and keep client users on the smallest useful role.",
},
}
}
func BuildBootstrapData(authenticated bool, email string, accounts []portalPageAccount, hasSelfHostedCommercial bool) BootstrapData {
return BuildBootstrapDataWithSignupPath(authenticated, email, accounts, hasSelfHostedCommercial, PortalSignupPath)
}
func BuildAnonymousBootstrapDataWithSignupPath(signupPath string) BootstrapData {
return BuildBootstrapDataWithSignupPath(false, "", nil, false, signupPath)
}
func BuildAnonymousBootstrapData() BootstrapData {
return BuildAnonymousBootstrapDataWithSignupPath(PortalSignupPath)
}