Merge branch 'view-ui'

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-09 15:17:49 +00:00
commit ca0f148b78
29 files changed
+623 -55

No files matched your search

+3
View File
@@ -15,6 +15,7 @@ import (
"github.com/spf13/cobra"
"jarl/internal/api"
"jarl/internal/model"
"jarl/internal/rr"
)
@@ -71,6 +72,8 @@ func argCompletion(c *cobra.Command) cobra.CompletionFunc {
return lookup(cmd, "zone")
case p == "VIEW":
return lookup(cmd, "view")
case p == "COLOR":
return append([]string{"default"}, model.ViewColors...), cobra.ShellCompDirectiveNoFileComp
case p == "ACL":
return lookup(cmd, "acl")
case p == "USER":
+39 -3
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"io"
"net/url"
"slices"
"strings"
"github.com/spf13/cobra"
@@ -27,9 +28,9 @@ func viewCmd() *cobra.Command {
return err
}
return output(views, func(w io.Writer) {
fmt.Fprintln(w, "#\tID\tVIEW\tMATCH-CLIENTS\tMATCH-DESTINATIONS\tZONES")
fmt.Fprintln(w, "#\tID\tVIEW\tMATCH-CLIENTS\tMATCH-DESTINATIONS\tZONES\tCOLOR")
for i, v := range views {
fmt.Fprintf(w, "%d\t%d\t%s\t%s\t%s\t%d\n", i+1, v.ID, v.Name, orAny(v.MatchClients), orAny(v.MatchDestinations), v.Zones)
fmt.Fprintf(w, "%d\t%d\t%s\t%s\t%s\t%d\t%s\n", i+1, v.ID, v.Name, orAny(v.MatchClients), orAny(v.MatchDestinations), v.Zones, orDefault(v.Color))
}
})
},
@@ -172,10 +173,45 @@ func viewCmd() *cobra.Command {
return report(resp)
},
}
cmd.AddCommand(list, create, update, del, order)
color := &cobra.Command{
Use: "color VIEW COLOR",
Short: "Set the colour the web UI shows a view in",
Long: "Set the colour the web UI shows a view in, so zones of the same view are easy to spot.\n" +
"Colours: default (the link colour), " + strings.Join(model.ViewColors, ", ") + ".\n" +
"This only changes the web UI: it applies at once, with no changeset or deploy.",
Example: ` jarl view color internal green
jarl view color internal default`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newClient()
if err != nil {
return err
}
col := strings.ToLower(args[1])
if col == "default" {
col = ""
} else if !slices.Contains(model.ViewColors, col) {
return fmt.Errorf("unknown colour %q: use default, %s", args[1], strings.Join(model.ViewColors, ", "))
}
var v api.View
if err := c.Do(ctx(), "PUT", "/views/"+url.PathEscape(args[0])+"/color", nil, api.ViewColor{Color: col}, &v); err != nil {
return err
}
fmt.Printf("View %s: colour %s\n", v.Name, orDefault(v.Color))
return nil
},
}
cmd.AddCommand(list, create, update, del, order, color)
return cmd
}
func orDefault(s string) string {
if s == "" {
return "default"
}
return s
}
func orAny(list []string) string {
if len(list) == 0 {
return "any"
+1 -1
View File
@@ -225,7 +225,7 @@ Exit codes: 0 ok, 1 error, 3 not authenticated or not allowed, 4 not found,
5 conflict, 6 invalid or rejected by validation, 7 server or BIND error.`,
SilenceUsage: true,
SilenceErrors: true,
Version: version.Version,
Version: version.String(),
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return checkFanOut(cmd)
},
+1 -1
View File
@@ -383,7 +383,7 @@ func versionCmd() *cobra.Command {
Use: "version",
Short: "Print the version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("jarld", version.Version)
fmt.Println("jarld", version.String())
},
}
}
+3 -3
View File
@@ -51,7 +51,7 @@ func rootCmd() *cobra.Command {
Short: "Jarl: manage BIND (named) through a web UI, a REST API and a CLI",
SilenceUsage: true,
SilenceErrors: true,
Version: version.Version,
Version: version.String(),
}
def := os.Getenv("JARL_CONFIG")
if def == "" {
@@ -303,7 +303,7 @@ func serveCmd() *cobra.Command {
Listen: a.cfg.Listen, TLSCertFile: a.cfg.TLS.CertFile, TLSKeyFile: a.cfg.TLS.KeyFile,
TrustedProxies: a.cfg.Proxies(), SessionLifetime: time.Duration(a.cfg.Session.Lifetime),
SessionIdle: time.Duration(a.cfg.Session.IdleTimeout), CookieSecure: a.cfg.Session.CookieSecure,
Version: version.Version, Stats: collector, Socket: a.cfg.Socket,
Version: version.String(), Stats: collector, Socket: a.cfg.Socket,
BackupDir: a.cfg.Backup.Dir, BackupWarnAfter: time.Duration(a.cfg.Backup.WarnAfter),
Porkbun: pb, PorkbunAllows: a.cfg.Porkbun.Allows,
TOTPRequire: a.cfg.TOTP.Require, TOTPIssuer: a.cfg.TOTP.IssuerName(),
@@ -323,7 +323,7 @@ func serveCmd() *cobra.Command {
}
}()
go auditRetention(ctx, a)
a.log.Info("jarld starting", "version", version.Version, "database", a.cfg.Database, "bind_config", a.cfg.BIND.ConfigDir)
a.log.Info("jarld starting", "version", version.String(), "database", a.cfg.Database, "bind_config", a.cfg.BIND.ConfigDir)
err = srv.Serve(ctx, func() { sdNotify("READY=1\nSTATUS=serving") })
sdNotify("STOPPING=1")
a.log.Info("jarld stopped")