github.com/kyleu/dbaudit@v0.0.2-0.20240321155047-ff2f2c940496/app/controller/cutil/page.go (about) 1 // Package cutil - Content managed by Project Forge, see [projectforge.md] for details. 2 package cutil 3 4 import ( 5 "context" 6 "fmt" 7 "net/http" 8 "net/url" 9 "slices" 10 "time" 11 12 "github.com/samber/lo" 13 14 "github.com/kyleu/dbaudit/app" 15 "github.com/kyleu/dbaudit/app/controller/cmenu" 16 "github.com/kyleu/dbaudit/app/lib/filter" 17 "github.com/kyleu/dbaudit/app/lib/menu" 18 "github.com/kyleu/dbaudit/app/lib/telemetry" 19 "github.com/kyleu/dbaudit/app/lib/theme" 20 "github.com/kyleu/dbaudit/app/lib/user" 21 "github.com/kyleu/dbaudit/app/util" 22 ) 23 24 const ( 25 DefaultSearchPath = "/search" 26 DefaultProfilePath = "/profile" 27 defaultIcon = "app" 28 ) 29 30 var ( 31 defaultRootTitleAppend = util.GetEnv("app_display_name_append") 32 defaultRootTitle = func() string { 33 if tmp := util.GetEnv("app_display_name"); tmp != "" { 34 return tmp 35 } 36 return util.AppName 37 }() 38 ) 39 40 type PageState struct { 41 Action string `json:"action,omitempty"` 42 Title string `json:"title,omitempty"` 43 Description string `json:"description,omitempty"` 44 Method string `json:"method,omitempty"` 45 URI *url.URL `json:"-"` 46 Menu menu.Items `json:"menu,omitempty"` 47 Breadcrumbs cmenu.Breadcrumbs `json:"breadcrumbs,omitempty"` 48 Flashes []string `json:"flashes,omitempty"` 49 Session util.ValueMap `json:"-"` 50 Profile *user.Profile `json:"profile,omitempty"` 51 Accounts user.Accounts `json:"accounts,omitempty"` 52 Authed bool `json:"authed,omitempty"` 53 Admin bool `json:"admin,omitempty"` 54 Params filter.ParamSet `json:"params,omitempty"` 55 Icons []string `json:"icons,omitempty"` 56 DefaultNavIcon string `json:"defaultNavIcon,omitempty"` 57 RootIcon string `json:"rootIcon,omitempty"` 58 RootPath string `json:"rootPath,omitempty"` 59 RootTitle string `json:"rootTitle,omitempty"` 60 SearchPath string `json:"searchPath,omitempty"` 61 ProfilePath string `json:"profilePath,omitempty"` 62 HideMenu bool `json:"hideMenu,omitempty"` 63 ForceRedirect string `json:"forceRedirect,omitempty"` 64 HeaderContent string `json:"headerContent,omitempty"` 65 Browser string `json:"browser,omitempty"` 66 BrowserVersion string `json:"browserVersion,omitempty"` 67 OS string `json:"os,omitempty"` 68 OSVersion string `json:"osVersion,omitempty"` 69 Platform string `json:"platform,omitempty"` 70 Data any `json:"data,omitempty"` 71 Started time.Time `json:"started,omitempty"` 72 RenderElapsed float64 `json:"renderElapsed,omitempty"` 73 RequestBody []byte `json:"-"` 74 Logger util.Logger `json:"-"` 75 Context context.Context `json:"-"` //nolint:containedctx // properly closed, never directly used 76 Span *telemetry.Span `json:"-"` 77 } 78 79 func (p *PageState) AddIcon(keys ...string) { 80 lo.ForEach(keys, func(k string, _ int) { 81 if !lo.Contains(p.Icons, k) { 82 p.Icons = append(p.Icons, k) 83 } 84 }) 85 slices.Sort(p.Icons) 86 } 87 88 func (p *PageState) TitleString() string { 89 if p.Title == "" { 90 return util.AppName 91 } 92 return fmt.Sprintf("%s - %s", p.Title, util.AppName) 93 } 94 95 func (p *PageState) Username() string { 96 return p.Profile.Name 97 } 98 99 func (p *PageState) AuthString() string { 100 n := p.Profile.String() 101 msg := fmt.Sprintf("signed in as %s", n) 102 if len(p.Accounts) == 0 { 103 if n == user.DefaultProfile.Name { 104 return "click to sign in" 105 } 106 return msg 107 } 108 return fmt.Sprintf("%s using [%s]", msg, p.Accounts.TitleString()) 109 } 110 111 func (p *PageState) Clean(_ *http.Request, as *app.State) error { 112 if p.Profile != nil && p.Profile.Theme == "" { 113 p.Profile.Theme = theme.Default.Key 114 } 115 if p.RootIcon == "" { 116 p.RootIcon = defaultIcon 117 } 118 if p.RootPath == "" { 119 p.RootPath = "/" 120 } 121 if p.RootTitle == "" { 122 p.RootTitle = defaultRootTitle 123 } 124 if defaultRootTitleAppend != "" { 125 p.RootTitle += " " + defaultRootTitleAppend 126 } 127 if p.SearchPath == "" { 128 p.SearchPath = DefaultSearchPath 129 } 130 if p.ProfilePath == "" { 131 p.ProfilePath = DefaultProfilePath 132 } 133 if len(p.Menu) == 0 { 134 m, data, err := cmenu.MenuFor(p.Context, p.Authed, p.Admin, p.Profile, p.Params, as, p.Logger) 135 if err != nil { 136 return err 137 } 138 if data != nil && p.Data == nil { 139 p.Data = data 140 } 141 p.Menu = m 142 } 143 return nil 144 } 145 146 func (p *PageState) Close() { 147 if p.Span != nil { 148 p.Span.Complete() 149 } 150 } 151 152 func (p *PageState) LogError(msg string, args ...any) { 153 p.Logger.Errorf(msg, args...) 154 } 155 156 func (p *PageState) ClassDecl() string { 157 ret := &util.StringSlice{} 158 if p.Profile.Mode != "" { 159 ret.Push(p.Profile.ModeClass()) 160 } 161 if p.Browser != "" { 162 ret.Push("browser-" + p.Browser) 163 } 164 if p.OS != "" { 165 ret.Push("os-" + p.OS) 166 } 167 if p.Platform != "" { 168 ret.Push("platform-" + p.Platform) 169 } 170 if ret.Empty() { 171 return "" 172 } 173 classes := ret.Join(" ") 174 return fmt.Sprintf(` class=%q`, classes) 175 } 176 177 func (p *PageState) SetTitleAndData(title string, data any) { 178 p.Title = title 179 p.Data = data 180 }