mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 13:16:47 +00:00
feat(status): persist cat visibility preference
- Add loadCatHidden/saveCatHidden functions - Save preference to ~/.config/mole/status_prefs - Load preference on startup
This commit is contained in:
@@ -9,15 +9,13 @@ run:
|
|||||||
modules-download-mode: readonly
|
modules-download-mode: readonly
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
|
||||||
enable:
|
enable:
|
||||||
# Default linters
|
# Default linters
|
||||||
- govet
|
- govet
|
||||||
- staticcheck
|
- staticcheck
|
||||||
|
- errcheck
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- unused
|
- unused
|
||||||
# Additional useful linters
|
|
||||||
- errcheck
|
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
govet:
|
govet:
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ mo purge --paths # Configure project scan directories
|
|||||||
- **Safety**: Built with strict protections. See [Security Audit](SECURITY_AUDIT.md). Preview changes with `mo clean --dry-run`.
|
- **Safety**: Built with strict protections. See [Security Audit](SECURITY_AUDIT.md). Preview changes with `mo clean --dry-run`.
|
||||||
- **Debug Mode**: Use `--debug` for detailed logs (e.g., `mo clean --debug`). Combine with `--dry-run` for comprehensive preview including risk levels and file details.
|
- **Debug Mode**: Use `--debug` for detailed logs (e.g., `mo clean --debug`). Combine with `--dry-run` for comprehensive preview including risk levels and file details.
|
||||||
- **Navigation**: Supports arrow keys and Vim bindings (`h/j/k/l`).
|
- **Navigation**: Supports arrow keys and Vim bindings (`h/j/k/l`).
|
||||||
|
- **Status Shortcuts**: In `mo status`, press `k` to toggle cat visibility and save preference, `q` to quit.
|
||||||
- **Configuration**: Run `mo touchid` for Touch ID sudo, `mo completion` for shell tab completion, `mo clean --whitelist` to manage protected paths.
|
- **Configuration**: Run `mo touchid` for Touch ID sudo, `mo completion` for shell tab completion, `mo clean --whitelist` to manage protected paths.
|
||||||
|
|
||||||
## Features in Detail
|
## Features in Detail
|
||||||
@@ -185,7 +186,7 @@ Up ▮▯▯▯▯ 0.8 MB/s Chrome ▮▮▮▯▯ 2
|
|||||||
Proxy HTTP · 192.168.1.100 Terminal ▮▯▯▯▯ 12.5%
|
Proxy HTTP · 192.168.1.100 Terminal ▮▯▯▯▯ 12.5%
|
||||||
```
|
```
|
||||||
|
|
||||||
Health score based on CPU, memory, disk, temperature, and I/O load. Color-coded by range. Press `k` to hide/show cat, `q` to quit.
|
Health score based on CPU, memory, disk, temperature, and I/O load. Color-coded by range.
|
||||||
|
|
||||||
### Project Artifact Purge
|
### Project Artifact Purge
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
// Package main provides the mo status command for real-time system monitoring.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
@@ -37,10 +40,50 @@ type model struct {
|
|||||||
catHidden bool // true = hidden, false = visible
|
catHidden bool // true = hidden, false = visible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getConfigPath returns the path to the status preferences file.
|
||||||
|
func getConfigPath() string {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(home, ".config", "mole", "status_prefs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadCatHidden loads the cat hidden preference from config file.
|
||||||
|
func loadCatHidden() bool {
|
||||||
|
path := getConfigPath()
|
||||||
|
if path == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(data)) == "cat_hidden=true"
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveCatHidden saves the cat hidden preference to config file.
|
||||||
|
func saveCatHidden(hidden bool) {
|
||||||
|
path := getConfigPath()
|
||||||
|
if path == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Ensure directory exists
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value := "cat_hidden=false"
|
||||||
|
if hidden {
|
||||||
|
value = "cat_hidden=true"
|
||||||
|
}
|
||||||
|
_ = os.WriteFile(path, []byte(value+"\n"), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
func newModel() model {
|
func newModel() model {
|
||||||
return model{
|
return model{
|
||||||
collector: NewCollector(),
|
collector: NewCollector(),
|
||||||
catHidden: false,
|
catHidden: loadCatHidden(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +98,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
case "q", "esc", "ctrl+c":
|
case "q", "esc", "ctrl+c":
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
case "k":
|
case "k":
|
||||||
// Toggle cat visibility
|
// Toggle cat visibility and persist preference
|
||||||
m.catHidden = !m.catHidden
|
m.catHidden = !m.catHidden
|
||||||
|
saveCatHidden(m.catHidden)
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
case tea.WindowSizeMsg:
|
case tea.WindowSizeMsg:
|
||||||
|
|||||||
@@ -286,9 +286,8 @@ func commandExists(name string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil { //nolint:staticcheck
|
// Treat LookPath panics as "missing".
|
||||||
// Treat LookPath panics as "missing".
|
_ = recover()
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
_, err := exec.LookPath(name)
|
_, err := exec.LookPath(name)
|
||||||
return err == nil
|
return err == nil
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func collectCPU() (CPUStatus, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Two-call pattern for more reliable CPU usage.
|
// Two-call pattern for more reliable CPU usage.
|
||||||
warmUpCpu()
|
warmUpCPU()
|
||||||
time.Sleep(cpuSampleInterval)
|
time.Sleep(cpuSampleInterval)
|
||||||
percents, err := cpu.Percent(0, true)
|
percents, err := cpu.Percent(0, true)
|
||||||
var totalPercent float64
|
var totalPercent float64
|
||||||
@@ -256,6 +256,6 @@ func fallbackCPUUtilization(logical int) (float64, []float64, error) {
|
|||||||
return avg, perCore, nil
|
return avg, perCore, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func warmUpCpu() {
|
func warmUpCPU() {
|
||||||
cpu.Percent(0, true) //nolint:errcheck
|
cpu.Percent(0, true) //nolint:errcheck
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,15 +134,16 @@ func calculateHealthScore(cpu CPUStatus, mem MemoryStatus, disks []DiskStatus, d
|
|||||||
|
|
||||||
// Build message.
|
// Build message.
|
||||||
var msg string
|
var msg string
|
||||||
if score >= 90 {
|
switch {
|
||||||
|
case score >= 90:
|
||||||
msg = "Excellent"
|
msg = "Excellent"
|
||||||
} else if score >= 75 {
|
case score >= 75:
|
||||||
msg = "Good"
|
msg = "Good"
|
||||||
} else if score >= 60 {
|
case score >= 60:
|
||||||
msg = "Fair"
|
msg = "Fair"
|
||||||
} else if score >= 40 {
|
case score >= 40:
|
||||||
msg = "Poor"
|
msg = "Poor"
|
||||||
} else {
|
default:
|
||||||
msg = "Critical"
|
msg = "Critical"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -187,15 +187,16 @@ func renderHeader(m MetricsSnapshot, errMsg string, animFrame int, termWidth int
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getScoreStyle(score int) lipgloss.Style {
|
func getScoreStyle(score int) lipgloss.Style {
|
||||||
if score >= 90 {
|
switch {
|
||||||
|
case score >= 90:
|
||||||
return lipgloss.NewStyle().Foreground(lipgloss.Color("#87FF87")).Bold(true)
|
return lipgloss.NewStyle().Foreground(lipgloss.Color("#87FF87")).Bold(true)
|
||||||
} else if score >= 75 {
|
case score >= 75:
|
||||||
return lipgloss.NewStyle().Foreground(lipgloss.Color("#87D787")).Bold(true)
|
return lipgloss.NewStyle().Foreground(lipgloss.Color("#87D787")).Bold(true)
|
||||||
} else if score >= 60 {
|
case score >= 60:
|
||||||
return lipgloss.NewStyle().Foreground(lipgloss.Color("#FFD75F")).Bold(true)
|
return lipgloss.NewStyle().Foreground(lipgloss.Color("#FFD75F")).Bold(true)
|
||||||
} else if score >= 40 {
|
case score >= 40:
|
||||||
return lipgloss.NewStyle().Foreground(lipgloss.Color("#FFAF5F")).Bold(true)
|
return lipgloss.NewStyle().Foreground(lipgloss.Color("#FFAF5F")).Bold(true)
|
||||||
} else {
|
default:
|
||||||
return lipgloss.NewStyle().Foreground(lipgloss.Color("#FF6B6B")).Bold(true)
|
return lipgloss.NewStyle().Foreground(lipgloss.Color("#FF6B6B")).Bold(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -707,11 +708,11 @@ func humanBytesCompact(v uint64) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func shorten(s string, max int) string {
|
func shorten(s string, maxLen int) string {
|
||||||
if len(s) <= max {
|
if len(s) <= maxLen {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
return s[:max-1] + "…"
|
return s[:maxLen-1] + "…"
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTwoColumns(cards []cardData, width int) string {
|
func renderTwoColumns(cards []cardData, width int) string {
|
||||||
|
|||||||
Reference in New Issue
Block a user