mirror of
https://github.com/ditatompel/xmr-remote-nodes.git
synced 2025-01-08 05:52:10 +07:00
List Probers CLI command #2
Listing probers is now only available from server CLI. The `ProbersQueryParams` struct also changed. I don't think that I will use more than 20 probers in this project, so paging is not required. The search param also simplified in one field struct `Search" which search both in `name` and `api_key` column.
This commit is contained in:
parent
49b786ed80
commit
59f1dd9421
4 changed files with 81 additions and 141 deletions
65
cmd/probers.go
Normal file
65
cmd/probers.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
"time"
|
||||||
|
"xmr-remote-nodes/internal/database"
|
||||||
|
"xmr-remote-nodes/internal/repo"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var probersCmd = &cobra.Command{
|
||||||
|
Use: "probers [search]",
|
||||||
|
Short: "[server] Get registered probers",
|
||||||
|
Long: `Get list of registered prober machines.
|
||||||
|
|
||||||
|
Use [search] args to filter results by name or api key.
|
||||||
|
|
||||||
|
"sort-by" flag can be "id" or "last_submit_ts"
|
||||||
|
"sort-dir" flag can be "asc" or "desc"`,
|
||||||
|
Example: `# To sort probers by last submit time in ascending order that contains "sin1":
|
||||||
|
xmr-nodes probers -s last_submit_ts -d asc sin1`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if err := database.ConnectDB(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
sortBy, _ := cmd.Flags().GetString("sort-by")
|
||||||
|
sortDir, _ := cmd.Flags().GetString("sort-dir")
|
||||||
|
|
||||||
|
probersRepo := repo.NewProberRepo(database.GetDB())
|
||||||
|
probers, err := probersRepo.Probers(repo.ProbersQueryParams{
|
||||||
|
Search: strings.Join(args, " "),
|
||||||
|
SortBy: sortBy,
|
||||||
|
SortDirection: sortDir,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(probers) == 0 {
|
||||||
|
fmt.Println("No probers found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
|
||||||
|
fmt.Fprintf(w, "ID\t| Name\t| Last Submit\t| API Key\n")
|
||||||
|
for _, prober := range probers {
|
||||||
|
fmt.Fprintf(w, "%d\t| %s\t| %s\t| %s\n",
|
||||||
|
prober.Id,
|
||||||
|
prober.Name,
|
||||||
|
time.Unix(prober.LastSubmitTs, 0).Format(time.RFC3339),
|
||||||
|
prober.ApiKey,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(probersCmd)
|
||||||
|
probersCmd.Flags().StringP("sort-by", "s", "last_submit_ts", "Sort by column name, can be id or last_submit_ts")
|
||||||
|
probersCmd.Flags().StringP("sort-dir", "d", "desc", "Sort direction, can be asc or desc")
|
||||||
|
}
|
|
@ -60,101 +60,6 @@ func Logout(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Prober(c *fiber.Ctx) error {
|
|
||||||
proberRepo := repo.NewProberRepo(database.GetDB())
|
|
||||||
|
|
||||||
if c.Method() == "POST" {
|
|
||||||
payload := repo.Prober{}
|
|
||||||
if err := c.BodyParser(&payload); err != nil {
|
|
||||||
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
|
|
||||||
"status": "error",
|
|
||||||
"message": err.Error(),
|
|
||||||
"data": nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if payload.Name == "" {
|
|
||||||
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
|
|
||||||
"status": "error",
|
|
||||||
"message": "Please fill prober name",
|
|
||||||
"data": nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
err := proberRepo.AddProber(payload.Name)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"status": "error",
|
|
||||||
"message": err.Error(),
|
|
||||||
"data": nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if c.Method() == "DELETE" {
|
|
||||||
id, _ := strconv.Atoi(c.Params("id"))
|
|
||||||
err := proberRepo.Delete(id)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"status": "error",
|
|
||||||
"message": err.Error(),
|
|
||||||
"data": nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(fiber.Map{
|
|
||||||
"status": "ok",
|
|
||||||
"message": "Success",
|
|
||||||
"data": nil,
|
|
||||||
})
|
|
||||||
} else if c.Method() == "PATCH" {
|
|
||||||
payload := repo.Prober{}
|
|
||||||
if err := c.BodyParser(&payload); err != nil {
|
|
||||||
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
|
|
||||||
"status": "error",
|
|
||||||
"message": err.Error(),
|
|
||||||
"data": nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if payload.Name == "" {
|
|
||||||
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
|
|
||||||
"status": "error",
|
|
||||||
"message": "Please fill prober name",
|
|
||||||
"data": nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
id, _ := strconv.Atoi(c.Params("id"))
|
|
||||||
err := proberRepo.Update(id, payload.Name)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"status": "error",
|
|
||||||
"message": err.Error(),
|
|
||||||
"data": nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query := repo.ProbersQueryParams{
|
|
||||||
RowsPerPage: c.QueryInt("limit", 10),
|
|
||||||
Page: c.QueryInt("page", 1),
|
|
||||||
SortBy: c.Query("sort_by", "id"),
|
|
||||||
SortDirection: c.Query("sort_direction", "desc"),
|
|
||||||
Name: c.Query("name"),
|
|
||||||
ApiKey: c.Query("api_key"),
|
|
||||||
}
|
|
||||||
|
|
||||||
prober, err := proberRepo.Probers(query)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"status": "error",
|
|
||||||
"message": err.Error(),
|
|
||||||
"data": nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(fiber.Map{
|
|
||||||
"status": "ok",
|
|
||||||
"message": "Success",
|
|
||||||
"data": prober,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func MoneroNode(c *fiber.Ctx) error {
|
func MoneroNode(c *fiber.Ctx) error {
|
||||||
nodeId, err := c.ParamsInt("id", 0)
|
nodeId, err := c.ParamsInt("id", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -12,10 +12,6 @@ func AppRoute(app *fiber.App) {
|
||||||
func V1Api(app *fiber.App) {
|
func V1Api(app *fiber.App) {
|
||||||
v1 := app.Group("/api/v1")
|
v1 := app.Group("/api/v1")
|
||||||
|
|
||||||
v1.Get("/prober", CookieProtected, Prober)
|
|
||||||
v1.Post("/prober", CookieProtected, Prober)
|
|
||||||
v1.Patch("/prober/:id", CookieProtected, Prober)
|
|
||||||
v1.Delete("/prober/:id", CookieProtected, Prober)
|
|
||||||
v1.Get("/crons", CookieProtected, Crons)
|
v1.Get("/crons", CookieProtected, Crons)
|
||||||
v1.Get("/nodes", MoneroNodes)
|
v1.Get("/nodes", MoneroNodes)
|
||||||
v1.Post("/nodes", AddNode)
|
v1.Post("/nodes", AddNode)
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
type ProberRepository interface {
|
type ProberRepository interface {
|
||||||
AddProber(name string) error
|
AddProber(name string) error
|
||||||
Update(id int, name string) error
|
Update(id int, name string) error
|
||||||
Probers(q ProbersQueryParams) (Probers, error)
|
Probers(q ProbersQueryParams) ([]Prober, error)
|
||||||
CheckApi(key string) (Prober, error)
|
CheckApi(key string) (Prober, error)
|
||||||
Delete(id int) error
|
Delete(id int) error
|
||||||
}
|
}
|
||||||
|
@ -28,24 +28,6 @@ type Prober struct {
|
||||||
LastSubmitTs int64 `json:"last_submit_ts" db:"last_submit_ts"`
|
LastSubmitTs int64 `json:"last_submit_ts" db:"last_submit_ts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProbersQueryParams struct {
|
|
||||||
Name string
|
|
||||||
ApiKey string
|
|
||||||
|
|
||||||
RowsPerPage int
|
|
||||||
Page int
|
|
||||||
SortBy string
|
|
||||||
SortDirection string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Probers struct {
|
|
||||||
TotalRows int `json:"total_rows"`
|
|
||||||
RowsPerPage int `json:"rows_per_page"`
|
|
||||||
CurrentPage int `json:"current_page"`
|
|
||||||
NextPage int `json:"next_page"`
|
|
||||||
Items []*Prober `json:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProberRepo(db *database.DB) ProberRepository {
|
func NewProberRepo(db *database.DB) ProberRepository {
|
||||||
return &ProberRepo{db}
|
return &ProberRepo{db}
|
||||||
}
|
}
|
||||||
|
@ -71,35 +53,31 @@ func (repo *ProberRepo) Delete(id int) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *ProberRepo) Probers(q ProbersQueryParams) (Probers, error) {
|
type ProbersQueryParams struct {
|
||||||
|
Search string
|
||||||
|
SortBy string
|
||||||
|
SortDirection string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *ProberRepo) Probers(q ProbersQueryParams) ([]Prober, error) {
|
||||||
queryParams := []interface{}{}
|
queryParams := []interface{}{}
|
||||||
whereQueries := []string{}
|
whereQueries := []string{}
|
||||||
where := ""
|
where := ""
|
||||||
|
|
||||||
if q.Name != "" {
|
if q.Search != "" {
|
||||||
whereQueries = append(whereQueries, "name LIKE ?")
|
whereQueries = append(whereQueries, "(name LIKE ? OR api_key LIKE ?)")
|
||||||
queryParams = append(queryParams, "%"+q.Name+"%")
|
queryParams = append(queryParams, "%"+q.Search+"%")
|
||||||
}
|
queryParams = append(queryParams, "%"+q.Search+"%")
|
||||||
if q.ApiKey != "" {
|
|
||||||
whereQueries = append(whereQueries, "api_key LIKE ?")
|
|
||||||
queryParams = append(queryParams, "%"+q.ApiKey+"%")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(whereQueries) > 0 {
|
if len(whereQueries) > 0 {
|
||||||
where = "WHERE " + strings.Join(whereQueries, " AND ")
|
where = "WHERE " + strings.Join(whereQueries, " AND ")
|
||||||
}
|
}
|
||||||
|
|
||||||
probers := Probers{}
|
probers := []Prober{}
|
||||||
queryTotalRows := fmt.Sprintf("SELECT COUNT(id) AS total_rows FROM tbl_prober %s", where)
|
|
||||||
|
|
||||||
err := repo.db.QueryRow(queryTotalRows, queryParams...).Scan(&probers.TotalRows)
|
|
||||||
if err != nil {
|
|
||||||
return probers, err
|
|
||||||
}
|
|
||||||
queryParams = append(queryParams, q.RowsPerPage, (q.Page-1)*q.RowsPerPage)
|
|
||||||
|
|
||||||
allowedSort := []string{"id", "last_submit_ts"}
|
allowedSort := []string{"id", "last_submit_ts"}
|
||||||
sortBy := "id"
|
sortBy := "last_submit_ts"
|
||||||
if slices.Contains(allowedSort, q.SortBy) {
|
if slices.Contains(allowedSort, q.SortBy) {
|
||||||
sortBy = q.SortBy
|
sortBy = q.SortBy
|
||||||
}
|
}
|
||||||
|
@ -108,7 +86,7 @@ func (repo *ProberRepo) Probers(q ProbersQueryParams) (Probers, error) {
|
||||||
sortDirection = "ASC"
|
sortDirection = "ASC"
|
||||||
}
|
}
|
||||||
|
|
||||||
query := fmt.Sprintf("SELECT id, name, api_key, last_submit_ts FROM tbl_prober %s ORDER BY %s %s LIMIT ? OFFSET ?", where, sortBy, sortDirection)
|
query := fmt.Sprintf("SELECT id, name, api_key, last_submit_ts FROM tbl_prober %s ORDER BY %s %s", where, sortBy, sortDirection)
|
||||||
|
|
||||||
row, err := repo.db.Query(query, queryParams...)
|
row, err := repo.db.Query(query, queryParams...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -116,17 +94,13 @@ func (repo *ProberRepo) Probers(q ProbersQueryParams) (Probers, error) {
|
||||||
}
|
}
|
||||||
defer row.Close()
|
defer row.Close()
|
||||||
|
|
||||||
probers.RowsPerPage = q.RowsPerPage
|
|
||||||
probers.CurrentPage = q.Page
|
|
||||||
probers.NextPage = q.Page + 1
|
|
||||||
|
|
||||||
for row.Next() {
|
for row.Next() {
|
||||||
prober := Prober{}
|
prober := Prober{}
|
||||||
err = row.Scan(&prober.Id, &prober.Name, &prober.ApiKey, &prober.LastSubmitTs)
|
err = row.Scan(&prober.Id, &prober.Name, &prober.ApiKey, &prober.LastSubmitTs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return probers, err
|
return probers, err
|
||||||
}
|
}
|
||||||
probers.Items = append(probers.Items, &prober)
|
probers = append(probers, prober)
|
||||||
}
|
}
|
||||||
return probers, nil
|
return probers, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue