From d979c537fe325838924d48b8dcc0d1a7befbd963 Mon Sep 17 00:00:00 2001 From: Pavlo Golub Date: Wed, 15 Feb 2023 16:16:30 +0100 Subject: [PATCH 1/4] [+] add `GET /metric` endpoint, closes #93 --- src/api.go | 7 ++++++ src/webserver/restapi.go | 46 ++++++++++++++++++++++++++++++++++++++++ src/webserver/server.go | 2 ++ 3 files changed, 55 insertions(+) diff --git a/src/api.go b/src/api.go index fd0bb2386..44f1d3553 100644 --- a/src/api.go +++ b/src/api.go @@ -8,6 +8,13 @@ type uiapihandler struct{} var uiapi uiapihandler +// GetMetrics returns the list of monitored databases +func (uiapi uiapihandler) GetMetrics() (res string, err error) { + sql := `select coalesce(jsonb_agg(to_jsonb(m)), '[]') from metric m` + err = configDb.Get(&res, sql) + return +} + // GetDatabases returns the list of monitored databases func (uiapi uiapihandler) GetDatabases() (res string, err error) { sql := `select coalesce(jsonb_agg(to_jsonb(db)), '[]') from monitored_db db` diff --git a/src/webserver/restapi.go b/src/webserver/restapi.go index 23bcdf3b8..7bd429598 100644 --- a/src/webserver/restapi.go +++ b/src/webserver/restapi.go @@ -5,6 +5,52 @@ import ( "net/http" ) +func (Server *WebUIServer) handleMetrics(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + // return stored metrics + dbs, err := Server.api.GetMetrics() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + _, _ = w.Write([]byte(dbs)) + + // case http.MethodPost: + // // add new monitored database + // p, err := io.ReadAll(r.Body) + // if err != nil { + // http.Error(w, err.Error(), http.StatusBadRequest) + // } + // if err := Server.api.AddDatabase(p); err != nil { + // http.Error(w, err.Error(), http.StatusBadRequest) + // } + // case http.MethodPatch: + // // update monitored database + // p, err := io.ReadAll(r.Body) + // if err != nil { + // http.Error(w, err.Error(), http.StatusBadRequest) + // } + // if err := Server.api.UpdateDatabase(r.URL.Query().Get("id"), p); err != nil { + // http.Error(w, err.Error(), http.StatusBadRequest) + // } + + // case http.MethodDelete: + // // delete monitored database + // if err := Server.api.DeleteDatabase(r.URL.Query().Get("id")); err != nil { + // http.Error(w, err.Error(), http.StatusBadRequest) + // } + + case http.MethodOptions: + w.Header().Set("Allow", "GET, POST, PATCH, DELETE, OPTIONS") + w.WriteHeader(http.StatusNoContent) + + default: + w.Header().Set("Allow", "GET, POST, PATCH, DELETE, OPTIONS") + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + } +} + func (Server *WebUIServer) handleDBs(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: diff --git a/src/webserver/server.go b/src/webserver/server.go index 4a6a3824e..4b3d6d32f 100644 --- a/src/webserver/server.go +++ b/src/webserver/server.go @@ -19,6 +19,7 @@ type apiHandler interface { AddDatabase(params []byte) error DeleteDatabase(id string) error UpdateDatabase(id string, params []byte) error + GetMetrics() (res string, err error) } type WebUIServer struct { @@ -50,6 +51,7 @@ func Init(addr string, webuifs fs.FS, api apiHandler) *WebUIServer { mux.HandleFunc("/health", s.handleHealth) mux.HandleFunc("/db", s.handleDBs) + mux.HandleFunc("/metric", s.handleMetrics) mux.HandleFunc("/", s.handleStatic) if 8080 != 0 { From e0173fca5faf7c2eb10b8ed8efde3681a574e5cb Mon Sep 17 00:00:00 2001 From: Pavlo Golub Date: Wed, 15 Feb 2023 16:30:30 +0100 Subject: [PATCH 2/4] [+] add `DELETE /metric` endpoint, closes #97 --- src/api.go | 6 ++++++ src/webserver/restapi.go | 16 +++++++++++----- src/webserver/server.go | 1 + 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/api.go b/src/api.go index 44f1d3553..a20dccbf9 100644 --- a/src/api.go +++ b/src/api.go @@ -15,6 +15,12 @@ func (uiapi uiapihandler) GetMetrics() (res string, err error) { return } +// DeleteMetric removes the database from the list of monitored databases +func (uiapi uiapihandler) DeleteMetric(id int) error { + _, err := configDb.Exec("DELETE FROM pgwatch3.metric WHERE m_id = $1", id) + return err +} + // GetDatabases returns the list of monitored databases func (uiapi uiapihandler) GetDatabases() (res string, err error) { sql := `select coalesce(jsonb_agg(to_jsonb(db)), '[]') from monitored_db db` diff --git a/src/webserver/restapi.go b/src/webserver/restapi.go index 7bd429598..7c5a923a9 100644 --- a/src/webserver/restapi.go +++ b/src/webserver/restapi.go @@ -3,6 +3,7 @@ package webserver import ( "io" "net/http" + "strconv" ) func (Server *WebUIServer) handleMetrics(w http.ResponseWriter, r *http.Request) { @@ -35,11 +36,16 @@ func (Server *WebUIServer) handleMetrics(w http.ResponseWriter, r *http.Request) // http.Error(w, err.Error(), http.StatusBadRequest) // } - // case http.MethodDelete: - // // delete monitored database - // if err := Server.api.DeleteDatabase(r.URL.Query().Get("id")); err != nil { - // http.Error(w, err.Error(), http.StatusBadRequest) - // } + case http.MethodDelete: + // delete stored metric + id, err := strconv.Atoi(r.URL.Query().Get("id")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if err := Server.api.DeleteMetric(id); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } case http.MethodOptions: w.Header().Set("Allow", "GET, POST, PATCH, DELETE, OPTIONS") diff --git a/src/webserver/server.go b/src/webserver/server.go index 4b3d6d32f..527a9aee0 100644 --- a/src/webserver/server.go +++ b/src/webserver/server.go @@ -20,6 +20,7 @@ type apiHandler interface { DeleteDatabase(id string) error UpdateDatabase(id string, params []byte) error GetMetrics() (res string, err error) + DeleteMetric(id int) error } type WebUIServer struct { From 5917c0640bc770bcff56ef02957f0feef441003c Mon Sep 17 00:00:00 2001 From: Pavlo Golub Date: Wed, 15 Feb 2023 16:40:00 +0100 Subject: [PATCH 3/4] [+] add `POST /metric` endpoint, closes #95 --- src/api.go | 17 +++++++++++++++++ src/webserver/restapi.go | 19 ++++++++++--------- src/webserver/server.go | 1 + 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/api.go b/src/api.go index a20dccbf9..86964447b 100644 --- a/src/api.go +++ b/src/api.go @@ -21,6 +21,23 @@ func (uiapi uiapihandler) DeleteMetric(id int) error { return err } +// AddMetric adds the metric to the list of stored metrics +func (uiapi uiapihandler) AddMetric(params []byte) error { + sql := `INSERT INTO pgwatch3.metric( +m_name, m_pg_version_from, m_sql, m_comment, m_is_active, m_is_helper, +m_master_only, m_standby_only, m_column_attrs, m_sql_su) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)` + var m map[string]any + err := json.Unmarshal(params, &m) + if err == nil { + _, err = configDb.Exec(sql, m["m_name"], m["m_pg_version_from"], + m["m_sql"], m["m_comment"], m["m_is_active"], + m["m_is_helper"], m["m_master_only"], m["m_standby_only"], + m["m_column_attrs"], m["m_sql_su"]) + } + return err +} + // GetDatabases returns the list of monitored databases func (uiapi uiapihandler) GetDatabases() (res string, err error) { sql := `select coalesce(jsonb_agg(to_jsonb(db)), '[]') from monitored_db db` diff --git a/src/webserver/restapi.go b/src/webserver/restapi.go index 7c5a923a9..9cf7a8b2b 100644 --- a/src/webserver/restapi.go +++ b/src/webserver/restapi.go @@ -17,15 +17,16 @@ func (Server *WebUIServer) handleMetrics(w http.ResponseWriter, r *http.Request) } _, _ = w.Write([]byte(dbs)) - // case http.MethodPost: - // // add new monitored database - // p, err := io.ReadAll(r.Body) - // if err != nil { - // http.Error(w, err.Error(), http.StatusBadRequest) - // } - // if err := Server.api.AddDatabase(p); err != nil { - // http.Error(w, err.Error(), http.StatusBadRequest) - // } + case http.MethodPost: + // add new stored metric + p, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } + if err := Server.api.AddMetric(p); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } + // case http.MethodPatch: // // update monitored database // p, err := io.ReadAll(r.Body) diff --git a/src/webserver/server.go b/src/webserver/server.go index 527a9aee0..762a0eb64 100644 --- a/src/webserver/server.go +++ b/src/webserver/server.go @@ -20,6 +20,7 @@ type apiHandler interface { DeleteDatabase(id string) error UpdateDatabase(id string, params []byte) error GetMetrics() (res string, err error) + AddMetric(params []byte) error DeleteMetric(id int) error } From 2414a5ba6e0d2b0cce5680a26b55fc17c73f94f5 Mon Sep 17 00:00:00 2001 From: Pavlo Golub Date: Wed, 15 Feb 2023 17:13:14 +0100 Subject: [PATCH 4/4] [+] add `PATCH /metric` endpoint, closes #96 --- src/api.go | 18 +++++++++++++ src/webserver/restapi.go | 56 ++++++++++++++++++++++------------------ src/webserver/server.go | 1 + 3 files changed, 50 insertions(+), 25 deletions(-) diff --git a/src/api.go b/src/api.go index 86964447b..1fae02c80 100644 --- a/src/api.go +++ b/src/api.go @@ -38,6 +38,24 @@ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)` return err } +// UpdateMetric updates the stored metric information +func (uiapi uiapihandler) UpdateMetric(id int, params []byte) error { + sql := `UPDATE pgwatch3.metric( +m_name, m_pg_version_from, m_sql, m_comment, m_is_active, m_is_helper, +m_master_only, m_standby_only, m_column_attrs, m_sql_su) += ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) +WHERE m_id = $11` + var m map[string]any + err := json.Unmarshal(params, &m) + if err == nil { + _, err = configDb.Exec(sql, m["m_name"], m["m_pg_version_from"], + m["m_sql"], m["m_comment"], m["m_is_active"], + m["m_is_helper"], m["m_master_only"], m["m_standby_only"], + m["m_column_attrs"], m["m_sql_su"], id) + } + return err +} + // GetDatabases returns the list of monitored databases func (uiapi uiapihandler) GetDatabases() (res string, err error) { sql := `select coalesce(jsonb_agg(to_jsonb(db)), '[]') from monitored_db db` diff --git a/src/webserver/restapi.go b/src/webserver/restapi.go index 9cf7a8b2b..399720e49 100644 --- a/src/webserver/restapi.go +++ b/src/webserver/restapi.go @@ -7,46 +7,50 @@ import ( ) func (Server *WebUIServer) handleMetrics(w http.ResponseWriter, r *http.Request) { + var ( + err error + params []byte + res string + id int + ) + + defer func() { + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }() + switch r.Method { case http.MethodGet: // return stored metrics - dbs, err := Server.api.GetMetrics() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + if res, err = Server.api.GetMetrics(); err != nil { return } - _, _ = w.Write([]byte(dbs)) + _, err = w.Write([]byte(res)) case http.MethodPost: // add new stored metric - p, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - } - if err := Server.api.AddMetric(p); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) + if params, err = io.ReadAll(r.Body); err != nil { + return } + err = Server.api.AddMetric(params) - // case http.MethodPatch: - // // update monitored database - // p, err := io.ReadAll(r.Body) - // if err != nil { - // http.Error(w, err.Error(), http.StatusBadRequest) - // } - // if err := Server.api.UpdateDatabase(r.URL.Query().Get("id"), p); err != nil { - // http.Error(w, err.Error(), http.StatusBadRequest) - // } + case http.MethodPatch: + // update monitored database + if params, err = io.ReadAll(r.Body); err != nil { + return + } + if id, err = strconv.Atoi(r.URL.Query().Get("id")); err != nil { + return + } + err = Server.api.UpdateMetric(id, params) case http.MethodDelete: // delete stored metric - id, err := strconv.Atoi(r.URL.Query().Get("id")) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) + if id, err = strconv.Atoi(r.URL.Query().Get("id")); err != nil { return } - if err := Server.api.DeleteMetric(id); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - } + err = Server.api.DeleteMetric(id) case http.MethodOptions: w.Header().Set("Allow", "GET, POST, PATCH, DELETE, OPTIONS") @@ -74,6 +78,7 @@ func (Server *WebUIServer) handleDBs(w http.ResponseWriter, r *http.Request) { p, err := io.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) + return } if err := Server.api.AddDatabase(p); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -83,6 +88,7 @@ func (Server *WebUIServer) handleDBs(w http.ResponseWriter, r *http.Request) { p, err := io.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) + return } if err := Server.api.UpdateDatabase(r.URL.Query().Get("id"), p); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) diff --git a/src/webserver/server.go b/src/webserver/server.go index 762a0eb64..4b8a80d02 100644 --- a/src/webserver/server.go +++ b/src/webserver/server.go @@ -22,6 +22,7 @@ type apiHandler interface { GetMetrics() (res string, err error) AddMetric(params []byte) error DeleteMetric(id int) error + UpdateMetric(id int, params []byte) error } type WebUIServer struct {