Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92402f0fdb | |||
| 682510d1bc | |||
| 83ad62b6b5 |
21
SYSTEMD.md
21
SYSTEMD.md
@@ -116,8 +116,9 @@ sudo chmod 755 /usr/local/bin/dbbackup
|
|||||||
### Step 2: Create Configuration
|
### Step 2: Create Configuration
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Main configuration
|
# Main configuration in working directory (where service runs from)
|
||||||
sudo tee /etc/dbbackup/dbbackup.conf << 'EOF'
|
# dbbackup reads .dbbackup.conf from WorkingDirectory
|
||||||
|
sudo tee /var/lib/dbbackup/.dbbackup.conf << 'EOF'
|
||||||
# DBBackup Configuration
|
# DBBackup Configuration
|
||||||
db-type=postgres
|
db-type=postgres
|
||||||
host=localhost
|
host=localhost
|
||||||
@@ -128,6 +129,8 @@ compression=6
|
|||||||
retention-days=30
|
retention-days=30
|
||||||
min-backups=7
|
min-backups=7
|
||||||
EOF
|
EOF
|
||||||
|
sudo chown dbbackup:dbbackup /var/lib/dbbackup/.dbbackup.conf
|
||||||
|
sudo chmod 600 /var/lib/dbbackup/.dbbackup.conf
|
||||||
|
|
||||||
# Instance credentials (secure permissions)
|
# Instance credentials (secure permissions)
|
||||||
sudo tee /etc/dbbackup/env.d/cluster.conf << 'EOF'
|
sudo tee /etc/dbbackup/env.d/cluster.conf << 'EOF'
|
||||||
@@ -157,13 +160,15 @@ Group=dbbackup
|
|||||||
# Load configuration
|
# Load configuration
|
||||||
EnvironmentFile=-/etc/dbbackup/env.d/cluster.conf
|
EnvironmentFile=-/etc/dbbackup/env.d/cluster.conf
|
||||||
|
|
||||||
# Working directory
|
# Working directory (config is loaded from .dbbackup.conf here)
|
||||||
WorkingDirectory=/var/lib/dbbackup
|
WorkingDirectory=/var/lib/dbbackup
|
||||||
|
|
||||||
# Execute backup
|
# Execute backup (reads .dbbackup.conf from WorkingDirectory)
|
||||||
ExecStart=/usr/local/bin/dbbackup backup cluster \
|
ExecStart=/usr/local/bin/dbbackup backup cluster \
|
||||||
--config /etc/dbbackup/dbbackup.conf \
|
|
||||||
--backup-dir /var/lib/dbbackup/backups \
|
--backup-dir /var/lib/dbbackup/backups \
|
||||||
|
--host localhost \
|
||||||
|
--port 5432 \
|
||||||
|
--user postgres \
|
||||||
--allow-root
|
--allow-root
|
||||||
|
|
||||||
# Security hardening
|
# Security hardening
|
||||||
@@ -443,12 +448,12 @@ sudo systemctl status dbbackup-cluster.service
|
|||||||
# View detailed error
|
# View detailed error
|
||||||
sudo journalctl -u dbbackup-cluster.service -n 50 --no-pager
|
sudo journalctl -u dbbackup-cluster.service -n 50 --no-pager
|
||||||
|
|
||||||
# Test manually as dbbackup user
|
# Test manually as dbbackup user (run from working directory with .dbbackup.conf)
|
||||||
sudo -u dbbackup /usr/local/bin/dbbackup backup cluster --config /etc/dbbackup/dbbackup.conf
|
cd /var/lib/dbbackup && sudo -u dbbackup /usr/local/bin/dbbackup backup cluster
|
||||||
|
|
||||||
# Check permissions
|
# Check permissions
|
||||||
ls -la /var/lib/dbbackup/
|
ls -la /var/lib/dbbackup/
|
||||||
ls -la /etc/dbbackup/
|
ls -la /var/lib/dbbackup/.dbbackup.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
### Permission Denied
|
### Permission Denied
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ This directory contains pre-compiled binaries for the DB Backup Tool across mult
|
|||||||
|
|
||||||
## Build Information
|
## Build Information
|
||||||
- **Version**: 3.42.10
|
- **Version**: 3.42.10
|
||||||
- **Build Time**: 2026-01-08_09:19:02_UTC
|
- **Build Time**: 2026-01-08_10:18:23_UTC
|
||||||
- **Git Commit**: 1831bd7
|
- **Git Commit**: 682510d
|
||||||
|
|
||||||
## Recent Updates (v1.1.0)
|
## Recent Updates (v1.1.0)
|
||||||
- ✅ Fixed TUI progress display with line-by-line output
|
- ✅ Fixed TUI progress display with line-by-line output
|
||||||
|
|||||||
@@ -33,8 +33,11 @@ RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
|||||||
# Environment
|
# Environment
|
||||||
EnvironmentFile=-/etc/dbbackup/env.d/cluster.conf
|
EnvironmentFile=-/etc/dbbackup/env.d/cluster.conf
|
||||||
|
|
||||||
|
# Working directory (config is loaded from .dbbackup.conf here)
|
||||||
|
WorkingDirectory=/var/lib/dbbackup
|
||||||
|
|
||||||
# Execution - cluster backup (all databases)
|
# Execution - cluster backup (all databases)
|
||||||
ExecStart={{.BinaryPath}} backup cluster --config {{.ConfigPath}}
|
ExecStart={{.BinaryPath}} backup cluster --backup-dir {{.BackupDir}}
|
||||||
TimeoutStartSec={{.TimeoutSeconds}}
|
TimeoutStartSec={{.TimeoutSeconds}}
|
||||||
|
|
||||||
# Post-backup metrics export
|
# Post-backup metrics export
|
||||||
|
|||||||
@@ -33,8 +33,11 @@ RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
|||||||
# Environment
|
# Environment
|
||||||
EnvironmentFile=-/etc/dbbackup/env.d/%i.conf
|
EnvironmentFile=-/etc/dbbackup/env.d/%i.conf
|
||||||
|
|
||||||
|
# Working directory (config is loaded from .dbbackup.conf here)
|
||||||
|
WorkingDirectory=/var/lib/dbbackup
|
||||||
|
|
||||||
# Execution
|
# Execution
|
||||||
ExecStart={{.BinaryPath}} backup {{.BackupType}} %i --config {{.ConfigPath}}
|
ExecStart={{.BinaryPath}} backup {{.BackupType}} %i --backup-dir {{.BackupDir}}
|
||||||
TimeoutStartSec={{.TimeoutSeconds}}
|
TimeoutStartSec={{.TimeoutSeconds}}
|
||||||
|
|
||||||
# Post-backup metrics export
|
# Post-backup metrics export
|
||||||
|
|||||||
@@ -130,15 +130,24 @@ func (m BackupManagerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
// Block input during operations
|
// Allow escape/cancel even during operations
|
||||||
|
if msg.String() == "ctrl+c" || msg.String() == "esc" || msg.String() == "q" {
|
||||||
|
if m.opState != OpIdle {
|
||||||
|
// Cancel current operation
|
||||||
|
m.opState = OpIdle
|
||||||
|
m.opTarget = ""
|
||||||
|
m.message = "Operation cancelled"
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return m.parent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block other input during operations
|
||||||
if m.opState != OpIdle {
|
if m.opState != OpIdle {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
case "ctrl+c", "q", "esc":
|
|
||||||
return m.parent, nil
|
|
||||||
|
|
||||||
case "up", "k":
|
case "up", "k":
|
||||||
if m.cursor > 0 {
|
if m.cursor > 0 {
|
||||||
m.cursor--
|
m.cursor--
|
||||||
@@ -221,64 +230,64 @@ func (m BackupManagerModel) View() string {
|
|||||||
var s strings.Builder
|
var s strings.Builder
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
s.WriteString(titleStyle.Render("[DB] Backup Archive Manager"))
|
s.WriteString(TitleStyle.Render("[DB] Backup Archive Manager"))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
|
|
||||||
// Operation Status Box (always visible)
|
// Status line (no box, bold+color accents)
|
||||||
s.WriteString("+--[ STATUS ]" + strings.Repeat("-", 47) + "+\n")
|
|
||||||
switch m.opState {
|
switch m.opState {
|
||||||
case OpVerifying:
|
case OpVerifying:
|
||||||
spinner := spinnerFrames[m.spinnerFrame]
|
spinner := spinnerFrames[m.spinnerFrame]
|
||||||
statusText := fmt.Sprintf(" %s Verifying: %s", spinner, m.opTarget)
|
s.WriteString(StatusActiveStyle.Render(fmt.Sprintf("%s Verifying: %s", spinner, m.opTarget)))
|
||||||
s.WriteString("|" + statusText + strings.Repeat(" ", 59-len(statusText)) + "|\n")
|
s.WriteString("\n\n")
|
||||||
case OpDeleting:
|
case OpDeleting:
|
||||||
spinner := spinnerFrames[m.spinnerFrame]
|
spinner := spinnerFrames[m.spinnerFrame]
|
||||||
statusText := fmt.Sprintf(" %s Deleting: %s", spinner, m.opTarget)
|
s.WriteString(StatusActiveStyle.Render(fmt.Sprintf("%s Deleting: %s", spinner, m.opTarget)))
|
||||||
s.WriteString("|" + statusText + strings.Repeat(" ", 59-len(statusText)) + "|\n")
|
s.WriteString("\n\n")
|
||||||
default:
|
default:
|
||||||
if m.loading {
|
if m.loading {
|
||||||
spinner := spinnerFrames[m.spinnerFrame]
|
spinner := spinnerFrames[m.spinnerFrame]
|
||||||
statusText := fmt.Sprintf(" %s Loading archives...", spinner)
|
s.WriteString(StatusActiveStyle.Render(fmt.Sprintf("%s Loading archives...", spinner)))
|
||||||
s.WriteString("|" + statusText + strings.Repeat(" ", 59-len(statusText)) + "|\n")
|
s.WriteString("\n\n")
|
||||||
} else if m.message != "" {
|
} else if m.message != "" {
|
||||||
msgText := " " + m.message
|
// Color based on message content
|
||||||
if len(msgText) > 58 {
|
if strings.HasPrefix(m.message, "[+]") || strings.HasPrefix(m.message, "Valid") {
|
||||||
msgText = msgText[:55] + "..."
|
s.WriteString(StatusSuccessStyle.Render(m.message))
|
||||||
}
|
} else if strings.HasPrefix(m.message, "[-]") || strings.HasPrefix(m.message, "Error") {
|
||||||
s.WriteString("|" + msgText + strings.Repeat(" ", 59-len(msgText)) + "|\n")
|
s.WriteString(StatusErrorStyle.Render(m.message))
|
||||||
} else {
|
} else {
|
||||||
statusText := " Ready"
|
s.WriteString(StatusActiveStyle.Render(m.message))
|
||||||
s.WriteString("|" + statusText + strings.Repeat(" ", 59-len(statusText)) + "|\n")
|
|
||||||
}
|
}
|
||||||
|
s.WriteString("\n\n")
|
||||||
|
}
|
||||||
|
// No "Ready" message when idle - cleaner UI
|
||||||
}
|
}
|
||||||
s.WriteString("+" + strings.Repeat("-", 60) + "+\n\n")
|
|
||||||
|
|
||||||
if m.loading {
|
if m.loading {
|
||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.err != nil {
|
if m.err != nil {
|
||||||
s.WriteString(errorStyle.Render(fmt.Sprintf("[FAIL] Error: %v", m.err)))
|
s.WriteString(StatusErrorStyle.Render(fmt.Sprintf("[FAIL] Error: %v", m.err)))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
s.WriteString(infoStyle.Render("Press Esc to go back"))
|
s.WriteString(ShortcutStyle.Render("Press Esc to go back"))
|
||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
s.WriteString(infoStyle.Render(fmt.Sprintf("Total Archives: %d | Total Size: %s",
|
s.WriteString(LabelStyle.Render(fmt.Sprintf("Total Archives: %d | Total Size: %s",
|
||||||
len(m.archives), formatSize(m.totalSize))))
|
len(m.archives), formatSize(m.totalSize))))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
|
|
||||||
// Archives list
|
// Archives list
|
||||||
if len(m.archives) == 0 {
|
if len(m.archives) == 0 {
|
||||||
s.WriteString(infoStyle.Render("No backup archives found"))
|
s.WriteString(StatusReadyStyle.Render("No backup archives found"))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
s.WriteString(infoStyle.Render("Press Esc to go back"))
|
s.WriteString(ShortcutStyle.Render("Press Esc to go back"))
|
||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Column headers with better alignment
|
// Column headers with better alignment
|
||||||
s.WriteString(archiveHeaderStyle.Render(fmt.Sprintf(" %-32s %-22s %10s %-16s",
|
s.WriteString(ListHeaderStyle.Render(fmt.Sprintf(" %-32s %-22s %10s %-16s",
|
||||||
"FILENAME", "FORMAT", "SIZE", "MODIFIED")))
|
"FILENAME", "FORMAT", "SIZE", "MODIFIED")))
|
||||||
s.WriteString("\n")
|
s.WriteString("\n")
|
||||||
s.WriteString(strings.Repeat("-", 90))
|
s.WriteString(strings.Repeat("-", 90))
|
||||||
@@ -297,18 +306,18 @@ func (m BackupManagerModel) View() string {
|
|||||||
for i := start; i < end; i++ {
|
for i := start; i < end; i++ {
|
||||||
archive := m.archives[i]
|
archive := m.archives[i]
|
||||||
cursor := " "
|
cursor := " "
|
||||||
style := archiveNormalStyle
|
style := ListNormalStyle
|
||||||
|
|
||||||
if i == m.cursor {
|
if i == m.cursor {
|
||||||
cursor = "> "
|
cursor = "> "
|
||||||
style = archiveSelectedStyle
|
style = ListSelectedStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status icon - consistent 4-char width
|
// Status icon - consistent 4-char width
|
||||||
statusIcon := " [+]"
|
statusIcon := " [+]"
|
||||||
if !archive.Valid {
|
if !archive.Valid {
|
||||||
statusIcon = " [-]"
|
statusIcon = " [-]"
|
||||||
style = archiveInvalidStyle
|
style = ItemInvalidStyle
|
||||||
} else if time.Since(archive.Modified) > 30*24*time.Hour {
|
} else if time.Since(archive.Modified) > 30*24*time.Hour {
|
||||||
statusIcon = " [!]"
|
statusIcon = " [!]"
|
||||||
}
|
}
|
||||||
@@ -331,17 +340,11 @@ func (m BackupManagerModel) View() string {
|
|||||||
// Footer
|
// Footer
|
||||||
s.WriteString("\n")
|
s.WriteString("\n")
|
||||||
|
|
||||||
s.WriteString(infoStyle.Render(fmt.Sprintf("Selected: %d/%d", m.cursor+1, len(m.archives))))
|
s.WriteString(StatusReadyStyle.Render(fmt.Sprintf("Selected: %d/%d", m.cursor+1, len(m.archives))))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
|
|
||||||
// Grouped keyboard shortcuts for better readability
|
// Grouped keyboard shortcuts
|
||||||
s.WriteString("+--[ SHORTCUTS ]" + strings.Repeat("-", 44) + "+\n")
|
s.WriteString(ShortcutStyle.Render("SHORTCUTS: Up/Down=Move | r=Restore | v=Verify | d=Delete | i=Info | R=Refresh | Esc=Back | q=Quit"))
|
||||||
s.WriteString("| NAVIGATE ACTIONS OTHER |\n")
|
|
||||||
s.WriteString("| Up/Down: Move r: Restore R: Refresh |\n")
|
|
||||||
s.WriteString("| v: Verify Esc: Back |\n")
|
|
||||||
s.WriteString("| d: Delete q: Quit |\n")
|
|
||||||
s.WriteString("| i: Info |\n")
|
|
||||||
s.WriteString("+" + strings.Repeat("-", 60) + "+")
|
|
||||||
|
|
||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,13 +146,12 @@ func (m StatusViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
if !m.loading {
|
// Always allow escape, even during loading
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
case "ctrl+c", "q", "esc", "enter":
|
case "ctrl+c", "q", "esc", "enter":
|
||||||
return m.parent, nil
|
return m.parent, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|||||||
133
internal/tui/styles.go
Normal file
133
internal/tui/styles.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package tui
|
||||||
|
|
||||||
|
import "github.com/charmbracelet/lipgloss"
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// GLOBAL TUI STYLE DEFINITIONS
|
||||||
|
// =============================================================================
|
||||||
|
// Design Language:
|
||||||
|
// - Bold text for labels and headers
|
||||||
|
// - Colors for semantic meaning (green=success, red=error, yellow=warning)
|
||||||
|
// - No emoticons - use simple text prefixes like [OK], [FAIL], [!]
|
||||||
|
// - No boxes for inline status - use bold+color accents
|
||||||
|
// - Consistent color palette across all views
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// Color Palette (ANSI 256 colors for terminal compatibility)
|
||||||
|
const (
|
||||||
|
ColorWhite = lipgloss.Color("15") // Bright white
|
||||||
|
ColorGray = lipgloss.Color("250") // Light gray
|
||||||
|
ColorDim = lipgloss.Color("244") // Dim gray
|
||||||
|
ColorDimmer = lipgloss.Color("240") // Darker gray
|
||||||
|
ColorSuccess = lipgloss.Color("2") // Green
|
||||||
|
ColorError = lipgloss.Color("1") // Red
|
||||||
|
ColorWarning = lipgloss.Color("3") // Yellow
|
||||||
|
ColorInfo = lipgloss.Color("6") // Cyan
|
||||||
|
ColorAccent = lipgloss.Color("4") // Blue
|
||||||
|
)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// TITLE & HEADER STYLES
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// TitleStyle - main view title (bold white on gray background)
|
||||||
|
var TitleStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorWhite).
|
||||||
|
Background(ColorDimmer).
|
||||||
|
Padding(0, 1)
|
||||||
|
|
||||||
|
// HeaderStyle - section headers (bold gray)
|
||||||
|
var HeaderStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorDim)
|
||||||
|
|
||||||
|
// LabelStyle - field labels (bold cyan)
|
||||||
|
var LabelStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorInfo)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// STATUS STYLES
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// StatusReadyStyle - idle/ready state (dim)
|
||||||
|
var StatusReadyStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorDim)
|
||||||
|
|
||||||
|
// StatusActiveStyle - operation in progress (bold cyan)
|
||||||
|
var StatusActiveStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorInfo)
|
||||||
|
|
||||||
|
// StatusSuccessStyle - success messages (bold green)
|
||||||
|
var StatusSuccessStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorSuccess)
|
||||||
|
|
||||||
|
// StatusErrorStyle - error messages (bold red)
|
||||||
|
var StatusErrorStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorError)
|
||||||
|
|
||||||
|
// StatusWarningStyle - warning messages (bold yellow)
|
||||||
|
var StatusWarningStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorWarning)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// LIST & TABLE STYLES
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// ListNormalStyle - unselected list items
|
||||||
|
var ListNormalStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorGray)
|
||||||
|
|
||||||
|
// ListSelectedStyle - selected/cursor item (bold white)
|
||||||
|
var ListSelectedStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorWhite).
|
||||||
|
Bold(true)
|
||||||
|
|
||||||
|
// ListHeaderStyle - column headers (bold dim)
|
||||||
|
var ListHeaderStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorDim)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// ITEM STATUS STYLES
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// ItemValidStyle - valid/OK items (green)
|
||||||
|
var ItemValidStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorSuccess)
|
||||||
|
|
||||||
|
// ItemInvalidStyle - invalid/failed items (red)
|
||||||
|
var ItemInvalidStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorError)
|
||||||
|
|
||||||
|
// ItemOldStyle - old/stale items (yellow)
|
||||||
|
var ItemOldStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorWarning)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SHORTCUT STYLE
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// ShortcutStyle - keyboard shortcuts footer (dim)
|
||||||
|
var ShortcutStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorDim)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// HELPER PREFIXES (no emoticons)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
const (
|
||||||
|
PrefixOK = "[OK]"
|
||||||
|
PrefixFail = "[FAIL]"
|
||||||
|
PrefixWarn = "[!]"
|
||||||
|
PrefixInfo = "[i]"
|
||||||
|
PrefixPlus = "[+]"
|
||||||
|
PrefixMinus = "[-]"
|
||||||
|
PrefixArrow = ">"
|
||||||
|
PrefixSpinner = "" // Spinner character added dynamically
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user