Create CNAME

This commit is contained in:
2025-11-28 12:31:45 +00:00
parent 22552aec99
commit 9c1c1eb9c6
17 changed files with 939 additions and 156 deletions
+182 -46
View File
@@ -24,6 +24,7 @@ const (
ViewForm
ViewPresets
ViewGroups
ViewBackups
ViewHelp
ViewSearch
)
@@ -34,16 +35,13 @@ type Model struct {
client *client.Client
connected bool
// Config
configPath string
config *config.Manager
// Views
mode ViewMode
list *ListView
form *Form
presetPicker *PresetPicker
groupPicker *GroupPicker
backupPicker *BackupPicker
searchInput textinput.Model
// State
@@ -117,6 +115,18 @@ type (
groups []string
err error
}
rollbackMsg struct {
name string
err error
}
refreshBackupsMsg struct {
backups []protocol.BackupInfo
err error
}
backupContentMsg struct {
content string
err error
}
clearMsgMsg struct{}
tickMsg struct{}
updateMsg struct {
@@ -126,7 +136,7 @@ type (
)
// NewModel creates a new TUI model.
func NewModel(socketPath, configPath string) *Model {
func NewModel(socketPath string) *Model {
searchInput := textinput.New()
searchInput.Placeholder = "Search..."
searchInput.CharLimit = 100
@@ -134,12 +144,11 @@ func NewModel(socketPath, configPath string) *Model {
return &Model{
client: client.New(socketPath),
configPath: configPath,
config: config.NewManager(configPath),
list: NewListView(),
form: NewForm(),
presetPicker: NewPresetPicker(),
groupPicker: NewGroupPicker(),
backupPicker: NewBackupPicker(),
searchInput: searchInput,
mode: ViewList,
}
@@ -251,6 +260,27 @@ func (m *Model) refreshGroups() tea.Cmd {
}
}
func (m *Model) rollback(backupName string) tea.Cmd {
return func() tea.Msg {
err := m.client.Rollback(backupName)
return rollbackMsg{name: backupName, err: err}
}
}
func (m *Model) refreshBackups() tea.Cmd {
return func() tea.Msg {
backups, err := m.client.ListBackups()
return refreshBackupsMsg{backups: backups, err: err}
}
}
func (m *Model) fetchBackupContent(backupName string) tea.Cmd {
return func() tea.Msg {
content, err := m.client.GetBackupContent(backupName)
return backupContentMsg{content: content, err: err}
}
}
func (m *Model) tick() tea.Cmd {
return tea.Tick(time.Second*3, func(t time.Time) tea.Msg {
return tickMsg{}
@@ -291,6 +321,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.form.SetSize(msg.Width, msg.Height)
m.presetPicker.SetSize(msg.Width, msg.Height)
m.groupPicker.SetSize(msg.Width, msg.Height)
m.backupPicker.SetSize(msg.Width, msg.Height)
// Set search input width
searchWidth := msg.Width - 20
if searchWidth > 60 {
@@ -313,7 +344,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds = append(cmds, m.refresh())
cmds = append(cmds, m.refreshPresets())
cmds = append(cmds, m.refreshGroups())
m.loadConfig()
}
case refreshMsg:
@@ -421,6 +451,30 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.groupPicker.SetGroups(msg.groups)
}
case rollbackMsg:
if msg.err != nil {
m.setError(fmt.Sprintf("Rollback failed: %v", msg.err))
} else {
cmds = append(cmds, m.refresh())
m.setSuccess("Restored from backup")
}
m.backupPicker.Cancel()
m.mode = ViewList
case refreshBackupsMsg:
if msg.err == nil && msg.backups != nil {
m.backupPicker.SetBackups(msg.backups)
// Fetch content for the first backup
if len(msg.backups) > 0 {
cmds = append(cmds, m.fetchBackupContent(msg.backups[0].Name))
}
}
case backupContentMsg:
if msg.err == nil {
m.backupPicker.SetPreviewContent(msg.content)
}
case clearMsgMsg:
if time.Since(m.messageTime) >= time.Second*3 {
m.message = ""
@@ -461,6 +515,8 @@ func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd {
return m.handlePresetKey(msg)
case ViewGroups:
return m.handleGroupKey(msg)
case ViewBackups:
return m.handleBackupKey(msg)
case ViewHelp:
return m.handleHelpKey(msg)
case ViewSearch:
@@ -502,9 +558,14 @@ func (m *Model) handleListKey(msg tea.KeyMsg) tea.Cmd {
}
case "p":
m.mode = ViewPresets
// Pass available aliases to preset picker
m.presetPicker.SetAvailableAliases(m.list.GetAliases())
case "g":
m.mode = ViewGroups
return m.refreshGroups()
case "b":
m.mode = ViewBackups
return m.refreshBackups()
case "/":
m.mode = ViewSearch
m.searchInput.Focus()
@@ -551,6 +612,8 @@ func (m *Model) handlePresetKey(msg tea.KeyMsg) tea.Cmd {
return m.handlePresetSelectKey(msg)
case PresetModeAdd, PresetModeEdit:
return m.handlePresetFormKey(msg)
case PresetModePickEnable, PresetModePickDisable:
return m.handlePresetPickerKey(msg)
case PresetModeConfirmDelete:
return m.handlePresetDeleteKey(msg)
}
@@ -585,27 +648,54 @@ func (m *Model) handlePresetFormKey(msg tea.KeyMsg) tea.Cmd {
m.presetPicker.CancelForm()
return nil
case "enter":
if errMsg := m.presetPicker.ValidateForm(); errMsg != "" {
m.setError(errMsg)
return m.clearMsg()
// Check which field is focused
switch m.presetPicker.Focus() {
case PresetFieldEnable:
m.presetPicker.OpenEnablePicker()
return nil
case PresetFieldDisable:
m.presetPicker.OpenDisablePicker()
return nil
case PresetFieldSave:
// Save the preset
if errMsg := m.presetPicker.ValidateForm(); errMsg != "" {
m.setError(errMsg)
return m.clearMsg()
}
name, enable, disable := m.presetPicker.FormValues()
if m.presetPicker.IsEdit() {
// For edit, delete old and add new
oldName := m.presetPicker.EditName()
return tea.Sequence(
func() tea.Msg {
m.client.DeletePreset(oldName)
return nil
},
m.addPreset(name, enable, disable),
)
}
return m.addPreset(name, enable, disable)
}
name, enable, disable := m.presetPicker.FormValues()
if m.presetPicker.IsEdit() {
// For edit, delete old and add new
oldName := m.presetPicker.EditName()
return tea.Sequence(
func() tea.Msg {
m.client.DeletePreset(oldName)
return nil
},
m.addPreset(name, enable, disable),
)
}
return m.addPreset(name, enable, disable)
}
return m.presetPicker.Update(msg)
}
func (m *Model) handlePresetPickerKey(msg tea.KeyMsg) tea.Cmd {
switch msg.String() {
case "esc":
m.presetPicker.ClosePicker()
case "enter":
m.presetPicker.ClosePicker()
case "up", "k":
m.presetPicker.PickerMoveUp()
case "down", "j":
m.presetPicker.PickerMoveDown()
case " ":
m.presetPicker.TogglePickerSelection()
}
return nil
}
func (m *Model) handlePresetDeleteKey(msg tea.KeyMsg) tea.Cmd {
switch msg.String() {
case "y", "Y":
@@ -683,6 +773,57 @@ func (m *Model) handleGroupDeleteKey(msg tea.KeyMsg) tea.Cmd {
return nil
}
func (m *Model) handleBackupKey(msg tea.KeyMsg) tea.Cmd {
switch m.backupPicker.Mode() {
case BackupModeSelect:
return m.handleBackupSelectKey(msg)
case BackupModeConfirmRestore:
return m.handleBackupRestoreKey(msg)
}
return nil
}
func (m *Model) handleBackupSelectKey(msg tea.KeyMsg) tea.Cmd {
switch msg.String() {
case "esc", "q":
m.mode = ViewList
case "up", "k":
m.backupPicker.MoveUp()
// Fetch content for newly selected backup
if backup := m.backupPicker.Selected(); backup != "" && m.backupPicker.PreviewContent() == "" {
return m.fetchBackupContent(backup)
}
case "down", "j":
m.backupPicker.MoveDown()
// Fetch content for newly selected backup
if backup := m.backupPicker.Selected(); backup != "" && m.backupPicker.PreviewContent() == "" {
return m.fetchBackupContent(backup)
}
case "shift+up", "K":
m.backupPicker.ScrollPreviewUp()
case "shift+down", "J":
m.backupPicker.ScrollPreviewDown()
case "enter":
m.backupPicker.InitRestore()
case "r":
return m.refreshBackups()
}
return nil
}
func (m *Model) handleBackupRestoreKey(msg tea.KeyMsg) tea.Cmd {
switch msg.String() {
case "y", "Y":
if backup := m.backupPicker.Selected(); backup != "" {
return m.rollback(backup)
}
m.backupPicker.Cancel()
case "n", "N", "esc":
m.backupPicker.Cancel()
}
return nil
}
func (m *Model) handleHelpKey(msg tea.KeyMsg) tea.Cmd {
switch msg.String() {
case "esc", "q", "?":
@@ -719,23 +860,6 @@ func (m *Model) toggleSelected() tea.Cmd {
return m.toggle(item.Entry.Alias, !item.Entry.Enabled)
}
func (m *Model) loadConfig() {
if err := m.config.Load(); err != nil {
return
}
cfg := m.config.Get()
if cfg == nil {
return
}
var presetNames []string
for _, p := range cfg.Presets {
presetNames = append(presetNames, p.Name)
}
m.presetPicker.SetPresets(presetNames)
}
func (m *Model) setError(msg string) {
m.message = msg
m.messageStyle = "error"
@@ -774,6 +898,8 @@ func (m *Model) View() string {
sb.WriteString(m.presetPicker.View())
case ViewGroups:
sb.WriteString(m.groupPicker.View())
case ViewBackups:
sb.WriteString(m.backupPicker.View())
case ViewHelp:
sb.WriteString(m.helpView())
case ViewSearch:
@@ -813,7 +939,7 @@ func (m *Model) View() string {
}
func (m *Model) helpBar() string {
return helpBarStyle.Render(fmt.Sprintf("%s/%s: Navigate %s: Toggle %s: New %s: Edit %s: Delete %s: Presets %s: Groups %s: Search %s: Help %s: Quit",
return helpBarStyle.Render(fmt.Sprintf("%s/%s: Navigate %s: Toggle %s: New %s: Edit %s: Delete %s: Presets %s: Groups %s: Backups %s: Search %s: Help %s: Quit",
helpKeyStyle.Render("↑↓"),
helpKeyStyle.Render("jk"),
helpKeyStyle.Render("Space"),
@@ -822,6 +948,7 @@ func (m *Model) helpBar() string {
helpKeyStyle.Render("d"),
helpKeyStyle.Render("p"),
helpKeyStyle.Render("g"),
helpKeyStyle.Render("b"),
helpKeyStyle.Render("/"),
helpKeyStyle.Render("?"),
helpKeyStyle.Render("q")))
@@ -855,6 +982,7 @@ func (m *Model) helpView() string {
{"d", "Delete selected entry"},
{"p", "Open preset manager"},
{"g", "Open group manager"},
{"b", "Open backup manager"},
{"/", "Search"},
{"r", "Refresh list"},
{"?", "Toggle this help"},
@@ -867,6 +995,14 @@ func (m *Model) helpView() string {
helpDescStyle.Render(h.desc)))
}
// Show blocked domains
sb.WriteString("\n")
sb.WriteString(inputLabelStyle.Render("Blocked Domains:"))
sb.WriteString("\n")
blockedDomains := config.GetBlockedDomains()
sb.WriteString(helpDescStyle.Render(" " + strings.Join(blockedDomains, ", ")))
sb.WriteString("\n")
sb.WriteString("\n")
sb.WriteString(helpDescStyle.Render("Press ? or Esc to close"))
@@ -887,13 +1023,13 @@ func (m *Model) searchView() string {
}
// Run starts the TUI application.
func Run(socketPath, configPath string) error {
return RunWithVersion(socketPath, configPath, "dev", "", "")
func Run(socketPath string) error {
return RunWithVersion(socketPath, "dev", "", "")
}
// RunWithVersion starts the TUI application with version info for update checking.
func RunWithVersion(socketPath, configPath, version, githubOwner, githubRepo string) error {
m := NewModel(socketPath, configPath)
func RunWithVersion(socketPath, version, githubOwner, githubRepo string) error {
m := NewModel(socketPath)
m.version = version
m.githubOwner = githubOwner
m.githubRepo = githubRepo