Move sub-surfaces into their sections: billing views get a pill row, org types a header button. Replace inline IdP handoff copy with an SVG icon and tooltip, add help icons to dense form rows, and delete the registry-driven sidebar nav plumbing. Update specs and tests.
210 lines
8.3 KiB
Go
210 lines
8.3 KiB
Go
package web
|
|
|
|
import (
|
|
"bytes"
|
|
"database/sql"
|
|
"html/template"
|
|
"io/fs"
|
|
"log/slog"
|
|
"net/http"
|
|
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/auth"
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/config"
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/embeds"
|
|
fwmod "git.coopcloud.tech/wiki-cafe/member-console/internal/integrations/fedwiki/store"
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/organization"
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/server"
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/workflows"
|
|
temporalsdk "go.temporal.io/sdk/client"
|
|
)
|
|
|
|
// FedWikiSiteViewModel represents a site for the operator template
|
|
// rendering. Renamed from the core package's OperatorSiteViewModel
|
|
// (openspec/changes/integration-extraction task 2.3): that name suggested
|
|
// a generic "sites" concept, but it was 100% FedWiki-specific — its only
|
|
// consumer was this page.
|
|
type FedWikiSiteViewModel struct {
|
|
ID string
|
|
Domain string
|
|
URL string // composed with the configured fedwiki-site-scheme; see GetSitesPage
|
|
OwnerOrgName string
|
|
IsCustomDomain bool
|
|
CreatedAt string
|
|
Status string // active | readonly | archived
|
|
}
|
|
|
|
// FedWikiSitesData holds data for the fedwiki_operator_sites.html partial.
|
|
// Renamed from the core package's OperatorSitesData (see
|
|
// FedWikiSiteViewModel's doc comment for why).
|
|
type FedWikiSitesData struct {
|
|
Sites []FedWikiSiteViewModel
|
|
Error string
|
|
// Sync health (latest schedule-spawned sync execution, from Temporal)
|
|
// — same panel the Discourse operator page leads with; the shared
|
|
// anatomy is documented in docs/building-an-integration.md.
|
|
SyncStatus string
|
|
SyncTime string
|
|
}
|
|
|
|
// FedWikiOperatorHandler serves the read-only operator-tier view of every
|
|
// FedWiki site in the system, at the path the FedWiki provider manifest
|
|
// declares (OperatorSurfacePath). Moved from the core OperatorPartialsHandler
|
|
// (GetFedWikiSitesPage, internal/server/operator_pages.go:100-152 pre-move)
|
|
// per openspec/changes/integration-extraction task 2.3: unlike the
|
|
// member-facing API/partials handlers, this page was previously grafted
|
|
// onto the shared operator handler rather than owning its own handler
|
|
// type — it now does, mirroring FedWikiHandler/FedWikiPartialsHandler.
|
|
type FedWikiOperatorHandler struct {
|
|
SiteQ fwmod.Querier
|
|
OrgQ organization.Querier
|
|
Database *sql.DB
|
|
Logger *slog.Logger
|
|
AuthConfig *auth.Config
|
|
FedWikiSiteScheme string // http or https; mirrors FedWikiHandler/FedWikiPartialsHandler's field
|
|
Templates *server.SafeTemplates
|
|
Temporal temporalsdk.Client
|
|
SyncScheduleID string
|
|
}
|
|
|
|
// FedWikiOperatorHandlerConfig holds configuration for FedWikiOperatorHandler.
|
|
type FedWikiOperatorHandlerConfig struct {
|
|
SiteQ fwmod.Querier
|
|
OrgQ organization.Querier
|
|
Database *sql.DB
|
|
Logger *slog.Logger
|
|
AuthConfig *auth.Config
|
|
FedWikiSiteScheme string
|
|
// TemplatesFS is FedWiki's own template directory, supplied by the
|
|
// Adapter from its embedded templates tree — see
|
|
// FedWikiPartialsConfig.TemplatesFS's doc comment for why it's injected
|
|
// rather than embedded in this package directly (the same cycle
|
|
// argument applies here). Core's operator.html shell, by contrast, is
|
|
// fetched directly from internal/embeds below — no cycle risk, since
|
|
// embeds is a leaf core package.
|
|
TemplatesFS fs.FS
|
|
// Temporal + SyncScheduleID feed the Sync health panel (nil/"" →
|
|
// "no sync yet"); see workflows.ScheduleRunStatus for why health is
|
|
// resolved through the schedule rather than a workflow ID.
|
|
Temporal temporalsdk.Client
|
|
SyncScheduleID string
|
|
}
|
|
|
|
// NewFedWikiOperatorHandler creates a new FedWikiOperatorHandler.
|
|
func NewFedWikiOperatorHandler(cfg FedWikiOperatorHandlerConfig) (*FedWikiOperatorHandler, error) {
|
|
shellFS, err := fs.Sub(embeds.Templates, "templates")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// operator.html dispatches to a named body partial via the `renderBody`
|
|
// template func (html/template's `{{ template "literal" }}` action
|
|
// requires a compile-time-literal name, hence the indirection). This
|
|
// handler only ever renders its own one body partial
|
|
// (fedwiki_operator_sites.html), so, unlike OperatorPartialsHandler's
|
|
// shared set, renderBody here is a closure over that single template
|
|
// rather than a dynamic dispatcher over many.
|
|
var tmpl *template.Template
|
|
tmpl = template.New("fedwiki-operator").Funcs(template.FuncMap{
|
|
"renderBody": func(name string, data any) (template.HTML, error) {
|
|
var buf bytes.Buffer
|
|
if err := tmpl.ExecuteTemplate(&buf, name, data); err != nil {
|
|
return "", err
|
|
}
|
|
return template.HTML(buf.String()), nil
|
|
},
|
|
"deploymentName": config.DeploymentName,
|
|
})
|
|
// The shell's landing branch references operator_lookup_result.html;
|
|
// html/template's escape analysis requires every referenced template in
|
|
// the set even on branches this page never takes, so parse the shell's
|
|
// partial dependency alongside it or every render fails.
|
|
tmpl, err = tmpl.ParseFS(shellFS, "operator.html", "partials/operator_lookup_result.html", "partials/operator_setup.html")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tmpl, err = tmpl.ParseFS(cfg.TemplatesFS, "fedwiki_operator_sites.html")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &FedWikiOperatorHandler{
|
|
SiteQ: cfg.SiteQ,
|
|
OrgQ: cfg.OrgQ,
|
|
Database: cfg.Database,
|
|
Logger: cfg.Logger,
|
|
AuthConfig: cfg.AuthConfig,
|
|
FedWikiSiteScheme: cfg.FedWikiSiteScheme,
|
|
Templates: server.NewSafeTemplates(tmpl, cfg.Logger),
|
|
Temporal: cfg.Temporal,
|
|
SyncScheduleID: cfg.SyncScheduleID,
|
|
}, nil
|
|
}
|
|
|
|
// GetSitesPage handles GET <the FedWiki provider manifest's
|
|
// OperatorSurfacePath> — read-only integration-tier view of every FedWiki
|
|
// site in the system. The existing fedwiki_operator_sites.html partial is
|
|
// already read-only (table + external links, no forms), so no template
|
|
// changes were needed for the move.
|
|
func (h *FedWikiOperatorHandler) GetSitesPage(w http.ResponseWriter, r *http.Request) {
|
|
bodyData := FedWikiSitesData{}
|
|
bodyData.SyncStatus, bodyData.SyncTime = workflows.ScheduleRunStatus(r.Context(), h.Temporal, h.SyncScheduleID)
|
|
if bodyData.SyncStatus == "" {
|
|
bodyData.SyncStatus = "No sync has run yet"
|
|
}
|
|
|
|
sites, err := h.SiteQ.ListAllSites(r.Context())
|
|
if err != nil {
|
|
h.Logger.Error("failed to list sites", slog.Any("error", err))
|
|
bodyData.Error = "Failed to retrieve sites"
|
|
} else {
|
|
// Same config knob the member-facing FedWikiPartialsHandler.buildSiteURL
|
|
// uses (default https) — audit finding #37: this page used to hardcode
|
|
// https:// regardless, breaking links on http-only dev/test farms.
|
|
scheme := h.FedWikiSiteScheme
|
|
if scheme == "" {
|
|
scheme = "https"
|
|
}
|
|
|
|
// workspace_id → org name cache, same shape as the legacy GetSites
|
|
// handler to avoid N+1 lookups when multiple sites belong to the
|
|
// same workspace.
|
|
orgNameCache := make(map[string]string)
|
|
siteVMs := make([]FedWikiSiteViewModel, len(sites))
|
|
for i, site := range sites {
|
|
orgName := ""
|
|
if cached, ok := orgNameCache[site.WorkspaceID]; ok {
|
|
orgName = cached
|
|
} else {
|
|
if ws, wErr := h.OrgQ.GetWorkspaceByID(r.Context(), site.WorkspaceID); wErr == nil {
|
|
if org, oErr := h.OrgQ.GetOrganizationByID(r.Context(), ws.OrgID); oErr == nil {
|
|
orgName = org.Name
|
|
}
|
|
}
|
|
orgNameCache[site.WorkspaceID] = orgName
|
|
}
|
|
siteVMs[i] = FedWikiSiteViewModel{
|
|
ID: site.SiteID,
|
|
Domain: site.Domain,
|
|
URL: scheme + "://" + site.Domain,
|
|
OwnerOrgName: orgName,
|
|
IsCustomDomain: site.IsCustomDomain,
|
|
CreatedAt: site.CreatedAt.Format("Jan 2, 2006"),
|
|
Status: site.Status,
|
|
}
|
|
}
|
|
bodyData.Sites = siteVMs
|
|
}
|
|
|
|
page := server.BuildOperatorPageData(r, h.AuthConfig, h.Database, h.Logger)
|
|
// Second-level page of the Integrations section: the section's sidebar
|
|
// entry lights up via ActiveCapability (the page itself is reached from
|
|
// the Integrations home's provider cards, not the sidebar); the
|
|
// ia-position tuple nests under the section landing.
|
|
page.IAPosition = "integration:integrations:fedwiki"
|
|
page.ActiveCapability = "integrations"
|
|
page.BodyTemplate = "fedwiki_operator_sites.html"
|
|
page.BodyData = bodyData
|
|
|
|
h.Templates.Render(w, "operator.html", page)
|
|
}
|