package main import ( "fmt" "strings" tea "github.com/charmbracelet/bubbletea" ) type editorTab int const ( tabCommands editorTab = iota tabFilesystem tabNetwork tabEnv ) var editorTabNames = []string{"Commands", "Filesystem", "Network", "Environment"} type EditorModel struct { subTab editorTab cmdLists [2]EditableList fsLists [4]EditableList netList EditableList envLists [2]EditableList netBlock bool listIdx int width int } func NewEditorModel(p *Profile) EditorModel { m := EditorModel{subTab: tabCommands} if p != nil { m.cmdLists[0] = NewEditableList("Allow", p.Commands.Allow) m.cmdLists[1] = NewEditableList("Deny", p.Commands.Deny) m.fsLists[0] = NewEditableList("Allow", p.FS.Allow) m.fsLists[1] = NewEditableList("Read", p.FS.Read) m.fsLists[2] = NewEditableList("Write", p.FS.Write) m.fsLists[3] = NewEditableList("Deny", p.FS.Deny) m.netList = NewEditableList("Allow Domains", p.Network.AllowDomain) m.envLists[0] = NewEditableList("Set Vars (key=value)", keyValueStrings(p.Env.SetVars)) m.envLists[1] = NewEditableList("Deny Vars", p.Env.DenyVars) m.netBlock = p.Network.Block } return m } func (m EditorModel) listCount() int { switch m.subTab { case tabCommands: return 2 case tabFilesystem: return 4 case tabNetwork: return 1 case tabEnv: return 2 } return 0 } func (m EditorModel) currentList() EditableList { switch m.subTab { case tabCommands: return m.cmdLists[m.listIdx] case tabFilesystem: return m.fsLists[m.listIdx] case tabNetwork: return m.netList case tabEnv: return m.envLists[m.listIdx] } return EditableList{} } func (m EditorModel) withList(idx int, l EditableList) EditorModel { switch m.subTab { case tabCommands: m.cmdLists[idx] = l case tabFilesystem: m.fsLists[idx] = l case tabNetwork: m.netList = l case tabEnv: m.envLists[idx] = l } return m } func (m EditorModel) listTitles() []string { switch m.subTab { case tabCommands: return []string{"Allow", "Deny"} case tabFilesystem: return []string{"Allow", "Read", "Write", "Deny"} case tabNetwork: return []string{"Allow Domains"} case tabEnv: return []string{"Set Vars", "Deny Vars"} } return nil } func (m EditorModel) Update(msg tea.Msg) (EditorModel, tea.Cmd) { current := m.currentList() if current.IsEditing() { updated, cmd := current.Update(msg) m = m.withList(m.listIdx, updated) return m, cmd } switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case "1": m.subTab = tabCommands m.listIdx = 0 return m, nil case "2": m.subTab = tabFilesystem m.listIdx = 0 return m, nil case "3": m.subTab = tabNetwork m.listIdx = 0 return m, nil case "4": m.subTab = tabEnv m.listIdx = 0 return m, nil case "left": if m.listIdx > 0 { m.listIdx-- } return m, nil case "right": if m.listIdx < m.listCount()-1 { m.listIdx++ } return m, nil case "b": if m.subTab == tabNetwork { m.netBlock = !m.netBlock return m, nil } default: updated, cmd := current.Update(msg) m = m.withList(m.listIdx, updated) return m, cmd } } return m, nil } func (m EditorModel) View() string { var b strings.Builder b.WriteString(" ") for i, name := range editorTabNames { num := fmt.Sprintf("%d", i+1) if editorTab(i) == m.subTab { b.WriteString(activeTabStyle.Render(num + " " + name)) } else { b.WriteString(tabStyle.Render(num + " " + name)) } b.WriteString(" ") } b.WriteString("\n\n") current := m.currentList() titles := m.listTitles() if len(titles) > 1 { list := current list.title = fmt.Sprintf("%s (%d/%d)", titles[m.listIdx], m.listIdx+1, m.listCount()) b.WriteString(list.View()) } else { b.WriteString(current.View()) } if m.subTab == tabNetwork { blockStr := "no" if m.netBlock { blockStr = "yes" } b.WriteString("\n\n Block: " + blockStr + " (b to toggle)") } b.WriteString("\n\n" + helpStyle.Render(" 1-4:sub-tab <-/->:switch list a:add x:remove enter:edit s:save Tab:dashboard")) return b.String() } func (m EditorModel) SyncToProfile(p *Profile) { p.Commands.Allow = m.cmdLists[0].Items() p.Commands.Deny = m.cmdLists[1].Items() p.FS.Allow = m.fsLists[0].Items() p.FS.Read = m.fsLists[1].Items() p.FS.Write = m.fsLists[2].Items() p.FS.Deny = m.fsLists[3].Items() p.Network.AllowDomain = m.netList.Items() p.Network.Block = m.netBlock p.Env.SetVars = parseKeyValueStrings(m.envLists[0].Items()) p.Env.DenyVars = m.envLists[1].Items() } func keyValueStrings(m map[string]string) []string { var result []string for k, v := range m { result = append(result, k+"="+v) } return result } func parseKeyValueStrings(items []string) map[string]string { result := make(map[string]string) for _, item := range items { parts := strings.SplitN(item, "=", 2) if len(parts) == 2 { result[parts[0]] = parts[1] } } return result }