From b886c3e9e8cc158e0ba09d617e9f360b005f8dcf Mon Sep 17 00:00:00 2001 From: Pepe44DEV Date: Sun, 3 May 2026 02:03:57 +0200 Subject: [PATCH] Added Quick commands +Added Quick command Functionality --- config.yaml | 9 ++ internal/models/server.go | 10 +- main.go | 193 +++++++++++++++++++++++++++++++++++++- 3 files changed, 209 insertions(+), 3 deletions(-) diff --git a/config.yaml b/config.yaml index 93897b3..04ad319 100644 --- a/config.yaml +++ b/config.yaml @@ -13,3 +13,12 @@ servers: key: "" password_id: unraid-root kitty_fix: true +quick_commands: + - name: Docker PS + command: docker ps + - name: Disk Usage + command: df -h + - name: RAM Usage + command: free -h + - name: Uptime + command: uptime diff --git a/internal/models/server.go b/internal/models/server.go index e55516c..febcaf9 100644 --- a/internal/models/server.go +++ b/internal/models/server.go @@ -23,6 +23,12 @@ type Server struct { } type AppConfig struct { - Settings Settings `yaml:"settings"` - Servers []Server `yaml:"servers"` + Settings Settings `yaml:"settings"` + Servers []Server `yaml:"servers"` + QuickCommands []QuickCommand `yaml:"quick_commands"` +} + +type QuickCommand struct { + Name string `yaml:"name"` + Command string `yaml:"command"` } \ No newline at end of file diff --git a/main.go b/main.go index 92e0af0..bac59ab 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,8 @@ const ( ViewAddServer viewMode = "add_server" ViewEditServer viewMode = "edit_server" ViewDeleteConfirm viewMode = "delete_confirm" + ViewCommands viewMode = "commands" + ViewCommandOutput viewMode = "command_output" ) type model struct { @@ -40,6 +42,10 @@ type model struct { addInputs []textinput.Model addFocus int editIndex int + commandSelected int + commandOutput string + commandTitle string + commandError string } var ( @@ -190,6 +196,12 @@ func (m *model) saveNewServer() error { return nil } +type quickCommandResultMsg struct { + Title string + Output string + Error string +} + func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.err != nil { return m, nil @@ -200,8 +212,28 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.width = msg.Width m.height = msg.Height + case quickCommandResultMsg: + m.commandTitle = msg.Title + m.commandOutput = msg.Output + m.commandError = msg.Error + m.view = ViewCommandOutput + return m, nil + case tea.KeyMsg: + // Command Output offen lassen, bis du ihn wegdrückst + if m.view == ViewCommandOutput { + switch msg.String() { + case "q", "esc", "enter": + m.view = ViewCommands + return m, nil + + case "ctrl+c": + return m, tea.Quit + } + } + + // Add/Edit Formular if m.view == ViewAddServer || m.view == ViewEditServer { switch msg.String() { case "ctrl+c": @@ -252,6 +284,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } + // Delete Confirm if m.view == ViewDeleteConfirm { switch msg.String() { case "y", "Y", "j": @@ -267,6 +300,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } + // Normale Navigation switch msg.String() { case "ctrl+c": return m, tea.Quit @@ -301,27 +335,48 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return m, nil + case "c": + if len(m.servers) > 0 { + m.view = ViewCommands + m.commandSelected = 0 + } + return m, nil + case "s": m.view = ViewSettings + return m, nil case "h": m.view = ViewHelp + return m, nil case "up", "k": if m.view == ViewServers && m.selected > 0 { m.selected-- } + if m.view == ViewCommands && m.commandSelected > 0 { + m.commandSelected-- + } case "down", "j": if m.view == ViewServers && m.selected < len(m.servers)-1 { m.selected++ } + if m.view == ViewCommands && m.commandSelected < len(m.cfg.QuickCommands)-1 { + m.commandSelected++ + } case "enter": if m.view == ViewServers && len(m.servers) > 0 { server := m.servers[m.selected] return m, connectSSH(server, m.cfg.Settings) } + + if m.view == ViewCommands && len(m.servers) > 0 && len(m.cfg.QuickCommands) > 0 { + server := m.servers[m.selected] + quick := m.cfg.QuickCommands[m.commandSelected] + return m, runQuickCommand(server, m.cfg.Settings, quick) + } } } @@ -367,6 +422,10 @@ func (m model) View() string { right = m.renderEditServerContent() case ViewDeleteConfirm: right = m.renderDeleteConfirmContent() + case ViewCommands: + right = m.renderCommandsContent() + case ViewCommandOutput: + right = m.renderCommandOutputContent() default: right = m.renderServerList() } @@ -378,7 +437,7 @@ func (m model) View() string { right, ) - footer := helpStyle.Render("↑/↓ Auswahl Enter Verbinden a Hinzufügen e Editieren d Löschen Tab Ansicht q Zurück/Beenden") + footer := helpStyle.Render("↑/↓ Auswahl Enter Verbinden/Ausführen a Hinzufügen e Editieren d Löschen c Commands Tab Ansicht q Zurück/Beenden") content := lipgloss.JoinVertical( lipgloss.Left, @@ -421,6 +480,7 @@ func (m model) renderNavigation() string { {"󰐕", "Add Server", ViewAddServer}, {"󰢹", "Settings", ViewSettings}, {"󰋖", "Help", ViewHelp}, + {"󰘳", "Commands", ViewCommands}, } var lines []string @@ -775,6 +835,137 @@ func (m model) renderServerFormContent(badge string, title string) string { Render(strings.Join(lines, "\n")) } +func (m model) renderCommandOutputContent() string { + lines := []string{ + badgeStyle.Render(" COMMAND OUTPUT "), + "", + selectedStyle.Render(m.commandTitle), + "", + } + + if m.commandError != "" { + lines = append(lines, warnStyle().Render("Fehler: "+m.commandError)) + lines = append(lines, "") + } + + output := strings.TrimSpace(m.commandOutput) + if output == "" { + output = "Keine Ausgabe." + } + + lines = append(lines, output) + lines = append(lines, "") + lines = append(lines, mutedStyle.Render("q / Esc / Enter: zurück zu Quick Commands")) + + return panelStyle. + Width(max(m.width-36, 60)). + Height(max(m.height-9, 14)). + Render(strings.Join(lines, "\n")) +} + +func (m model) renderCommandsContent() string { + lines := []string{ + badgeStyle.Render(" QUICK COMMANDS "), + "", + } + + if len(m.servers) == 0 { + lines = append(lines, mutedStyle.Render("Kein Server ausgewählt.")) + } else { + server := m.servers[m.selected] + lines = append(lines, fmt.Sprintf("Server: %s %s@%s:%d", server.Name, server.User, server.Host, server.Port)) + lines = append(lines, "") + } + + if len(m.cfg.QuickCommands) == 0 { + lines = append(lines, mutedStyle.Render("Keine quick_commands in config.yaml gefunden.")) + } else { + for i, cmd := range m.cfg.QuickCommands { + line := fmt.Sprintf("%s → %s", cmd.Name, cmd.Command) + + if i == m.commandSelected { + lines = append(lines, selectedStyle.Render("> "+line)) + } else { + lines = append(lines, normalStyle.Render(" "+line)) + } + } + } + + lines = append(lines, "") + lines = append(lines, mutedStyle.Render("Enter: Command ausführen ↑/↓ Auswahl q: zurück")) + + return panelStyle. + Width(max(m.width-36, 60)). + Height(max(m.height-9, 14)). + Render(strings.Join(lines, "\n")) +} + +func runQuickCommand(server models.Server, settings models.Settings, quick models.QuickCommand) tea.Cmd { + return func() tea.Msg { + title := fmt.Sprintf("%s auf %s", quick.Name, server.Name) + + args := []string{ + "-p", strconv.Itoa(server.Port), + } + + if settings.Terminal.EnableKittyFix && server.KittyFix { + args = append(args, "-t") + } + + var cmd *exec.Cmd + + if server.Auth == "key" { + if server.Key != "" { + args = append(args, "-i", expandHome(server.Key)) + } + + args = append(args, server.User+"@"+server.Host, quick.Command) + + cmd = exec.Command("ssh", args...) + cmd.Env = buildSSHEnv(server, settings) + } else if server.Auth == "password" { + password, err := secret.GetPassword(server.PasswordID) + if err != nil { + return quickCommandResultMsg{ + Title: title, + Output: "", + Error: fmt.Sprintf("kein passwort gespeichert für %s", server.PasswordID), + } + } + + sshArgs := []string{ + "-e", + "ssh", + "-p", strconv.Itoa(server.Port), + server.User + "@" + server.Host, + quick.Command, + } + + cmd = exec.Command("sshpass", sshArgs...) + cmd.Env = append(buildSSHEnv(server, settings), "SSHPASS="+password) + } else { + return quickCommandResultMsg{ + Title: title, + Output: "", + Error: fmt.Sprintf("unbekannte auth methode: %s", server.Auth), + } + } + + output, err := cmd.CombinedOutput() + + errorText := "" + if err != nil { + errorText = err.Error() + } + + return quickCommandResultMsg{ + Title: title, + Output: string(output), + Error: errorText, + } + } +} + func (m model) renderDeleteConfirmContent() string { if len(m.servers) == 0 { return panelStyle.Render("Kein Server zum Löschen vorhanden.")