mirror of
https://github.com/tw93/Mole.git
synced 2026-02-12 19:50:15 +00:00
feat: enhance status UI with new styles and icons, refactor battery metrics with caching, and centralize Apple Silicon clean logic.
This commit is contained in:
@@ -138,10 +138,18 @@ type BluetoothDevice struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Collector struct {
|
type Collector struct {
|
||||||
prevNet map[string]net.IOCountersStat
|
// Static Cache (Collected once at startup)
|
||||||
lastNetAt time.Time
|
cachedHW HardwareInfo
|
||||||
|
lastHWAt time.Time
|
||||||
|
hasStatic bool
|
||||||
|
|
||||||
|
// Slow Cache (Collected every 30s-1m)
|
||||||
lastBTAt time.Time
|
lastBTAt time.Time
|
||||||
lastBT []BluetoothDevice
|
lastBT []BluetoothDevice
|
||||||
|
|
||||||
|
// Fast Metrics (Collected every 1 second)
|
||||||
|
prevNet map[string]net.IOCountersStat
|
||||||
|
lastNetAt time.Time
|
||||||
lastGPUAt time.Time
|
lastGPUAt time.Time
|
||||||
cachedGPU []GPUStatus
|
cachedGPU []GPUStatus
|
||||||
prevDiskIO disk.IOCountersStat
|
prevDiskIO disk.IOCountersStat
|
||||||
@@ -209,14 +217,32 @@ func (c *Collector) Collect() (MetricsSnapshot, error) {
|
|||||||
collect(func() (err error) { thermalStats = collectThermal(); return nil })
|
collect(func() (err error) { thermalStats = collectThermal(); return nil })
|
||||||
collect(func() (err error) { sensorStats, _ = collectSensors(); return nil })
|
collect(func() (err error) { sensorStats, _ = collectSensors(); return nil })
|
||||||
collect(func() (err error) { gpuStats, err = c.collectGPU(now); return })
|
collect(func() (err error) { gpuStats, err = c.collectGPU(now); return })
|
||||||
collect(func() (err error) { btStats = c.collectBluetooth(now); return nil })
|
collect(func() (err error) {
|
||||||
|
// Bluetooth is slow, cache for 30s
|
||||||
|
if now.Sub(c.lastBTAt) > 30*time.Second || len(c.lastBT) == 0 {
|
||||||
|
btStats = c.collectBluetooth(now)
|
||||||
|
c.lastBT = btStats
|
||||||
|
c.lastBTAt = now
|
||||||
|
} else {
|
||||||
|
btStats = c.lastBT
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
collect(func() (err error) { topProcs = collectTopProcesses(); return nil })
|
collect(func() (err error) { topProcs = collectTopProcesses(); return nil })
|
||||||
|
|
||||||
// Wait for all to complete
|
// Wait for all to complete
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
// Dependent tasks (must run after others)
|
// Dependent tasks (must run after others)
|
||||||
hwInfo := collectHardware(memStats.Total, diskStats)
|
// Dependent tasks (must run after others)
|
||||||
|
// Cache hardware info as it's expensive and rarely changes
|
||||||
|
if !c.hasStatic || now.Sub(c.lastHWAt) > 10*time.Minute {
|
||||||
|
c.cachedHW = collectHardware(memStats.Total, diskStats)
|
||||||
|
c.lastHWAt = now
|
||||||
|
c.hasStatic = true
|
||||||
|
}
|
||||||
|
hwInfo := c.cachedHW
|
||||||
|
|
||||||
score, scoreMsg := calculateHealthScore(cpuStats, memStats, diskStats, diskIO, thermalStats)
|
score, scoreMsg := calculateHealthScore(cpuStats, memStats, diskStats, diskIO, thermalStats)
|
||||||
|
|
||||||
return MetricsSnapshot{
|
return MetricsSnapshot{
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ import (
|
|||||||
"github.com/shirou/gopsutil/v3/host"
|
"github.com/shirou/gopsutil/v3/host"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Package-level cache for heavy system_profiler data
|
||||||
|
lastPowerAt time.Time
|
||||||
|
cachedPower string
|
||||||
|
powerCacheTTL = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
func collectBatteries() (batts []BatteryStatus, err error) {
|
func collectBatteries() (batts []BatteryStatus, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@@ -22,10 +29,12 @@ func collectBatteries() (batts []BatteryStatus, err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// macOS: pmset
|
// macOS: pmset (fast, for real-time percentage/status)
|
||||||
if runtime.GOOS == "darwin" && commandExists("pmset") {
|
if runtime.GOOS == "darwin" && commandExists("pmset") {
|
||||||
if out, err := runCmd(context.Background(), "pmset", "-g", "batt"); err == nil {
|
if out, err := runCmd(context.Background(), "pmset", "-g", "batt"); err == nil {
|
||||||
if batts := parsePMSet(out); len(batts) > 0 {
|
// Get heavy info (health, cycles) from cached system_profiler
|
||||||
|
health, cycles := getCachedPowerData()
|
||||||
|
if batts := parsePMSet(out, health, cycles); len(batts) > 0 {
|
||||||
return batts, nil
|
return batts, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +67,7 @@ func collectBatteries() (batts []BatteryStatus, err error) {
|
|||||||
return nil, errors.New("no battery data found")
|
return nil, errors.New("no battery data found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePMSet(raw string) []BatteryStatus {
|
func parsePMSet(raw string, health string, cycles int) []BatteryStatus {
|
||||||
lines := strings.Split(raw, "\n")
|
lines := strings.Split(raw, "\n")
|
||||||
var out []BatteryStatus
|
var out []BatteryStatus
|
||||||
var timeLeft string
|
var timeLeft string
|
||||||
@@ -101,9 +110,6 @@ func parsePMSet(raw string) []BatteryStatus {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get battery health and cycle count
|
|
||||||
health, cycles := getBatteryHealth()
|
|
||||||
|
|
||||||
out = append(out, BatteryStatus{
|
out = append(out, BatteryStatus{
|
||||||
Percent: percent,
|
Percent: percent,
|
||||||
Status: status,
|
Status: status,
|
||||||
@@ -115,20 +121,12 @@ func parsePMSet(raw string) []BatteryStatus {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBatteryHealth() (string, int) {
|
// getCachedPowerData returns condition, cycles, and fan speed from cached system_profiler output.
|
||||||
if runtime.GOOS != "darwin" {
|
func getCachedPowerData() (health string, cycles int) {
|
||||||
|
out := getSystemPowerOutput()
|
||||||
|
if out == "" {
|
||||||
return "", 0
|
return "", 0
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
out, err := runCmd(ctx, "system_profiler", "SPPowerDataType")
|
|
||||||
if err != nil {
|
|
||||||
return "", 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var health string
|
|
||||||
var cycles int
|
|
||||||
|
|
||||||
lines := strings.Split(out, "\n")
|
lines := strings.Split(out, "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
@@ -149,6 +147,27 @@ func getBatteryHealth() (string, int) {
|
|||||||
return health, cycles
|
return health, cycles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSystemPowerOutput() string {
|
||||||
|
if runtime.GOOS != "darwin" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if cachedPower != "" && now.Sub(lastPowerAt) < powerCacheTTL {
|
||||||
|
return cachedPower
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
out, err := runCmd(ctx, "system_profiler", "SPPowerDataType")
|
||||||
|
if err == nil {
|
||||||
|
cachedPower = out
|
||||||
|
lastPowerAt = now
|
||||||
|
}
|
||||||
|
return cachedPower
|
||||||
|
}
|
||||||
|
|
||||||
func collectThermal() ThermalStatus {
|
func collectThermal() ThermalStatus {
|
||||||
if runtime.GOOS != "darwin" {
|
if runtime.GOOS != "darwin" {
|
||||||
return ThermalStatus{}
|
return ThermalStatus{}
|
||||||
@@ -156,12 +175,9 @@ func collectThermal() ThermalStatus {
|
|||||||
|
|
||||||
var thermal ThermalStatus
|
var thermal ThermalStatus
|
||||||
|
|
||||||
// Get fan info from system_profiler
|
// Get fan info from cached system_profiler
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
out := getSystemPowerOutput()
|
||||||
defer cancel()
|
if out != "" {
|
||||||
|
|
||||||
out, err := runCmd(ctx, "system_profiler", "SPPowerDataType")
|
|
||||||
if err == nil {
|
|
||||||
lines := strings.Split(out, "\n")
|
lines := strings.Split(out, "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
lower := strings.ToLower(line)
|
lower := strings.ToLower(line)
|
||||||
|
|||||||
@@ -84,6 +84,13 @@ func isZeroLoad(avg load.AvgStat) bool {
|
|||||||
return avg.Load1 == 0 && avg.Load5 == 0 && avg.Load15 == 0
|
return avg.Load1 == 0 && avg.Load5 == 0 && avg.Load15 == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Package-level cache for core topology
|
||||||
|
lastTopologyAt time.Time
|
||||||
|
cachedP, cachedE int
|
||||||
|
topologyTTL = 10 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
// getCoreTopology returns P-core and E-core counts on Apple Silicon.
|
// getCoreTopology returns P-core and E-core counts on Apple Silicon.
|
||||||
// Returns (0, 0) on non-Apple Silicon or if detection fails.
|
// Returns (0, 0) on non-Apple Silicon or if detection fails.
|
||||||
func getCoreTopology() (pCores, eCores int) {
|
func getCoreTopology() (pCores, eCores int) {
|
||||||
@@ -91,6 +98,13 @@ func getCoreTopology() (pCores, eCores int) {
|
|||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if cachedP > 0 || cachedE > 0 {
|
||||||
|
if now.Sub(lastTopologyAt) < topologyTTL {
|
||||||
|
return cachedP, cachedE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -130,6 +144,8 @@ func getCoreTopology() (pCores, eCores int) {
|
|||||||
eCores = level1Count
|
eCores = level1Count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cachedP, cachedE = pCores, eCores
|
||||||
|
lastTopologyAt = now
|
||||||
return pCores, eCores
|
return pCores, eCores
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,26 +93,42 @@ func collectDisks() ([]DiskStatus, error) {
|
|||||||
return disks, nil
|
return disks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Package-level cache for external disk status
|
||||||
|
lastDiskCacheAt time.Time
|
||||||
|
diskTypeCache = make(map[string]bool)
|
||||||
|
diskCacheTTL = 2 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
func annotateDiskTypes(disks []DiskStatus) {
|
func annotateDiskTypes(disks []DiskStatus) {
|
||||||
if len(disks) == 0 || runtime.GOOS != "darwin" || !commandExists("diskutil") {
|
if len(disks) == 0 || runtime.GOOS != "darwin" || !commandExists("diskutil") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cache := make(map[string]bool)
|
|
||||||
|
now := time.Now()
|
||||||
|
// Clear cache if stale
|
||||||
|
if now.Sub(lastDiskCacheAt) > diskCacheTTL {
|
||||||
|
diskTypeCache = make(map[string]bool)
|
||||||
|
lastDiskCacheAt = now
|
||||||
|
}
|
||||||
|
|
||||||
for i := range disks {
|
for i := range disks {
|
||||||
base := baseDeviceName(disks[i].Device)
|
base := baseDeviceName(disks[i].Device)
|
||||||
if base == "" {
|
if base == "" {
|
||||||
base = disks[i].Device
|
base = disks[i].Device
|
||||||
}
|
}
|
||||||
if val, ok := cache[base]; ok {
|
|
||||||
|
if val, ok := diskTypeCache[base]; ok {
|
||||||
disks[i].External = val
|
disks[i].External = val
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
external, err := isExternalDisk(base)
|
external, err := isExternalDisk(base)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
external = strings.HasPrefix(disks[i].Mount, "/Volumes/")
|
external = strings.HasPrefix(disks[i].Mount, "/Volumes/")
|
||||||
}
|
}
|
||||||
disks[i].External = external
|
disks[i].External = external
|
||||||
cache[base] = external
|
diskTypeCache[base] = external
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,24 +12,25 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
titleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#C79FD7")).Bold(true)
|
titleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#C79FD7")).Bold(true)
|
||||||
subtleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#9E9E9E"))
|
subtleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#737373"))
|
||||||
warnStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFD75F"))
|
warnStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFD75F"))
|
||||||
dangerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF6B6B")).Bold(true)
|
dangerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF5F5F")).Bold(true)
|
||||||
okStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#87D787"))
|
okStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#A5D6A7"))
|
||||||
lineStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#5A5A5A"))
|
lineStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#404040"))
|
||||||
hatStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000"))
|
hatStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF4D4D"))
|
||||||
|
primaryStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#BD93F9"))
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
colWidth = 38
|
colWidth = 38
|
||||||
iconCPU = "⚙"
|
iconCPU = "◉"
|
||||||
iconMemory = "▦"
|
iconMemory = "◫"
|
||||||
iconGPU = "▣"
|
iconGPU = "◧"
|
||||||
iconDisk = "▤"
|
iconDisk = "▥"
|
||||||
iconNetwork = "⇅"
|
iconNetwork = "⇅"
|
||||||
iconBattery = "▮"
|
iconBattery = "◪"
|
||||||
iconSensors = "♨"
|
iconSensors = "◈"
|
||||||
iconProcs = "▶"
|
iconProcs = "❊"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check if it's Christmas season (Dec 10-31)
|
// Check if it's Christmas season (Dec 10-31)
|
||||||
@@ -167,39 +168,46 @@ func renderHeader(m MetricsSnapshot, errMsg string, animFrame int, termWidth int
|
|||||||
// Title
|
// Title
|
||||||
title := titleStyle.Render("Mole Status")
|
title := titleStyle.Render("Mole Status")
|
||||||
|
|
||||||
// Health Score with color and label
|
// Health Score
|
||||||
scoreStyle := getScoreStyle(m.HealthScore)
|
scoreStyle := getScoreStyle(m.HealthScore)
|
||||||
scoreText := subtleStyle.Render("Health ") + scoreStyle.Render(fmt.Sprintf("● %d", m.HealthScore))
|
scoreText := subtleStyle.Render("Health ") + scoreStyle.Render(fmt.Sprintf("● %d", m.HealthScore))
|
||||||
|
|
||||||
// Hardware info
|
// Hardware info - compact for single line
|
||||||
infoParts := []string{}
|
infoParts := []string{}
|
||||||
if m.Hardware.Model != "" {
|
if m.Hardware.Model != "" {
|
||||||
infoParts = append(infoParts, m.Hardware.Model)
|
infoParts = append(infoParts, primaryStyle.Render(m.Hardware.Model))
|
||||||
}
|
}
|
||||||
if m.Hardware.CPUModel != "" {
|
if m.Hardware.CPUModel != "" {
|
||||||
cpuInfo := m.Hardware.CPUModel
|
cpuInfo := m.Hardware.CPUModel
|
||||||
|
// Add GPU core count if available (compact format)
|
||||||
if len(m.GPU) > 0 && m.GPU[0].CoreCount > 0 {
|
if len(m.GPU) > 0 && m.GPU[0].CoreCount > 0 {
|
||||||
cpuInfo += fmt.Sprintf(" (%d GPU cores)", m.GPU[0].CoreCount)
|
cpuInfo += fmt.Sprintf(" (%dGPU)", m.GPU[0].CoreCount)
|
||||||
}
|
}
|
||||||
infoParts = append(infoParts, cpuInfo)
|
infoParts = append(infoParts, cpuInfo)
|
||||||
}
|
}
|
||||||
|
// Combine RAM and Disk to save space
|
||||||
|
var specs []string
|
||||||
if m.Hardware.TotalRAM != "" {
|
if m.Hardware.TotalRAM != "" {
|
||||||
infoParts = append(infoParts, m.Hardware.TotalRAM)
|
specs = append(specs, m.Hardware.TotalRAM)
|
||||||
}
|
}
|
||||||
if m.Hardware.DiskSize != "" {
|
if m.Hardware.DiskSize != "" {
|
||||||
infoParts = append(infoParts, m.Hardware.DiskSize)
|
specs = append(specs, m.Hardware.DiskSize)
|
||||||
|
}
|
||||||
|
if len(specs) > 0 {
|
||||||
|
infoParts = append(infoParts, strings.Join(specs, "/"))
|
||||||
}
|
}
|
||||||
if m.Hardware.OSVersion != "" {
|
if m.Hardware.OSVersion != "" {
|
||||||
infoParts = append(infoParts, m.Hardware.OSVersion)
|
infoParts = append(infoParts, m.Hardware.OSVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Single line compact header
|
||||||
headerLine := title + " " + scoreText + " " + subtleStyle.Render(strings.Join(infoParts, " · "))
|
headerLine := title + " " + scoreText + " " + subtleStyle.Render(strings.Join(infoParts, " · "))
|
||||||
|
|
||||||
// Running mole animation
|
// Running mole animation
|
||||||
mole := getMoleFrame(animFrame, termWidth)
|
mole := getMoleFrame(animFrame, termWidth)
|
||||||
|
|
||||||
if errMsg != "" {
|
if errMsg != "" {
|
||||||
return lipgloss.JoinVertical(lipgloss.Left, headerLine, "", mole, dangerStyle.Render(errMsg), "")
|
return lipgloss.JoinVertical(lipgloss.Left, headerLine, "", mole, dangerStyle.Render("ERROR: "+errMsg), "")
|
||||||
}
|
}
|
||||||
return headerLine + "\n" + mole
|
return headerLine + "\n" + mole
|
||||||
}
|
}
|
||||||
@@ -580,11 +588,11 @@ func renderSensorsCard(sensors []SensorReading) cardData {
|
|||||||
|
|
||||||
func renderCard(data cardData, width int, height int) string {
|
func renderCard(data cardData, width int, height int) string {
|
||||||
titleText := data.icon + " " + data.title
|
titleText := data.icon + " " + data.title
|
||||||
lineLen := width - lipgloss.Width(titleText) - 1
|
lineLen := width - lipgloss.Width(titleText) - 2
|
||||||
if lineLen < 4 {
|
if lineLen < 4 {
|
||||||
lineLen = 4
|
lineLen = 4
|
||||||
}
|
}
|
||||||
header := titleStyle.Render(titleText) + " " + lineStyle.Render(strings.Repeat("─", lineLen))
|
header := titleStyle.Render(titleText) + " " + lineStyle.Render(strings.Repeat("╌", lineLen))
|
||||||
content := header + "\n" + strings.Join(data.lines, "\n")
|
content := header + "\n" + strings.Join(data.lines, "\n")
|
||||||
|
|
||||||
// Pad to target height
|
// Pad to target height
|
||||||
@@ -596,7 +604,7 @@ func renderCard(data cardData, width int, height int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func progressBar(percent float64) string {
|
func progressBar(percent float64) string {
|
||||||
total := 18
|
total := 16
|
||||||
if percent < 0 {
|
if percent < 0 {
|
||||||
percent = 0
|
percent = 0
|
||||||
}
|
}
|
||||||
@@ -604,9 +612,6 @@ func progressBar(percent float64) string {
|
|||||||
percent = 100
|
percent = 100
|
||||||
}
|
}
|
||||||
filled := int(percent / 100 * float64(total))
|
filled := int(percent / 100 * float64(total))
|
||||||
if filled > total {
|
|
||||||
filled = total
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
@@ -620,7 +625,7 @@ func progressBar(percent float64) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func batteryProgressBar(percent float64) string {
|
func batteryProgressBar(percent float64) string {
|
||||||
total := 18
|
total := 16
|
||||||
if percent < 0 {
|
if percent < 0 {
|
||||||
percent = 0
|
percent = 0
|
||||||
}
|
}
|
||||||
@@ -628,9 +633,6 @@ func batteryProgressBar(percent float64) string {
|
|||||||
percent = 100
|
percent = 100
|
||||||
}
|
}
|
||||||
filled := int(percent / 100 * float64(total))
|
filled := int(percent / 100 * float64(total))
|
||||||
if filled > total {
|
|
||||||
filled = total
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
@@ -645,9 +647,9 @@ func batteryProgressBar(percent float64) string {
|
|||||||
|
|
||||||
func colorizePercent(percent float64, s string) string {
|
func colorizePercent(percent float64, s string) string {
|
||||||
switch {
|
switch {
|
||||||
case percent >= 90:
|
case percent >= 85:
|
||||||
return dangerStyle.Render(s)
|
return dangerStyle.Render(s)
|
||||||
case percent >= 70:
|
case percent >= 60:
|
||||||
return warnStyle.Render(s)
|
return warnStyle.Render(s)
|
||||||
default:
|
default:
|
||||||
return okStyle.Render(s)
|
return okStyle.Render(s)
|
||||||
|
|||||||
Reference in New Issue
Block a user