Add ntfy Basic auth, sign-out button, config-only credentials

- ntfy client supports HTTP Basic auth (username/password) for servers with
  access control; [ntfy] config gains username/password. Scheduler + Test Ntfy
  use it instead of the old settings-only bearer token.
- Settings page no longer has credential inputs (Apify/eBay/ntfy/Resend); they
  are managed in config.toml. Read-only status + Test buttons remain.
- Sidebar gains a Sign out button; in forward-auth mode it routes through the
  Authentik outpost sign_out so the IdP session is cleared.
This commit is contained in:
prosolis
2026-06-20 14:56:24 -07:00
parent 4fb1a4553e
commit 72f4bdd88a
11 changed files with 166 additions and 118 deletions

View File

@@ -70,9 +70,15 @@ client_secret = ""
environment = "production" environment = "production"
daily_call_limit = 5000 daily_call_limit = 5000
# ntfy push notifications. If the server has access control
# (auth-default-access: deny-all), set username/password to an ntfy user that
# has write access to the topics you publish to; Veola authenticates publishes
# with HTTP Basic auth. Leave both blank for an open server.
[ntfy] [ntfy]
base_url = "https://ntfy.yourdomain.com" base_url = "https://ntfy.yourdomain.com"
default_topic = "veola" default_topic = "veola"
username = ""
password = ""
# Resend transactional email (https://resend.com) for opt-in deal alerts and # Resend transactional email (https://resend.com) for opt-in deal alerts and
# the weekly digest. api_key and from can also be set via /settings. The from # the weekly digest. api_key and from can also be set via /settings. The from

View File

@@ -126,6 +126,11 @@ type ActorConfig struct {
type NtfyConfig struct { type NtfyConfig struct {
BaseURL string `toml:"base_url"` BaseURL string `toml:"base_url"`
DefaultTopic string `toml:"default_topic"` DefaultTopic string `toml:"default_topic"`
// Username/Password authenticate publishes via HTTP Basic auth, for ntfy
// servers with access control (auth-default-access: deny-all). Leave both
// empty for an open server.
Username string `toml:"username"`
Password string `toml:"password"`
} }
type SchedulerConfig struct { type SchedulerConfig struct {

View File

@@ -49,6 +49,14 @@ func (a *App) PostLogin(w http.ResponseWriter, r *http.Request) {
func (a *App) PostLogout(w http.ResponseWriter, r *http.Request) { func (a *App) PostLogout(w http.ResponseWriter, r *http.Request) {
_ = a.Auth.LogOut(r.Context()) _ = a.Auth.LogOut(r.Context())
// In forward-auth mode, destroying only the local session is pointless: the
// next request still carries a valid Authentik proxy session and would be
// re-provisioned immediately. Send the user through the Authentik outpost
// sign-out so the IdP session is cleared too.
if a.Cfg.Auth.ForwardAuthEnabled() {
http.Redirect(w, r, "/outpost.goauthentik.io/sign_out", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/login", http.StatusSeeOther) http.Redirect(w, r, "/login", http.StatusSeeOther)
} }

View File

@@ -16,52 +16,38 @@ import (
"veola/templates" "veola/templates"
) )
// settingsKeys are the non-secret operational settings editable from the
// Settings page. API credentials (Apify, eBay, ntfy, Resend) are deliberately
// NOT here — they are managed only in config.toml on the server, so the UI
// never reads or writes them. credentialStatus surfaces whether each is
// configured, and the Test buttons verify them.
var settingsKeys = []string{ var settingsKeys = []string{
"apify_api_key",
"ebay_client_id",
"ebay_client_secret",
"ebay_daily_call_limit", "ebay_daily_call_limit",
"ntfy_base_url", "ntfy_base_url",
"ntfy_default_topic", "ntfy_default_topic",
"ntfy_token",
"global_poll_interval_minutes", "global_poll_interval_minutes",
"match_confidence_threshold", "match_confidence_threshold",
"apify_cost_per_call", "apify_cost_per_call",
"monthly_budget_usd", "monthly_budget_usd",
"resend_api_key",
"resend_from", "resend_from",
} }
// secretSettingsKeys are credential fields. Their values are never rendered // credentialStatus reports, per credential, whether it is configured in
// back into the form, so a blank submission means "leave unchanged" rather // config.toml — without exposing the secret itself. ntfy_auth reflects the
// than "clear" — see PostSettings. // Basic-auth user/password pair.
var secretSettingsKeys = map[string]bool{ func (a *App) credentialStatus() map[string]string {
"apify_api_key": true, configured := map[string]bool{
"ebay_client_id": true, "apify_api_key": strings.TrimSpace(a.Cfg.Apify.APIKey) != "",
"ebay_client_secret": true, "ebay_client_id": strings.TrimSpace(a.Cfg.Ebay.ClientID) != "",
"ntfy_token": true, "ebay_client_secret": strings.TrimSpace(a.Cfg.Ebay.ClientSecret) != "",
"resend_api_key": true, "ntfy_auth": strings.TrimSpace(a.Cfg.Ntfy.Username) != "" && a.Cfg.Ntfy.Password != "",
} "resend_api_key": strings.TrimSpace(a.Cfg.Resend.APIKey) != "",
// credentialStatus reports, per secret key, whether a value is saved in the
// settings table, inherited from config.toml, or absent — without exposing
// the secret itself.
func (a *App) credentialStatus(values map[string]string) map[string]string {
configVals := map[string]string{
"apify_api_key": a.Cfg.Apify.APIKey,
"ebay_client_id": a.Cfg.Ebay.ClientID,
"ebay_client_secret": a.Cfg.Ebay.ClientSecret,
"ntfy_token": "",
"resend_api_key": a.Cfg.Resend.APIKey,
} }
status := make(map[string]string, len(secretSettingsKeys)) status := make(map[string]string, len(configured))
for k := range secretSettingsKeys { for k, ok := range configured {
switch { if ok {
case strings.TrimSpace(values[k]) != "":
status[k] = "Saved in settings"
case strings.TrimSpace(configVals[k]) != "":
status[k] = "Set in config.toml" status[k] = "Set in config.toml"
default: } else {
status[k] = "Not set" status[k] = "Not set"
} }
} }
@@ -83,7 +69,7 @@ func (a *App) settingsData(r *http.Request) (templates.SettingsData, error) {
return templates.SettingsData{ return templates.SettingsData{
Page: a.page(r, "Settings", "settings"), Page: a.page(r, "Settings", "settings"),
Values: values, Values: values,
CredentialStatus: a.credentialStatus(values), CredentialStatus: a.credentialStatus(),
IsAdmin: cur != nil && cur.Role == models.RoleAdmin, IsAdmin: cur != nil && cur.Role == models.RoleAdmin,
Users: users, Users: users,
EbayUsedToday: ebayUsed, EbayUsedToday: ebayUsed,
@@ -212,12 +198,6 @@ func (a *App) PostSettings(w http.ResponseWriter, r *http.Request) {
} }
for _, k := range settingsKeys { for _, k := range settingsKeys {
v := strings.TrimSpace(r.PostFormValue(k)) v := strings.TrimSpace(r.PostFormValue(k))
// Secret fields are never rendered back into the form, so a blank
// submission is the normal state and means "leave unchanged" — not
// "clear". (To clear a stored credential, edit the settings table.)
if v == "" && secretSettingsKeys[k] {
continue
}
if err := a.Store.SetSetting(r.Context(), k, v); err != nil { if err := a.Store.SetSetting(r.Context(), k, v); err != nil {
a.serverError(w, r, err) a.serverError(w, r, err)
return return
@@ -284,14 +264,19 @@ func (a *App) PostTestNtfy(w http.ResponseWriter, r *http.Request) {
return return
} }
baseURL := strings.TrimSpace(d.Values["ntfy_base_url"]) baseURL := strings.TrimSpace(d.Values["ntfy_base_url"])
if baseURL == "" {
baseURL = a.Cfg.Ntfy.BaseURL
}
topic := strings.TrimSpace(d.Values["ntfy_default_topic"]) topic := strings.TrimSpace(d.Values["ntfy_default_topic"])
token := strings.TrimSpace(d.Values["ntfy_token"]) if topic == "" {
topic = a.Cfg.Ntfy.DefaultTopic
}
if baseURL == "" || topic == "" { if baseURL == "" || topic == "" {
d.TestNtfyOK = "Set ntfy base URL and default topic first." d.TestNtfyOK = "Set ntfy base URL and default topic first."
render(w, r, templates.Settings(d)) render(w, r, templates.Settings(d))
return return
} }
client := ntfy.NewWithToken(baseURL, token) client := ntfy.NewWithBasicAuth(baseURL, a.Cfg.Ntfy.Username, a.Cfg.Ntfy.Password)
if err := client.Send(r.Context(), ntfy.Notification{ if err := client.Send(r.Context(), ntfy.Notification{
Topic: topic, Topic: topic,
Title: "Veola test", Title: "Veola test",

View File

@@ -13,7 +13,12 @@ import (
type Client struct { type Client struct {
BaseURL string BaseURL string
Token string Token string
HTTP *http.Client // Username/Password take precedence over Token when set, using HTTP Basic
// auth (for ntfy servers that authenticate with user accounts rather than
// access tokens).
Username string
Password string
HTTP *http.Client
} }
func New(baseURL string) *Client { func New(baseURL string) *Client {
@@ -24,13 +29,23 @@ func New(baseURL string) *Client {
} }
// NewWithToken returns a ntfy Client with bearer-token auth set. Use this // NewWithToken returns a ntfy Client with bearer-token auth set. Use this
// when the ntfy server requires authentication. // when the ntfy server requires token authentication.
func NewWithToken(baseURL, token string) *Client { func NewWithToken(baseURL, token string) *Client {
c := New(baseURL) c := New(baseURL)
c.Token = strings.TrimSpace(token) c.Token = strings.TrimSpace(token)
return c return c
} }
// NewWithBasicAuth returns a ntfy Client that authenticates publishes with an
// ntfy user account (HTTP Basic auth). Use this for servers configured with
// auth-default-access: deny-all and per-user topic ACLs.
func NewWithBasicAuth(baseURL, username, password string) *Client {
c := New(baseURL)
c.Username = strings.TrimSpace(username)
c.Password = password
return c
}
type Notification struct { type Notification struct {
Topic string Topic string
Title string Title string
@@ -58,7 +73,9 @@ func (c *Client) Send(ctx context.Context, n Notification) error {
return err return err
} }
req.Header.Set("Content-Type", "text/plain; charset=utf-8") req.Header.Set("Content-Type", "text/plain; charset=utf-8")
if c.Token != "" { if c.Username != "" {
req.SetBasicAuth(c.Username, c.Password)
} else if c.Token != "" {
req.Header.Set("Authorization", "Bearer "+c.Token) req.Header.Set("Authorization", "Bearer "+c.Token)
} }
if n.Title != "" { if n.Title != "" {
@@ -73,17 +90,18 @@ func (c *Client) Send(ctx context.Context, n Notification) error {
if n.Click != "" { if n.Click != "" {
req.Header.Set("Click", n.Click) req.Header.Set("Click", n.Click)
} }
tokenLen := len(c.Token) authMode := "none"
tokenPrefix := "" switch {
if tokenLen >= 4 { case c.Username != "":
tokenPrefix = c.Token[:4] authMode = "basic"
case c.Token != "":
authMode = "bearer"
} }
slog.Info("ntfy publish", slog.Info("ntfy publish",
"url", url, "url", url,
"topic", n.Topic, "topic", n.Topic,
"auth_header_set", c.Token != "", "auth", authMode,
"token_prefix", tokenPrefix, "user", c.Username,
"token_len", tokenLen,
) )
resp, err := c.HTTP.Do(req) resp, err := c.HTTP.Do(req)
if err != nil { if err != nil {

View File

@@ -489,8 +489,7 @@ func (s *Scheduler) sendNotification(ctx context.Context, it models.Item, r apif
if v, _ := s.store.GetSetting(ctx, "ntfy_base_url"); v != "" { if v, _ := s.store.GetSetting(ctx, "ntfy_base_url"); v != "" {
baseURL = v baseURL = v
} }
token, _ := s.store.GetSetting(ctx, "ntfy_token") client := ntfy.NewWithBasicAuth(baseURL, s.cfg.Ntfy.Username, s.cfg.Ntfy.Password)
client := ntfy.NewWithToken(baseURL, token)
return client.Send(ctx, ntfy.Notification{ return client.Send(ctx, ntfy.Notification{
Topic: topic, Topic: topic,
Title: fmt.Sprintf("Veola Alert: %s", it.Name), Title: fmt.Sprintf("Veola Alert: %s", it.Name),

View File

@@ -304,6 +304,23 @@ a.v-feed-name:hover { color: var(--accent); text-decoration: none; }
} }
.v-side-nav a:hover { color: white; } .v-side-nav a:hover { color: white; }
/* Sign-out button, styled to read like a nav link (it lives in a <form>). */
.v-side-signout {
display: flex;
align-items: center;
gap: 0.6rem;
width: 100%;
padding: 0.7rem 1rem;
color: var(--text-2);
background: transparent;
border: 0;
border-left: 3px solid transparent;
text-align: left;
cursor: pointer;
font: inherit;
}
.v-side-signout:hover { color: white; }
/* The brand wordmark at the top of the sidebar is also an anchor (→ /), but /* The brand wordmark at the top of the sidebar is also an anchor (→ /), but
shouldn't pick up the active-item border-left / padding treatment that shouldn't pick up the active-item border-left / padding treatment that
the nav links get. Higher specificity overrides .v-side-nav a defaults. */ the nav links get. Higher specificity overrides .v-side-nav a defaults. */

View File

@@ -31,7 +31,7 @@ templ head(title string) {
</head> </head>
} }
templ Sidebar(active string) { templ Sidebar(active, csrf string) {
<nav class="v-side-nav flex flex-col"> <nav class="v-side-nav flex flex-col">
<a href="/" class="v-side-brand px-4 py-5 flex items-center gap-2"> <a href="/" class="v-side-brand px-4 py-5 flex items-center gap-2">
<span class="text-2xl">🐝</span> <span class="text-2xl">🐝</span>
@@ -41,7 +41,11 @@ templ Sidebar(active string) {
<a href="/items" class={ navClass("items", active) }>Items</a> <a href="/items" class={ navClass("items", active) }>Items</a>
<a href="/results" class={ navClass("results", active) }>Results</a> <a href="/results" class={ navClass("results", active) }>Results</a>
<a href="/settings" class={ navClass("settings", active) }>Settings</a> <a href="/settings" class={ navClass("settings", active) }>Settings</a>
<div class="mt-auto px-4 py-4 v-muted text-xs"> <form method="post" action="/logout" class="mt-auto">
@CSRFInput(csrf)
<button type="submit" class="v-side-signout">Sign out</button>
</form>
<div class="px-4 py-4 v-muted text-xs">
Track. Watch. Notice. Track. Watch. Notice.
</div> </div>
</nav> </nav>
@@ -59,7 +63,7 @@ templ Layout(p Page, body templ.Component) {
<html lang="en"> <html lang="en">
@head(p.Title) @head(p.Title)
<body class="min-h-screen flex"> <body class="min-h-screen flex">
@Sidebar(p.Active) @Sidebar(p.Active, p.CSRFToken)
<main class="flex-1 p-6 max-w-6xl"> <main class="flex-1 p-6 max-w-6xl">
if p.Flash != "" { if p.Flash != "" {
<div class="v-flash">{ p.Flash }</div> <div class="v-flash">{ p.Flash }</div>

View File

@@ -63,7 +63,7 @@ func head(title string) templ.Component {
}) })
} }
func Sidebar(active string) templ.Component { func Sidebar(active, csrf string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -172,7 +172,15 @@ func Sidebar(active string) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">Settings</a><div class=\"mt-auto px-4 py-4 v-muted text-xs\">Track. Watch. Notice.</div></nav>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">Settings</a><form method=\"post\" action=\"/logout\" class=\"mt-auto\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = CSRFInput(csrf).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<button type=\"submit\" class=\"v-side-signout\">Sign out</button></form><div class=\"px-4 py-4 v-muted text-xs\">Track. Watch. Notice.</div></nav>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -208,7 +216,7 @@ func Layout(p Page, body templ.Component) templ.Component {
templ_7745c5c3_Var12 = templ.NopComponent templ_7745c5c3_Var12 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<!doctype html><html lang=\"en\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -216,52 +224,52 @@ func Layout(p Page, body templ.Component) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<body class=\"min-h-screen flex\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<body class=\"min-h-screen flex\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = Sidebar(p.Active).Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = Sidebar(p.Active, p.CSRFToken).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<main class=\"flex-1 p-6 max-w-6xl\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<main class=\"flex-1 p-6 max-w-6xl\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if p.Flash != "" { if p.Flash != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"v-flash\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<div class=\"v-flash\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var13 string var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(p.Flash) templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(p.Flash)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 65, Col: 35} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 69, Col: 35}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
if p.FlashError != "" { if p.FlashError != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"v-flash-error\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<div class=\"v-flash-error\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var14 string var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(p.FlashError) templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(p.FlashError)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 68, Col: 46} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 72, Col: 46}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -270,7 +278,7 @@ func Layout(p Page, body templ.Component) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</main></body></html>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</main></body></html>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -300,7 +308,7 @@ func Bare(p Page, body templ.Component) templ.Component {
templ_7745c5c3_Var15 = templ.NopComponent templ_7745c5c3_Var15 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<!doctype html><html lang=\"en\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -308,7 +316,7 @@ func Bare(p Page, body templ.Component) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<body class=\"min-h-screen\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<body class=\"min-h-screen\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -316,7 +324,7 @@ func Bare(p Page, body templ.Component) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</body></html>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</body></html>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -346,20 +354,20 @@ func CSRFInput(token string) templ.Component {
templ_7745c5c3_Var16 = templ.NopComponent templ_7745c5c3_Var16 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<input type=\"hidden\" name=\"csrf_token\" value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<input type=\"hidden\" name=\"csrf_token\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var17 string var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(token) templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(token)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 89, Col: 53} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 93, Col: 53}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

@@ -67,21 +67,21 @@ templ settingsBody(d SettingsData) {
<section class="v-card p-6"> <section class="v-card p-6">
<h2 class="v-section-title mb-4">Apify, eBay and Ntfy</h2> <h2 class="v-section-title mb-4">Apify, eBay and Ntfy</h2>
<div class="v-muted text-sm mb-4">
API credentials (Apify, eBay, ntfy, Resend) are managed in config.toml on the server, not here. Use the Test buttons to verify them.
</div>
<form method="post" action="/settings" class="space-y-4"> <form method="post" action="/settings" class="space-y-4">
@CSRFInput(d.CSRFToken) @CSRFInput(d.CSRFToken)
<div> <div>
<label class="v-label">Apify API Key</label> <label class="v-label">Apify API Key</label>
<input class="v-input font-mono" type="password" name="apify_api_key" autocomplete="off" placeholder="leave blank to keep current value"/>
@credStatus(d, "apify_api_key") @credStatus(d, "apify_api_key")
</div> </div>
<div class="border-t border-white/10 pt-4"> <div class="border-t border-white/10 pt-4">
<label class="v-label">eBay App ID (Client ID)</label> <label class="v-label">eBay App ID (Client ID)</label>
<input class="v-input font-mono" type="password" name="ebay_client_id" autocomplete="off" placeholder="used for eBay marketplaces instead of Apify"/>
@credStatus(d, "ebay_client_id") @credStatus(d, "ebay_client_id")
</div> </div>
<div> <div>
<label class="v-label">eBay Cert ID (Client Secret)</label> <label class="v-label">eBay Cert ID (Client Secret)</label>
<input class="v-input font-mono" type="password" name="ebay_client_secret" autocomplete="off" placeholder="leave blank to keep current value"/>
@credStatus(d, "ebay_client_secret") @credStatus(d, "ebay_client_secret")
</div> </div>
<div> <div>
@@ -108,9 +108,8 @@ templ settingsBody(d SettingsData) {
<input class="v-input" name="ntfy_default_topic" value={ d.Values["ntfy_default_topic"] }/> <input class="v-input" name="ntfy_default_topic" value={ d.Values["ntfy_default_topic"] }/>
</div> </div>
<div> <div>
<label class="v-label">Ntfy Token</label> <label class="v-label">Ntfy Auth (user / password)</label>
<input class="v-input font-mono" type="password" name="ntfy_token" autocomplete="off" placeholder="tk_... (leave blank to keep current value)"/> @credStatus(d, "ntfy_auth")
@credStatus(d, "ntfy_token")
</div> </div>
<div> <div>
<label class="v-label">Global Poll Interval (minutes)</label> <label class="v-label">Global Poll Interval (minutes)</label>
@@ -122,7 +121,6 @@ templ settingsBody(d SettingsData) {
</div> </div>
<div class="border-t border-white/10 pt-4"> <div class="border-t border-white/10 pt-4">
<label class="v-label">Resend API Key</label> <label class="v-label">Resend API Key</label>
<input class="v-input font-mono" type="password" name="resend_api_key" autocomplete="off" placeholder="re_... (leave blank to keep current value)"/>
@credStatus(d, "resend_api_key") @credStatus(d, "resend_api_key")
</div> </div>
<div> <div>

View File

@@ -123,7 +123,7 @@ func settingsBody(d SettingsData) templ.Component {
templ_7745c5c3_Var3 = templ.NopComponent templ_7745c5c3_Var3 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"space-y-8 max-w-3xl\"><div><h1 class=\"v-page-title\">Settings</h1><p class=\"v-page-sub\">Credentials, budget, notifications, and users</p></div><section class=\"v-card p-6\"><h2 class=\"v-section-title mb-4\">Apify, eBay and Ntfy</h2><form method=\"post\" action=\"/settings\" class=\"space-y-4\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"space-y-8 max-w-3xl\"><div><h1 class=\"v-page-title\">Settings</h1><p class=\"v-page-sub\">Credentials, budget, notifications, and users</p></div><section class=\"v-card p-6\"><h2 class=\"v-section-title mb-4\">Apify, eBay and Ntfy</h2><div class=\"v-muted text-sm mb-4\">API credentials (Apify, eBay, ntfy, Resend) are managed in config.toml on the server, not here. Use the Test buttons to verify them.</div><form method=\"post\" action=\"/settings\" class=\"space-y-4\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -131,7 +131,7 @@ func settingsBody(d SettingsData) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div><label class=\"v-label\">Apify API Key</label> <input class=\"v-input font-mono\" type=\"password\" name=\"apify_api_key\" autocomplete=\"off\" placeholder=\"leave blank to keep current value\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div><label class=\"v-label\">Apify API Key</label>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -139,7 +139,7 @@ func settingsBody(d SettingsData) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div><div class=\"border-t border-white/10 pt-4\"><label class=\"v-label\">eBay App ID (Client ID)</label> <input class=\"v-input font-mono\" type=\"password\" name=\"ebay_client_id\" autocomplete=\"off\" placeholder=\"used for eBay marketplaces instead of Apify\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div><div class=\"border-t border-white/10 pt-4\"><label class=\"v-label\">eBay App ID (Client ID)</label>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -147,7 +147,7 @@ func settingsBody(d SettingsData) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div><div><label class=\"v-label\">eBay Cert ID (Client Secret)</label> <input class=\"v-input font-mono\" type=\"password\" name=\"ebay_client_secret\" autocomplete=\"off\" placeholder=\"leave blank to keep current value\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div><div><label class=\"v-label\">eBay Cert ID (Client Secret)</label>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -241,11 +241,11 @@ func settingsBody(d SettingsData) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\"></div><div><label class=\"v-label\">Ntfy Token</label> <input class=\"v-input font-mono\" type=\"password\" name=\"ntfy_token\" autocomplete=\"off\" placeholder=\"tk_... (leave blank to keep current value)\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\"></div><div><label class=\"v-label\">Ntfy Auth (user / password)</label>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = credStatus(d, "ntfy_token").Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = credStatus(d, "ntfy_auth").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -256,7 +256,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var9 string var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.Values["global_poll_interval_minutes"]) templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.Values["global_poll_interval_minutes"])
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 117, Col: 136} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 116, Col: 136}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -269,13 +269,13 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var10 string var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.Values["match_confidence_threshold"]) templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.Values["match_confidence_threshold"])
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 121, Col: 160} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 120, Col: 160}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\"></div><div class=\"border-t border-white/10 pt-4\"><label class=\"v-label\">Resend API Key</label> <input class=\"v-input font-mono\" type=\"password\" name=\"resend_api_key\" autocomplete=\"off\" placeholder=\"re_... (leave blank to keep current value)\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\"></div><div class=\"border-t border-white/10 pt-4\"><label class=\"v-label\">Resend API Key</label>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -290,7 +290,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var11 string var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.Values["resend_from"]) templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.Values["resend_from"])
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 130, Col: 78} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 128, Col: 78}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -303,7 +303,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var12 string var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.Values["apify_cost_per_call"]) templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.Values["apify_cost_per_call"])
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 135, Col: 139} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 133, Col: 139}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -316,7 +316,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var13 string var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.Values["monthly_budget_usd"]) templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.Values["monthly_budget_usd"])
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 140, Col: 133} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 138, Col: 133}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -349,7 +349,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var14 string var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(d.TestNtfyOK) templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(d.TestNtfyOK)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 156, Col: 44} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 154, Col: 44}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -368,7 +368,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var15 string var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(d.TestApifyOK) templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(d.TestApifyOK)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 159, Col: 45} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 157, Col: 45}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -387,7 +387,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var16 string var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(d.TestEbayOK) templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(d.TestEbayOK)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 162, Col: 44} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 160, Col: 44}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -406,7 +406,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var17 string var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(d.TestResendOK) templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(d.TestResendOK)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 165, Col: 46} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 163, Col: 46}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -424,7 +424,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var18 string var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", d.ApifyMonth)) templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", d.ApifyMonth))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 169, Col: 61} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 167, Col: 61}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -442,7 +442,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var19 string var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("(~$%.2f est.)", d.ApifyMonthCost())) templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("(~$%.2f est.)", d.ApifyMonthCost()))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 171, Col: 82} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 169, Col: 82}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -460,7 +460,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var20 string var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", d.ApifyToday)) templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", d.ApifyToday))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 174, Col: 61} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 172, Col: 61}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -478,7 +478,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var21 string var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(d.PasswordError) templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(d.PasswordError)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 181, Col: 48} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 179, Col: 48}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -497,7 +497,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var22 string var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(d.PasswordMsg) templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(d.PasswordMsg)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 184, Col: 40} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 182, Col: 40}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -528,7 +528,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var23 string var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(d.EmailError) templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(d.EmailError)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 207, Col: 45} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 205, Col: 45}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -547,7 +547,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var24 string var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(d.EmailMsg) templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(d.EmailMsg)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 210, Col: 37} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 208, Col: 37}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -578,7 +578,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var25 string var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.ResolveAttributeValue(currentEmail(d)) templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.ResolveAttributeValue(currentEmail(d))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 217, Col: 65} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 215, Col: 65}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var25) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var25)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -596,7 +596,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var26 string var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.ResolveAttributeValue(currentEmail(d)) templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.ResolveAttributeValue(currentEmail(d))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 220, Col: 78} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 218, Col: 78}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var26) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var26)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -644,7 +644,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var27 string var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(d.UserError) templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(d.UserError)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 240, Col: 45} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 238, Col: 45}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -663,7 +663,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var28 string var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(d.UserMsg) templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(d.UserMsg)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 243, Col: 37} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 241, Col: 37}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -686,7 +686,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var29 string var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username) templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 250, Col: 24} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 248, Col: 24}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -699,7 +699,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var30 string var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(string(u.Role)) templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(string(u.Role))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 251, Col: 44} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 249, Col: 44}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -712,7 +712,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var31 string var templ_7745c5c3_Var31 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(u.CreatedAt.Format("2006-01-02")) templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(u.CreatedAt.Format("2006-01-02"))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 252, Col: 70} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 250, Col: 70}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -725,7 +725,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var32 templ.SafeURL var templ_7745c5c3_Var32 templ.SafeURL
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(fmt.Sprintf("/users/%d/reset-password", u.ID))) templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(fmt.Sprintf("/users/%d/reset-password", u.ID)))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 254, Col: 113} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 252, Col: 113}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -738,7 +738,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var33 string var templ_7745c5c3_Var33 string
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.CSRFToken) templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.CSRFToken)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 255, Col: 68} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 253, Col: 68}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -751,7 +751,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var34 templ.SafeURL var templ_7745c5c3_Var34 templ.SafeURL
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(fmt.Sprintf("/users/%d/delete", u.ID))) templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(fmt.Sprintf("/users/%d/delete", u.ID)))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 259, Col: 105} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 257, Col: 105}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -764,7 +764,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var35 string var templ_7745c5c3_Var35 string
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.CSRFToken) templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.CSRFToken)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 260, Col: 68} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 258, Col: 68}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -782,7 +782,7 @@ func settingsBody(d SettingsData) templ.Component {
var templ_7745c5c3_Var36 string var templ_7745c5c3_Var36 string
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.CSRFToken) templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(d.CSRFToken)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 269, Col: 63} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/settings.templ`, Line: 267, Col: 63}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {