From 8322fe4aa197d2e60ab0ac63f3f9a87f632d05c3 Mon Sep 17 00:00:00 2001 From: ross96D Date: Mon, 11 Nov 2024 13:28:28 -0500 Subject: [PATCH] add client get config and reload config --- cmd/client/api/api.go | 93 +++++++++++++++++---- cmd/client/components/input_text/input.go | 61 ++++++++++++++ cmd/client/state/state.go | 25 ++++++ cmd/client/views/home.go | 99 +++++++++++++++++++++++ 4 files changed, 264 insertions(+), 14 deletions(-) create mode 100644 cmd/client/components/input_text/input.go diff --git a/cmd/client/api/api.go b/cmd/client/api/api.go index 43005a7..38d7467 100644 --- a/cmd/client/api/api.go +++ b/cmd/client/api/api.go @@ -34,6 +34,15 @@ func (err ErrNetworkMsg) Error() string { return ErrNetwork(err).Error() } +func CheckStatus(response *http.Response) error { + if response.StatusCode < 400 { + return nil + } + defer response.Body.Close() + errstr, _ := io.ReadAll(response.Body) + return fmt.Errorf("status: %d - %s", response.StatusCode, errstr) +} + func Request(method, url string, body io.Reader) (*http.Request, error) { request, err := http.NewRequestWithContext(context.Background(), method, url, body) request.Header.Set("User-Agent", "deplo-client") @@ -125,6 +134,10 @@ type Session struct { token []byte } +func (session Session) ServerName() string { + return session.servername +} + func (session Session) List() (server user_handler.Server, err error) { defer func() { if err != nil { @@ -143,18 +156,13 @@ func (session Session) List() (server user_handler.Server, err error) { } request.Header.Add("Authorization", "Bearer "+string(session.token)) resp, err := HttpClient().Do(request) - if err != nil { return } - b, _ := io.ReadAll(resp.Body) - if resp.StatusCode > 400 { - if b == nil { - b = []byte("") - } - err = fmt.Errorf("status: %d - %s", resp.StatusCode, string(b)) + if err = CheckStatus(resp); err != nil { return } + b, _ := io.ReadAll(resp.Body) err = json.Unmarshal(b, &server) return } @@ -179,13 +187,13 @@ func (session Session) Upgrade() (response string, err error) { if err != nil { return } + if err = CheckStatus(resp); err != nil { + return + } b, err := io.ReadAll(resp.Body) if err != nil { return } - if resp.StatusCode > 400 { - err = fmt.Errorf("status: %d - %s", resp.StatusCode, string(b)) - } response = string(b) return } @@ -217,10 +225,8 @@ func (session Session) Update(app user_handler.App, dryRun bool) (_ io.ReadClose if err != nil { return nil, fmt.Errorf("doing request %w", err) } - if resp.StatusCode >= 400 { - b, _ := io.ReadAll(resp.Body) - resp.Body.Close() - err = fmt.Errorf("status: %d - %s", resp.StatusCode, string(b)) + + if err = CheckStatus(resp); err != nil { return nil, err } return resp.Body, nil @@ -235,3 +241,62 @@ func (session Session) IsValid() bool { pretty.Print("token expiration", token.Expiration().String(), time.Now().String()) return time.Until(token.Expiration()) > time.Minute } + +func (session Session) Config() (response io.ReadCloser, err error) { + defer func() { + if err != nil { + err = ErrNetworkMsg{ + ServerName: session.servername, + Message: err.Error(), + } + } + }() + uri := session.url.JoinPath("config") + + request, err := Request(http.MethodGet, uri.String(), nil) + if err != nil { + return + } + request.Header.Add("Authorization", "Bearer "+string(session.token)) + resp, err := HttpClient().Do(request) + if err != nil { + return + } + if err = CheckStatus(resp); err != nil { + return + } + response = resp.Body + return +} + +func (session Session) Reload(data io.Reader) (response string, err error) { + defer func() { + if err != nil { + err = ErrNetworkMsg{ + ServerName: session.servername, + Message: err.Error(), + } + } + }() + uri := session.url.JoinPath("reload") + + request, err := Request(http.MethodPost, uri.String(), data) + if err != nil { + return + } + request.Header.Add("Authorization", "Bearer "+string(session.token)) + + resp, err := HttpClient().Do(request) + if err != nil { + return + } + if err = CheckStatus(resp); err != nil { + return + } + respstr, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + response = string(respstr) + return +} diff --git a/cmd/client/components/input_text/input.go b/cmd/client/components/input_text/input.go new file mode 100644 index 0000000..d51f0bd --- /dev/null +++ b/cmd/client/components/input_text/input.go @@ -0,0 +1,61 @@ +package input_text + +import ( + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/ross96D/updater/cmd/client/components" + "github.com/ross96D/updater/cmd/client/pretty" +) + +type Model struct { + Title string + Input textinput.Model + AcceptKey tea.Key + AcceptCmd func(string) tea.Cmd + acceptCmd func(string) tea.Cmd +} + +func (m *Model) Init() tea.Cmd { + if m.AcceptKey.Type == tea.KeyRunes { + panic("AcceptKey type cannot be of type runes") + } + m.acceptCmd = nil + m.Input = textinput.New() + return m.Input.Focus() +} + +func (m *Model) Enter() tea.Cmd { return nil } +func (m *Model) Out() tea.Cmd { + if m.acceptCmd != nil { + return m.acceptCmd(m.Input.Value()) + } else { + return nil + } +} + +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.Type { + case m.AcceptKey.Type: + m.acceptCmd = m.AcceptCmd + pretty.Print(m.AcceptCmd == nil, m.acceptCmd == nil) + return m, components.NavigatorPop + + case tea.KeyCtrlC: + return m, tea.Quit + + case tea.KeyRunes: + if msg.String() == "q" { + return m, tea.Quit + } + } + } + var cmd tea.Cmd + m.Input, cmd = m.Input.Update(msg) + return m, cmd +} + +func (m *Model) View() string { + return m.Title + "\n" + m.Input.View() +} diff --git a/cmd/client/state/state.go b/cmd/client/state/state.go index 9fde67c..6037c35 100644 --- a/cmd/client/state/state.go +++ b/cmd/client/state/state.go @@ -127,6 +127,31 @@ var ErrFetchFailCmd = func(name string, err error) tea.Cmd { } } +func (gs *GlobalState) FetchCmdBy(server models.Server) tea.Cmd { + if gs.servers == nil { + gs.servers = &[]models.Server{} + } + f := func(server models.Server) tea.Cmd { + return func() tea.Msg { + session, err := api.NewSession(server) + if err != nil { + return ErrFetchFailCmd(server.ServerName, err) + } + s, err := session.List() + if err != nil { + return ErrFetchFailCmd(server.ServerName, err) + } + return FetchResultMsg{ServerName: server.ServerName, Server: s} + } + } + for _, s := range *gs.servers { + if s.ServerName == server.ServerName { + return f(server) + } + } + return nil +} + func (gs *GlobalState) FetchCmd() tea.Cmd { if gs.servers == nil { gs.servers = &[]models.Server{} diff --git a/cmd/client/views/home.go b/cmd/client/views/home.go index c2c34e5..e1d5da4 100644 --- a/cmd/client/views/home.go +++ b/cmd/client/views/home.go @@ -2,13 +2,16 @@ package views import ( "fmt" + "io" "net/url" + "os" "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/ross96D/updater/cmd/client/api" "github.com/ross96D/updater/cmd/client/components" "github.com/ross96D/updater/cmd/client/components/confirmation_dialog" + "github.com/ross96D/updater/cmd/client/components/input_text" "github.com/ross96D/updater/cmd/client/components/list" "github.com/ross96D/updater/cmd/client/components/toast" "github.com/ross96D/updater/cmd/client/models" @@ -23,6 +26,10 @@ type homeAskDeleteSelectedMsg struct{} type homeDeleteSelectedMsg struct{} type homeAskUpgradeSelectedMsg struct{} type homeUpgradeSelectedMsg struct{} +type homeRequestConfigMsg struct{} +type homeConfigSelectedMsg struct{ string } +type homeRequestReloadMsg struct{} +type homeReloadSelectedMsg struct{ string } var homeViewInitializeMsg = func() tea.Msg { return homeViewInitialize{} } var homeViewSelectItemMsg = func() tea.Msg { return homeViewSelectItem{} } @@ -31,6 +38,10 @@ var homeAskDeleteSelectedCmd = func() tea.Msg { return homeAskDeleteSelectedMsg{ var homeDeleteSelectedCmd = func() tea.Msg { return homeDeleteSelectedMsg{} } var homeAskUpgradeSelectedCmd = func() tea.Msg { return homeAskUpgradeSelectedMsg{} } var homeUpgradeSelectedCmd = func() tea.Msg { return homeUpgradeSelectedMsg{} } +var homeRequestConfigCmd = func() tea.Msg { return homeRequestConfigMsg{} } +var homeConfigSelectedCmd = func(path string) tea.Cmd { return func() tea.Msg { return homeConfigSelectedMsg{path} } } +var homeRequestReloadCmd = func() tea.Msg { return homeRequestReloadMsg{} } +var homeReloadSelectedCmd = func(path string) tea.Cmd { return func() tea.Msg { return homeReloadSelectedMsg{path} } } type HomeView struct { State *state.GlobalState @@ -138,6 +149,76 @@ func (hv HomeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return hv, tea.Sequence(addToastCmd, upgCmd) + case homeRequestConfigMsg: + return hv, components.NavigatorPush(&input_text.Model{ + Title: "downloaded configuration file name", + AcceptKey: tea.Key{Type: tea.KeyEnter}, + AcceptCmd: homeConfigSelectedCmd, + }) + + case homeConfigSelectedMsg: + item, ok := hv.list.Selected() + if !ok { + return hv, nil + } + configCmd := func() tea.Msg { + server := *item.Value + session, err := api.NewSession(server) + if err != nil { + return state.ErrFetchFailMsg{ServerName: server.ServerName, Err: err} + } + resp, err := session.Config() + if err != nil { + return state.ErrFetchFailMsg{ServerName: server.ServerName, Err: err} + } + defer resp.Close() + f, err := os.Create(msg.string) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(f, resp) + if err != nil { + return state.ErrFetchFailMsg{ServerName: server.ServerName, Err: fmt.Errorf("io.Copy %w", err)} + } + return toast.AddToastMsg(toast.New("config saved to " + msg.string)) + } + return hv, configCmd + + case homeRequestReloadMsg: + return hv, components.NavigatorPush(&input_text.Model{ + Title: "downloaded configuration file name", + AcceptKey: tea.Key{Type: tea.KeyEnter}, + AcceptCmd: homeReloadSelectedCmd, + }) + + case homeReloadSelectedMsg: + item, ok := hv.list.Selected() + if !ok { + return hv, nil + } + reloadCmd := func() tea.Msg { + server := *item.Value + session, err := api.NewSession(server) + if err != nil { + return state.ErrFetchFailMsg{ServerName: server.ServerName, Err: err} + } + f, err := os.Open(msg.string) + if err != nil { + return err + } + + _, err = session.Reload(f) + if err != nil { + return state.ErrFetchFailMsg{ServerName: server.ServerName, Err: err} + } + return tea.Batch( + state.Configuration().State.FetchCmdBy(server), + components.MsgCmd(toast.AddToastMsg(toast.New("config reloaded from "+msg.string))), + ) + } + return hv, reloadCmd + case state.GlobalStateSyncMsg: hv.init() cmd = tea.WindowSize() @@ -231,6 +312,24 @@ func (hv *HomeView) init() { return homeAskUpgradeSelectedCmd }, }, + { + Key: key.NewBinding( + key.WithKeys("c", "C"), + key.WithHelp("c", "updgrade server"), + ), + Action: func() tea.Cmd { + return homeRequestConfigCmd + }, + }, + { + Key: key.NewBinding( + key.WithKeys("r", "R"), + key.WithHelp("r", "updgrade server"), + ), + Action: func() tea.Cmd { + return homeRequestReloadCmd + }, + }, }, }, true,