feat(cli): proxdeploy in Go
Befehle login, servers, distros, profiles, presets, vm, lxc, template list|build, batch, status, logs --follow, cancel. Renderer im Aufbau der Vorgabe (zeichengenau per Golden-File), 24-bit/256/ohne Farbe, --ascii, --quiet, --json, --dry-run; live im Terminal, zeilenweise ohne. SSE mit Wiederverbinden und Polling als Rückfall. Typen aus openapi.json per oapi-codegen; Release-Builds per cli/bauen.sh. Refs #013 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
// Package ausgabe rendert die CLI-Ausgabe im Aufbau der Vorgabe: Kopfzeile, Phasen, Abschlussbox.
|
||||
// Alle Texte kommen aus der Engine; hier wird nur gesetzt und gefärbt.
|
||||
package ausgabe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Farbmodus: 24-bit, 256 Farben oder keine.
|
||||
type Farbmodus int
|
||||
|
||||
const (
|
||||
Aus Farbmodus = iota
|
||||
Farben256
|
||||
Echtfarben
|
||||
)
|
||||
|
||||
// Die Markenfarben (siehe README, Abschnitt Branding).
|
||||
type rgb struct{ r, g, b uint8 }
|
||||
|
||||
var (
|
||||
paper = rgb{0xE6, 0xED, 0xF3}
|
||||
muted = rgb{0x8B, 0x98, 0xA5}
|
||||
teal = rgb{0x2D, 0xD4, 0xBF}
|
||||
amber = rgb{0xF5, 0xB8, 0x41}
|
||||
rot = rgb{0xF8, 0x71, 0x71}
|
||||
)
|
||||
|
||||
// Stil legt fest, wie gesetzt wird: Farben und ob nur ASCII-Symbole erlaubt sind.
|
||||
type Stil struct {
|
||||
Farbe Farbmodus
|
||||
ASCII bool
|
||||
}
|
||||
|
||||
// FarbmodusErmitteln: aus bei NO_COLOR, --no-color oder ohne Terminal; sonst 24-bit, wenn das
|
||||
// Terminal es meldet (COLORTERM), sonst 256 Farben.
|
||||
func FarbmodusErmitteln(keineFarbe, istTerminal bool) Farbmodus {
|
||||
if keineFarbe || !istTerminal {
|
||||
return Aus
|
||||
}
|
||||
if _, gesetzt := os.LookupEnv("NO_COLOR"); gesetzt {
|
||||
return Aus
|
||||
}
|
||||
if os.Getenv("TERM") == "dumb" {
|
||||
return Aus
|
||||
}
|
||||
switch strings.ToLower(os.Getenv("COLORTERM")) {
|
||||
case "truecolor", "24bit":
|
||||
return Echtfarben
|
||||
}
|
||||
return Farben256
|
||||
}
|
||||
|
||||
func (s Stil) faerben(f rgb, fett bool, text string) string {
|
||||
if s.Farbe == Aus || text == "" {
|
||||
return text
|
||||
}
|
||||
var code string
|
||||
switch s.Farbe {
|
||||
case Echtfarben:
|
||||
code = fmt.Sprintf("38;2;%d;%d;%d", f.r, f.g, f.b)
|
||||
default:
|
||||
code = fmt.Sprintf("38;5;%d", xterm256(f))
|
||||
}
|
||||
if fett {
|
||||
code = "1;" + code
|
||||
}
|
||||
return "\x1b[" + code + "m" + text + "\x1b[0m"
|
||||
}
|
||||
|
||||
func (s Stil) Paper(t string) string { return s.faerben(paper, false, t) }
|
||||
func (s Stil) Fett(t string) string { return s.faerben(paper, true, t) }
|
||||
func (s Stil) Muted(t string) string { return s.faerben(muted, false, t) }
|
||||
func (s Stil) Teal(t string) string { return s.faerben(teal, false, t) }
|
||||
func (s Stil) TealFett(t string) string { return s.faerben(teal, true, t) }
|
||||
func (s Stil) Amber(t string) string { return s.faerben(amber, false, t) }
|
||||
func (s Stil) AmberFett(t string) string {
|
||||
return s.faerben(amber, true, t)
|
||||
}
|
||||
func (s Stil) Rot(t string) string { return s.faerben(rot, false, t) }
|
||||
|
||||
// xterm256 wählt die nächste Farbe aus Würfel (16–231) oder Graustufen (232–255).
|
||||
func xterm256(f rgb) int {
|
||||
stufen := []int{0, 95, 135, 175, 215, 255}
|
||||
naechste := func(wert uint8) int {
|
||||
beste, abstand := 0, 1<<30
|
||||
for i, s := range stufen {
|
||||
d := int(wert) - s
|
||||
if d*d < abstand {
|
||||
beste, abstand = i, d*d
|
||||
}
|
||||
}
|
||||
return beste
|
||||
}
|
||||
r, g, b := naechste(f.r), naechste(f.g), naechste(f.b)
|
||||
wuerfel := 16 + 36*r + 6*g + b
|
||||
dw := quadAbstand(f, stufen[r], stufen[g], stufen[b])
|
||||
|
||||
grau := (int(f.r) + int(f.g) + int(f.b)) / 3
|
||||
gi := (grau - 8) / 10
|
||||
if gi < 0 {
|
||||
gi = 0
|
||||
} else if gi > 23 {
|
||||
gi = 23
|
||||
}
|
||||
gw := 8 + gi*10
|
||||
if quadAbstand(f, gw, gw, gw) < dw {
|
||||
return 232 + gi
|
||||
}
|
||||
return wuerfel
|
||||
}
|
||||
|
||||
func quadAbstand(f rgb, r, g, b int) int {
|
||||
dr, dg, db := int(f.r)-r, int(f.g)-g, int(f.b)-b
|
||||
return dr*dr + dg*dg + db*db
|
||||
}
|
||||
|
||||
// Symbole je Status, mit ASCII-Ersatz (--ascii).
|
||||
func (s Stil) symbol(status string) string {
|
||||
if s.ASCII {
|
||||
switch status {
|
||||
case "ERLEDIGT":
|
||||
return "[OK]"
|
||||
case "LAEUFT":
|
||||
return "[..]"
|
||||
case "FEHLER":
|
||||
return "[!!]"
|
||||
default:
|
||||
return "[--]"
|
||||
}
|
||||
}
|
||||
switch status {
|
||||
case "ERLEDIGT":
|
||||
return "✓"
|
||||
case "LAEUFT":
|
||||
return "◐"
|
||||
case "FEHLER":
|
||||
return "✗"
|
||||
default:
|
||||
return "○"
|
||||
}
|
||||
}
|
||||
|
||||
// Spinner für die laufende Phase: ◐◓◑◒.
|
||||
var spinner = []string{"◐", "◓", "◑", "◒"}
|
||||
Reference in New Issue
Block a user