Added Add Host Functionality
+added function to add server from tui +added function to eddit and delete server from tui
This commit is contained in:
35
config.yaml
35
config.yaml
@@ -1,24 +1,15 @@
|
|||||||
settings:
|
settings:
|
||||||
theme: neon-green
|
theme: neon-green
|
||||||
terminal:
|
terminal:
|
||||||
term: xterm-256color
|
term: xterm-256color
|
||||||
enable_kitty_fix: true
|
enable_kitty_fix: true
|
||||||
|
|
||||||
servers:
|
servers:
|
||||||
- name: Unraid
|
- name: Unraid
|
||||||
host: 10.0.0.15
|
host: 10.0.0.15
|
||||||
user: root
|
user: root
|
||||||
port: 22
|
port: 22
|
||||||
group: Homelab
|
group: Homelab
|
||||||
auth: password
|
auth: password
|
||||||
password_id: unraid-root
|
key: ""
|
||||||
kitty_fix: true
|
password_id: unraid-root
|
||||||
|
kitty_fix: true
|
||||||
- name: Test Server
|
|
||||||
host: 10.0.0.23
|
|
||||||
user: administrator
|
|
||||||
port: 22
|
|
||||||
group: Homelab
|
|
||||||
auth: password
|
|
||||||
password_id: test-server-admin
|
|
||||||
kitty_fix: true
|
|
||||||
|
|||||||
17
go.mod
17
go.mod
@@ -5,22 +5,27 @@ go 1.26.2
|
|||||||
require (
|
require (
|
||||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||||
github.com/99designs/keyring v1.2.2 // indirect
|
github.com/99designs/keyring v1.2.2 // indirect
|
||||||
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/charmbracelet/bubbles v1.0.0 // indirect
|
||||||
github.com/charmbracelet/bubbletea v1.3.10 // indirect
|
github.com/charmbracelet/bubbletea v1.3.10 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||||
|
github.com/clipperhouse/displaywidth v0.9.0 // indirect
|
||||||
|
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
|
||||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||||
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
|
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||||
github.com/mtibben/percent v0.2.1 // indirect
|
github.com/mtibben/percent v0.2.1 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
|
|||||||
22
go.sum
22
go.sum
@@ -2,20 +2,38 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMb
|
|||||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
||||||
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
|
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
|
||||||
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
|
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
|
||||||
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
|
||||||
|
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
|
||||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||||
|
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
||||||
|
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
||||||
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||||
|
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
||||||
|
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
||||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
|
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||||
|
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||||
|
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
|
||||||
|
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
|
||||||
|
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||||
|
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||||
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
||||||
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
|
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -31,12 +49,16 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||||
|
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||||
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
||||||
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||||
|
|||||||
@@ -18,3 +18,12 @@ func LoadConfig(path string) (models.AppConfig, error) {
|
|||||||
err = yaml.Unmarshal(data, &cfg)
|
err = yaml.Unmarshal(data, &cfg)
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SaveConfig(path string, cfg models.AppConfig) error {
|
||||||
|
data, err := yaml.Marshal(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(path, data, 0600)
|
||||||
|
}
|
||||||
369
main.go
369
main.go
@@ -13,15 +13,19 @@ import (
|
|||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
type viewMode string
|
type viewMode string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ViewServers viewMode = "servers"
|
ViewServers viewMode = "servers"
|
||||||
ViewSettings viewMode = "settings"
|
ViewSettings viewMode = "settings"
|
||||||
ViewHelp viewMode = "help"
|
ViewHelp viewMode = "help"
|
||||||
|
ViewAddServer viewMode = "add_server"
|
||||||
|
ViewEditServer viewMode = "edit_server"
|
||||||
|
ViewDeleteConfirm viewMode = "delete_confirm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type model struct {
|
type model struct {
|
||||||
@@ -32,6 +36,10 @@ type model struct {
|
|||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
err error
|
err error
|
||||||
|
|
||||||
|
addInputs []textinput.Model
|
||||||
|
addFocus int
|
||||||
|
editIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -85,18 +93,103 @@ func initialModel() model {
|
|||||||
return model{err: err}
|
return model{err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
return model{
|
m := model{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
servers: cfg.Servers,
|
servers: cfg.Servers,
|
||||||
selected: 0,
|
selected: 0,
|
||||||
view: ViewServers,
|
view: ViewServers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.initAddInputs()
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd {
|
func (m model) Init() tea.Cmd {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *model) initAddInputs() {
|
||||||
|
labels := []string{
|
||||||
|
"Name",
|
||||||
|
"Host",
|
||||||
|
"User",
|
||||||
|
"Port",
|
||||||
|
"Group",
|
||||||
|
"Auth (key/password)",
|
||||||
|
"Key Path",
|
||||||
|
"Password ID",
|
||||||
|
}
|
||||||
|
|
||||||
|
m.addInputs = make([]textinput.Model, len(labels))
|
||||||
|
|
||||||
|
for i, label := range labels {
|
||||||
|
input := textinput.New()
|
||||||
|
input.Placeholder = label
|
||||||
|
input.CharLimit = 120
|
||||||
|
input.Width = 40
|
||||||
|
|
||||||
|
switch label {
|
||||||
|
case "Port":
|
||||||
|
input.SetValue("22")
|
||||||
|
case "Auth (key/password)":
|
||||||
|
input.SetValue("key")
|
||||||
|
case "Group":
|
||||||
|
input.SetValue("Homelab")
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
input.Focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
m.addInputs[i] = input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) saveNewServer() error {
|
||||||
|
port, err := strconv.Atoi(m.addInputs[3].Value())
|
||||||
|
if err != nil {
|
||||||
|
port = 22
|
||||||
|
}
|
||||||
|
|
||||||
|
server := models.Server{
|
||||||
|
Name: m.addInputs[0].Value(),
|
||||||
|
Host: m.addInputs[1].Value(),
|
||||||
|
User: m.addInputs[2].Value(),
|
||||||
|
Port: port,
|
||||||
|
Group: m.addInputs[4].Value(),
|
||||||
|
Auth: m.addInputs[5].Value(),
|
||||||
|
Key: m.addInputs[6].Value(),
|
||||||
|
PasswordID: m.addInputs[7].Value(),
|
||||||
|
KittyFix: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.Name == "" || server.Host == "" || server.User == "" {
|
||||||
|
return fmt.Errorf("name, host und user sind pflichtfelder")
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.Auth == "" {
|
||||||
|
server.Auth = "key"
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.Auth == "password" && server.PasswordID == "" {
|
||||||
|
server.PasswordID = strings.ToLower(server.Name) + "-" + server.User
|
||||||
|
}
|
||||||
|
|
||||||
|
m.cfg.Servers = append(m.cfg.Servers, server)
|
||||||
|
m.servers = m.cfg.Servers
|
||||||
|
|
||||||
|
err = config.SaveConfig("config.yaml", m.cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.selected = len(m.servers) - 1
|
||||||
|
m.initAddInputs()
|
||||||
|
m.view = ViewServers
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
if m.err != nil {
|
if m.err != nil {
|
||||||
return m, nil
|
return m, nil
|
||||||
@@ -108,6 +201,72 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.height = msg.Height
|
m.height = msg.Height
|
||||||
|
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
|
|
||||||
|
if m.view == ViewAddServer || m.view == ViewEditServer {
|
||||||
|
switch msg.String() {
|
||||||
|
case "ctrl+c":
|
||||||
|
return m, tea.Quit
|
||||||
|
|
||||||
|
case "esc", "q":
|
||||||
|
m.view = ViewServers
|
||||||
|
return m, nil
|
||||||
|
|
||||||
|
case "tab", "down":
|
||||||
|
m.addInputs[m.addFocus].Blur()
|
||||||
|
m.addFocus = (m.addFocus + 1) % len(m.addInputs)
|
||||||
|
m.addInputs[m.addFocus].Focus()
|
||||||
|
return m, nil
|
||||||
|
|
||||||
|
case "shift+tab", "up":
|
||||||
|
m.addInputs[m.addFocus].Blur()
|
||||||
|
m.addFocus--
|
||||||
|
if m.addFocus < 0 {
|
||||||
|
m.addFocus = len(m.addInputs) - 1
|
||||||
|
}
|
||||||
|
m.addInputs[m.addFocus].Focus()
|
||||||
|
return m, nil
|
||||||
|
|
||||||
|
case "enter":
|
||||||
|
if m.addFocus < len(m.addInputs)-1 {
|
||||||
|
m.addInputs[m.addFocus].Blur()
|
||||||
|
m.addFocus++
|
||||||
|
m.addInputs[m.addFocus].Focus()
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if m.view == ViewAddServer {
|
||||||
|
err = m.saveNewServer()
|
||||||
|
} else {
|
||||||
|
err = m.saveEditedServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
m.err = err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd tea.Cmd
|
||||||
|
m.addInputs[m.addFocus], cmd = m.addInputs[m.addFocus].Update(msg)
|
||||||
|
return m, cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.view == ViewDeleteConfirm {
|
||||||
|
switch msg.String() {
|
||||||
|
case "y", "Y", "j":
|
||||||
|
err := m.deleteSelectedServer()
|
||||||
|
if err != nil {
|
||||||
|
m.err = err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
|
||||||
|
case "n", "N", "esc", "q":
|
||||||
|
m.view = ViewServers
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
case "ctrl+c":
|
case "ctrl+c":
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
@@ -122,6 +281,26 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
case "tab":
|
case "tab":
|
||||||
m.view = nextView(m.view)
|
m.view = nextView(m.view)
|
||||||
|
|
||||||
|
case "a":
|
||||||
|
m.view = ViewAddServer
|
||||||
|
m.addFocus = 0
|
||||||
|
m.initAddInputs()
|
||||||
|
return m, nil
|
||||||
|
|
||||||
|
case "e":
|
||||||
|
if m.view == ViewServers && len(m.servers) > 0 {
|
||||||
|
m.editIndex = m.selected
|
||||||
|
m.view = ViewEditServer
|
||||||
|
m.loadServerIntoForm(m.servers[m.selected])
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
|
||||||
|
case "d":
|
||||||
|
if m.view == ViewServers && len(m.servers) > 0 {
|
||||||
|
m.view = ViewDeleteConfirm
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
|
||||||
case "s":
|
case "s":
|
||||||
m.view = ViewSettings
|
m.view = ViewSettings
|
||||||
|
|
||||||
@@ -182,6 +361,12 @@ func (m model) View() string {
|
|||||||
right = m.renderSettingsContent()
|
right = m.renderSettingsContent()
|
||||||
case ViewHelp:
|
case ViewHelp:
|
||||||
right = m.renderHelpContent()
|
right = m.renderHelpContent()
|
||||||
|
case ViewAddServer:
|
||||||
|
right = m.renderAddServerContent()
|
||||||
|
case ViewEditServer:
|
||||||
|
right = m.renderEditServerContent()
|
||||||
|
case ViewDeleteConfirm:
|
||||||
|
right = m.renderDeleteConfirmContent()
|
||||||
default:
|
default:
|
||||||
right = m.renderServerList()
|
right = m.renderServerList()
|
||||||
}
|
}
|
||||||
@@ -193,7 +378,7 @@ func (m model) View() string {
|
|||||||
right,
|
right,
|
||||||
)
|
)
|
||||||
|
|
||||||
footer := helpStyle.Render("↑/↓ Auswahl Enter Verbinden Tab Ansicht wechseln s Settings h Hilfe q Zurück/Beenden")
|
footer := helpStyle.Render("↑/↓ Auswahl Enter Verbinden a Hinzufügen e Editieren d Löschen Tab Ansicht q Zurück/Beenden")
|
||||||
|
|
||||||
content := lipgloss.JoinVertical(
|
content := lipgloss.JoinVertical(
|
||||||
lipgloss.Left,
|
lipgloss.Left,
|
||||||
@@ -233,6 +418,7 @@ func (m model) renderNavigation() string {
|
|||||||
view viewMode
|
view viewMode
|
||||||
}{
|
}{
|
||||||
{"", "Server", ViewServers},
|
{"", "Server", ViewServers},
|
||||||
|
{"", "Add Server", ViewAddServer},
|
||||||
{"", "Settings", ViewSettings},
|
{"", "Settings", ViewSettings},
|
||||||
{"", "Help", ViewHelp},
|
{"", "Help", ViewHelp},
|
||||||
}
|
}
|
||||||
@@ -448,6 +634,179 @@ func max(a, b int) int {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (m *model) loadServerIntoForm(server models.Server) {
|
||||||
|
m.initAddInputs()
|
||||||
|
|
||||||
|
m.addInputs[0].SetValue(server.Name)
|
||||||
|
m.addInputs[1].SetValue(server.Host)
|
||||||
|
m.addInputs[2].SetValue(server.User)
|
||||||
|
m.addInputs[3].SetValue(strconv.Itoa(server.Port))
|
||||||
|
m.addInputs[4].SetValue(server.Group)
|
||||||
|
m.addInputs[5].SetValue(server.Auth)
|
||||||
|
m.addInputs[6].SetValue(server.Key)
|
||||||
|
m.addInputs[7].SetValue(server.PasswordID)
|
||||||
|
|
||||||
|
m.addFocus = 0
|
||||||
|
m.addInputs[0].Focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) saveEditedServer() error {
|
||||||
|
if m.editIndex < 0 || m.editIndex >= len(m.servers) {
|
||||||
|
return fmt.Errorf("ungültiger server index")
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(m.addInputs[3].Value())
|
||||||
|
if err != nil {
|
||||||
|
port = 22
|
||||||
|
}
|
||||||
|
|
||||||
|
server := models.Server{
|
||||||
|
Name: m.addInputs[0].Value(),
|
||||||
|
Host: m.addInputs[1].Value(),
|
||||||
|
User: m.addInputs[2].Value(),
|
||||||
|
Port: port,
|
||||||
|
Group: m.addInputs[4].Value(),
|
||||||
|
Auth: m.addInputs[5].Value(),
|
||||||
|
Key: m.addInputs[6].Value(),
|
||||||
|
PasswordID: m.addInputs[7].Value(),
|
||||||
|
KittyFix: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.Name == "" || server.Host == "" || server.User == "" {
|
||||||
|
return fmt.Errorf("name, host und user sind pflichtfelder")
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.Auth == "" {
|
||||||
|
server.Auth = "key"
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.Auth == "password" && server.PasswordID == "" {
|
||||||
|
server.PasswordID = strings.ToLower(server.Name) + "-" + server.User
|
||||||
|
}
|
||||||
|
|
||||||
|
m.cfg.Servers[m.editIndex] = server
|
||||||
|
m.servers = m.cfg.Servers
|
||||||
|
|
||||||
|
err = config.SaveConfig("config.yaml", m.cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.selected = m.editIndex
|
||||||
|
m.view = ViewServers
|
||||||
|
m.initAddInputs()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) deleteSelectedServer() error {
|
||||||
|
if m.selected < 0 || m.selected >= len(m.servers) {
|
||||||
|
return fmt.Errorf("kein gültiger server ausgewählt")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.cfg.Servers = append(
|
||||||
|
m.cfg.Servers[:m.selected],
|
||||||
|
m.cfg.Servers[m.selected+1:]...,
|
||||||
|
)
|
||||||
|
|
||||||
|
m.servers = m.cfg.Servers
|
||||||
|
|
||||||
|
if m.selected >= len(m.servers) && m.selected > 0 {
|
||||||
|
m.selected--
|
||||||
|
}
|
||||||
|
|
||||||
|
err := config.SaveConfig("config.yaml", m.cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.view = ViewServers
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (m model) renderEditServerContent() string {
|
||||||
|
return m.renderServerFormContent(" EDIT SERVER ", "Server bearbeiten")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) renderAddServerContent() string {
|
||||||
|
return m.renderServerFormContent(" ADD SERVER ", "Neuen SSH-Server hinzufügen")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) renderServerFormContent(badge string, title string) string {
|
||||||
|
lines := []string{
|
||||||
|
badgeStyle.Render(badge),
|
||||||
|
"",
|
||||||
|
title,
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := []string{
|
||||||
|
"Name",
|
||||||
|
"Host",
|
||||||
|
"User",
|
||||||
|
"Port",
|
||||||
|
"Group",
|
||||||
|
"Auth",
|
||||||
|
"Key Path",
|
||||||
|
"Password ID",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, input := range m.addInputs {
|
||||||
|
label := labels[i]
|
||||||
|
|
||||||
|
if i == m.addFocus {
|
||||||
|
lines = append(lines, selectedStyle.Render("> "+label))
|
||||||
|
} else {
|
||||||
|
lines = append(lines, normalStyle.Render(" "+label))
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, " "+input.View())
|
||||||
|
lines = append(lines, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, mutedStyle.Render("Enter: nächstes Feld / speichern Tab: weiter Esc/q: abbrechen"))
|
||||||
|
lines = append(lines, mutedStyle.Render("Auth: key oder password"))
|
||||||
|
|
||||||
|
return panelStyle.
|
||||||
|
Width(max(m.width-36, 60)).
|
||||||
|
Height(max(m.height-9, 18)).
|
||||||
|
Render(strings.Join(lines, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) renderDeleteConfirmContent() string {
|
||||||
|
if len(m.servers) == 0 {
|
||||||
|
return panelStyle.Render("Kein Server zum Löschen vorhanden.")
|
||||||
|
}
|
||||||
|
|
||||||
|
server := m.servers[m.selected]
|
||||||
|
|
||||||
|
lines := []string{
|
||||||
|
badgeStyle.Render(" DELETE SERVER "),
|
||||||
|
"",
|
||||||
|
warnStyle().Render("Diesen Server wirklich löschen?"),
|
||||||
|
"",
|
||||||
|
fmt.Sprintf("Name: %s", server.Name),
|
||||||
|
fmt.Sprintf("Host: %s@%s:%d", server.User, server.Host, server.Port),
|
||||||
|
fmt.Sprintf("Group: %s", server.Group),
|
||||||
|
"",
|
||||||
|
selectedStyle.Render("y / j = löschen"),
|
||||||
|
mutedStyle.Render("n / q / Esc = abbrechen"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return panelStyle.
|
||||||
|
Width(max(m.width-36, 60)).
|
||||||
|
Height(max(m.height-9, 14)).
|
||||||
|
Render(strings.Join(lines, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnStyle() lipgloss.Style {
|
||||||
|
return lipgloss.NewStyle().
|
||||||
|
Foreground(warn).
|
||||||
|
Bold(true)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
p := tea.NewProgram(
|
p := tea.NewProgram(
|
||||||
initialModel(),
|
initialModel(),
|
||||||
|
|||||||
Reference in New Issue
Block a user