feat(tui): upgrade users tab with lipgloss table, edit support, role select

Users tab now matches sites/alerts quality: lipgloss bordered table,
click-to-select zones, edit form with role picker, and UpdateUser
support across both store backends.
This commit is contained in:
2026-05-14 11:45:59 -04:00
parent c24bb7a0d4
commit b7592ee9e5
5 changed files with 210 additions and 49 deletions
+49 -18
View File
@@ -6,7 +6,7 @@ import (
"encoding/hex"
"encoding/json"
"go-upkeep/internal/models"
_ "github.com/mattn/go-sqlite3"
)
@@ -18,7 +18,9 @@ type SQLiteStore struct {
func (s *SQLiteStore) Init() error {
var err error
s.db, err = sql.Open("sqlite3", s.DBPath)
if err != nil { return err }
if err != nil {
return err
}
createTables := `
CREATE TABLE IF NOT EXISTS alerts (
@@ -51,13 +53,17 @@ func (s *SQLiteStore) Init() error {
func generateToken() string {
b := make([]byte, 16)
rand.Read(b)
if _, err := rand.Read(b); err != nil {
panic("crypto/rand failed: " + err.Error())
}
return hex.EncodeToString(b)
}
func (s *SQLiteStore) GetSites() []models.Site {
rows, err := s.db.Query("SELECT id, COALESCE(name, url), url, COALESCE(type, 'http'), COALESCE(token, ''), interval, alert_id, check_ssl, threshold, max_retries FROM sites")
if err != nil { return []models.Site{} }
if err != nil {
return []models.Site{}
}
defer rows.Close()
var sites []models.Site
for rows.Next() {
@@ -69,28 +75,37 @@ func (s *SQLiteStore) GetSites() []models.Site {
}
func (s *SQLiteStore) AddSite(name, url, sType string, interval, alertID int, checkSSL bool, threshold, retries int) {
token := ""
if sType == "push" { token = generateToken() }
if sType == "push" {
token = generateToken()
}
s.db.Exec("INSERT INTO sites (name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", name, url, sType, token, interval, alertID, checkSSL, threshold, retries)
}
func (s *SQLiteStore) UpdateSite(id int, name, url, sType string, interval, alertID int, checkSSL bool, threshold, retries int) {
var existingToken string
s.db.QueryRow("SELECT token FROM sites WHERE id=?", id).Scan(&existingToken)
if sType == "push" && existingToken == "" { existingToken = generateToken() }
if sType == "push" && existingToken == "" {
existingToken = generateToken()
}
s.db.Exec("UPDATE sites SET name=?, url=?, type=?, token=?, interval=?, alert_id=?, check_ssl=?, threshold=?, max_retries=? WHERE id=?", name, url, sType, existingToken, interval, alertID, checkSSL, threshold, retries, id)
}
func (s *SQLiteStore) DeleteSite(id int) {
s.db.Exec("DELETE FROM sites WHERE id=?", id)
var count int
s.db.QueryRow("SELECT COUNT(*) FROM sites").Scan(&count)
if count == 0 { s.db.Exec("DELETE FROM sqlite_sequence WHERE name='sites'") }
if count == 0 {
s.db.Exec("DELETE FROM sqlite_sequence WHERE name='sites'")
}
}
func (s *SQLiteStore) GetAllAlerts() []models.AlertConfig {
rows, err := s.db.Query("SELECT id, name, type, settings FROM alerts")
if err != nil { return []models.AlertConfig{} }
if err != nil {
return []models.AlertConfig{}
}
defer rows.Close()
var alerts []models.AlertConfig
for rows.Next() {
var a models.AlertConfig; var settingsJSON string
var a models.AlertConfig
var settingsJSON string
rows.Scan(&a.ID, &a.Name, &a.Type, &settingsJSON)
json.Unmarshal([]byte(settingsJSON), &a.Settings)
alerts = append(alerts, a)
@@ -98,9 +113,12 @@ func (s *SQLiteStore) GetAllAlerts() []models.AlertConfig {
return alerts
}
func (s *SQLiteStore) GetAlert(id int) (models.AlertConfig, bool) {
var a models.AlertConfig; var settingsJSON string
var a models.AlertConfig
var settingsJSON string
err := s.db.QueryRow("SELECT id, name, type, settings FROM alerts WHERE id = ?", id).Scan(&a.ID, &a.Name, &a.Type, &settingsJSON)
if err != nil { return a, false }
if err != nil {
return a, false
}
json.Unmarshal([]byte(settingsJSON), &a.Settings)
return a, true
}
@@ -116,11 +134,15 @@ func (s *SQLiteStore) DeleteAlert(id int) {
s.db.Exec("DELETE FROM alerts WHERE id=?", id)
var count int
s.db.QueryRow("SELECT COUNT(*) FROM alerts").Scan(&count)
if count == 0 { s.db.Exec("DELETE FROM sqlite_sequence WHERE name='alerts'") }
if count == 0 {
s.db.Exec("DELETE FROM sqlite_sequence WHERE name='alerts'")
}
}
func (s *SQLiteStore) GetAllUsers() []models.User {
rows, err := s.db.Query("SELECT id, username, public_key, role FROM users")
if err != nil { return []models.User{} }
if err != nil {
return []models.User{}
}
defer rows.Close()
var users []models.User
for rows.Next() {
@@ -134,6 +156,10 @@ func (s *SQLiteStore) AddUser(username, publicKey, role string) error {
_, err := s.db.Exec("INSERT INTO users (username, public_key, role) VALUES (?, ?, ?)", username, publicKey, role)
return err
}
func (s *SQLiteStore) UpdateUser(id int, username, publicKey, role string) error {
_, err := s.db.Exec("UPDATE users SET username=?, public_key=?, role=? WHERE id=?", username, publicKey, role, id)
return err
}
func (s *SQLiteStore) DeleteUser(id int) error {
_, err := s.db.Exec("DELETE FROM users WHERE id=?", id)
return err
@@ -151,12 +177,17 @@ func (s *SQLiteStore) ExportData() models.Backup {
func (s *SQLiteStore) ImportData(data models.Backup) error {
tx, err := s.db.Begin()
if err != nil { return err }
if err != nil {
return err
}
// Wipe Existing
tx.Exec("DELETE FROM sites"); tx.Exec("DELETE FROM sqlite_sequence WHERE name='sites'")
tx.Exec("DELETE FROM alerts"); tx.Exec("DELETE FROM sqlite_sequence WHERE name='alerts'")
tx.Exec("DELETE FROM users"); tx.Exec("DELETE FROM sqlite_sequence WHERE name='users'")
tx.Exec("DELETE FROM sites")
tx.Exec("DELETE FROM sqlite_sequence WHERE name='sites'")
tx.Exec("DELETE FROM alerts")
tx.Exec("DELETE FROM sqlite_sequence WHERE name='alerts'")
tx.Exec("DELETE FROM users")
tx.Exec("DELETE FROM sqlite_sequence WHERE name='users'")
// Insert New
for _, u := range data.Users {
@@ -172,4 +203,4 @@ func (s *SQLiteStore) ImportData(data models.Backup) error {
}
return tx.Commit()
}
}