mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-13 23:47:24 +02:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package termout
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StartupWebUIOptions configures the startup Web UI banner.
|
||||
type StartupWebUIOptions struct {
|
||||
Scheme string
|
||||
Port int
|
||||
SelfSigned bool
|
||||
HTTPRedirect bool
|
||||
}
|
||||
|
||||
// PrintConfigCreated prints a short notice when config.yaml is bootstrapped.
|
||||
func PrintConfigCreated() {
|
||||
s := New(os.Stdout)
|
||||
s.Println("")
|
||||
s.Println(s.Green("✔ ") + s.Bold("已创建 config.yaml") + s.Dim("(来自 config.example.yaml)"))
|
||||
s.BlankLine()
|
||||
}
|
||||
|
||||
// PrintStartupWebUI prints a colored startup banner for the Web UI.
|
||||
func PrintStartupWebUI(opts StartupWebUIOptions) {
|
||||
s := New(os.Stdout)
|
||||
scheme := opts.Scheme
|
||||
if scheme == "" {
|
||||
scheme = "http"
|
||||
}
|
||||
port := opts.Port
|
||||
if port <= 0 {
|
||||
port = 8080
|
||||
}
|
||||
url := fmt.Sprintf("%s://127.0.0.1:%d/", scheme, port)
|
||||
|
||||
s.BlankLine()
|
||||
s.Println(s.Bold(s.Cyan("CYBERSTRIKE AI")) + s.Dim(" / secure workspace"))
|
||||
s.Println(s.Dim(strings.Repeat("─", 60)))
|
||||
s.Println(s.Green("● ONLINE") + " " + s.Bold(s.White(url)))
|
||||
if opts.SelfSigned {
|
||||
s.Println(s.Dim(" TLS ") + s.Yellow("self-signed") + s.Dim(" · accept the browser warning once"))
|
||||
}
|
||||
if opts.HTTPRedirect {
|
||||
s.Println(s.Dim(" Redirect ") + fmt.Sprintf("http://127.0.0.1:%d/ → HTTPS", port))
|
||||
}
|
||||
s.BlankLine()
|
||||
}
|
||||
|
||||
// PrintBootstrapAdminCredentials prints the initial admin password banner.
|
||||
func PrintBootstrapAdminCredentials(password string) {
|
||||
password = strings.TrimSpace(password)
|
||||
if password == "" {
|
||||
return
|
||||
}
|
||||
|
||||
s := New(os.Stdout)
|
||||
s.Println(s.Bold(s.Yellow("ADMIN SETUP REQUIRED")))
|
||||
s.Println(s.Dim(strings.Repeat("─", 60)))
|
||||
s.Println(s.Dim(" Username ") + s.Bold(s.White("admin")))
|
||||
s.Println(s.Dim(" Password ") + s.Bold(s.Yellow(password)))
|
||||
s.BlankLine()
|
||||
s.Println(s.Yellow(" ! ") + s.White("Store this password securely. It is shown only once."))
|
||||
s.Println(s.Dim(" Change it in Settings immediately after signing in."))
|
||||
s.BlankLine()
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package termout
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDisplayWidthEmoji(t *testing.T) {
|
||||
if got := displayWidth("🚀"); got != 2 {
|
||||
t.Fatalf("displayWidth(emoji) = %d, want 2", got)
|
||||
}
|
||||
if got := displayWidth("ab"); got != 2 {
|
||||
t.Fatalf("displayWidth(ab) = %d, want 2", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisplayWidthIgnoresANSI(t *testing.T) {
|
||||
s := New(nil)
|
||||
colored := s.Bold("admin")
|
||||
if got := displayWidth(colored); got != 5 {
|
||||
t.Fatalf("displayWidth colored = %d, want 5", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPadRightDisplay(t *testing.T) {
|
||||
got := padRightDisplay("pwd", 10)
|
||||
if displayWidth(got) != 10 {
|
||||
t.Fatalf("padded width = %d, want 10", displayWidth(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestColorDisabledWithoutTTY(t *testing.T) {
|
||||
s := New(nil)
|
||||
if s.enabled {
|
||||
t.Fatal("expected colors disabled for nil writer")
|
||||
}
|
||||
if got := s.Cyan("x"); got != "x" {
|
||||
t.Fatalf("Cyan without TTY = %q, want plain text", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintBootstrapAdminCredentialsEmpty(t *testing.T) {
|
||||
PrintBootstrapAdminCredentials(" ")
|
||||
}
|
||||
|
||||
func TestPrintStartupWebUIOptions(t *testing.T) {
|
||||
PrintStartupWebUI(StartupWebUIOptions{
|
||||
Scheme: "https",
|
||||
Port: 8080,
|
||||
SelfSigned: true,
|
||||
HTTPRedirect: true,
|
||||
})
|
||||
}
|
||||
|
||||
func TestBoxRowAlignedWidth(t *testing.T) {
|
||||
s := New(nil)
|
||||
rows := []string{
|
||||
s.Bold("CyberStrikeAI") + s.White(" is ready"),
|
||||
s.Dim("Web UI ") + s.Bold("https://127.0.0.1:8080/"),
|
||||
}
|
||||
inner := maxDisplayWidth(rows...)
|
||||
for _, row := range rows {
|
||||
line := s.boxRow(inner, row)
|
||||
if !strings.Contains(line, "│") {
|
||||
t.Fatalf("box row missing border: %q", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxDisplayWidth(t *testing.T) {
|
||||
short := "abc"
|
||||
long := "https://127.0.0.1:8080/"
|
||||
if got := maxDisplayWidth(short, long); got != displayWidth(long) {
|
||||
t.Fatalf("maxDisplayWidth = %d, want %d", got, displayWidth(long))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package termout
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
codeReset = "\033[0m"
|
||||
codeBold = "\033[1m"
|
||||
codeDim = "\033[2m"
|
||||
codeRed = "\033[31m"
|
||||
codeGreen = "\033[32m"
|
||||
codeYellow = "\033[33m"
|
||||
codeBlue = "\033[34m"
|
||||
codeCyan = "\033[36m"
|
||||
codeWhite = "\033[97m"
|
||||
)
|
||||
|
||||
// Style wraps ANSI styling with TTY / NO_COLOR awareness.
|
||||
type Style struct {
|
||||
out io.Writer
|
||||
enabled bool
|
||||
}
|
||||
|
||||
// New creates a Style writing to out (typically os.Stdout).
|
||||
func New(out io.Writer) *Style {
|
||||
return &Style{out: out, enabled: colorEnabled(out)}
|
||||
}
|
||||
|
||||
func colorEnabled(w io.Writer) bool {
|
||||
if strings.TrimSpace(os.Getenv("NO_COLOR")) != "" {
|
||||
return false
|
||||
}
|
||||
force := strings.TrimSpace(os.Getenv("FORCE_COLOR"))
|
||||
if force == "1" || strings.EqualFold(force, "true") || strings.EqualFold(force, "yes") {
|
||||
return true
|
||||
}
|
||||
f, ok := w.(*os.File)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return stat.Mode()&os.ModeCharDevice != 0
|
||||
}
|
||||
|
||||
func (s *Style) paint(code, text string) string {
|
||||
if !s.enabled || text == "" {
|
||||
return text
|
||||
}
|
||||
return code + text + codeReset
|
||||
}
|
||||
|
||||
func (s *Style) Bold(text string) string { return s.paint(codeBold, text) }
|
||||
func (s *Style) Dim(text string) string { return s.paint(codeDim, text) }
|
||||
func (s *Style) Red(text string) string { return s.paint(codeRed, text) }
|
||||
func (s *Style) Green(text string) string { return s.paint(codeGreen, text) }
|
||||
func (s *Style) Yellow(text string) string { return s.paint(codeYellow, text) }
|
||||
func (s *Style) Blue(text string) string { return s.paint(codeBlue, text) }
|
||||
func (s *Style) Cyan(text string) string { return s.paint(codeCyan, text) }
|
||||
func (s *Style) White(text string) string { return s.paint(codeWhite, text) }
|
||||
|
||||
func (s *Style) Println(text string) {
|
||||
_, _ = fmt.Fprintln(s.out, text)
|
||||
}
|
||||
|
||||
func (s *Style) Printf(format string, args ...interface{}) {
|
||||
_, _ = fmt.Fprintf(s.out, format, args...)
|
||||
}
|
||||
|
||||
func (s *Style) BlankLine() {
|
||||
s.Println("")
|
||||
}
|
||||
|
||||
func (s *Style) boxTop(innerWidth int) string {
|
||||
return s.Cyan("╭" + strings.Repeat("─", innerWidth+2) + "╮")
|
||||
}
|
||||
|
||||
func (s *Style) boxBottom(innerWidth int) string {
|
||||
return s.Cyan("╰" + strings.Repeat("─", innerWidth+2) + "╯")
|
||||
}
|
||||
|
||||
func (s *Style) boxRow(innerWidth int, content string) string {
|
||||
return s.Cyan("│ ") + padRightDisplay(content, innerWidth) + s.Cyan(" │")
|
||||
}
|
||||
|
||||
func (s *Style) printBox(rows []string, minInner, maxInner int) {
|
||||
inner := maxDisplayWidth(rows...)
|
||||
if inner < minInner {
|
||||
inner = minInner
|
||||
}
|
||||
if maxInner > 0 && inner > maxInner {
|
||||
inner = maxInner
|
||||
}
|
||||
|
||||
s.BlankLine()
|
||||
s.Println(s.boxTop(inner))
|
||||
for _, row := range rows {
|
||||
s.Println(s.boxRow(inner, row))
|
||||
}
|
||||
s.Println(s.boxBottom(inner))
|
||||
s.BlankLine()
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package termout
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/width"
|
||||
)
|
||||
|
||||
var ansiEscapeRe = regexp.MustCompile(`\x1b\[[0-9;]*m`)
|
||||
|
||||
// displayWidth returns the terminal display width of text, ignoring ANSI codes.
|
||||
func displayWidth(text string) int {
|
||||
plain := ansiEscapeRe.ReplaceAllString(text, "")
|
||||
w := 0
|
||||
for _, r := range plain {
|
||||
w += runeDisplayWidth(r)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func runeDisplayWidth(r rune) int {
|
||||
if r == utf8.RuneError {
|
||||
return 0
|
||||
}
|
||||
// Most emoji / symbols render as double-width in modern terminals.
|
||||
if isEmojiLikeRune(r) {
|
||||
return 2
|
||||
}
|
||||
switch width.LookupRune(r).Kind() {
|
||||
case width.EastAsianWide, width.EastAsianFullwidth:
|
||||
return 2
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func isEmojiLikeRune(r rune) bool {
|
||||
switch {
|
||||
case r >= 0x1F300 && r <= 0x1FAFF: // pictographs / emoji
|
||||
return true
|
||||
case r >= 0x2600 && r <= 0x27BF: // misc symbols
|
||||
return true
|
||||
case r >= 0x2300 && r <= 0x23FF: // misc technical (⌚ etc.)
|
||||
return true
|
||||
case r >= 0x2B50 && r <= 0x2B55:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func padRightDisplay(text string, target int) string {
|
||||
if target <= 0 {
|
||||
return ""
|
||||
}
|
||||
gap := target - displayWidth(text)
|
||||
if gap <= 0 {
|
||||
return text
|
||||
}
|
||||
return text + strings.Repeat(" ", gap)
|
||||
}
|
||||
|
||||
func maxDisplayWidth(rows ...string) int {
|
||||
max := 0
|
||||
for _, row := range rows {
|
||||
if w := displayWidth(row); w > max {
|
||||
max = w
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
Reference in New Issue
Block a user