mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-28 13:50:25 +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