feat: Site audit and Crawls screens with their MCP tools
- golem15.seo.issues (Site audit): current issues errors first, labels, summary and fix columns, filters, P7 issue_details, Check this address again, Ignore, Stop ignoring, bulk Ignore, ignored rows badged - golem15.seo.crawls (Crawls): newest first, P6 crawl_summary with one status callout, staging and rendered-comparison lines, stats strip and per-kind table; Cancel; a running crawl refuses delete - Start crawl toolbar action on both, refusing already_running and no_site_url; navigation Site audit (gauge) and Crawls (globe) - routes and MCP tools start/cancel/list/get/delete crawls, list_issues with filters (open by default), recheck_url, ignore and unignore - AdminOperations maps both controllers; toolbar actions get their own operation kind - TestCrawlAdmin, TestCrawlTools, TestIssueKindLabels
This commit is contained in:
505
controllers/issues.go
Normal file
505
controllers/issues.go
Normal file
@@ -0,0 +1,505 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.golem15.com/golem15/sm-seo-plugin/classes"
|
||||
"git.golem15.com/golem15/sm-seo-plugin/classes/crawl"
|
||||
"git.golem15.com/golem15/sm-seo-plugin/models"
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// IssuesControllerID is the Site audit (UI-SPEC C23).
|
||||
const IssuesControllerID = "golem15.seo.issues"
|
||||
|
||||
// The Site audit's actions: the toolbar's start, the record actions and
|
||||
// the bulk ignore.
|
||||
const (
|
||||
ActionStartCrawl = "start_crawl"
|
||||
IssueActionCheck = "recheck"
|
||||
IssueActionIgnore = "ignore"
|
||||
IssueActionShow = "unignore"
|
||||
)
|
||||
|
||||
// errIssueReadOnly refuses creating or editing issues: the audit writes
|
||||
// them.
|
||||
var errIssueReadOnly = errors.New("golem15.seo: issues are read-only")
|
||||
|
||||
// issuesAdminController lists the audit's current issues, errors first,
|
||||
// and previews one with P7 and its record actions (check again, ignore,
|
||||
// stop ignoring); the toolbar starts a crawl and the bulk action ignores.
|
||||
type issuesAdminController struct {
|
||||
app func() *backpack.App
|
||||
}
|
||||
|
||||
func (issuesAdminController) ID() string { return IssuesControllerID }
|
||||
func (issuesAdminController) ModelName() string { return `Golem15\SEO\Models\Issue` }
|
||||
func (issuesAdminController) ConfigDir() string { return "controllers/issues" }
|
||||
func (issuesAdminController) NewRecord() any { return &models.Issue{} }
|
||||
func (issuesAdminController) RequiredPermissions() []string {
|
||||
return []string{PermissionManage}
|
||||
}
|
||||
|
||||
// FormBeforeCreate refuses: the audit writes issues.
|
||||
func (issuesAdminController) FormBeforeCreate(context.Context, any) error { return errIssueReadOnly }
|
||||
|
||||
// FormBeforeUpdate refuses: issues are never edited.
|
||||
func (issuesAdminController) FormBeforeUpdate(context.Context, any) error { return errIssueReadOnly }
|
||||
|
||||
func (c issuesAdminController) service() *classes.Service {
|
||||
svc, err := classes.NewService(appOf(c.app))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return svc
|
||||
}
|
||||
|
||||
// ListExtendQuery narrows the list to the current issues (the newest
|
||||
// crawl's; before any crawl the CMS-data issues, checked now) and puts
|
||||
// errors first, then warnings, then notices.
|
||||
func (c issuesAdminController) ListExtendQuery(ctx context.Context, q *gorm.DB) *gorm.DB {
|
||||
svc := c.service()
|
||||
db, err := database(ctx, c.app)
|
||||
if svc == nil || err != nil {
|
||||
return q.Where("1 = 0")
|
||||
}
|
||||
latest, err := crawl.Current(ctx, svc, db)
|
||||
if err != nil {
|
||||
log.Warn("golem15.seo: site audit list", "error", err)
|
||||
return q.Where("1 = 0")
|
||||
}
|
||||
return crawl.CurrentScope(q, latest).Order("golem15_seo_issues.rank ASC")
|
||||
}
|
||||
|
||||
// startCrawl is the toolbar's Start crawl (also on Crawls): it refuses with
|
||||
// crawls.already_running or crawls.no_site_url as an action error and
|
||||
// toasts crawls.started.
|
||||
func startCrawl(app func() *backpack.App) pact.AdminAction {
|
||||
return pact.AdminAction{
|
||||
Name: ActionStartCrawl,
|
||||
Label: "golem15.seo::lang.crawls.start",
|
||||
Permissions: []string{PermissionManage},
|
||||
Run: func(ctx context.Context, _ pact.AdminActionInput) (pact.AdminActionResult, error) {
|
||||
svc, err := classes.NewService(appOf(app))
|
||||
if err != nil {
|
||||
return pact.AdminActionResult{}, err
|
||||
}
|
||||
db, err := database(ctx, app)
|
||||
if err != nil {
|
||||
return pact.AdminActionResult{}, err
|
||||
}
|
||||
err = lagoon.Transaction(ctx, db, func(ctx context.Context, tx *gorm.DB) error {
|
||||
_, name, _ := svc.Actor(ctx, tx)
|
||||
_, err := crawl.Start(ctx, svc, tx, models.TriggerAdmin, name)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return pact.AdminActionResult{}, startRefusal(ctx, app, err)
|
||||
}
|
||||
return pact.AdminActionResult{Message: "golem15.seo::lang.crawls.started"}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// startRefusal turns a refused start into the admin's action error.
|
||||
func startRefusal(ctx context.Context, app func() *backpack.App, err error) error {
|
||||
tr := translator(app)
|
||||
var running *crawl.AlreadyRunningError
|
||||
switch {
|
||||
case errors.As(err, &running):
|
||||
return &cabana.ForbiddenError{Message: Phrase(ctx, tr, "golem15.seo::lang.crawls.already_running", map[string]string{"time": running.Since().UTC().Format("2006-01-02 15:04 UTC")})}
|
||||
case errors.Is(err, crawl.ErrNoSiteURL):
|
||||
return &cabana.ForbiddenError{Message: Phrase(ctx, tr, "golem15.seo::lang.crawls.no_site_url", nil)}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// AdminActions is the toolbar's Start crawl.
|
||||
func (c issuesAdminController) AdminActions() []pact.AdminAction {
|
||||
return []pact.AdminAction{startCrawl(c.app)}
|
||||
}
|
||||
|
||||
// ignored reports whether a record is an ignored issue.
|
||||
func (c issuesAdminController) ignored(ctx context.Context, record any) (bool, error) {
|
||||
issue, ok := record.(*models.Issue)
|
||||
if !ok || issue == nil {
|
||||
return false, nil
|
||||
}
|
||||
db, err := database(ctx, c.app)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return crawl.IsIgnored(db, *issue)
|
||||
}
|
||||
|
||||
// AdminRecordActions are the preview's Check this address again and Ignore
|
||||
// (while open) and Stop ignoring (while ignored).
|
||||
func (c issuesAdminController) AdminRecordActions() []pact.AdminRecordAction {
|
||||
open := func(ctx context.Context, record any) (bool, error) {
|
||||
ig, err := c.ignored(ctx, record)
|
||||
return !ig, err
|
||||
}
|
||||
return []pact.AdminRecordAction{
|
||||
{
|
||||
Name: IssueActionCheck,
|
||||
Label: "golem15.seo::lang.issues.recheck",
|
||||
Confirm: "golem15.seo::lang.issues.recheck_confirm",
|
||||
Permissions: []string{PermissionManage},
|
||||
Applies: open,
|
||||
Run: func(ctx context.Context, in pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) {
|
||||
issue, ok := in.Record.(*models.Issue)
|
||||
svc := c.service()
|
||||
if !ok || issue == nil || svc == nil {
|
||||
return pact.AdminRecordActionResult{}, classes.ErrNotBooted
|
||||
}
|
||||
db, err := database(ctx, c.app)
|
||||
if err != nil {
|
||||
return pact.AdminRecordActionResult{}, err
|
||||
}
|
||||
res, err := crawl.Recheck(ctx, svc, db, *issue)
|
||||
if err != nil {
|
||||
if errors.Is(err, crawl.ErrNoSiteURL) {
|
||||
return pact.AdminRecordActionResult{}, &cabana.ForbiddenError{Message: Phrase(ctx, translator(c.app), "golem15.seo::lang.crawls.no_site_url", nil)}
|
||||
}
|
||||
return pact.AdminRecordActionResult{}, err
|
||||
}
|
||||
return pact.AdminRecordActionResult{Message: RecheckMessage(ctx, svc, res)}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: IssueActionIgnore,
|
||||
Label: "golem15.seo::lang.issues.ignore",
|
||||
Confirm: "golem15.seo::lang.issues.ignore_confirm",
|
||||
Permissions: []string{PermissionManage},
|
||||
Applies: open,
|
||||
Run: func(ctx context.Context, in pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) {
|
||||
return c.ignore(ctx, []any{in.Record})
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: IssueActionShow,
|
||||
Label: "golem15.seo::lang.issues.unignore",
|
||||
Confirm: "golem15.seo::lang.issues.unignore_confirm",
|
||||
Permissions: []string{PermissionManage},
|
||||
Applies: c.ignored,
|
||||
Run: func(ctx context.Context, in pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) {
|
||||
issue, ok := in.Record.(*models.Issue)
|
||||
if !ok || issue == nil {
|
||||
return pact.AdminRecordActionResult{}, classes.ErrNotBooted
|
||||
}
|
||||
db, err := database(ctx, c.app)
|
||||
if err != nil {
|
||||
return pact.AdminRecordActionResult{}, err
|
||||
}
|
||||
n, err := crawl.Unignore(db, []models.Issue{*issue})
|
||||
if err != nil {
|
||||
return pact.AdminRecordActionResult{}, err
|
||||
}
|
||||
svc := c.service()
|
||||
return pact.AdminRecordActionResult{Message: svc.ChoiceCtx(ctx, "golem15.seo::lang.issues.unignored", n, nil)}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// RecheckMessage is the toast of a recheck: issues.rechecked with the
|
||||
// remaining count, or issues.recheck_fixed.
|
||||
func RecheckMessage(ctx context.Context, svc *classes.Service, res crawl.RecheckResult) string {
|
||||
if res.Remaining == 0 {
|
||||
return svc.PhraseCtx(ctx, "golem15.seo::lang.issues.recheck_fixed", nil)
|
||||
}
|
||||
return svc.ChoiceCtx(ctx, "golem15.seo::lang.issues.rechecked", res.Remaining, nil)
|
||||
}
|
||||
|
||||
// ignore stores the fingerprints of records as ignored.
|
||||
func (c issuesAdminController) ignore(ctx context.Context, records []any) (pact.AdminRecordActionResult, error) {
|
||||
svc := c.service()
|
||||
db, err := database(ctx, c.app)
|
||||
if err != nil || svc == nil {
|
||||
return pact.AdminRecordActionResult{}, errors.Join(err, classes.ErrNotBooted)
|
||||
}
|
||||
var list []models.Issue
|
||||
for _, r := range records {
|
||||
if issue, ok := r.(*models.Issue); ok && issue != nil {
|
||||
list = append(list, *issue)
|
||||
}
|
||||
}
|
||||
adminID, _, _ := svc.Actor(ctx, db)
|
||||
n, err := crawl.Ignore(db, list, adminID)
|
||||
if err != nil {
|
||||
return pact.AdminRecordActionResult{}, err
|
||||
}
|
||||
return pact.AdminRecordActionResult{Message: svc.ChoiceCtx(ctx, "golem15.seo::lang.issues.ignored", n, nil)}, nil
|
||||
}
|
||||
|
||||
// AdminBulkActions is the list's bulk Ignore issues.
|
||||
func (c issuesAdminController) AdminBulkActions() []pact.AdminBulkAction {
|
||||
return []pact.AdminBulkAction{{
|
||||
Name: IssueActionIgnore,
|
||||
Label: "golem15.seo::lang.issues.ignore_selected",
|
||||
Confirm: "golem15.seo::lang.issues.ignore_selected_confirm",
|
||||
Permissions: []string{PermissionManage},
|
||||
Run: func(ctx context.Context, in pact.AdminBulkActionInput) (pact.AdminBulkActionResult, error) {
|
||||
res, err := c.ignore(ctx, in.Records)
|
||||
if err != nil {
|
||||
return pact.AdminBulkActionResult{}, err
|
||||
}
|
||||
return pact.AdminBulkActionResult{Message: res.Message, Affected: len(in.Records)}, nil
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
// ListRowStates marks ignored issues disabled (issues.state_ignored).
|
||||
func (issuesAdminController) ListRowStates(ctx context.Context, db *gorm.DB, records []any) ([][]pact.RowState, error) {
|
||||
var fps []string
|
||||
for _, r := range records {
|
||||
if issue, ok := r.(*models.Issue); ok && issue != nil {
|
||||
fps = append(fps, issue.Fingerprint)
|
||||
}
|
||||
}
|
||||
set, err := crawl.IgnoredSet(db.WithContext(ctx), fps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([][]pact.RowState, len(records))
|
||||
for i, r := range records {
|
||||
if issue, ok := r.(*models.Issue); ok && issue != nil && set[issue.Fingerprint] {
|
||||
out[i] = []pact.RowState{pact.RowStateDisabled}
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// labelKeys are the phrase keys of the stored severity and source values.
|
||||
var labelKeys = map[string]string{
|
||||
models.SeverityError: "golem15.seo::lang.issues.sev_error",
|
||||
models.SeverityWarning: "golem15.seo::lang.issues.sev_warning",
|
||||
models.SeverityNotice: "golem15.seo::lang.issues.sev_notice",
|
||||
models.SourceCrawl: "golem15.seo::lang.issues.source_crawl",
|
||||
models.SourceCMS: "golem15.seo::lang.issues.source_cms",
|
||||
}
|
||||
|
||||
// FixCell is the Fix column: issues.fix_tool with the tool, or
|
||||
// issues.fix_code.
|
||||
func FixCell(ctx context.Context, svc *classes.Service, issue models.Issue) string {
|
||||
if issue.Fix == models.FixTool {
|
||||
return svc.PhraseCtx(ctx, "golem15.seo::lang.issues.fix_tool", map[string]string{"tool": issue.Tool})
|
||||
}
|
||||
return svc.PhraseCtx(ctx, "golem15.seo::lang.issues.fix_code", nil)
|
||||
}
|
||||
|
||||
// ListExtendRecords shows the severity, kind and source labels, the
|
||||
// one-line summary and the fix route in the request locale.
|
||||
func (c issuesAdminController) ListExtendRecords(ctx context.Context, _ *gorm.DB, records []any) error {
|
||||
svc := c.service()
|
||||
if svc == nil {
|
||||
return nil
|
||||
}
|
||||
for _, r := range records {
|
||||
issue, ok := r.(*models.Issue)
|
||||
if !ok || issue == nil {
|
||||
continue
|
||||
}
|
||||
issue.Summary = svc.IssueSummary(ctx, issue.Kind, issue.DetailsMap())
|
||||
issue.FixCell = FixCell(ctx, svc, *issue)
|
||||
issue.KindCell = svc.PhraseCtx(ctx, "golem15.seo::lang.issues.kind_"+issue.Kind, nil)
|
||||
if key, ok := labelKeys[issue.Severity]; ok {
|
||||
issue.Severity = svc.PhraseCtx(ctx, key, nil)
|
||||
}
|
||||
if key, ok := labelKeys[issue.Source]; ok {
|
||||
issue.Source = svc.PhraseCtx(ctx, key, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DropdownOptions labels the preview's read-only kind, severity and
|
||||
// source fields.
|
||||
func (issuesAdminController) DropdownOptions(field string) []pact.Option {
|
||||
switch field {
|
||||
case "kind":
|
||||
out := make([]pact.Option, 0, len(crawl.Kinds()))
|
||||
for _, k := range crawl.Kinds() {
|
||||
out = append(out, pact.Option{Value: k, Label: "golem15.seo::lang.issues.kind_" + k})
|
||||
}
|
||||
return out
|
||||
case "severity":
|
||||
return []pact.Option{
|
||||
{Value: models.SeverityError, Label: labelKeys[models.SeverityError]},
|
||||
{Value: models.SeverityWarning, Label: labelKeys[models.SeverityWarning]},
|
||||
{Value: models.SeverityNotice, Label: labelKeys[models.SeverityNotice]},
|
||||
}
|
||||
case "source":
|
||||
return []pact.Option{
|
||||
{Value: models.SourceCrawl, Label: labelKeys[models.SourceCrawl]},
|
||||
{Value: models.SourceCMS, Label: labelKeys[models.SourceCMS]},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FilterOptions serves the severity, kind, source, fix and status filters.
|
||||
func (c issuesAdminController) FilterOptions(scope string) []pact.Option {
|
||||
switch scope {
|
||||
case models.FilterIssueSeverity:
|
||||
return c.DropdownOptions("severity")
|
||||
case models.FilterIssueKind:
|
||||
return c.DropdownOptions("kind")
|
||||
case models.FilterIssueSource:
|
||||
return c.DropdownOptions("source")
|
||||
case models.FilterIssueFix:
|
||||
return []pact.Option{
|
||||
{Value: models.FixTool, Label: "golem15.seo::lang.issues.filter_fix_tool"},
|
||||
{Value: models.FixCode, Label: "golem15.seo::lang.issues.filter_fix_code"},
|
||||
}
|
||||
case models.FilterIssueStatus:
|
||||
return []pact.Option{
|
||||
{Value: models.IssueOpen, Label: "golem15.seo::lang.issues.status_open"},
|
||||
{Value: models.IssueIgnored, Label: "golem15.seo::lang.issues.status_ignored"},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// maxLinkedShown caps P7's linking pages before issues.and_more.
|
||||
const maxLinkedShown = 10
|
||||
|
||||
// PartialData serves P7 issue_details (UI-SPEC C23).
|
||||
func (c issuesAdminController) PartialData(ctx context.Context, name string, record any) (any, error) {
|
||||
issue, ok := record.(*models.Issue)
|
||||
svc := c.service()
|
||||
if name != "issue_details" || !ok || issue == nil || svc == nil {
|
||||
return classes.IssueDetailsView{}, nil
|
||||
}
|
||||
return IssueDetails(ctx, svc, c.app, *issue), nil
|
||||
}
|
||||
|
||||
// IssueDetails builds P7: the kind's found rows, the linking pages, the
|
||||
// orphan suggestions, how to fix it and the page address.
|
||||
func IssueDetails(ctx context.Context, svc *classes.Service, app func() *backpack.App, issue models.Issue) classes.IssueDetailsView {
|
||||
d := issue.DetailsMap()
|
||||
view := classes.IssueDetailsView{}
|
||||
label := func(key string) string { return svc.PhraseCtx(ctx, "golem15.seo::lang.issues."+key, nil) }
|
||||
view.Rows = append(view.Rows, classes.IssueRow{Label: label("summary"), Value: svc.IssueSummary(ctx, issue.Kind, d)})
|
||||
add := func(labelKey string, value any, mono bool) {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
text := fmt.Sprint(value)
|
||||
if list, ok := value.([]any); ok {
|
||||
parts := make([]string, 0, len(list))
|
||||
for _, e := range list {
|
||||
switch x := e.(type) {
|
||||
case map[string]any:
|
||||
var kv []string
|
||||
for _, k := range []string{"type", "property", "text", "target", "locale", "url", "status", "from", "to"} {
|
||||
if v, ok := x[k]; ok {
|
||||
kv = append(kv, fmt.Sprint(v))
|
||||
}
|
||||
}
|
||||
parts = append(parts, strings.Join(kv, " · "))
|
||||
default:
|
||||
parts = append(parts, fmt.Sprint(x))
|
||||
}
|
||||
}
|
||||
text = strings.Join(parts, "; ")
|
||||
}
|
||||
if strings.TrimSpace(text) == "" {
|
||||
return
|
||||
}
|
||||
view.Rows = append(view.Rows, classes.IssueRow{Label: label(labelKey), Value: text, Mono: mono})
|
||||
}
|
||||
add("detail_h1", d["h1"], false)
|
||||
add("detail_skips", d["skips"], false)
|
||||
add("detail_missing", d["missing"], false)
|
||||
add("detail_error", d["error"], true)
|
||||
add("detail_anchors", d["anchors"], false)
|
||||
add("detail_alternates", d["alternates"], true)
|
||||
add("detail_images", d["images"], true)
|
||||
add("detail_target", d["target"], true)
|
||||
add("detail_what", d["what"], false)
|
||||
if from, ok := d["from"].([]any); ok && issue.Kind != classes.IssueImageAlt {
|
||||
if _, isLevel := d["to"]; !isLevel {
|
||||
for i, f := range from {
|
||||
if i >= maxLinkedShown {
|
||||
break
|
||||
}
|
||||
view.LinkedFrom = append(view.LinkedFrom, classes.IssueLink{URL: fmt.Sprint(f)})
|
||||
}
|
||||
total := len(from)
|
||||
if n, ok := d["count"].(float64); ok && int(n) > total {
|
||||
total = int(n)
|
||||
}
|
||||
if total > len(view.LinkedFrom) {
|
||||
view.More = svc.PhraseCtx(ctx, "golem15.seo::lang.issues.and_more", map[string]string{"count": fmt.Sprint(total - len(view.LinkedFrom))})
|
||||
}
|
||||
}
|
||||
}
|
||||
if issue.Kind == crawl.KindOrphanPage {
|
||||
view.Orphan = true
|
||||
if list, ok := d["suggestions"].([]any); ok {
|
||||
for _, e := range list {
|
||||
s, _ := e.(map[string]any)
|
||||
u := fmt.Sprint(s["url"])
|
||||
view.Suggestions = append(view.Suggestions, classes.IssueLink{URL: u, Phrase: fmt.Sprint(s["phrase"])})
|
||||
}
|
||||
}
|
||||
}
|
||||
if issue.Fix == models.FixTool {
|
||||
view.FixText = svc.PhraseCtx(ctx, "golem15.seo::lang.issues.fix_tool_text", map[string]string{"tool": issue.Tool, "where": fixWhere(ctx, svc, issue)})
|
||||
} else {
|
||||
what, _ := d["what"].(string)
|
||||
view.FixText = svc.PhraseCtx(ctx, "golem15.seo::lang.issues.fix_code_text", map[string]string{"what": what})
|
||||
}
|
||||
site := svc.SiteURL()
|
||||
if strings.HasPrefix(issue.URL, "/") && site != "" {
|
||||
view.PageURL = site + issue.URL
|
||||
}
|
||||
if strings.HasPrefix(issue.Kind, "jsonld_") && view.PageURL != "" {
|
||||
view.RichResults = true
|
||||
view.RichURL = "https://search.google.com/test/rich-results?url=" + view.PageURL
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
// fixWhere names the admin screen where a CMS tool's value is changed.
|
||||
func fixWhere(ctx context.Context, svc *classes.Service, issue models.Issue) string {
|
||||
key := "golem15.seo::lang.issues.where_record"
|
||||
switch issue.Tool {
|
||||
case crawl.ToolUpdateRobots:
|
||||
key = "golem15.seo::lang.issues.where_robots"
|
||||
case crawl.ToolUpdateSettings:
|
||||
key = "golem15.seo::lang.issues.where_settings"
|
||||
case crawl.ToolCreateRedirect, "seo_update_redirect":
|
||||
key = "golem15.seo::lang.issues.where_redirects"
|
||||
case crawl.ToolCreatePage, crawl.ToolUpdatePage:
|
||||
key = "golem15.seo::lang.issues.where_pages"
|
||||
case "seo_update_rule":
|
||||
key = "golem15.seo::lang.issues.where_rules"
|
||||
}
|
||||
return svc.PhraseCtx(ctx, key, nil)
|
||||
}
|
||||
|
||||
var (
|
||||
_ pact.AdminController = issuesAdminController{}
|
||||
_ pact.AdminPermissioned = issuesAdminController{}
|
||||
_ pact.AdminRecordSource = issuesAdminController{}
|
||||
_ pact.FormBeforeCreate = issuesAdminController{}
|
||||
_ pact.FormBeforeUpdate = issuesAdminController{}
|
||||
_ pact.ListExtendQuery = issuesAdminController{}
|
||||
_ pact.HasAdminActions = issuesAdminController{}
|
||||
_ pact.HasAdminRecordActions = issuesAdminController{}
|
||||
_ pact.HasAdminBulkActions = issuesAdminController{}
|
||||
_ pact.ListRowStates = issuesAdminController{}
|
||||
_ pact.ListExtendRecords = issuesAdminController{}
|
||||
_ pact.DropdownOptionsProvider = issuesAdminController{}
|
||||
_ pact.FilterOptions = issuesAdminController{}
|
||||
_ pact.AdminPartialData = issuesAdminController{}
|
||||
)
|
||||
Reference in New Issue
Block a user