├── .dockerignore ├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── pkg ├── testdata │ ├── pauseMonitor.json │ ├── startMonitor.json │ ├── monitor_http.txt │ ├── errorResponse.json │ ├── newMonitor.json │ ├── deleteMonitor.json │ ├── monitor_subtype.txt │ ├── ensure.json │ ├── account_template.txt │ ├── monitor_keyword.txt │ ├── monitor_keyword_notexists.txt │ ├── marshal.json │ ├── requestNewMonitor.json │ ├── getAccountDetails.json │ ├── getAlertContacts.json │ ├── unmarshal.json │ ├── getMonitorByID.json │ ├── getMonitorsBySearch.json │ ├── getMonitors.json │ ├── getMonitorsPage1.json │ └── getMonitorsPage2.json ├── alert_contact.go ├── account.go ├── uptimerobot_integration_test.go ├── constants.go ├── monitor.go ├── client.go └── uptimerobot_test.go ├── main.go ├── .gitattributes ├── .gitignore ├── release.sh ├── Dockerfile ├── cmd ├── version.go ├── account.go ├── get.go ├── monitors.go ├── contacts.go ├── delete.go ├── search.go ├── pause.go ├── start.go ├── new.go ├── ensure.go └── root.go ├── go.mod ├── LICENSE └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .gitignore 3 | .git 4 | uptimerobot 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: bitfield 4 | -------------------------------------------------------------------------------- /pkg/testdata/pauseMonitor.json: -------------------------------------------------------------------------------- 1 | { 2 | "stat": "ok", 3 | "monitor": { 4 | "id": 677810870 5 | } 6 | } -------------------------------------------------------------------------------- /pkg/testdata/startMonitor.json: -------------------------------------------------------------------------------- 1 | { 2 | "stat": "ok", 3 | "monitor": { 4 | "id": 677810870 5 | } 6 | } -------------------------------------------------------------------------------- /pkg/testdata/monitor_http.txt: -------------------------------------------------------------------------------- 1 | ID: 777749809 2 | Name: Google 3 | URL: http://www.google.com 4 | Status: Up 5 | Port: 80 6 | Type: HTTP -------------------------------------------------------------------------------- /pkg/testdata/errorResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "stat": "false", 3 | "error": { 4 | "message": "Please do not press this button again" 5 | } 6 | } -------------------------------------------------------------------------------- /pkg/testdata/newMonitor.json: -------------------------------------------------------------------------------- 1 | { 2 | "stat": "ok", 3 | "monitor": { 4 | "id": 777810874, 5 | "status": 1, 6 | "type": 1 7 | } 8 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/bitfield/uptimerobot/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /pkg/testdata/deleteMonitor.json: -------------------------------------------------------------------------------- 1 | { 2 | "stat": "ok", 3 | "monitor": { 4 | "id": 777810874, 5 | "status": 1, 6 | "type": 1 7 | } 8 | } -------------------------------------------------------------------------------- /pkg/testdata/monitor_subtype.txt: -------------------------------------------------------------------------------- 1 | ID: 777749812 2 | Name: Google 3 | URL: http://www.google.com 4 | Status: Paused 5 | Port: 80 6 | Type: Port 7 | Subtype: FTP (21) -------------------------------------------------------------------------------- /pkg/testdata/ensure.json: -------------------------------------------------------------------------------- 1 | { 2 | "stat": "ok", 3 | "pagination": { 4 | "offset": 0, 5 | "limit": 50, 6 | "total": 0 7 | }, 8 | "monitors": [] 9 | } -------------------------------------------------------------------------------- /pkg/testdata/account_template.txt: -------------------------------------------------------------------------------- 1 | Email: j.random@example.com 2 | Monitor limit: 300 3 | Monitor interval: 1 4 | Up monitors: 208 5 | Down monitors: 2 6 | Paused monitors: 0 -------------------------------------------------------------------------------- /pkg/testdata/monitor_keyword.txt: -------------------------------------------------------------------------------- 1 | ID: 777749810 2 | Name: Google 3 | URL: http://www.google.com 4 | Status: MaybeDown 5 | Port: 80 6 | Type: Keyword 7 | KeywordType: Exists 8 | Keyword: bogus -------------------------------------------------------------------------------- /pkg/testdata/monitor_keyword_notexists.txt: -------------------------------------------------------------------------------- 1 | ID: 777749811 2 | Name: Google 3 | URL: http://www.google.com 4 | Status: Paused 5 | Port: 80 6 | Type: Keyword 7 | KeywordType: NotExists 8 | Keyword: bogus -------------------------------------------------------------------------------- /pkg/testdata/marshal.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 777749809, 3 | "friendly_name": "Google", 4 | "url": "http://www.google.com", 5 | "type": 1, 6 | "port": 80, 7 | "alert_contacts": "3_0_0-5_0_0-7_0_0" 8 | } -------------------------------------------------------------------------------- /pkg/testdata/requestNewMonitor.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_key": "dummy", 3 | "format": "json", 4 | "friendly_name": "My test monitor", 5 | "url": "http://example.com", 6 | "type": 1, 7 | "port": 80, 8 | "alert_contacts": "3_0_0-5_0_0-7_0_0" 9 | } -------------------------------------------------------------------------------- /pkg/testdata/getAccountDetails.json: -------------------------------------------------------------------------------- 1 | { 2 | "stat": "ok", 3 | "account": { 4 | "email": "test@domain.com", 5 | "monitor_limit": 50, 6 | "monitor_interval": 1, 7 | "up_monitors": 1, 8 | "down_monitors": 0, 9 | "paused_monitors": 2 10 | } 11 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Treat all files in this repo as binary, with no git magic updating line 2 | # endings. Windows users contributing to the project will need to use a modern 3 | # version of git and editors capable of LF line endings. 4 | # 5 | # See https://github.com/golang/go/issues/9281 6 | * -text -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | uptimerobot* 8 | 9 | # Test binary, build with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | .vscode/spellright.dict 15 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | TAG=$1 3 | BUCKET=$2 4 | if [ "${TAG}" == "" ]; then 5 | echo Usage: $0 TAG — for example \'v0.1.0\' 6 | exit 1 7 | fi 8 | go test ./... 9 | OS=linux 10 | ARCH=amd64 11 | BINARY=uptimerobot-${TAG}-${OS}-${ARCH} 12 | GOOS=$OS GOARCH=$ARCH go build -o ./$BINARY 13 | s3cmd put $BINARY $BUCKET 14 | s3cmd setacl $BUCKET/$BINARY --acl-public 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12-alpine AS builder 2 | WORKDIR /src/ 3 | COPY . . 4 | ENV CGO_ENABLED=0 5 | RUN apk --no-cache add git ca-certificates 6 | RUN go test ./... 7 | RUN go build -o /uptimerobot 8 | 9 | FROM scratch 10 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 11 | COPY --from=builder /uptimerobot /uptimerobot 12 | ENTRYPOINT ["/uptimerobot"] 13 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var version = "0.13.2" 10 | 11 | var versionCmd = &cobra.Command{ 12 | Use: "version", 13 | Short: "Show version", 14 | Long: `Show uptimerobot client version`, 15 | Run: func(cmd *cobra.Command, args []string) { 16 | fmt.Println("uptimerobot version", version) 17 | }, 18 | } 19 | 20 | func init() { 21 | RootCmd.AddCommand(versionCmd) 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bitfield/uptimerobot 2 | 3 | require ( 4 | github.com/google/go-cmp v0.5.9 5 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 6 | github.com/magiconair/properties v1.8.7 // indirect 7 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 8 | github.com/spf13/afero v1.9.3 // indirect 9 | github.com/spf13/cobra v1.6.1 10 | github.com/spf13/viper v1.14.0 11 | golang.org/x/sys v0.3.0 // indirect 12 | golang.org/x/text v0.5.0 // indirect 13 | ) 14 | 15 | go 1.13 16 | -------------------------------------------------------------------------------- /pkg/testdata/getAlertContacts.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "stat": "ok", 4 | "limit": 50, 5 | "offset": 0, 6 | "total": 2, 7 | "alert_contacts": [ 8 | { 9 | "id": "0993765", 10 | "friendly_name": "John Doe", 11 | "type": 2, 12 | "status": 1, 13 | "value": "johndoe@gmail.com" 14 | }, 15 | { 16 | "id": "2403924", 17 | "friendly_name": "My Twitter", 18 | "type": 3, 19 | "status": 0, 20 | "value": "sampleTwitterAccount" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /cmd/account.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var accountCmd = &cobra.Command{ 11 | Use: "account", 12 | Short: "get account details", 13 | Long: `Show the account details associated with the API key.`, 14 | Run: func(cmd *cobra.Command, args []string) { 15 | account, err := client.GetAccountDetails() 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | fmt.Println(account) 20 | }, 21 | } 22 | 23 | func init() { 24 | RootCmd.AddCommand(accountCmd) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/testdata/unmarshal.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 777749809, 3 | "friendly_name": "Google", 4 | "url": "http://www.google.com", 5 | "type": 1, 6 | "sub_type": "", 7 | "keyword_type": "", 8 | "keyword_value": "", 9 | "http_username": "", 10 | "http_password": "", 11 | "port": "80", 12 | "interval": 900, 13 | "status": 1, 14 | "create_datetime": 1462565497, 15 | "monitor_group": 0, 16 | "is_group_main": 0, 17 | "logs": [ 18 | { 19 | "type": 98, 20 | "datetime": 1463540297, 21 | "duration": 1054134 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /pkg/alert_contact.go: -------------------------------------------------------------------------------- 1 | package uptimerobot 2 | 3 | // AlertContact represents an alert contact. 4 | type AlertContact struct { 5 | ID string `json:"id"` 6 | FriendlyName string `json:"friendly_name"` 7 | Type int `json:"type"` 8 | Status int `json:"status"` 9 | Value string `json:"value"` 10 | } 11 | 12 | const alertContactTemplate = `ID: {{ .ID }} 13 | Name: {{ .FriendlyName }} 14 | Type: {{ .Type }} 15 | Status: {{ .Status }} 16 | Value: {{ .Value }}` 17 | 18 | // String returns a pretty-printed version of the alert contact. 19 | func (a AlertContact) String() string { 20 | return render(alertContactTemplate, a) 21 | } 22 | -------------------------------------------------------------------------------- /cmd/get.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var getCmd = &cobra.Command{ 12 | Use: "get", 13 | Short: "get monitor by ID", 14 | Long: `Show the monitor details for the specified monitor ID.`, 15 | Args: cobra.ExactArgs(1), 16 | Run: func(cmd *cobra.Command, args []string) { 17 | ID, err := strconv.ParseInt(args[0], 10, 64) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | monitor, err := client.GetMonitor(ID) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | fmt.Println(monitor) 26 | }, 27 | } 28 | 29 | func init() { 30 | RootCmd.AddCommand(getCmd) 31 | } 32 | -------------------------------------------------------------------------------- /cmd/monitors.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var monitorCmd = &cobra.Command{ 11 | Use: "monitors", 12 | Short: "lists monitors", 13 | Long: `Lists all monitors associated with the account`, 14 | Run: func(cmd *cobra.Command, args []string) { 15 | monitors, err := client.AllMonitors() 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | if len(monitors) == 0 { 20 | log.Fatal("No matching monitors found") 21 | } 22 | for _, m := range monitors { 23 | fmt.Println(m) 24 | fmt.Println() 25 | } 26 | }, 27 | } 28 | 29 | func init() { 30 | RootCmd.AddCommand(monitorCmd) 31 | } 32 | -------------------------------------------------------------------------------- /cmd/contacts.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var contactsCmd = &cobra.Command{ 11 | Use: "contacts", 12 | Short: "list alert contacts", 13 | Long: `Show all alert contacts associated with the account`, 14 | Run: func(cmd *cobra.Command, args []string) { 15 | contacts, err := client.AllAlertContacts() 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | if len(contacts) == 0 { 20 | fmt.Println("No contacts found") 21 | } 22 | for _, c := range contacts { 23 | fmt.Println(c) 24 | fmt.Println() 25 | } 26 | }, 27 | } 28 | 29 | func init() { 30 | RootCmd.AddCommand(contactsCmd) 31 | } 32 | -------------------------------------------------------------------------------- /cmd/delete.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var deleteCmd = &cobra.Command{ 12 | Use: "delete", 13 | Short: "delete a monitor", 14 | Long: `Delete the monitor with the specified ID`, 15 | Args: cobra.ExactArgs(1), 16 | Run: func(cmd *cobra.Command, args []string) { 17 | ID, err := strconv.ParseInt(args[0], 10, 64) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | if err = client.DeleteMonitor(ID); err != nil { 22 | log.Fatal(err) 23 | } 24 | fmt.Printf("Monitor ID %d successfully deleted\n", ID) 25 | }, 26 | } 27 | 28 | func init() { 29 | RootCmd.AddCommand(deleteCmd) 30 | } 31 | -------------------------------------------------------------------------------- /cmd/search.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var searchCmd = &cobra.Command{ 12 | Use: "search", 13 | Short: "search monitors", 14 | Long: `Lists all monitors matching a search string`, 15 | Args: cobra.MinimumNArgs(1), 16 | Run: func(cmd *cobra.Command, args []string) { 17 | monitors, err := client.SearchMonitors(args[0]) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | if len(monitors) == 0 { 22 | fmt.Println("No matching monitors found") 23 | os.Exit(1) 24 | } 25 | for _, m := range monitors { 26 | fmt.Println(m) 27 | fmt.Println() 28 | } 29 | }, 30 | } 31 | 32 | func init() { 33 | RootCmd.AddCommand(searchCmd) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/pause.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | 8 | "github.com/bitfield/uptimerobot/pkg" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var pauseCmd = &cobra.Command{ 13 | Use: "pause", 14 | Short: "pause a monitor", 15 | Long: `Pause the monitor with the specified ID`, 16 | Args: cobra.ExactArgs(1), 17 | Run: func(cmd *cobra.Command, args []string) { 18 | ID, err := strconv.ParseInt(args[0], 10, 64) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | m := uptimerobot.Monitor{ 23 | ID: ID, 24 | } 25 | new, err := client.PauseMonitor(m) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | fmt.Printf("Monitor ID %d paused\n", new.ID) 30 | }, 31 | } 32 | 33 | func init() { 34 | RootCmd.AddCommand(pauseCmd) 35 | } 36 | -------------------------------------------------------------------------------- /cmd/start.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | 8 | "github.com/bitfield/uptimerobot/pkg" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var startCmd = &cobra.Command{ 13 | Use: "start", 14 | Short: "start a monitor", 15 | Long: `Start (unpause) the monitor with the specified ID`, 16 | Args: cobra.ExactArgs(1), 17 | Run: func(cmd *cobra.Command, args []string) { 18 | ID, err := strconv.ParseInt(args[0], 10, 64) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | m := uptimerobot.Monitor{ 23 | ID: ID, 24 | } 25 | new, err := client.StartMonitor(m) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | fmt.Printf("Monitor ID %d started\n", new.ID) 30 | }, 31 | } 32 | 33 | func init() { 34 | RootCmd.AddCommand(startCmd) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/account.go: -------------------------------------------------------------------------------- 1 | package uptimerobot 2 | 3 | // Account represents an Uptime Robot account. 4 | type Account struct { 5 | Email string `json:"email"` 6 | MonitorLimit int `json:"monitor_limit"` 7 | MonitorInterval int `json:"monitor_interval"` 8 | UpMonitors int `json:"up_monitors"` 9 | DownMonitors int `json:"down_monitors"` 10 | PausedMonitors int `json:"paused_monitors"` 11 | } 12 | 13 | const accountTemplate = `Email: {{ .Email }} 14 | Monitor limit: {{ .MonitorLimit }} 15 | Monitor interval: {{ .MonitorInterval }} 16 | Up monitors: {{ .UpMonitors }} 17 | Down monitors: {{ .DownMonitors }} 18 | Paused monitors: {{ .PausedMonitors }}` 19 | 20 | // String returns a pretty-printed version of the account details. 21 | func (a Account) String() string { 22 | return render(accountTemplate, a) 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/mvdan/github-actions-golang 2 | on: [push, pull_request] 3 | name: Tests 4 | jobs: 5 | test: 6 | strategy: 7 | matrix: 8 | go-version: [1.19.x, 1.20.x] 9 | os: [ubuntu-latest, macos-latest, windows-latest] 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - uses: actions/setup-go@v3 13 | with: 14 | go-version: ${{ matrix.go-version }} 15 | - uses: actions/checkout@v3 16 | - run: go test ./... 17 | integration: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/setup-go@v3 21 | with: 22 | go-version: 1.20.x 23 | - uses: actions/checkout@v3 24 | - run: go test -tags=integration ./... 25 | env: 26 | UPTIMEROBOT_API_KEY: ${{ secrets.UPTIMEROBOT_API_KEY }} 27 | -------------------------------------------------------------------------------- /pkg/testdata/getMonitorByID.json: -------------------------------------------------------------------------------- 1 | { 2 | "stat": "ok", 3 | "pagination": { 4 | "offset": 0, 5 | "limit": 50, 6 | "total": 1 7 | }, 8 | "monitors": [ 9 | { 10 | "id": 777749809, 11 | "friendly_name": "Google", 12 | "url": "http://www.google.com", 13 | "type": 1, 14 | "sub_type": "", 15 | "keyword_type": "", 16 | "keyword_value": "", 17 | "http_username": "", 18 | "http_password": "", 19 | "port": "80", 20 | "interval": 900, 21 | "status": 1, 22 | "create_datetime": 1462565497, 23 | "monitor_group": 0, 24 | "is_group_main": 0, 25 | "logs": [ 26 | { 27 | "type": 98, 28 | "datetime": 1463540297, 29 | "duration": 1054134 30 | } 31 | ] 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /cmd/new.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | 8 | uptimerobot "github.com/bitfield/uptimerobot/pkg" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var newCmd = &cobra.Command{ 13 | Use: "new", 14 | Short: "add a new monitor", 15 | Long: `Create a new monitor with the specified URL and friendly name`, 16 | Args: cobra.ExactArgs(2), 17 | Run: func(cmd *cobra.Command, args []string) { 18 | m := uptimerobot.Monitor{ 19 | URL: args[0], 20 | FriendlyName: args[1], 21 | Type: uptimerobot.TypeHTTP, 22 | AlertContacts: contacts, 23 | Port: 80, 24 | } 25 | if strings.HasPrefix(m.URL, "https") { 26 | m.Port = 443 27 | } 28 | ID, err := client.CreateMonitor(m) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | fmt.Printf("New monitor created with ID %d\n", ID) 33 | }, 34 | } 35 | 36 | var contacts []string 37 | 38 | func init() { 39 | newCmd.Flags().StringSliceVarP(&contacts, "contacts", "c", []string{}, "Comma-separated list of contact IDs to notify") 40 | RootCmd.AddCommand(newCmd) 41 | } 42 | -------------------------------------------------------------------------------- /cmd/ensure.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | 8 | uptimerobot "github.com/bitfield/uptimerobot/pkg" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var ensureCmd = &cobra.Command{ 13 | Use: "ensure", 14 | Short: "add a new monitor if not present", 15 | Long: `Create a new monitor with the specified URL and friendly name, if the monitor does not already exist`, 16 | Args: cobra.ExactArgs(2), 17 | Run: func(cmd *cobra.Command, args []string) { 18 | m := uptimerobot.Monitor{ 19 | URL: args[0], 20 | FriendlyName: args[1], 21 | Type: uptimerobot.TypeHTTP, 22 | AlertContacts: contacts, 23 | Port: 80, 24 | } 25 | if strings.HasPrefix(m.URL, "https") { 26 | m.Port = 443 27 | } 28 | ID, err := client.EnsureMonitor(m) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | fmt.Printf("Monitor ID %d ensured\n", ID) 33 | }, 34 | } 35 | 36 | func init() { 37 | ensureCmd.Flags().StringSliceVarP(&contacts, "contacts", "c", []string{}, "Comma-separated list of contact IDs to notify") 38 | RootCmd.AddCommand(ensureCmd) 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 John Arundel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/uptimerobot_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package uptimerobot 5 | 6 | import ( 7 | "log" 8 | "os" 9 | "testing" 10 | "time" 11 | 12 | "github.com/google/go-cmp/cmp" 13 | ) 14 | 15 | var client Client 16 | 17 | func init() { 18 | key := os.Getenv("UPTIMEROBOT_API_KEY") 19 | if key == "" { 20 | log.Fatal("'UPTIMEROBOT_API_KEY' must be set for integration tests") 21 | } 22 | client = New(key) 23 | debug := os.Getenv("UPTIMEROBOT_DEBUG") 24 | if debug != "" { 25 | client.Debug = os.Stdout 26 | } 27 | } 28 | 29 | func exampleMonitor(name string) Monitor { 30 | return Monitor{ 31 | FriendlyName: name, 32 | URL: "http://example.com/" + name, 33 | Type: TypeHTTP, 34 | SubType: SubTypeHTTP, 35 | Port: 80, 36 | } 37 | } 38 | 39 | func TestIntegration(t *testing.T) { 40 | t.Parallel() 41 | ID, err := client.CreateMonitor(exampleMonitor("create_test")) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | got, err := client.GetMonitor(ID) 46 | if !cmp.Equal(ID, got.ID) { 47 | t.Error(cmp.Diff(ID, got.ID)) 48 | } 49 | time.Sleep(10 * time.Second) // avoid rate limit 50 | if err = client.DeleteMonitor(ID); err != nil { 51 | t.Error(err) 52 | } 53 | time.Sleep(10 * time.Second) // avoid rate limit 54 | _, err = client.GetAccountDetails() 55 | if err != nil { 56 | t.Error(err) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /pkg/testdata/getMonitorsBySearch.json: -------------------------------------------------------------------------------- 1 | { 2 | "stat": "ok", 3 | "pagination": { 4 | "offset": 0, 5 | "limit": 50, 6 | "total": 1 7 | }, 8 | "monitors": [ 9 | { 10 | "id": 777712827, 11 | "friendly_name": "My Web Page", 12 | "url": "http://mywebpage.com/", 13 | "type": 1, 14 | "sub_type": "", 15 | "keyword_type": "", 16 | "keyword_value": "", 17 | "http_username": "", 18 | "http_password": "", 19 | "port": "", 20 | "interval": 60, 21 | "status": 2, 22 | "create_datetime": 1462465496, 23 | "monitor_group": 0, 24 | "is_group_main": 0, 25 | "logs": [ 26 | { 27 | "type": 98, 28 | "datetime": 1462465202, 29 | "duration": 32 30 | }, 31 | { 32 | "type": 1, 33 | "datetime": 1462465234, 34 | "duration": 490140 35 | }, 36 | { 37 | "type": 2, 38 | "datetime": 1462955374, 39 | "duration": 85 40 | }, 41 | { 42 | "type": 99, 43 | "datetime": 1462955588, 44 | "duration": 12 45 | }, 46 | { 47 | "type": 98, 48 | "datetime": 1462955600, 49 | "duration": 22 50 | } 51 | ] 52 | } 53 | ] 54 | } -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | uptimerobot "github.com/bitfield/uptimerobot/pkg" 9 | "github.com/spf13/cobra" 10 | "github.com/spf13/viper" 11 | ) 12 | 13 | // RootCmd represents the base command when called without any subcommands. 14 | var RootCmd = &cobra.Command{ 15 | Use: "uptimerobot", 16 | Short: "uptimerobot is a client for the Uptime Robot V2 API", 17 | Long: `uptimerobot is a command-line client for the Uptime Robot monitoring 18 | service. It allows you to search for existing monitors, delete monitors, 19 | and create new monitors. You can also inspect your account details and 20 | any alert contacts you have configured. 21 | 22 | For more information, see https://github.com/bitfield/uptimerobot`, 23 | } 24 | 25 | // Execute adds all child commands to the root command and sets flags appropriately. 26 | // This is called by main.main(). It only needs to happen once to the rootCmd. 27 | func Execute() { 28 | if err := RootCmd.Execute(); err != nil { 29 | fmt.Println(err) 30 | os.Exit(1) 31 | } 32 | } 33 | 34 | var apiKey string 35 | var debug bool 36 | var client uptimerobot.Client 37 | 38 | func init() { 39 | viper.SetConfigName(".uptimerobot") 40 | viper.AddConfigPath("$HOME") 41 | viper.AddConfigPath(".") 42 | if err := viper.ReadInConfig(); err != nil { 43 | if _, ok := err.(viper.ConfigFileNotFoundError); !ok { 44 | log.Fatalf("failed to read config: %v\n", err) 45 | } 46 | } 47 | viper.SetEnvPrefix("uptimerobot") 48 | viper.AutomaticEnv() 49 | cobra.OnInitialize(func() { 50 | client = uptimerobot.New(viper.GetString("apiKey")) 51 | if debug { 52 | client.Debug = os.Stdout 53 | } 54 | }) 55 | RootCmd.PersistentFlags().StringVar(&apiKey, "apiKey", "", "Uptime Robot API key") 56 | viper.BindPFlag("apiKey", RootCmd.PersistentFlags().Lookup("apiKey")) 57 | viper.BindEnv("apiKey", "UPTIMEROBOT_API_KEY") 58 | RootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Debug mode (show API request and response)") 59 | } 60 | -------------------------------------------------------------------------------- /pkg/constants.go: -------------------------------------------------------------------------------- 1 | package uptimerobot 2 | 3 | // TypeHTTP represents an HTTP monitor. 4 | const TypeHTTP = 1 5 | 6 | // TypeKeyword represents a keyword monitor. 7 | const TypeKeyword = 2 8 | 9 | // TypePing represents a ping monitor. 10 | const TypePing = 3 11 | 12 | // TypePort represents a port monitor. 13 | const TypePort = 4 14 | 15 | // SubTypeHTTP represents an HTTP monitor subtype. 16 | const SubTypeHTTP = 1 17 | 18 | // SubTypeHTTPS represents an HTTPS monitor subtype. 19 | const SubTypeHTTPS = 2 20 | 21 | // SubTypeFTP represents an FTP monitor subtype. 22 | const SubTypeFTP = 3 23 | 24 | // SubTypeSMTP represents an SMTP monitor subtype. 25 | const SubTypeSMTP = 4 26 | 27 | // SubTypePOP3 represents a POP3 monitor subtype. 28 | const SubTypePOP3 = 5 29 | 30 | // SubTypeIMAP represents an IMAP monitor subtype. 31 | const SubTypeIMAP = 6 32 | 33 | // SubTypeCustomPort represents a custom port monitor subtype. 34 | const SubTypeCustomPort = 99 35 | 36 | // KeywordExists represents a keyword check which is critical if the keyword is 37 | // found. 38 | const KeywordExists = 1 39 | 40 | // KeywordNotExists represents a keyword check which is critical if the keyword 41 | // is not found. 42 | const KeywordNotExists = 2 43 | 44 | // StatusPaused is the status value which sets a monitor to paused status when 45 | // calling EditMonitor. 46 | const StatusPaused = 0 47 | 48 | // StatusResumed is the status value which sets a monitor to resumed (unpaused) 49 | // status when calling EditMonitor. 50 | const StatusResumed = 1 51 | 52 | // StatusUnknown is the status value indicating that the monitor status is 53 | // currently unknown. 54 | const StatusUnknown = 1 55 | 56 | // StatusUp is the status value indicating that the monitor is currently up. 57 | const StatusUp = 2 58 | 59 | // StatusMaybeDown is the status value indicating that the monitor may be down, 60 | // but this has not yet been confirmed. 61 | const StatusMaybeDown = 8 62 | 63 | // StatusDown is the status value indicating that the monitor is currently down. 64 | const StatusDown = 9 65 | -------------------------------------------------------------------------------- /pkg/testdata/getMonitors.json: -------------------------------------------------------------------------------- 1 | { 2 | "stat": "ok", 3 | "pagination": { 4 | "offset": 0, 5 | "limit": 50, 6 | "total": 4 7 | }, 8 | "monitors": [ 9 | { 10 | "id": 777749809, 11 | "friendly_name": "Google", 12 | "url": "http://www.google.com", 13 | "type": 1, 14 | "sub_type": "", 15 | "keyword_type": "", 16 | "keyword_value": "", 17 | "http_username": "", 18 | "http_password": "", 19 | "port": "80", 20 | "interval": 900, 21 | "status": 1, 22 | "create_datetime": 1462565497, 23 | "monitor_group": 0, 24 | "is_group_main": 0, 25 | "logs": [ 26 | { 27 | "type": 98, 28 | "datetime": 1463540297, 29 | "duration": 1054134 30 | } 31 | ] 32 | }, 33 | { 34 | "id": 777712827, 35 | "friendly_name": "My Web Page", 36 | "url": "http://mywebpage.com/", 37 | "type": 1, 38 | "sub_type": "", 39 | "keyword_type": "", 40 | "keyword_value": "", 41 | "http_username": "", 42 | "http_password": "", 43 | "port": "", 44 | "interval": 60, 45 | "status": 2, 46 | "create_datetime": 1462465496, 47 | "monitor_group": 0, 48 | "is_group_main": 0, 49 | "logs": [ 50 | { 51 | "type": 98, 52 | "datetime": 1462465202, 53 | "duration": 32 54 | }, 55 | { 56 | "type": 1, 57 | "datetime": 1462465234, 58 | "duration": 490140 59 | }, 60 | { 61 | "type": 2, 62 | "datetime": 1462955374, 63 | "duration": 85 64 | }, 65 | { 66 | "type": 99, 67 | "datetime": 1462955588, 68 | "duration": 12 69 | }, 70 | { 71 | "type": 98, 72 | "datetime": 1462955600, 73 | "duration": 22 74 | } 75 | ] 76 | }, 77 | { 78 | "id": 777559666, 79 | "friendly_name": "My FTP Server", 80 | "url": "ftp.mywebpage.com", 81 | "type": 4, 82 | "sub_type": 3, 83 | "keyword_type": null, 84 | "keyword_value": "", 85 | "http_username": "", 86 | "http_password": "", 87 | "port": 21, 88 | "interval": 60, 89 | "status": 2, 90 | "create_datetime": 0 91 | }, 92 | { 93 | "id": 781397847, 94 | "friendly_name": "PortTest", 95 | "url": "mywebpage.com", 96 | "type": 4, 97 | "sub_type": 99, 98 | "keyword_type": null, 99 | "keyword_value": "", 100 | "http_username": "", 101 | "http_password": "", 102 | "port": 8000, 103 | "interval": 300, 104 | "status": 1, 105 | "create_datetime": 1541256390 106 | } 107 | ] 108 | } -------------------------------------------------------------------------------- /pkg/monitor.go: -------------------------------------------------------------------------------- 1 | package uptimerobot 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // Monitor represents an Uptime Robot monitor. 11 | type Monitor struct { 12 | ID int64 `json:"id,omitempty"` 13 | FriendlyName string `json:"friendly_name"` 14 | URL string `json:"url"` 15 | Type int `json:"type"` 16 | SubType int `json:"sub_type,omitempty"` 17 | KeywordType int `json:"keyword_type,omitempty"` 18 | Port int `json:"port"` 19 | KeywordValue string `json:"keyword_value,omitempty"` 20 | AlertContacts []string `json:"alert_contacts,omitempty"` 21 | Status int `json:"status,omitempty"` 22 | } 23 | 24 | const monitorTemplate = `ID: {{ .ID }} 25 | Name: {{ .FriendlyName }} 26 | URL: {{ .URL }} 27 | Status: {{ .FriendlyStatus -}} 28 | {{ if .Port }}{{ printf "\nPort: %d" .Port }}{{ end -}} 29 | {{ if .Type }}{{ printf "\nType: %s" .FriendlyType }}{{ end -}} 30 | {{ if .SubType }}{{ printf "\nSubtype: %s" .FriendlySubType }}{{ end -}} 31 | {{ if .KeywordType }}{{ printf "\nKeywordType: %s" .FriendlyKeywordType }}{{ end -}} 32 | {{ if .KeywordValue }}{{ printf "\nKeyword: %s" .KeywordValue }}{{ end }}` 33 | 34 | // String returns a pretty-printed version of the monitor. 35 | func (m Monitor) String() string { 36 | return render(monitorTemplate, m) 37 | } 38 | 39 | // FriendlyType returns a human-readable name for the monitor type. 40 | func (m Monitor) FriendlyType() string { 41 | switch m.Type { 42 | case TypeHTTP: 43 | return "HTTP" 44 | case TypeKeyword: 45 | return "Keyword" 46 | case TypePing: 47 | return "Ping" 48 | case TypePort: 49 | return "Port" 50 | default: 51 | return fmt.Sprintf("%d", m.Type) 52 | } 53 | } 54 | 55 | // FriendlySubType returns a human-readable name for the monitor subtype, 56 | // including the port number. 57 | func (m Monitor) FriendlySubType() string { 58 | switch m.SubType { 59 | case SubTypeHTTP: 60 | return "HTTP (80)" 61 | case SubTypeHTTPS: 62 | return "HTTPS (443)" 63 | case SubTypeFTP: 64 | return "FTP (21)" 65 | case SubTypeSMTP: 66 | return "SMTP (25)" 67 | case SubTypePOP3: 68 | return "POP3 (110)" 69 | case SubTypeIMAP: 70 | return "IMAP (143)" 71 | case SubTypeCustomPort: 72 | return fmt.Sprintf("Custom port (%d)", m.Port) 73 | default: 74 | return fmt.Sprintf("%d", m.SubType) 75 | } 76 | } 77 | 78 | // FriendlyKeywordType returns a human-readable name for the monitor keyword type. 79 | func (m Monitor) FriendlyKeywordType() string { 80 | switch m.KeywordType { 81 | case KeywordExists: 82 | return "Exists" 83 | case KeywordNotExists: 84 | return "NotExists" 85 | default: 86 | return fmt.Sprintf("%d", m.KeywordType) 87 | } 88 | } 89 | 90 | func (m Monitor) FriendlyStatus() string { 91 | switch m.Status { 92 | case StatusPaused: 93 | return "Paused" 94 | case StatusUnknown: 95 | return "Unknown" 96 | case StatusUp: 97 | return "Up" 98 | case StatusMaybeDown: 99 | return "MaybeDown" 100 | case StatusDown: 101 | return "Down" 102 | default: 103 | return fmt.Sprintf("%d", m.Status) 104 | } 105 | } 106 | 107 | // MarshalJSON converts a Monitor struct into its string JSON representation, 108 | // handling the special encoding of the alert_contacts field. 109 | func (m Monitor) MarshalJSON() ([]byte, error) { 110 | // Use a temporary type definition to avoid infinite recursion when 111 | // marshaling 112 | type MonitorAlias Monitor 113 | ma := MonitorAlias(m) 114 | data, err := json.Marshal(ma) 115 | if err != nil { 116 | return []byte{}, err 117 | } 118 | // Create a temporary map and unmarshal the data into it 119 | tmp := map[string]interface{}{} 120 | err = json.Unmarshal(data, &tmp) 121 | if err != nil { 122 | return []byte{}, err 123 | } 124 | contacts := make([]string, len(m.AlertContacts)) 125 | for i, c := range m.AlertContacts { 126 | contacts[i] = c + "_0_0" 127 | } 128 | tmp["alert_contacts"] = strings.Join(contacts, "-") 129 | // Marshal the cleaned-up data back to JSON again 130 | data, err = json.Marshal(tmp) 131 | if err != nil { 132 | return []byte{}, err 133 | } 134 | return data, nil 135 | } 136 | 137 | // UnmarshalJSON converts a JSON monitor representation to a Monitor struct, 138 | // handling the Uptime Robot API's invalid encoding of integer zeros as empty 139 | // strings. 140 | func (m *Monitor) UnmarshalJSON(data []byte) error { 141 | // We need a custom unmarshaler because keyword_type, sub_type, and port 142 | // are returned as either a quoted integer (if set) or an empty string 143 | // (if unset), which Go's JSON library won't parse for integer fields: 144 | // https://github.com/golang/go/issues/22182 145 | // 146 | // Create a temporary map and unmarshal the data into it 147 | raw := map[string]interface{}{} 148 | err := json.Unmarshal(data, &raw) 149 | if err != nil { 150 | return err 151 | } 152 | // Check and clean up any problematic fields 153 | fields := []string{ 154 | "sub_type", 155 | "keyword_type", 156 | "port", 157 | } 158 | for _, f := range fields { 159 | // If the field is empty string, that means zero. 160 | if raw[f] == "" { 161 | raw[f] = 0 162 | } 163 | // Otherwise, try to convert it to int. 164 | if s, ok := raw[f].(string); ok { 165 | v, err := strconv.Atoi(s) 166 | if err != nil { 167 | return err 168 | } 169 | raw[f] = v 170 | } 171 | } 172 | // Marshal the cleaned-up data back to JSON 173 | data, err = json.Marshal(raw) 174 | if err != nil { 175 | return err 176 | } 177 | // Use a temporary type definition to avoid infinite recursion when unmarshaling 178 | type MonitorAlias Monitor 179 | var ma MonitorAlias 180 | if err := json.Unmarshal(data, &ma); err != nil { 181 | return err 182 | } 183 | // Finally, convert the temporary type back to a Monitor 184 | *m = Monitor(ma) 185 | return nil 186 | } 187 | -------------------------------------------------------------------------------- /pkg/client.go: -------------------------------------------------------------------------------- 1 | package uptimerobot 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "net/http/httputil" 12 | "os" 13 | "strings" 14 | "text/template" 15 | "time" 16 | ) 17 | 18 | // Client represents an Uptime Robot client. 19 | // 20 | // The HTTPClient field holds a pointer to the HTTP client which will be used to 21 | // make the requests; the default client is configured with a timeout of 10 22 | // seconds. If you would like to use a client with different settings, create an 23 | // http.Client with the parameters you want, and assign it to the HTTPClient 24 | // field. 25 | // 26 | // If the Debug field is set to any io.Writer (for example os.Stdout), then the 27 | // client will dump all HTTP requests and responses to the supplied writer. 28 | // 29 | // The URL field determines where requests will be sent; by default this is 30 | // 'https://api.uptimerobot.com', but if you want to use an alternate or test 31 | // server URL, set it here. For example, if you are writing tests which use the 32 | // Uptime Robot client and you do not want it to make network calls, create an 33 | // httptest.NewTLSServer and set the URL field to the test server's URL. 34 | type Client struct { 35 | apiKey string 36 | HTTPClient *http.Client 37 | URL string 38 | Debug io.Writer 39 | } 40 | 41 | // New takes an Uptime Robot API key and returns a Client. See the documentation 42 | // for the Client type for configuration options. 43 | func New(apiKey string) Client { 44 | client := Client{ 45 | apiKey: apiKey, 46 | URL: "https://api.uptimerobot.com", 47 | HTTPClient: &http.Client{Timeout: 10 * time.Second}, 48 | } 49 | if os.Getenv("UPTIMEROBOT_DEBUG") != "" { 50 | client.Debug = os.Stdout 51 | } 52 | return client 53 | } 54 | 55 | // Error represents an API error response. 56 | type Error map[string]interface{} 57 | 58 | // Pagination represents the pagination info of an API response. 59 | type Pagination struct { 60 | Offset int `json:"offset"` 61 | Limit int `json:"limit"` 62 | Total int `json:"total"` 63 | } 64 | 65 | // Response represents an API response. 66 | type Response struct { 67 | Stat string `json:"stat"` 68 | Account Account `json:"account"` 69 | Monitors []Monitor `json:"monitors"` 70 | Monitor Monitor `json:"monitor"` 71 | AlertContacts []AlertContact `json:"alert_contacts"` 72 | Error Error `json:"error,omitempty"` 73 | Pagination Pagination `json:"pagination"` 74 | } 75 | 76 | // GetAccountDetails returns an Account representing the account details. 77 | func (c *Client) GetAccountDetails() (Account, error) { 78 | r := Response{} 79 | if err := c.MakeAPICall("getAccountDetails", &r, []byte{}); err != nil { 80 | return Account{}, err 81 | } 82 | return r.Account, nil 83 | } 84 | 85 | // GetMonitor takes an int64 representing the ID number of an existing monitor, 86 | // and returns the corresponding Monitor, or an error if the operation failed. 87 | func (c *Client) GetMonitor(ID int64) (Monitor, error) { 88 | r := Response{} 89 | data := []byte(fmt.Sprintf("{\"monitors\": \"%d\"}", ID)) 90 | if err := c.MakeAPICall("getMonitors", &r, data); err != nil { 91 | return Monitor{}, err 92 | } 93 | if len(r.Monitors) == 0 { 94 | return Monitor{}, fmt.Errorf("monitor %d not found", ID) 95 | } 96 | return r.Monitors[0], nil 97 | } 98 | 99 | // AllMonitors returns a slice of Monitors representing the monitors currently 100 | // configured in your Uptime Robot account. 101 | func (c *Client) AllMonitors() ([]Monitor, error) { 102 | monitors := []Monitor{} 103 | // This limit is imposed by the API. 104 | const maxRecordsPerRequest = 50 105 | offset := 0 106 | r := Response{} 107 | for offset <= r.Pagination.Total { 108 | data := []byte(fmt.Sprintf("{\"offset\": \"%d\", \"limit\": \"%d\"}", offset, maxRecordsPerRequest)) 109 | if err := c.MakeAPICall("getMonitors", &r, data); err != nil { 110 | return nil, err 111 | } 112 | monitors = append(monitors, r.Monitors...) 113 | offset = r.Pagination.Offset + maxRecordsPerRequest 114 | } 115 | return monitors, nil 116 | } 117 | 118 | // SearchMonitors returns a slice of Monitors whose FriendlyName or URL 119 | // match the search string. 120 | func (c *Client) SearchMonitors(s string) ([]Monitor, error) { 121 | r := Response{} 122 | data := []byte(`{"search": "` + s + `"}`) 123 | if err := c.MakeAPICall("getMonitors", &r, data); err != nil { 124 | return []Monitor{}, err 125 | } 126 | return r.Monitors, nil 127 | } 128 | 129 | // AllAlertContacts returns all the AlertContacts associated with the account. 130 | func (c *Client) AllAlertContacts() ([]AlertContact, error) { 131 | r := Response{} 132 | if err := c.MakeAPICall("getAlertContacts", &r, []byte{}); err != nil { 133 | return []AlertContact{}, err 134 | } 135 | return r.AlertContacts, nil 136 | } 137 | 138 | // CreateMonitor takes a Monitor and creates a new Uptime Robot monitor with the 139 | // specified details. It returns the ID of the newly created monitor, or an 140 | // error if the operation failed. 141 | func (c *Client) CreateMonitor(m Monitor) (int64, error) { 142 | r := Response{} 143 | data, err := json.Marshal(m) 144 | if err != nil { 145 | return 0, err 146 | } 147 | if err := c.MakeAPICall("newMonitor", &r, data); err != nil { 148 | return 0, err 149 | } 150 | return r.Monitor.ID, nil 151 | } 152 | 153 | // EnsureMonitor takes a Monitor and creates a new Uptime Robot monitor with the 154 | // specified details, if a monitor for the same URL does not already exist. It 155 | // returns the ID of the newly created monitor or the existing monitor if it 156 | // already existed, or an error if the operation failed. 157 | func (c *Client) EnsureMonitor(m Monitor) (int64, error) { 158 | monitors, err := c.SearchMonitors(m.URL) 159 | if err != nil { 160 | return 0, err 161 | } 162 | if len(monitors) == 0 { 163 | ID, err := c.CreateMonitor(m) 164 | if err != nil { 165 | return 0, err 166 | } 167 | return ID, nil 168 | } 169 | return monitors[0].ID, nil 170 | } 171 | 172 | // PauseMonitor takes a Monitor with the ID field set, and attempts to set the 173 | // monitor status to paused via the API. It returns a Monitor with the ID field 174 | // set to the ID of the monitor, or an error if the operation failed. 175 | func (c *Client) PauseMonitor(m Monitor) (Monitor, error) { 176 | r := Response{} 177 | data := []byte(fmt.Sprintf("{\"id\": \"%d\",\"status\": %d}", m.ID, StatusPaused)) 178 | if err := c.MakeAPICall("editMonitor", &r, data); err != nil { 179 | return Monitor{}, err 180 | } 181 | return r.Monitor, nil 182 | } 183 | 184 | // StartMonitor takes a Monitor with the ID field set, and attempts to set the 185 | // monitor status to resumed (unpaused) via the API. It returns a Monitor with 186 | // the ID field set to the ID of the monitor, or an error if the operation 187 | // failed. 188 | func (c *Client) StartMonitor(m Monitor) (Monitor, error) { 189 | r := Response{} 190 | data := []byte(fmt.Sprintf("{\"id\": \"%d\",\"status\": %d}", m.ID, StatusResumed)) 191 | if err := c.MakeAPICall("editMonitor", &r, data); err != nil { 192 | return Monitor{}, err 193 | } 194 | return r.Monitor, nil 195 | } 196 | 197 | // DeleteMonitor takes a monitor ID and deletes the corresponding monitor. It returns 198 | // an error if the operation failed. 199 | func (c *Client) DeleteMonitor(ID int64) error { 200 | data := []byte(fmt.Sprintf("{\"id\": \"%d\"}", ID)) 201 | if err := c.MakeAPICall("deleteMonitor", &Response{}, data); err != nil { 202 | return err 203 | } 204 | return nil 205 | } 206 | 207 | // MakeAPICall calls the Uptime Robot API with the specified verb and data, and 208 | // stores the returned data in the Response struct. 209 | func (c *Client) MakeAPICall(verb string, r *Response, data []byte) error { 210 | data, err := decorateRequestData(data, c.apiKey) 211 | if err != nil { 212 | return err 213 | } 214 | requestURL := c.URL + "/v2/" + verb 215 | req, err := http.NewRequest(http.MethodPost, requestURL, bytes.NewBuffer(data)) 216 | if err != nil { 217 | return fmt.Errorf("failed to create HTTP request: %v", err) 218 | } 219 | req.Header.Add("content-type", "application/json") 220 | if c.Debug != nil { 221 | requestDump, err := httputil.DumpRequestOut(req, true) 222 | if err != nil { 223 | return fmt.Errorf("error dumping HTTP request: %v", err) 224 | } 225 | fmt.Fprintln(c.Debug, string(requestDump)) 226 | fmt.Fprintln(c.Debug) 227 | } 228 | resp, err := c.HTTPClient.Do(req) 229 | if err != nil { 230 | return fmt.Errorf("HTTP request failed: %v", err) 231 | } 232 | defer resp.Body.Close() 233 | if c.Debug != nil { 234 | responseDump, err := httputil.DumpResponse(resp, true) 235 | if err != nil { 236 | return fmt.Errorf("error dumping HTTP response: %v", err) 237 | } 238 | fmt.Fprintln(c.Debug, string(responseDump)) 239 | fmt.Fprintln(c.Debug) 240 | } 241 | respBytes, err := ioutil.ReadAll(resp.Body) 242 | if err != nil { 243 | return fmt.Errorf("reading response body: %v", err) 244 | } 245 | resp.Body.Close() 246 | respString := string(respBytes) 247 | resp.Body = ioutil.NopCloser(strings.NewReader(respString)) 248 | if resp.StatusCode != http.StatusOK { 249 | return fmt.Errorf("unexpected response status %d: %q", resp.StatusCode, respString) 250 | } 251 | if err = json.NewDecoder(resp.Body).Decode(&r); err != nil { 252 | return fmt.Errorf("decoding error for %q: %v", respString, err) 253 | } 254 | if r.Stat != "ok" { 255 | e, _ := json.MarshalIndent(r.Error, "", " ") 256 | return fmt.Errorf("API error: %s", e) 257 | } 258 | return nil 259 | } 260 | 261 | // decorateRequestData takes JSON data representing an API request, and adds the 262 | // required 'api_key' and 'format' fields to it. 263 | func decorateRequestData(data []byte, apiKey string) ([]byte, error) { 264 | // Create a temporary map and unmarshal the data into it 265 | tmp := map[string]interface{}{} 266 | // Skip unmarshaling empty data 267 | if len(data) > 0 { 268 | err := json.Unmarshal(data, &tmp) 269 | if err != nil { 270 | return []byte{}, fmt.Errorf("unmarshaling request data: %v", err) 271 | } 272 | } 273 | // Add in the necessary request fields 274 | tmp["api_key"] = apiKey 275 | tmp["format"] = "json" 276 | // Marshal it back into string form 277 | data, err := json.MarshalIndent(tmp, "", " ") 278 | if err != nil { 279 | return []byte{}, fmt.Errorf("remarshaling cleaned-up request data: %v", err) 280 | } 281 | return data, nil 282 | } 283 | 284 | // render takes a template and a data value, and returns the string result of 285 | // executing the template in the context of the value. 286 | func render(templateName string, value interface{}) string { 287 | var output bytes.Buffer 288 | tmpl, err := template.New("").Parse(templateName) 289 | if err != nil { 290 | log.Fatal(err) 291 | } 292 | err = tmpl.Execute(&output, value) 293 | if err != nil { 294 | log.Fatal(err) 295 | } 296 | return output.String() 297 | } 298 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Reference](https://pkg.go.dev/badge/github.com/bitfield/uptimerobot.svg)](https://pkg.go.dev/github.com/bitfield/uptimerobot) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/bitfield/uptimerobot)](https://goreportcard.com/report/github.com/bitfield/uptimerobot) 3 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go) 4 | ![Tests](https://github.com/bitfield/uptimerobot/actions/workflows/test.yml/badge.svg) 5 | 6 | # uptimerobot 7 | 8 | `uptimerobot` is a Go library and command-line client for the [Uptime Robot](https://uptimerobot.com/) website monitoring service. It allows you to search for existing monitors, delete monitors, create new monitors, and also inspect your account details and alert contacts. 9 | 10 | ## Installing the command-line client 11 | 12 | To install the client binary, run: 13 | 14 | ``` 15 | go get -u github.com/bitfield/uptimerobot 16 | ``` 17 | 18 | ## Running the command-line client in Docker 19 | 20 | To use the client in a Docker container, run: 21 | 22 | ``` 23 | docker container run bitfield/uptimerobot 24 | ``` 25 | 26 | ## Using the command-line client 27 | 28 | To see help on using the client, run: 29 | 30 | ``` 31 | uptimerobot -h 32 | ``` 33 | 34 | ## Setting your API key 35 | 36 | To use the client with your Uptime Robot account, you will need the Main API Key for the account. Go to the [Uptime Robot Settings page](https://uptimerobot.com/dashboard.php#mySettings) and click 'Show/hide it' under the 'Main API Key' section. 37 | 38 | There are three ways to pass your API key to the client: in a config file, in an environment variable, or on the command line. 39 | 40 | ### In a config file 41 | 42 | The `uptimerobot` client will read a config file named `.uptimerobot.yaml` (or `.uptimerobot.json`, or any other extension that Viper supports) in your home directory, or in the current directory. 43 | 44 | For example, you can put your API key in the file `$HOME/.uptimerobot.yaml`, and `uptimerobot` will find and read it automatically (replace `XXX` with your own API key): 45 | 46 | ```yaml 47 | apiKey: XXX 48 | ``` 49 | 50 | ### In an environment variable 51 | 52 | `uptimerobot` will look for the API key in an environment variable named UPTIMEROBOT_API_KEY: 53 | 54 | ``` 55 | export UPTIMEROBOT_API_KEY=XXX 56 | uptimerobot ... 57 | ``` 58 | 59 | (For historical reasons, the variable can also be named `UPTIMEROBOT_APIKEY`.) 60 | 61 | ### On the command line 62 | 63 | You can also pass your API key to the `uptimerobot` client using the `--apiKey` flag like this: 64 | 65 | ``` 66 | uptimerobot --apiKey XXX ... 67 | ``` 68 | 69 | ## Testing your configuration 70 | 71 | To test that your API key is correct and `uptimerobot` is reading it properly, run: 72 | 73 | ``` 74 | uptimerobot account 75 | ``` 76 | 77 | You should see your account details listed: 78 | 79 | ``` 80 | Email: j.random@example.com 81 | Monitor limit: 300 82 | Monitor interval: 1 83 | Up monitors: 208 84 | Down monitors: 2 85 | Paused monitors: 0 86 | ``` 87 | 88 | If you get an error message, double-check you have the correct API key: 89 | 90 | ``` 91 | 2018/07/12 16:04:26 API error: { 92 | "message": "api_key not found.", 93 | "parameter_name": "api_key", 94 | "passed_value": "XXX", 95 | "type": "invalid_parameter" 96 | } 97 | ``` 98 | 99 | ## Listing contacts 100 | 101 | The `uptimerobot contacts` command will list your configured alert contacts by ID number: 102 | 103 | ``` 104 | uptimerobot contacts 105 | ID: 0102759 106 | Name: Jay Random 107 | Type: 2 108 | Status: 2 109 | Value: j.random@example.com 110 | 111 | ID: 2053888 112 | Name: Slack 113 | Type: 11 114 | Status: 2 115 | Value: https://hooks.slack.com/services/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 116 | ``` 117 | 118 | This will be useful when you create a new monitor, because you can add the contact IDs which should be alerted when the check fails (see 'Creating a new monitor' below). 119 | 120 | ## Listing or searching for monitors 121 | 122 | Use `uptimerobot search` to list all monitors whose 'friendly name' or check URL match a certain string: 123 | 124 | ``` 125 | uptimerobot search www.example.com 126 | ID: 780689017 127 | Name: Example.com website 128 | URL: https://www.example.com/ 129 | Status: Up 130 | Type: HTTP 131 | ``` 132 | 133 | (Use `uptimerobot monitors` to list all existing monitors.) 134 | 135 | If there are no monitors found matching your search, the exit status of the command will be 1. Otherwise it will be 0. (If you're checking whether a monitor already exists before creating it, try the `ensure` command instead.) 136 | 137 | ## Deleting monitors 138 | 139 | Note the ID number of the monitor you want to delete, and run `uptimerobot delete`: 140 | 141 | ``` 142 | uptimerobot delete 780689017 143 | Monitor ID 780689017 deleted 144 | ``` 145 | 146 | ## Pausing or starting monitors 147 | 148 | Note the ID number of the monitor you want to pause, and run `uptimerobot pause`: 149 | 150 | ``` 151 | uptimerobot pause 780689017 152 | Monitor ID 780689017 paused 153 | ``` 154 | 155 | To resume a paused monitor, run `uptimerobot start` with the monitor ID: 156 | 157 | ``` 158 | uptimerobot start 780689017 159 | Monitor ID 780689017 started 160 | ``` 161 | 162 | ## Creating a new monitor 163 | 164 | Run `uptimerobot new URL NAME` to create a new monitor: 165 | 166 | ``` 167 | uptimerobot new https://www.example.com/ "Example.com website" 168 | New monitor created with ID 780689018 169 | ``` 170 | 171 | To create a new monitor with alert contacts configured, use the `-c` flag followed by a comma-separated list of contact IDs, with no spaces: 172 | 173 | ``` 174 | uptimerobot new -c 0102759,2053888 https://www.example.com/ "Example.com website" 175 | New monitor created with ID 780689019 176 | ``` 177 | 178 | ## Ensuring a monitor exists 179 | 180 | Sometimes you want to create a new monitor only if a monitor doesn't already exist for the same URL. This is especially useful in automation. 181 | 182 | To do this, run `uptimerobot ensure URL NAME`: 183 | 184 | ``` 185 | uptimerobot ensure https://www.example.com/ "Example.com website" 186 | Monitor ID 780689018 ensured 187 | ``` 188 | 189 | If the monitor doesn't already exist, it will be created. 190 | 191 | You can use the `-c` flag to add alert contacts, just as for the `uptimerobot new` command. 192 | 193 | ## Checking the version number 194 | 195 | To see what version of the command-line client you're using, run `uptimerobot version`. 196 | 197 | ## Viewing debug output 198 | 199 | When things aren't going quite as they should, you can add the `--debug` flag to your command line to see a dump of the HTTP request and response from the server. This is helpful if you want to report problems with the client, for example. 200 | 201 | ## Using the Go library 202 | 203 | If the command-line client doesn't do quite what you need, or if you want to use Uptime Robot API access in your own programs, import the library using: 204 | 205 | ```go 206 | import "github.com/bitfield/uptimerobot/pkg" 207 | ``` 208 | 209 | Create a new `Client` object by calling `uptimerobot.New()` with an API key: 210 | 211 | ```go 212 | client = uptimerobot.New(apiKey) 213 | ``` 214 | 215 | Once you have a client, you can use it to call various Uptime Robot API features: 216 | 217 | ```go 218 | monitors, err := client.AllMonitors() 219 | if err != nil { 220 | log.Fatal(err) 221 | } 222 | for _, m := range monitors { 223 | fmt.Println(m) 224 | fmt.Println() 225 | } 226 | ``` 227 | 228 | Most API operations use the `Monitor` struct, which looks like this: 229 | 230 | ```go 231 | type Monitor struct { 232 | ID int64 `json:"id,omitempty"` 233 | FriendlyName string `json:"friendly_name"` 234 | URL string `json:"url"` 235 | ... 236 | } 237 | ``` 238 | 239 | For example, to delete a monitor, find the ID of the monitor you want to delete, and pass it to `DeleteMonitor()`: 240 | 241 | ```go 242 | if err := client.DeleteMonitor(780689017); err != nil { 243 | log.Fatal(err) 244 | } 245 | ``` 246 | 247 | To call an Uptime Robot API verb not implemented by the `uptimerobot` library, you can use the `MakeAPICall()` method directly, passing it some suitable JSON data: 248 | 249 | ```go 250 | r := uptimerobot.Response{} 251 | data := []byte(fmt.Sprintf("{\"id\": \"%d\"}", m.ID)) 252 | if err := client.MakeAPICall("deleteMonitor", &r, data); err != nil { 253 | log.Fatal(err) 254 | } 255 | fmt.Println(r.Monitor.ID) 256 | ``` 257 | 258 | The API response is returned in the `Response` struct. If the call fails, `MakeAPICall()` will return the error message. Otherwise, the requested data will be available in the appropriate field of the `Response` struct: 259 | 260 | ```go 261 | type Response struct { 262 | Stat string `json:"stat"` 263 | Account Account `json:"account"` 264 | Monitors []Monitor `json:"monitors"` 265 | Monitor Monitor `json:"monitor"` 266 | AlertContacts []AlertContact `json:"alert_contacts"` 267 | Error Error `json:"error"` 268 | } 269 | ``` 270 | 271 | For example, when creating a new monitor, the ID of the created monitor will be returned as `r.Monitor.ID`. 272 | 273 | If things aren't working as you expect, you can use the debug facility to dump the raw request and response data from every API call. To do this, set the environment variable `UPTIMEROBOT_DEBUG`, which will dump debug information to the standard output, or set `client.Debug` to any `io.Writer` to send output to that writer. 274 | 275 | Here's an example of the debug output shown when creating a new monitor: 276 | 277 | ```http 278 | POST /v2/newMonitor HTTP/1.1 279 | Host: api.uptimerobot.com 280 | User-Agent: Go-http-client/1.1 281 | Content-Length: 221 282 | Content-Type: application/json 283 | Accept-Encoding: gzip 284 | 285 | { 286 | "alert_contacts": "0335551_0_0-2416450_0_0", 287 | "api_key": "XXX", 288 | "format": "json", 289 | "friendly_name": "Example check", 290 | "port": 443, 291 | "type": 1, 292 | "url": "https://www.example.com" 293 | } 294 | 295 | HTTP/2.0 200 OK 296 | Access-Control-Allow-Origin: * 297 | Cf-Ray: 505422654b04dbf3-LHR 298 | Content-Type: application/json; charset=utf-8 299 | Date: Mon, 12 Aug 2019 17:22:57 GMT 300 | Etag: W/"33-NlNt8dOhQvno31TtQYsI0xTJ9w" 301 | Expect-Ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" 302 | Server: cloudflare 303 | Set-Cookie: __cfduid=d9ec99b8a777d9f806956432718fb5c81565630577; expires=Tue, 11-Aug-20 17:22:57 GMT; path=/; domain=.uptimerobot.com; HttpOnly 304 | Vary: Accept-Encoding 305 | 306 | {"stat":"ok","monitor":{"id":783263671,"status":1}} 307 | ``` 308 | 309 | ## Bugs and feature requests 310 | 311 | If you find a bug in the `uptimerobot` client or library, please [open an issue](https://github.com/bitfield/uptimerobot/issues). Similarly, if you'd like a feature added or improved, let me know via an issue. 312 | 313 | Not all the functionality of the Uptime Robot API is implemented yet. 314 | 315 | Pull requests welcome! 316 | -------------------------------------------------------------------------------- /pkg/uptimerobot_test.go: -------------------------------------------------------------------------------- 1 | package uptimerobot 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "net/http/httptest" 10 | "os" 11 | "testing" 12 | 13 | "github.com/google/go-cmp/cmp" 14 | ) 15 | 16 | func TestMarshalMonitor(t *testing.T) { 17 | t.Parallel() 18 | m := Monitor{ 19 | ID: 777749809, 20 | FriendlyName: "Google", 21 | URL: "http://www.google.com", 22 | Type: TypeHTTP, 23 | Port: 80, 24 | AlertContacts: []string{"3", "5", "7"}, 25 | } 26 | got, err := json.MarshalIndent(m, "", " ") 27 | if err != nil { 28 | t.Error(err) 29 | } 30 | want, err := ioutil.ReadFile("testdata/marshal.json") 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | // Convert the actual data and the expected data to maps, for ease of 35 | // comparison 36 | wantMap := map[string]interface{}{} 37 | err = json.Unmarshal(want, &wantMap) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | gotMap := map[string]interface{}{} 42 | err = json.Unmarshal(got, &gotMap) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | if !cmp.Equal(wantMap, gotMap) { 47 | t.Error(cmp.Diff(wantMap, gotMap)) 48 | } 49 | } 50 | 51 | func TestUnmarshalMonitor(t *testing.T) { 52 | t.Parallel() 53 | want := Monitor{ 54 | ID: 777749809, 55 | FriendlyName: "Google", 56 | URL: "http://www.google.com", 57 | Type: TypeHTTP, 58 | Port: 80, 59 | Status: StatusUnknown, 60 | } 61 | data, err := ioutil.ReadFile("testdata/unmarshal.json") 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | got := Monitor{} 66 | err = got.UnmarshalJSON(data) 67 | if err != nil { 68 | t.Error(err) 69 | } 70 | if !cmp.Equal(want, got) { 71 | t.Error(cmp.Diff(want, got)) 72 | } 73 | 74 | } 75 | 76 | func TestCreate(t *testing.T) { 77 | t.Parallel() 78 | client := New("dummy") 79 | // force test coverage of the client's dump functionality 80 | client.Debug = ioutil.Discard 81 | ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 82 | if r.Method != http.MethodPost { 83 | t.Errorf("want POST request, got %q", r.Method) 84 | } 85 | wantURL := "/v2/newMonitor" 86 | if r.URL.EscapedPath() != wantURL { 87 | t.Errorf("want %q, got %q", wantURL, r.URL.EscapedPath()) 88 | } 89 | body, err := ioutil.ReadAll(r.Body) 90 | if err != nil { 91 | t.Fatal(err) 92 | } 93 | r.Body.Close() 94 | want, err := ioutil.ReadFile("testdata/requestNewMonitor.json") 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | // Convert the received body and the expected body to maps, for 99 | // ease of comparison 100 | wantMap := map[string]interface{}{} 101 | err = json.Unmarshal(want, &wantMap) 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | bodyMap := map[string]interface{}{} 106 | err = json.Unmarshal(body, &bodyMap) 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | if !cmp.Equal(wantMap, bodyMap) { 111 | t.Error(cmp.Diff(wantMap, bodyMap)) 112 | } 113 | w.WriteHeader(http.StatusOK) 114 | data, err := os.Open("testdata/newMonitor.json") 115 | if err != nil { 116 | t.Fatal(err) 117 | } 118 | defer data.Close() 119 | io.Copy(w, data) 120 | })) 121 | defer ts.Close() 122 | client.HTTPClient = ts.Client() 123 | client.URL = ts.URL 124 | create := Monitor{ 125 | FriendlyName: "My test monitor", 126 | URL: "http://example.com", 127 | Type: TypeHTTP, 128 | Port: 80, 129 | AlertContacts: []string{"3", "5", "7"}, 130 | } 131 | got, err := client.CreateMonitor(create) 132 | if err != nil { 133 | t.Error(err) 134 | } 135 | var want int64 = 777810874 136 | if !cmp.Equal(want, got) { 137 | t.Error(cmp.Diff(want, got)) 138 | } 139 | } 140 | 141 | func TestGetAccountDetails(t *testing.T) { 142 | t.Parallel() 143 | client := New("dummy") 144 | ts := cannedResponseServer(t, "testdata/getAccountDetails.json") 145 | defer ts.Close() 146 | client.HTTPClient = ts.Client() 147 | client.URL = ts.URL 148 | got, err := client.GetAccountDetails() 149 | if err != nil { 150 | t.Error(err) 151 | } 152 | want := Account{ 153 | Email: "test@domain.com", 154 | MonitorLimit: 50, 155 | MonitorInterval: 1, 156 | UpMonitors: 1, 157 | DownMonitors: 0, 158 | PausedMonitors: 2, 159 | } 160 | if !cmp.Equal(want, got) { 161 | t.Error(cmp.Diff(want, got)) 162 | } 163 | } 164 | 165 | func TestAllAlertContacts(t *testing.T) { 166 | t.Parallel() 167 | client := New("dummy") 168 | ts := cannedResponseServer(t, "testdata/getAlertContacts.json") 169 | defer ts.Close() 170 | client.HTTPClient = ts.Client() 171 | client.URL = ts.URL 172 | want := []AlertContact{ 173 | { 174 | ID: "0993765", 175 | FriendlyName: "John Doe", 176 | Type: 2, 177 | Status: 1, 178 | Value: "johndoe@gmail.com", 179 | }, 180 | { 181 | ID: "2403924", 182 | FriendlyName: "My Twitter", 183 | Type: 3, 184 | Status: 0, 185 | Value: "sampleTwitterAccount", 186 | }, 187 | } 188 | got, err := client.AllAlertContacts() 189 | if err != nil { 190 | t.Error(err) 191 | } 192 | if !cmp.Equal(want, got) { 193 | t.Error(cmp.Diff(want, got)) 194 | } 195 | } 196 | 197 | func TestGetMonitorByID(t *testing.T) { 198 | t.Parallel() 199 | client := New("dummy") 200 | ts := cannedResponseServer(t, "testdata/getMonitorByID.json") 201 | defer ts.Close() 202 | client.HTTPClient = ts.Client() 203 | client.URL = ts.URL 204 | want := Monitor{ 205 | ID: 777749809, 206 | FriendlyName: "Google", 207 | URL: "http://www.google.com", 208 | Type: TypeHTTP, 209 | Port: 80, 210 | Status: StatusUnknown, 211 | } 212 | got, err := client.GetMonitor(want.ID) 213 | if err != nil { 214 | t.Error(err) 215 | } 216 | if !cmp.Equal(want, got) { 217 | t.Error(cmp.Diff(want, got)) 218 | } 219 | } 220 | 221 | func TestGetMonitorsPages(t *testing.T) { 222 | t.Parallel() 223 | client := New("dummy") 224 | ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 225 | bodyMap := map[string]interface{}{} 226 | if err := json.NewDecoder(r.Body).Decode(&bodyMap); err != nil { 227 | t.Fatal(err) 228 | } 229 | var datafile string 230 | switch bodyMap["offset"] { 231 | case "0": 232 | datafile = "testdata/getMonitorsPage1.json" 233 | case "50": 234 | datafile = "testdata/getMonitorsPage2.json" 235 | default: 236 | t.Fatalf("unexpected offset %s", bodyMap["offset"]) 237 | } 238 | data, err := os.Open(datafile) 239 | if err != nil { 240 | t.Fatal(err) 241 | } 242 | w.WriteHeader(http.StatusOK) 243 | defer data.Close() 244 | io.Copy(w, data) 245 | })) 246 | defer ts.Close() 247 | client.HTTPClient = ts.Client() 248 | client.URL = ts.URL 249 | monitors, err := client.AllMonitors() 250 | if err != nil { 251 | t.Error(err) 252 | } 253 | if len(monitors) != 100 { 254 | t.Fatalf("Wanted 100 monitors, but got %d", len(monitors)) 255 | } 256 | for i, m := range monitors { 257 | want := fmt.Sprintf("monitor-%d", i+1) 258 | got := m.FriendlyName 259 | if !cmp.Equal(want, got) { 260 | t.Error(cmp.Diff(want, got)) 261 | } 262 | } 263 | } 264 | 265 | func TestGetMonitors(t *testing.T) { 266 | t.Parallel() 267 | client := New("dummy") 268 | ts := cannedResponseServer(t, "testdata/getMonitors.json") 269 | defer ts.Close() 270 | client.HTTPClient = ts.Client() 271 | client.URL = ts.URL 272 | want := []Monitor{ 273 | { 274 | ID: 777749809, 275 | FriendlyName: "Google", 276 | URL: "http://www.google.com", 277 | Type: TypeHTTP, 278 | Port: 80, 279 | Status: StatusUnknown, 280 | }, 281 | { 282 | ID: 777712827, 283 | FriendlyName: "My Web Page", 284 | URL: "http://mywebpage.com/", 285 | Type: TypeHTTP, 286 | Status: StatusUp, 287 | }, 288 | { 289 | ID: 777559666, 290 | FriendlyName: "My FTP Server", 291 | URL: "ftp.mywebpage.com", 292 | Type: TypePort, 293 | SubType: SubTypeFTP, 294 | Port: 21, 295 | Status: StatusUp, 296 | }, 297 | { 298 | ID: 781397847, 299 | FriendlyName: "PortTest", 300 | URL: "mywebpage.com", 301 | Type: TypePort, 302 | SubType: SubTypeCustomPort, 303 | Port: 8000, 304 | Status: StatusUnknown, 305 | }, 306 | } 307 | got, err := client.AllMonitors() 308 | if err != nil { 309 | t.Error(err) 310 | } 311 | if !cmp.Equal(want, got) { 312 | t.Error(cmp.Diff(want, got)) 313 | } 314 | } 315 | 316 | func TestGetMonitorsBySearch(t *testing.T) { 317 | t.Parallel() 318 | client := New("dummy") 319 | ts := cannedResponseServer(t, "testdata/getMonitorsBySearch.json") 320 | defer ts.Close() 321 | client.HTTPClient = ts.Client() 322 | client.URL = ts.URL 323 | want := []Monitor{ 324 | { 325 | ID: 777712827, 326 | FriendlyName: "My Web Page", 327 | URL: "http://mywebpage.com/", 328 | Type: TypeHTTP, 329 | Status: StatusUp, 330 | }, 331 | } 332 | got, err := client.SearchMonitors("My Web Page") 333 | if err != nil { 334 | t.Error(err) 335 | } 336 | if !cmp.Equal(want, got) { 337 | t.Error(cmp.Diff(want, got)) 338 | } 339 | } 340 | 341 | func TestPauseMonitor(t *testing.T) { 342 | t.Parallel() 343 | client := New("dummy") 344 | ts := cannedResponseServer(t, "testdata/pauseMonitor.json") 345 | defer ts.Close() 346 | client.HTTPClient = ts.Client() 347 | client.URL = ts.URL 348 | want := Monitor{ 349 | ID: 677810870, 350 | } 351 | got, err := client.PauseMonitor(want) 352 | if err != nil { 353 | t.Error(err) 354 | } 355 | if !cmp.Equal(want, got) { 356 | t.Error(cmp.Diff(want, got)) 357 | } 358 | } 359 | 360 | func TestStartMonitor(t *testing.T) { 361 | t.Parallel() 362 | client := New("dummy") 363 | ts := cannedResponseServer(t, "testdata/startMonitor.json") 364 | defer ts.Close() 365 | client.HTTPClient = ts.Client() 366 | client.URL = ts.URL 367 | want := Monitor{ 368 | ID: 677810870, 369 | } 370 | got, err := client.StartMonitor(want) 371 | if err != nil { 372 | t.Error(err) 373 | } 374 | if !cmp.Equal(want, got) { 375 | t.Error(cmp.Diff(want, got)) 376 | } 377 | } 378 | 379 | func TestEnsure(t *testing.T) { 380 | t.Parallel() 381 | client := New("dummy") 382 | ts := cannedResponseServer(t, "testdata/ensure.json") 383 | defer ts.Close() 384 | client.HTTPClient = ts.Client() 385 | client.URL = ts.URL 386 | mon := Monitor{ 387 | ID: 777712827, 388 | FriendlyName: "My Web Page", 389 | URL: "http://mywebpage.com/", 390 | Type: TypeHTTP, 391 | } 392 | // The client will do a SearchMonitors and get a canned response from 393 | // the test server containing no matches. It will now try to create the 394 | // monitor, and the test server will just respond with an empty body and 395 | // OK. The resulting monitor will have an ID of 0. 396 | want := int64(0) 397 | got, err := client.EnsureMonitor(mon) 398 | if err != nil { 399 | t.Error(err) 400 | } 401 | if !cmp.Equal(want, got) { 402 | t.Error(cmp.Diff(want, got)) 403 | } 404 | } 405 | 406 | func TestDeleteMonitor(t *testing.T) { 407 | t.Parallel() 408 | client := New("dummy") 409 | ts := cannedResponseServer(t, "testdata/deleteMonitor.json") 410 | defer ts.Close() 411 | client.HTTPClient = ts.Client() 412 | client.URL = ts.URL 413 | var want int64 = 777810874 414 | if err := client.DeleteMonitor(want); err != nil { 415 | t.Error(err) 416 | } 417 | } 418 | 419 | func TestRenderMonitor(t *testing.T) { 420 | t.Parallel() 421 | tcs := []struct { 422 | name string 423 | input Monitor 424 | wantFile string 425 | }{ 426 | { 427 | name: "Simple HTTP", 428 | input: Monitor{ 429 | ID: 777749809, 430 | FriendlyName: "Google", 431 | URL: "http://www.google.com", 432 | Type: TypeHTTP, 433 | Port: 0, 434 | AlertContacts: []string{"3", "5", "7"}, 435 | Status: StatusUp, 436 | }, 437 | wantFile: "testdata/monitor_http.txt", 438 | }, 439 | { 440 | name: "Keyword exists", 441 | input: Monitor{ 442 | ID: 777749810, 443 | FriendlyName: "Google", 444 | URL: "http://www.google.com", 445 | Type: TypeKeyword, 446 | KeywordType: KeywordExists, 447 | KeywordValue: "bogus", 448 | Port: 80, 449 | Status: StatusMaybeDown, 450 | }, 451 | wantFile: "testdata/monitor_keyword.txt", 452 | }, 453 | { 454 | name: "Keyword not exists", 455 | input: Monitor{ 456 | ID: 777749811, 457 | FriendlyName: "Google", 458 | URL: "http://www.google.com", 459 | Type: TypeKeyword, 460 | KeywordType: KeywordNotExists, 461 | KeywordValue: "bogus", 462 | Port: 80, 463 | Status: StatusUnknown, 464 | }, 465 | wantFile: "testdata/monitor_keyword_notexists.txt", 466 | }, 467 | { 468 | name: "Subtype", 469 | input: Monitor{ 470 | ID: 777749812, 471 | FriendlyName: "Google", 472 | URL: "http://www.google.com", 473 | Type: TypePort, 474 | SubType: SubTypeFTP, 475 | Port: 80, 476 | Status: StatusPaused, 477 | }, 478 | wantFile: "testdata/monitor_subtype.txt", 479 | }, 480 | } 481 | for _, tc := range tcs { 482 | t.Run(tc.name, func(t *testing.T) { 483 | t.Parallel() 484 | wantBytes, err := ioutil.ReadFile(tc.wantFile) 485 | if err != nil { 486 | t.Fatal(err) 487 | } 488 | want := string(wantBytes) 489 | got := render(monitorTemplate, tc.input) 490 | if !cmp.Equal(want, got) { 491 | t.Error(cmp.Diff(want, got)) 492 | } 493 | 494 | }) 495 | } 496 | } 497 | 498 | func TestRenderAccount(t *testing.T) { 499 | t.Parallel() 500 | input := Account{ 501 | Email: "j.random@example.com", 502 | MonitorLimit: 300, 503 | MonitorInterval: 1, 504 | UpMonitors: 208, 505 | DownMonitors: 2, 506 | PausedMonitors: 0, 507 | } 508 | wantBytes, err := ioutil.ReadFile("testdata/account_template.txt") 509 | if err != nil { 510 | t.Fatal(err) 511 | } 512 | want := string(wantBytes) 513 | got := render(accountTemplate, input) 514 | if !cmp.Equal(want, got) { 515 | t.Error(cmp.Diff(want, got)) 516 | } 517 | } 518 | 519 | func TestFriendlyType(t *testing.T) { 520 | t.Parallel() 521 | m := Monitor{ 522 | Type: TypeHTTP, 523 | } 524 | want := "HTTP" 525 | got := m.FriendlyType() 526 | if !cmp.Equal(want, got) { 527 | t.Error(cmp.Diff(want, got)) 528 | } 529 | } 530 | 531 | func TestFriendlySubType(t *testing.T) { 532 | t.Parallel() 533 | tcs := []struct { 534 | name string 535 | mon Monitor 536 | want string 537 | }{ 538 | { 539 | name: "HTTPS", 540 | mon: Monitor{ 541 | Type: TypePort, 542 | SubType: SubTypeHTTPS, 543 | }, 544 | want: "HTTPS (443)", 545 | }, 546 | { 547 | name: "Custom port", 548 | mon: Monitor{ 549 | Type: TypePort, 550 | SubType: SubTypeCustomPort, 551 | Port: 8080, 552 | }, 553 | want: "Custom port (8080)", 554 | }, 555 | } 556 | for _, tc := range tcs { 557 | t.Run(tc.name, func(t *testing.T) { 558 | t.Parallel() 559 | got := tc.mon.FriendlySubType() 560 | if !cmp.Equal(tc.want, got) { 561 | t.Error(cmp.Diff(tc.want, got)) 562 | } 563 | }) 564 | } 565 | } 566 | 567 | func TestFriendlyKeywordType(t *testing.T) { 568 | t.Parallel() 569 | m := Monitor{ 570 | Type: TypeKeyword, 571 | KeywordType: KeywordExists, 572 | } 573 | want := "Exists" 574 | got := m.FriendlyKeywordType() 575 | if !cmp.Equal(want, got) { 576 | t.Error(cmp.Diff(want, got)) 577 | } 578 | } 579 | 580 | // cannedResponseServer returns a test TLS server which responds to any request 581 | // with a specified file of canned JSON data. 582 | func cannedResponseServer(t *testing.T, path string) *httptest.Server { 583 | return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 584 | w.WriteHeader(http.StatusOK) 585 | data, err := os.Open(path) 586 | if err != nil { 587 | t.Fatal(err) 588 | } 589 | defer data.Close() 590 | io.Copy(w, data) 591 | })) 592 | } 593 | -------------------------------------------------------------------------------- /pkg/testdata/getMonitorsPage1.json: -------------------------------------------------------------------------------- 1 | { 2 | "stat": "ok", 3 | "pagination": { 4 | "offset": 0, 5 | "limit": 50, 6 | "total": 100 7 | }, 8 | "monitors": [ 9 | { 10 | "id": 777749809, 11 | "friendly_name": "monitor-1", 12 | "url": "http://www.monitor.com", 13 | "type": 1, 14 | "sub_type": "", 15 | "keyword_type": "", 16 | "keyword_value": "", 17 | "http_username": "", 18 | "http_password": "", 19 | "port": "", 20 | "interval": 900, 21 | "status": 1, 22 | "create_datetime": 1462565497, 23 | "monitor_group": 0, 24 | "is_group_main": 0, 25 | "logs": [ 26 | { 27 | "type": 98, 28 | "datetime": 1463540297, 29 | "duration": 1054134 30 | } 31 | ] 32 | }, 33 | { 34 | "id": 777749809, 35 | "friendly_name": "monitor-2", 36 | "url": "http://www.monitor.com", 37 | "type": 1, 38 | "sub_type": "", 39 | "keyword_type": "", 40 | "keyword_value": "", 41 | "http_username": "", 42 | "http_password": "", 43 | "port": "", 44 | "interval": 900, 45 | "status": 1, 46 | "create_datetime": 1462565497, 47 | "monitor_group": 0, 48 | "is_group_main": 0, 49 | "logs": [ 50 | { 51 | "type": 98, 52 | "datetime": 1463540297, 53 | "duration": 1054134 54 | } 55 | ] 56 | }, 57 | { 58 | "id": 777749809, 59 | "friendly_name": "monitor-3", 60 | "url": "http://www.monitor.com", 61 | "type": 1, 62 | "sub_type": "", 63 | "keyword_type": "", 64 | "keyword_value": "", 65 | "http_username": "", 66 | "http_password": "", 67 | "port": "", 68 | "interval": 900, 69 | "status": 1, 70 | "create_datetime": 1462565497, 71 | "monitor_group": 0, 72 | "is_group_main": 0, 73 | "logs": [ 74 | { 75 | "type": 98, 76 | "datetime": 1463540297, 77 | "duration": 1054134 78 | } 79 | ] 80 | }, 81 | { 82 | "id": 777749809, 83 | "friendly_name": "monitor-4", 84 | "url": "http://www.monitor.com", 85 | "type": 1, 86 | "sub_type": "", 87 | "keyword_type": "", 88 | "keyword_value": "", 89 | "http_username": "", 90 | "http_password": "", 91 | "port": "", 92 | "interval": 900, 93 | "status": 1, 94 | "create_datetime": 1462565497, 95 | "monitor_group": 0, 96 | "is_group_main": 0, 97 | "logs": [ 98 | { 99 | "type": 98, 100 | "datetime": 1463540297, 101 | "duration": 1054134 102 | } 103 | ] 104 | }, 105 | { 106 | "id": 777749809, 107 | "friendly_name": "monitor-5", 108 | "url": "http://www.monitor.com", 109 | "type": 1, 110 | "sub_type": "", 111 | "keyword_type": "", 112 | "keyword_value": "", 113 | "http_username": "", 114 | "http_password": "", 115 | "port": "", 116 | "interval": 900, 117 | "status": 1, 118 | "create_datetime": 1462565497, 119 | "monitor_group": 0, 120 | "is_group_main": 0, 121 | "logs": [ 122 | { 123 | "type": 98, 124 | "datetime": 1463540297, 125 | "duration": 1054134 126 | } 127 | ] 128 | }, 129 | { 130 | "id": 777749809, 131 | "friendly_name": "monitor-6", 132 | "url": "http://www.monitor.com", 133 | "type": 1, 134 | "sub_type": "", 135 | "keyword_type": "", 136 | "keyword_value": "", 137 | "http_username": "", 138 | "http_password": "", 139 | "port": "", 140 | "interval": 900, 141 | "status": 1, 142 | "create_datetime": 1462565497, 143 | "monitor_group": 0, 144 | "is_group_main": 0, 145 | "logs": [ 146 | { 147 | "type": 98, 148 | "datetime": 1463540297, 149 | "duration": 1054134 150 | } 151 | ] 152 | }, 153 | { 154 | "id": 777749809, 155 | "friendly_name": "monitor-7", 156 | "url": "http://www.monitor.com", 157 | "type": 1, 158 | "sub_type": "", 159 | "keyword_type": "", 160 | "keyword_value": "", 161 | "http_username": "", 162 | "http_password": "", 163 | "port": "", 164 | "interval": 900, 165 | "status": 1, 166 | "create_datetime": 1462565497, 167 | "monitor_group": 0, 168 | "is_group_main": 0, 169 | "logs": [ 170 | { 171 | "type": 98, 172 | "datetime": 1463540297, 173 | "duration": 1054134 174 | } 175 | ] 176 | }, 177 | { 178 | "id": 777749809, 179 | "friendly_name": "monitor-8", 180 | "url": "http://www.monitor.com", 181 | "type": 1, 182 | "sub_type": "", 183 | "keyword_type": "", 184 | "keyword_value": "", 185 | "http_username": "", 186 | "http_password": "", 187 | "port": "", 188 | "interval": 900, 189 | "status": 1, 190 | "create_datetime": 1462565497, 191 | "monitor_group": 0, 192 | "is_group_main": 0, 193 | "logs": [ 194 | { 195 | "type": 98, 196 | "datetime": 1463540297, 197 | "duration": 1054134 198 | } 199 | ] 200 | }, 201 | { 202 | "id": 777749809, 203 | "friendly_name": "monitor-9", 204 | "url": "http://www.monitor.com", 205 | "type": 1, 206 | "sub_type": "", 207 | "keyword_type": "", 208 | "keyword_value": "", 209 | "http_username": "", 210 | "http_password": "", 211 | "port": "", 212 | "interval": 900, 213 | "status": 1, 214 | "create_datetime": 1462565497, 215 | "monitor_group": 0, 216 | "is_group_main": 0, 217 | "logs": [ 218 | { 219 | "type": 98, 220 | "datetime": 1463540297, 221 | "duration": 1054134 222 | } 223 | ] 224 | }, 225 | { 226 | "id": 777749809, 227 | "friendly_name": "monitor-10", 228 | "url": "http://www.monitor.com", 229 | "type": 1, 230 | "sub_type": "", 231 | "keyword_type": "", 232 | "keyword_value": "", 233 | "http_username": "", 234 | "http_password": "", 235 | "port": "", 236 | "interval": 900, 237 | "status": 1, 238 | "create_datetime": 1462565497, 239 | "monitor_group": 0, 240 | "is_group_main": 0, 241 | "logs": [ 242 | { 243 | "type": 98, 244 | "datetime": 1463540297, 245 | "duration": 1054134 246 | } 247 | ] 248 | }, 249 | { 250 | "id": 777749809, 251 | "friendly_name": "monitor-11", 252 | "url": "http://www.monitor.com", 253 | "type": 1, 254 | "sub_type": "", 255 | "keyword_type": "", 256 | "keyword_value": "", 257 | "http_username": "", 258 | "http_password": "", 259 | "port": "", 260 | "interval": 900, 261 | "status": 1, 262 | "create_datetime": 1462565497, 263 | "monitor_group": 0, 264 | "is_group_main": 0, 265 | "logs": [ 266 | { 267 | "type": 98, 268 | "datetime": 1463540297, 269 | "duration": 1054134 270 | } 271 | ] 272 | }, 273 | { 274 | "id": 777749809, 275 | "friendly_name": "monitor-12", 276 | "url": "http://www.monitor.com", 277 | "type": 1, 278 | "sub_type": "", 279 | "keyword_type": "", 280 | "keyword_value": "", 281 | "http_username": "", 282 | "http_password": "", 283 | "port": "", 284 | "interval": 900, 285 | "status": 1, 286 | "create_datetime": 1462565497, 287 | "monitor_group": 0, 288 | "is_group_main": 0, 289 | "logs": [ 290 | { 291 | "type": 98, 292 | "datetime": 1463540297, 293 | "duration": 1054134 294 | } 295 | ] 296 | }, 297 | { 298 | "id": 777749809, 299 | "friendly_name": "monitor-13", 300 | "url": "http://www.monitor.com", 301 | "type": 1, 302 | "sub_type": "", 303 | "keyword_type": "", 304 | "keyword_value": "", 305 | "http_username": "", 306 | "http_password": "", 307 | "port": "", 308 | "interval": 900, 309 | "status": 1, 310 | "create_datetime": 1462565497, 311 | "monitor_group": 0, 312 | "is_group_main": 0, 313 | "logs": [ 314 | { 315 | "type": 98, 316 | "datetime": 1463540297, 317 | "duration": 1054134 318 | } 319 | ] 320 | }, 321 | { 322 | "id": 777749809, 323 | "friendly_name": "monitor-14", 324 | "url": "http://www.monitor.com", 325 | "type": 1, 326 | "sub_type": "", 327 | "keyword_type": "", 328 | "keyword_value": "", 329 | "http_username": "", 330 | "http_password": "", 331 | "port": "", 332 | "interval": 900, 333 | "status": 1, 334 | "create_datetime": 1462565497, 335 | "monitor_group": 0, 336 | "is_group_main": 0, 337 | "logs": [ 338 | { 339 | "type": 98, 340 | "datetime": 1463540297, 341 | "duration": 1054134 342 | } 343 | ] 344 | }, 345 | { 346 | "id": 777749809, 347 | "friendly_name": "monitor-15", 348 | "url": "http://www.monitor.com", 349 | "type": 1, 350 | "sub_type": "", 351 | "keyword_type": "", 352 | "keyword_value": "", 353 | "http_username": "", 354 | "http_password": "", 355 | "port": "", 356 | "interval": 900, 357 | "status": 1, 358 | "create_datetime": 1462565497, 359 | "monitor_group": 0, 360 | "is_group_main": 0, 361 | "logs": [ 362 | { 363 | "type": 98, 364 | "datetime": 1463540297, 365 | "duration": 1054134 366 | } 367 | ] 368 | }, 369 | { 370 | "id": 777749809, 371 | "friendly_name": "monitor-16", 372 | "url": "http://www.monitor.com", 373 | "type": 1, 374 | "sub_type": "", 375 | "keyword_type": "", 376 | "keyword_value": "", 377 | "http_username": "", 378 | "http_password": "", 379 | "port": "", 380 | "interval": 900, 381 | "status": 1, 382 | "create_datetime": 1462565497, 383 | "monitor_group": 0, 384 | "is_group_main": 0, 385 | "logs": [ 386 | { 387 | "type": 98, 388 | "datetime": 1463540297, 389 | "duration": 1054134 390 | } 391 | ] 392 | }, 393 | { 394 | "id": 777749809, 395 | "friendly_name": "monitor-17", 396 | "url": "http://www.monitor.com", 397 | "type": 1, 398 | "sub_type": "", 399 | "keyword_type": "", 400 | "keyword_value": "", 401 | "http_username": "", 402 | "http_password": "", 403 | "port": "", 404 | "interval": 900, 405 | "status": 1, 406 | "create_datetime": 1462565497, 407 | "monitor_group": 0, 408 | "is_group_main": 0, 409 | "logs": [ 410 | { 411 | "type": 98, 412 | "datetime": 1463540297, 413 | "duration": 1054134 414 | } 415 | ] 416 | }, 417 | { 418 | "id": 777749809, 419 | "friendly_name": "monitor-18", 420 | "url": "http://www.monitor.com", 421 | "type": 1, 422 | "sub_type": "", 423 | "keyword_type": "", 424 | "keyword_value": "", 425 | "http_username": "", 426 | "http_password": "", 427 | "port": "", 428 | "interval": 900, 429 | "status": 1, 430 | "create_datetime": 1462565497, 431 | "monitor_group": 0, 432 | "is_group_main": 0, 433 | "logs": [ 434 | { 435 | "type": 98, 436 | "datetime": 1463540297, 437 | "duration": 1054134 438 | } 439 | ] 440 | }, 441 | { 442 | "id": 777749809, 443 | "friendly_name": "monitor-19", 444 | "url": "http://www.monitor.com", 445 | "type": 1, 446 | "sub_type": "", 447 | "keyword_type": "", 448 | "keyword_value": "", 449 | "http_username": "", 450 | "http_password": "", 451 | "port": "", 452 | "interval": 900, 453 | "status": 1, 454 | "create_datetime": 1462565497, 455 | "monitor_group": 0, 456 | "is_group_main": 0, 457 | "logs": [ 458 | { 459 | "type": 98, 460 | "datetime": 1463540297, 461 | "duration": 1054134 462 | } 463 | ] 464 | }, 465 | { 466 | "id": 777749809, 467 | "friendly_name": "monitor-20", 468 | "url": "http://www.monitor.com", 469 | "type": 1, 470 | "sub_type": "", 471 | "keyword_type": "", 472 | "keyword_value": "", 473 | "http_username": "", 474 | "http_password": "", 475 | "port": "", 476 | "interval": 900, 477 | "status": 1, 478 | "create_datetime": 1462565497, 479 | "monitor_group": 0, 480 | "is_group_main": 0, 481 | "logs": [ 482 | { 483 | "type": 98, 484 | "datetime": 1463540297, 485 | "duration": 1054134 486 | } 487 | ] 488 | }, 489 | { 490 | "id": 777749809, 491 | "friendly_name": "monitor-21", 492 | "url": "http://www.monitor.com", 493 | "type": 1, 494 | "sub_type": "", 495 | "keyword_type": "", 496 | "keyword_value": "", 497 | "http_username": "", 498 | "http_password": "", 499 | "port": "", 500 | "interval": 900, 501 | "status": 1, 502 | "create_datetime": 1462565497, 503 | "monitor_group": 0, 504 | "is_group_main": 0, 505 | "logs": [ 506 | { 507 | "type": 98, 508 | "datetime": 1463540297, 509 | "duration": 1054134 510 | } 511 | ] 512 | }, 513 | { 514 | "id": 777749809, 515 | "friendly_name": "monitor-22", 516 | "url": "http://www.monitor.com", 517 | "type": 1, 518 | "sub_type": "", 519 | "keyword_type": "", 520 | "keyword_value": "", 521 | "http_username": "", 522 | "http_password": "", 523 | "port": "", 524 | "interval": 900, 525 | "status": 1, 526 | "create_datetime": 1462565497, 527 | "monitor_group": 0, 528 | "is_group_main": 0, 529 | "logs": [ 530 | { 531 | "type": 98, 532 | "datetime": 1463540297, 533 | "duration": 1054134 534 | } 535 | ] 536 | }, 537 | { 538 | "id": 777749809, 539 | "friendly_name": "monitor-23", 540 | "url": "http://www.monitor.com", 541 | "type": 1, 542 | "sub_type": "", 543 | "keyword_type": "", 544 | "keyword_value": "", 545 | "http_username": "", 546 | "http_password": "", 547 | "port": "", 548 | "interval": 900, 549 | "status": 1, 550 | "create_datetime": 1462565497, 551 | "monitor_group": 0, 552 | "is_group_main": 0, 553 | "logs": [ 554 | { 555 | "type": 98, 556 | "datetime": 1463540297, 557 | "duration": 1054134 558 | } 559 | ] 560 | }, 561 | { 562 | "id": 777749809, 563 | "friendly_name": "monitor-24", 564 | "url": "http://www.monitor.com", 565 | "type": 1, 566 | "sub_type": "", 567 | "keyword_type": "", 568 | "keyword_value": "", 569 | "http_username": "", 570 | "http_password": "", 571 | "port": "", 572 | "interval": 900, 573 | "status": 1, 574 | "create_datetime": 1462565497, 575 | "monitor_group": 0, 576 | "is_group_main": 0, 577 | "logs": [ 578 | { 579 | "type": 98, 580 | "datetime": 1463540297, 581 | "duration": 1054134 582 | } 583 | ] 584 | }, 585 | { 586 | "id": 777749809, 587 | "friendly_name": "monitor-25", 588 | "url": "http://www.monitor.com", 589 | "type": 1, 590 | "sub_type": "", 591 | "keyword_type": "", 592 | "keyword_value": "", 593 | "http_username": "", 594 | "http_password": "", 595 | "port": "", 596 | "interval": 900, 597 | "status": 1, 598 | "create_datetime": 1462565497, 599 | "monitor_group": 0, 600 | "is_group_main": 0, 601 | "logs": [ 602 | { 603 | "type": 98, 604 | "datetime": 1463540297, 605 | "duration": 1054134 606 | } 607 | ] 608 | }, 609 | { 610 | "id": 777749809, 611 | "friendly_name": "monitor-26", 612 | "url": "http://www.monitor.com", 613 | "type": 1, 614 | "sub_type": "", 615 | "keyword_type": "", 616 | "keyword_value": "", 617 | "http_username": "", 618 | "http_password": "", 619 | "port": "", 620 | "interval": 900, 621 | "status": 1, 622 | "create_datetime": 1462565497, 623 | "monitor_group": 0, 624 | "is_group_main": 0, 625 | "logs": [ 626 | { 627 | "type": 98, 628 | "datetime": 1463540297, 629 | "duration": 1054134 630 | } 631 | ] 632 | }, 633 | { 634 | "id": 777749809, 635 | "friendly_name": "monitor-27", 636 | "url": "http://www.monitor.com", 637 | "type": 1, 638 | "sub_type": "", 639 | "keyword_type": "", 640 | "keyword_value": "", 641 | "http_username": "", 642 | "http_password": "", 643 | "port": "", 644 | "interval": 900, 645 | "status": 1, 646 | "create_datetime": 1462565497, 647 | "monitor_group": 0, 648 | "is_group_main": 0, 649 | "logs": [ 650 | { 651 | "type": 98, 652 | "datetime": 1463540297, 653 | "duration": 1054134 654 | } 655 | ] 656 | }, 657 | { 658 | "id": 777749809, 659 | "friendly_name": "monitor-28", 660 | "url": "http://www.monitor.com", 661 | "type": 1, 662 | "sub_type": "", 663 | "keyword_type": "", 664 | "keyword_value": "", 665 | "http_username": "", 666 | "http_password": "", 667 | "port": "", 668 | "interval": 900, 669 | "status": 1, 670 | "create_datetime": 1462565497, 671 | "monitor_group": 0, 672 | "is_group_main": 0, 673 | "logs": [ 674 | { 675 | "type": 98, 676 | "datetime": 1463540297, 677 | "duration": 1054134 678 | } 679 | ] 680 | }, 681 | { 682 | "id": 777749809, 683 | "friendly_name": "monitor-29", 684 | "url": "http://www.monitor.com", 685 | "type": 1, 686 | "sub_type": "", 687 | "keyword_type": "", 688 | "keyword_value": "", 689 | "http_username": "", 690 | "http_password": "", 691 | "port": "", 692 | "interval": 900, 693 | "status": 1, 694 | "create_datetime": 1462565497, 695 | "monitor_group": 0, 696 | "is_group_main": 0, 697 | "logs": [ 698 | { 699 | "type": 98, 700 | "datetime": 1463540297, 701 | "duration": 1054134 702 | } 703 | ] 704 | }, 705 | { 706 | "id": 777749809, 707 | "friendly_name": "monitor-30", 708 | "url": "http://www.monitor.com", 709 | "type": 1, 710 | "sub_type": "", 711 | "keyword_type": "", 712 | "keyword_value": "", 713 | "http_username": "", 714 | "http_password": "", 715 | "port": "", 716 | "interval": 900, 717 | "status": 1, 718 | "create_datetime": 1462565497, 719 | "monitor_group": 0, 720 | "is_group_main": 0, 721 | "logs": [ 722 | { 723 | "type": 98, 724 | "datetime": 1463540297, 725 | "duration": 1054134 726 | } 727 | ] 728 | }, 729 | { 730 | "id": 777749809, 731 | "friendly_name": "monitor-31", 732 | "url": "http://www.monitor.com", 733 | "type": 1, 734 | "sub_type": "", 735 | "keyword_type": "", 736 | "keyword_value": "", 737 | "http_username": "", 738 | "http_password": "", 739 | "port": "", 740 | "interval": 900, 741 | "status": 1, 742 | "create_datetime": 1462565497, 743 | "monitor_group": 0, 744 | "is_group_main": 0, 745 | "logs": [ 746 | { 747 | "type": 98, 748 | "datetime": 1463540297, 749 | "duration": 1054134 750 | } 751 | ] 752 | }, 753 | { 754 | "id": 777749809, 755 | "friendly_name": "monitor-32", 756 | "url": "http://www.monitor.com", 757 | "type": 1, 758 | "sub_type": "", 759 | "keyword_type": "", 760 | "keyword_value": "", 761 | "http_username": "", 762 | "http_password": "", 763 | "port": "", 764 | "interval": 900, 765 | "status": 1, 766 | "create_datetime": 1462565497, 767 | "monitor_group": 0, 768 | "is_group_main": 0, 769 | "logs": [ 770 | { 771 | "type": 98, 772 | "datetime": 1463540297, 773 | "duration": 1054134 774 | } 775 | ] 776 | }, 777 | { 778 | "id": 777749809, 779 | "friendly_name": "monitor-33", 780 | "url": "http://www.monitor.com", 781 | "type": 1, 782 | "sub_type": "", 783 | "keyword_type": "", 784 | "keyword_value": "", 785 | "http_username": "", 786 | "http_password": "", 787 | "port": "", 788 | "interval": 900, 789 | "status": 1, 790 | "create_datetime": 1462565497, 791 | "monitor_group": 0, 792 | "is_group_main": 0, 793 | "logs": [ 794 | { 795 | "type": 98, 796 | "datetime": 1463540297, 797 | "duration": 1054134 798 | } 799 | ] 800 | }, 801 | { 802 | "id": 777749809, 803 | "friendly_name": "monitor-34", 804 | "url": "http://www.monitor.com", 805 | "type": 1, 806 | "sub_type": "", 807 | "keyword_type": "", 808 | "keyword_value": "", 809 | "http_username": "", 810 | "http_password": "", 811 | "port": "", 812 | "interval": 900, 813 | "status": 1, 814 | "create_datetime": 1462565497, 815 | "monitor_group": 0, 816 | "is_group_main": 0, 817 | "logs": [ 818 | { 819 | "type": 98, 820 | "datetime": 1463540297, 821 | "duration": 1054134 822 | } 823 | ] 824 | }, 825 | { 826 | "id": 777749809, 827 | "friendly_name": "monitor-35", 828 | "url": "http://www.monitor.com", 829 | "type": 1, 830 | "sub_type": "", 831 | "keyword_type": "", 832 | "keyword_value": "", 833 | "http_username": "", 834 | "http_password": "", 835 | "port": "", 836 | "interval": 900, 837 | "status": 1, 838 | "create_datetime": 1462565497, 839 | "monitor_group": 0, 840 | "is_group_main": 0, 841 | "logs": [ 842 | { 843 | "type": 98, 844 | "datetime": 1463540297, 845 | "duration": 1054134 846 | } 847 | ] 848 | }, 849 | { 850 | "id": 777749809, 851 | "friendly_name": "monitor-36", 852 | "url": "http://www.monitor.com", 853 | "type": 1, 854 | "sub_type": "", 855 | "keyword_type": "", 856 | "keyword_value": "", 857 | "http_username": "", 858 | "http_password": "", 859 | "port": "", 860 | "interval": 900, 861 | "status": 1, 862 | "create_datetime": 1462565497, 863 | "monitor_group": 0, 864 | "is_group_main": 0, 865 | "logs": [ 866 | { 867 | "type": 98, 868 | "datetime": 1463540297, 869 | "duration": 1054134 870 | } 871 | ] 872 | }, 873 | { 874 | "id": 777749809, 875 | "friendly_name": "monitor-37", 876 | "url": "http://www.monitor.com", 877 | "type": 1, 878 | "sub_type": "", 879 | "keyword_type": "", 880 | "keyword_value": "", 881 | "http_username": "", 882 | "http_password": "", 883 | "port": "", 884 | "interval": 900, 885 | "status": 1, 886 | "create_datetime": 1462565497, 887 | "monitor_group": 0, 888 | "is_group_main": 0, 889 | "logs": [ 890 | { 891 | "type": 98, 892 | "datetime": 1463540297, 893 | "duration": 1054134 894 | } 895 | ] 896 | }, 897 | { 898 | "id": 777749809, 899 | "friendly_name": "monitor-38", 900 | "url": "http://www.monitor.com", 901 | "type": 1, 902 | "sub_type": "", 903 | "keyword_type": "", 904 | "keyword_value": "", 905 | "http_username": "", 906 | "http_password": "", 907 | "port": "", 908 | "interval": 900, 909 | "status": 1, 910 | "create_datetime": 1462565497, 911 | "monitor_group": 0, 912 | "is_group_main": 0, 913 | "logs": [ 914 | { 915 | "type": 98, 916 | "datetime": 1463540297, 917 | "duration": 1054134 918 | } 919 | ] 920 | }, 921 | { 922 | "id": 777749809, 923 | "friendly_name": "monitor-39", 924 | "url": "http://www.monitor.com", 925 | "type": 1, 926 | "sub_type": "", 927 | "keyword_type": "", 928 | "keyword_value": "", 929 | "http_username": "", 930 | "http_password": "", 931 | "port": "", 932 | "interval": 900, 933 | "status": 1, 934 | "create_datetime": 1462565497, 935 | "monitor_group": 0, 936 | "is_group_main": 0, 937 | "logs": [ 938 | { 939 | "type": 98, 940 | "datetime": 1463540297, 941 | "duration": 1054134 942 | } 943 | ] 944 | }, 945 | { 946 | "id": 777749809, 947 | "friendly_name": "monitor-40", 948 | "url": "http://www.monitor.com", 949 | "type": 1, 950 | "sub_type": "", 951 | "keyword_type": "", 952 | "keyword_value": "", 953 | "http_username": "", 954 | "http_password": "", 955 | "port": "", 956 | "interval": 900, 957 | "status": 1, 958 | "create_datetime": 1462565497, 959 | "monitor_group": 0, 960 | "is_group_main": 0, 961 | "logs": [ 962 | { 963 | "type": 98, 964 | "datetime": 1463540297, 965 | "duration": 1054134 966 | } 967 | ] 968 | }, 969 | { 970 | "id": 777749809, 971 | "friendly_name": "monitor-41", 972 | "url": "http://www.monitor.com", 973 | "type": 1, 974 | "sub_type": "", 975 | "keyword_type": "", 976 | "keyword_value": "", 977 | "http_username": "", 978 | "http_password": "", 979 | "port": "", 980 | "interval": 900, 981 | "status": 1, 982 | "create_datetime": 1462565497, 983 | "monitor_group": 0, 984 | "is_group_main": 0, 985 | "logs": [ 986 | { 987 | "type": 98, 988 | "datetime": 1463540297, 989 | "duration": 1054134 990 | } 991 | ] 992 | }, 993 | { 994 | "id": 777749809, 995 | "friendly_name": "monitor-42", 996 | "url": "http://www.monitor.com", 997 | "type": 1, 998 | "sub_type": "", 999 | "keyword_type": "", 1000 | "keyword_value": "", 1001 | "http_username": "", 1002 | "http_password": "", 1003 | "port": "", 1004 | "interval": 900, 1005 | "status": 1, 1006 | "create_datetime": 1462565497, 1007 | "monitor_group": 0, 1008 | "is_group_main": 0, 1009 | "logs": [ 1010 | { 1011 | "type": 98, 1012 | "datetime": 1463540297, 1013 | "duration": 1054134 1014 | } 1015 | ] 1016 | }, 1017 | { 1018 | "id": 777749809, 1019 | "friendly_name": "monitor-43", 1020 | "url": "http://www.monitor.com", 1021 | "type": 1, 1022 | "sub_type": "", 1023 | "keyword_type": "", 1024 | "keyword_value": "", 1025 | "http_username": "", 1026 | "http_password": "", 1027 | "port": "", 1028 | "interval": 900, 1029 | "status": 1, 1030 | "create_datetime": 1462565497, 1031 | "monitor_group": 0, 1032 | "is_group_main": 0, 1033 | "logs": [ 1034 | { 1035 | "type": 98, 1036 | "datetime": 1463540297, 1037 | "duration": 1054134 1038 | } 1039 | ] 1040 | }, 1041 | { 1042 | "id": 777749809, 1043 | "friendly_name": "monitor-44", 1044 | "url": "http://www.monitor.com", 1045 | "type": 1, 1046 | "sub_type": "", 1047 | "keyword_type": "", 1048 | "keyword_value": "", 1049 | "http_username": "", 1050 | "http_password": "", 1051 | "port": "", 1052 | "interval": 900, 1053 | "status": 1, 1054 | "create_datetime": 1462565497, 1055 | "monitor_group": 0, 1056 | "is_group_main": 0, 1057 | "logs": [ 1058 | { 1059 | "type": 98, 1060 | "datetime": 1463540297, 1061 | "duration": 1054134 1062 | } 1063 | ] 1064 | }, 1065 | { 1066 | "id": 777749809, 1067 | "friendly_name": "monitor-45", 1068 | "url": "http://www.monitor.com", 1069 | "type": 1, 1070 | "sub_type": "", 1071 | "keyword_type": "", 1072 | "keyword_value": "", 1073 | "http_username": "", 1074 | "http_password": "", 1075 | "port": "", 1076 | "interval": 900, 1077 | "status": 1, 1078 | "create_datetime": 1462565497, 1079 | "monitor_group": 0, 1080 | "is_group_main": 0, 1081 | "logs": [ 1082 | { 1083 | "type": 98, 1084 | "datetime": 1463540297, 1085 | "duration": 1054134 1086 | } 1087 | ] 1088 | }, 1089 | { 1090 | "id": 777749809, 1091 | "friendly_name": "monitor-46", 1092 | "url": "http://www.monitor.com", 1093 | "type": 1, 1094 | "sub_type": "", 1095 | "keyword_type": "", 1096 | "keyword_value": "", 1097 | "http_username": "", 1098 | "http_password": "", 1099 | "port": "", 1100 | "interval": 900, 1101 | "status": 1, 1102 | "create_datetime": 1462565497, 1103 | "monitor_group": 0, 1104 | "is_group_main": 0, 1105 | "logs": [ 1106 | { 1107 | "type": 98, 1108 | "datetime": 1463540297, 1109 | "duration": 1054134 1110 | } 1111 | ] 1112 | }, 1113 | { 1114 | "id": 777749809, 1115 | "friendly_name": "monitor-47", 1116 | "url": "http://www.monitor.com", 1117 | "type": 1, 1118 | "sub_type": "", 1119 | "keyword_type": "", 1120 | "keyword_value": "", 1121 | "http_username": "", 1122 | "http_password": "", 1123 | "port": "", 1124 | "interval": 900, 1125 | "status": 1, 1126 | "create_datetime": 1462565497, 1127 | "monitor_group": 0, 1128 | "is_group_main": 0, 1129 | "logs": [ 1130 | { 1131 | "type": 98, 1132 | "datetime": 1463540297, 1133 | "duration": 1054134 1134 | } 1135 | ] 1136 | }, 1137 | { 1138 | "id": 777749809, 1139 | "friendly_name": "monitor-48", 1140 | "url": "http://www.monitor.com", 1141 | "type": 1, 1142 | "sub_type": "", 1143 | "keyword_type": "", 1144 | "keyword_value": "", 1145 | "http_username": "", 1146 | "http_password": "", 1147 | "port": "", 1148 | "interval": 900, 1149 | "status": 1, 1150 | "create_datetime": 1462565497, 1151 | "monitor_group": 0, 1152 | "is_group_main": 0, 1153 | "logs": [ 1154 | { 1155 | "type": 98, 1156 | "datetime": 1463540297, 1157 | "duration": 1054134 1158 | } 1159 | ] 1160 | }, 1161 | { 1162 | "id": 777749809, 1163 | "friendly_name": "monitor-49", 1164 | "url": "http://www.monitor.com", 1165 | "type": 1, 1166 | "sub_type": "", 1167 | "keyword_type": "", 1168 | "keyword_value": "", 1169 | "http_username": "", 1170 | "http_password": "", 1171 | "port": "", 1172 | "interval": 900, 1173 | "status": 1, 1174 | "create_datetime": 1462565497, 1175 | "monitor_group": 0, 1176 | "is_group_main": 0, 1177 | "logs": [ 1178 | { 1179 | "type": 98, 1180 | "datetime": 1463540297, 1181 | "duration": 1054134 1182 | } 1183 | ] 1184 | }, 1185 | { 1186 | "id": 777749809, 1187 | "friendly_name": "monitor-50", 1188 | "url": "http://www.monitor.com", 1189 | "type": 1, 1190 | "sub_type": "", 1191 | "keyword_type": "", 1192 | "keyword_value": "", 1193 | "http_username": "", 1194 | "http_password": "", 1195 | "port": "", 1196 | "interval": 900, 1197 | "status": 1, 1198 | "create_datetime": 1462565497, 1199 | "monitor_group": 0, 1200 | "is_group_main": 0, 1201 | "logs": [ 1202 | { 1203 | "type": 98, 1204 | "datetime": 1463540297, 1205 | "duration": 1054134 1206 | } 1207 | ] 1208 | } 1209 | ] 1210 | } -------------------------------------------------------------------------------- /pkg/testdata/getMonitorsPage2.json: -------------------------------------------------------------------------------- 1 | { 2 | "stat": "ok", 3 | "pagination": { 4 | "offset": 51, 5 | "limit": 50, 6 | "total": 100 7 | }, 8 | "monitors": [ 9 | { 10 | "id": 777749809, 11 | "friendly_name": "monitor-51", 12 | "url": "http://www.monitor.com", 13 | "type": 1, 14 | "sub_type": "", 15 | "keyword_type": "", 16 | "keyword_value": "", 17 | "http_username": "", 18 | "http_password": "", 19 | "port": "", 20 | "interval": 900, 21 | "status": 1, 22 | "create_datetime": 1462565497, 23 | "monitor_group": 0, 24 | "is_group_main": 0, 25 | "logs": [ 26 | { 27 | "type": 98, 28 | "datetime": 1463540297, 29 | "duration": 1054134 30 | } 31 | ] 32 | }, 33 | { 34 | "id": 777749809, 35 | "friendly_name": "monitor-52", 36 | "url": "http://www.monitor.com", 37 | "type": 1, 38 | "sub_type": "", 39 | "keyword_type": "", 40 | "keyword_value": "", 41 | "http_username": "", 42 | "http_password": "", 43 | "port": "", 44 | "interval": 900, 45 | "status": 1, 46 | "create_datetime": 1462565497, 47 | "monitor_group": 0, 48 | "is_group_main": 0, 49 | "logs": [ 50 | { 51 | "type": 98, 52 | "datetime": 1463540297, 53 | "duration": 1054134 54 | } 55 | ] 56 | }, 57 | { 58 | "id": 777749809, 59 | "friendly_name": "monitor-53", 60 | "url": "http://www.monitor.com", 61 | "type": 1, 62 | "sub_type": "", 63 | "keyword_type": "", 64 | "keyword_value": "", 65 | "http_username": "", 66 | "http_password": "", 67 | "port": "", 68 | "interval": 900, 69 | "status": 1, 70 | "create_datetime": 1462565497, 71 | "monitor_group": 0, 72 | "is_group_main": 0, 73 | "logs": [ 74 | { 75 | "type": 98, 76 | "datetime": 1463540297, 77 | "duration": 1054134 78 | } 79 | ] 80 | }, 81 | { 82 | "id": 777749809, 83 | "friendly_name": "monitor-54", 84 | "url": "http://www.monitor.com", 85 | "type": 1, 86 | "sub_type": "", 87 | "keyword_type": "", 88 | "keyword_value": "", 89 | "http_username": "", 90 | "http_password": "", 91 | "port": "", 92 | "interval": 900, 93 | "status": 1, 94 | "create_datetime": 1462565497, 95 | "monitor_group": 0, 96 | "is_group_main": 0, 97 | "logs": [ 98 | { 99 | "type": 98, 100 | "datetime": 1463540297, 101 | "duration": 1054134 102 | } 103 | ] 104 | }, 105 | { 106 | "id": 777749809, 107 | "friendly_name": "monitor-55", 108 | "url": "http://www.monitor.com", 109 | "type": 1, 110 | "sub_type": "", 111 | "keyword_type": "", 112 | "keyword_value": "", 113 | "http_username": "", 114 | "http_password": "", 115 | "port": "", 116 | "interval": 900, 117 | "status": 1, 118 | "create_datetime": 1462565497, 119 | "monitor_group": 0, 120 | "is_group_main": 0, 121 | "logs": [ 122 | { 123 | "type": 98, 124 | "datetime": 1463540297, 125 | "duration": 1054134 126 | } 127 | ] 128 | }, 129 | { 130 | "id": 777749809, 131 | "friendly_name": "monitor-56", 132 | "url": "http://www.monitor.com", 133 | "type": 1, 134 | "sub_type": "", 135 | "keyword_type": "", 136 | "keyword_value": "", 137 | "http_username": "", 138 | "http_password": "", 139 | "port": "", 140 | "interval": 900, 141 | "status": 1, 142 | "create_datetime": 1462565497, 143 | "monitor_group": 0, 144 | "is_group_main": 0, 145 | "logs": [ 146 | { 147 | "type": 98, 148 | "datetime": 1463540297, 149 | "duration": 1054134 150 | } 151 | ] 152 | }, 153 | { 154 | "id": 777749809, 155 | "friendly_name": "monitor-57", 156 | "url": "http://www.monitor.com", 157 | "type": 1, 158 | "sub_type": "", 159 | "keyword_type": "", 160 | "keyword_value": "", 161 | "http_username": "", 162 | "http_password": "", 163 | "port": "", 164 | "interval": 900, 165 | "status": 1, 166 | "create_datetime": 1462565497, 167 | "monitor_group": 0, 168 | "is_group_main": 0, 169 | "logs": [ 170 | { 171 | "type": 98, 172 | "datetime": 1463540297, 173 | "duration": 1054134 174 | } 175 | ] 176 | }, 177 | { 178 | "id": 777749809, 179 | "friendly_name": "monitor-58", 180 | "url": "http://www.monitor.com", 181 | "type": 1, 182 | "sub_type": "", 183 | "keyword_type": "", 184 | "keyword_value": "", 185 | "http_username": "", 186 | "http_password": "", 187 | "port": "", 188 | "interval": 900, 189 | "status": 1, 190 | "create_datetime": 1462565497, 191 | "monitor_group": 0, 192 | "is_group_main": 0, 193 | "logs": [ 194 | { 195 | "type": 98, 196 | "datetime": 1463540297, 197 | "duration": 1054134 198 | } 199 | ] 200 | }, 201 | { 202 | "id": 777749809, 203 | "friendly_name": "monitor-59", 204 | "url": "http://www.monitor.com", 205 | "type": 1, 206 | "sub_type": "", 207 | "keyword_type": "", 208 | "keyword_value": "", 209 | "http_username": "", 210 | "http_password": "", 211 | "port": "", 212 | "interval": 900, 213 | "status": 1, 214 | "create_datetime": 1462565497, 215 | "monitor_group": 0, 216 | "is_group_main": 0, 217 | "logs": [ 218 | { 219 | "type": 98, 220 | "datetime": 1463540297, 221 | "duration": 1054134 222 | } 223 | ] 224 | }, 225 | { 226 | "id": 777749809, 227 | "friendly_name": "monitor-60", 228 | "url": "http://www.monitor.com", 229 | "type": 1, 230 | "sub_type": "", 231 | "keyword_type": "", 232 | "keyword_value": "", 233 | "http_username": "", 234 | "http_password": "", 235 | "port": "", 236 | "interval": 900, 237 | "status": 1, 238 | "create_datetime": 1462565497, 239 | "monitor_group": 0, 240 | "is_group_main": 0, 241 | "logs": [ 242 | { 243 | "type": 98, 244 | "datetime": 1463540297, 245 | "duration": 1054134 246 | } 247 | ] 248 | }, 249 | { 250 | "id": 777749809, 251 | "friendly_name": "monitor-61", 252 | "url": "http://www.monitor.com", 253 | "type": 1, 254 | "sub_type": "", 255 | "keyword_type": "", 256 | "keyword_value": "", 257 | "http_username": "", 258 | "http_password": "", 259 | "port": "", 260 | "interval": 900, 261 | "status": 1, 262 | "create_datetime": 1462565497, 263 | "monitor_group": 0, 264 | "is_group_main": 0, 265 | "logs": [ 266 | { 267 | "type": 98, 268 | "datetime": 1463540297, 269 | "duration": 1054134 270 | } 271 | ] 272 | }, 273 | { 274 | "id": 777749809, 275 | "friendly_name": "monitor-62", 276 | "url": "http://www.monitor.com", 277 | "type": 1, 278 | "sub_type": "", 279 | "keyword_type": "", 280 | "keyword_value": "", 281 | "http_username": "", 282 | "http_password": "", 283 | "port": "", 284 | "interval": 900, 285 | "status": 1, 286 | "create_datetime": 1462565497, 287 | "monitor_group": 0, 288 | "is_group_main": 0, 289 | "logs": [ 290 | { 291 | "type": 98, 292 | "datetime": 1463540297, 293 | "duration": 1054134 294 | } 295 | ] 296 | }, 297 | { 298 | "id": 777749809, 299 | "friendly_name": "monitor-63", 300 | "url": "http://www.monitor.com", 301 | "type": 1, 302 | "sub_type": "", 303 | "keyword_type": "", 304 | "keyword_value": "", 305 | "http_username": "", 306 | "http_password": "", 307 | "port": "", 308 | "interval": 900, 309 | "status": 1, 310 | "create_datetime": 1462565497, 311 | "monitor_group": 0, 312 | "is_group_main": 0, 313 | "logs": [ 314 | { 315 | "type": 98, 316 | "datetime": 1463540297, 317 | "duration": 1054134 318 | } 319 | ] 320 | }, 321 | { 322 | "id": 777749809, 323 | "friendly_name": "monitor-64", 324 | "url": "http://www.monitor.com", 325 | "type": 1, 326 | "sub_type": "", 327 | "keyword_type": "", 328 | "keyword_value": "", 329 | "http_username": "", 330 | "http_password": "", 331 | "port": "", 332 | "interval": 900, 333 | "status": 1, 334 | "create_datetime": 1462565497, 335 | "monitor_group": 0, 336 | "is_group_main": 0, 337 | "logs": [ 338 | { 339 | "type": 98, 340 | "datetime": 1463540297, 341 | "duration": 1054134 342 | } 343 | ] 344 | }, 345 | { 346 | "id": 777749809, 347 | "friendly_name": "monitor-65", 348 | "url": "http://www.monitor.com", 349 | "type": 1, 350 | "sub_type": "", 351 | "keyword_type": "", 352 | "keyword_value": "", 353 | "http_username": "", 354 | "http_password": "", 355 | "port": "", 356 | "interval": 900, 357 | "status": 1, 358 | "create_datetime": 1462565497, 359 | "monitor_group": 0, 360 | "is_group_main": 0, 361 | "logs": [ 362 | { 363 | "type": 98, 364 | "datetime": 1463540297, 365 | "duration": 1054134 366 | } 367 | ] 368 | }, 369 | { 370 | "id": 777749809, 371 | "friendly_name": "monitor-66", 372 | "url": "http://www.monitor.com", 373 | "type": 1, 374 | "sub_type": "", 375 | "keyword_type": "", 376 | "keyword_value": "", 377 | "http_username": "", 378 | "http_password": "", 379 | "port": "", 380 | "interval": 900, 381 | "status": 1, 382 | "create_datetime": 1462565497, 383 | "monitor_group": 0, 384 | "is_group_main": 0, 385 | "logs": [ 386 | { 387 | "type": 98, 388 | "datetime": 1463540297, 389 | "duration": 1054134 390 | } 391 | ] 392 | }, 393 | { 394 | "id": 777749809, 395 | "friendly_name": "monitor-67", 396 | "url": "http://www.monitor.com", 397 | "type": 1, 398 | "sub_type": "", 399 | "keyword_type": "", 400 | "keyword_value": "", 401 | "http_username": "", 402 | "http_password": "", 403 | "port": "", 404 | "interval": 900, 405 | "status": 1, 406 | "create_datetime": 1462565497, 407 | "monitor_group": 0, 408 | "is_group_main": 0, 409 | "logs": [ 410 | { 411 | "type": 98, 412 | "datetime": 1463540297, 413 | "duration": 1054134 414 | } 415 | ] 416 | }, 417 | { 418 | "id": 777749809, 419 | "friendly_name": "monitor-68", 420 | "url": "http://www.monitor.com", 421 | "type": 1, 422 | "sub_type": "", 423 | "keyword_type": "", 424 | "keyword_value": "", 425 | "http_username": "", 426 | "http_password": "", 427 | "port": "", 428 | "interval": 900, 429 | "status": 1, 430 | "create_datetime": 1462565497, 431 | "monitor_group": 0, 432 | "is_group_main": 0, 433 | "logs": [ 434 | { 435 | "type": 98, 436 | "datetime": 1463540297, 437 | "duration": 1054134 438 | } 439 | ] 440 | }, 441 | { 442 | "id": 777749809, 443 | "friendly_name": "monitor-69", 444 | "url": "http://www.monitor.com", 445 | "type": 1, 446 | "sub_type": "", 447 | "keyword_type": "", 448 | "keyword_value": "", 449 | "http_username": "", 450 | "http_password": "", 451 | "port": "", 452 | "interval": 900, 453 | "status": 1, 454 | "create_datetime": 1462565497, 455 | "monitor_group": 0, 456 | "is_group_main": 0, 457 | "logs": [ 458 | { 459 | "type": 98, 460 | "datetime": 1463540297, 461 | "duration": 1054134 462 | } 463 | ] 464 | }, 465 | { 466 | "id": 777749809, 467 | "friendly_name": "monitor-70", 468 | "url": "http://www.monitor.com", 469 | "type": 1, 470 | "sub_type": "", 471 | "keyword_type": "", 472 | "keyword_value": "", 473 | "http_username": "", 474 | "http_password": "", 475 | "port": "", 476 | "interval": 900, 477 | "status": 1, 478 | "create_datetime": 1462565497, 479 | "monitor_group": 0, 480 | "is_group_main": 0, 481 | "logs": [ 482 | { 483 | "type": 98, 484 | "datetime": 1463540297, 485 | "duration": 1054134 486 | } 487 | ] 488 | }, 489 | { 490 | "id": 777749809, 491 | "friendly_name": "monitor-71", 492 | "url": "http://www.monitor.com", 493 | "type": 1, 494 | "sub_type": "", 495 | "keyword_type": "", 496 | "keyword_value": "", 497 | "http_username": "", 498 | "http_password": "", 499 | "port": "", 500 | "interval": 900, 501 | "status": 1, 502 | "create_datetime": 1462565497, 503 | "monitor_group": 0, 504 | "is_group_main": 0, 505 | "logs": [ 506 | { 507 | "type": 98, 508 | "datetime": 1463540297, 509 | "duration": 1054134 510 | } 511 | ] 512 | }, 513 | { 514 | "id": 777749809, 515 | "friendly_name": "monitor-72", 516 | "url": "http://www.monitor.com", 517 | "type": 1, 518 | "sub_type": "", 519 | "keyword_type": "", 520 | "keyword_value": "", 521 | "http_username": "", 522 | "http_password": "", 523 | "port": "", 524 | "interval": 900, 525 | "status": 1, 526 | "create_datetime": 1462565497, 527 | "monitor_group": 0, 528 | "is_group_main": 0, 529 | "logs": [ 530 | { 531 | "type": 98, 532 | "datetime": 1463540297, 533 | "duration": 1054134 534 | } 535 | ] 536 | }, 537 | { 538 | "id": 777749809, 539 | "friendly_name": "monitor-73", 540 | "url": "http://www.monitor.com", 541 | "type": 1, 542 | "sub_type": "", 543 | "keyword_type": "", 544 | "keyword_value": "", 545 | "http_username": "", 546 | "http_password": "", 547 | "port": "", 548 | "interval": 900, 549 | "status": 1, 550 | "create_datetime": 1462565497, 551 | "monitor_group": 0, 552 | "is_group_main": 0, 553 | "logs": [ 554 | { 555 | "type": 98, 556 | "datetime": 1463540297, 557 | "duration": 1054134 558 | } 559 | ] 560 | }, 561 | { 562 | "id": 777749809, 563 | "friendly_name": "monitor-74", 564 | "url": "http://www.monitor.com", 565 | "type": 1, 566 | "sub_type": "", 567 | "keyword_type": "", 568 | "keyword_value": "", 569 | "http_username": "", 570 | "http_password": "", 571 | "port": "", 572 | "interval": 900, 573 | "status": 1, 574 | "create_datetime": 1462565497, 575 | "monitor_group": 0, 576 | "is_group_main": 0, 577 | "logs": [ 578 | { 579 | "type": 98, 580 | "datetime": 1463540297, 581 | "duration": 1054134 582 | } 583 | ] 584 | }, 585 | { 586 | "id": 777749809, 587 | "friendly_name": "monitor-75", 588 | "url": "http://www.monitor.com", 589 | "type": 1, 590 | "sub_type": "", 591 | "keyword_type": "", 592 | "keyword_value": "", 593 | "http_username": "", 594 | "http_password": "", 595 | "port": "", 596 | "interval": 900, 597 | "status": 1, 598 | "create_datetime": 1462565497, 599 | "monitor_group": 0, 600 | "is_group_main": 0, 601 | "logs": [ 602 | { 603 | "type": 98, 604 | "datetime": 1463540297, 605 | "duration": 1054134 606 | } 607 | ] 608 | }, 609 | { 610 | "id": 777749809, 611 | "friendly_name": "monitor-76", 612 | "url": "http://www.monitor.com", 613 | "type": 1, 614 | "sub_type": "", 615 | "keyword_type": "", 616 | "keyword_value": "", 617 | "http_username": "", 618 | "http_password": "", 619 | "port": "", 620 | "interval": 900, 621 | "status": 1, 622 | "create_datetime": 1462565497, 623 | "monitor_group": 0, 624 | "is_group_main": 0, 625 | "logs": [ 626 | { 627 | "type": 98, 628 | "datetime": 1463540297, 629 | "duration": 1054134 630 | } 631 | ] 632 | }, 633 | { 634 | "id": 777749809, 635 | "friendly_name": "monitor-77", 636 | "url": "http://www.monitor.com", 637 | "type": 1, 638 | "sub_type": "", 639 | "keyword_type": "", 640 | "keyword_value": "", 641 | "http_username": "", 642 | "http_password": "", 643 | "port": "", 644 | "interval": 900, 645 | "status": 1, 646 | "create_datetime": 1462565497, 647 | "monitor_group": 0, 648 | "is_group_main": 0, 649 | "logs": [ 650 | { 651 | "type": 98, 652 | "datetime": 1463540297, 653 | "duration": 1054134 654 | } 655 | ] 656 | }, 657 | { 658 | "id": 777749809, 659 | "friendly_name": "monitor-78", 660 | "url": "http://www.monitor.com", 661 | "type": 1, 662 | "sub_type": "", 663 | "keyword_type": "", 664 | "keyword_value": "", 665 | "http_username": "", 666 | "http_password": "", 667 | "port": "", 668 | "interval": 900, 669 | "status": 1, 670 | "create_datetime": 1462565497, 671 | "monitor_group": 0, 672 | "is_group_main": 0, 673 | "logs": [ 674 | { 675 | "type": 98, 676 | "datetime": 1463540297, 677 | "duration": 1054134 678 | } 679 | ] 680 | }, 681 | { 682 | "id": 777749809, 683 | "friendly_name": "monitor-79", 684 | "url": "http://www.monitor.com", 685 | "type": 1, 686 | "sub_type": "", 687 | "keyword_type": "", 688 | "keyword_value": "", 689 | "http_username": "", 690 | "http_password": "", 691 | "port": "", 692 | "interval": 900, 693 | "status": 1, 694 | "create_datetime": 1462565497, 695 | "monitor_group": 0, 696 | "is_group_main": 0, 697 | "logs": [ 698 | { 699 | "type": 98, 700 | "datetime": 1463540297, 701 | "duration": 1054134 702 | } 703 | ] 704 | }, 705 | { 706 | "id": 777749809, 707 | "friendly_name": "monitor-80", 708 | "url": "http://www.monitor.com", 709 | "type": 1, 710 | "sub_type": "", 711 | "keyword_type": "", 712 | "keyword_value": "", 713 | "http_username": "", 714 | "http_password": "", 715 | "port": "", 716 | "interval": 900, 717 | "status": 1, 718 | "create_datetime": 1462565497, 719 | "monitor_group": 0, 720 | "is_group_main": 0, 721 | "logs": [ 722 | { 723 | "type": 98, 724 | "datetime": 1463540297, 725 | "duration": 1054134 726 | } 727 | ] 728 | }, 729 | { 730 | "id": 777749809, 731 | "friendly_name": "monitor-81", 732 | "url": "http://www.monitor.com", 733 | "type": 1, 734 | "sub_type": "", 735 | "keyword_type": "", 736 | "keyword_value": "", 737 | "http_username": "", 738 | "http_password": "", 739 | "port": "", 740 | "interval": 900, 741 | "status": 1, 742 | "create_datetime": 1462565497, 743 | "monitor_group": 0, 744 | "is_group_main": 0, 745 | "logs": [ 746 | { 747 | "type": 98, 748 | "datetime": 1463540297, 749 | "duration": 1054134 750 | } 751 | ] 752 | }, 753 | { 754 | "id": 777749809, 755 | "friendly_name": "monitor-82", 756 | "url": "http://www.monitor.com", 757 | "type": 1, 758 | "sub_type": "", 759 | "keyword_type": "", 760 | "keyword_value": "", 761 | "http_username": "", 762 | "http_password": "", 763 | "port": "", 764 | "interval": 900, 765 | "status": 1, 766 | "create_datetime": 1462565497, 767 | "monitor_group": 0, 768 | "is_group_main": 0, 769 | "logs": [ 770 | { 771 | "type": 98, 772 | "datetime": 1463540297, 773 | "duration": 1054134 774 | } 775 | ] 776 | }, 777 | { 778 | "id": 777749809, 779 | "friendly_name": "monitor-83", 780 | "url": "http://www.monitor.com", 781 | "type": 1, 782 | "sub_type": "", 783 | "keyword_type": "", 784 | "keyword_value": "", 785 | "http_username": "", 786 | "http_password": "", 787 | "port": "", 788 | "interval": 900, 789 | "status": 1, 790 | "create_datetime": 1462565497, 791 | "monitor_group": 0, 792 | "is_group_main": 0, 793 | "logs": [ 794 | { 795 | "type": 98, 796 | "datetime": 1463540297, 797 | "duration": 1054134 798 | } 799 | ] 800 | }, 801 | { 802 | "id": 777749809, 803 | "friendly_name": "monitor-84", 804 | "url": "http://www.monitor.com", 805 | "type": 1, 806 | "sub_type": "", 807 | "keyword_type": "", 808 | "keyword_value": "", 809 | "http_username": "", 810 | "http_password": "", 811 | "port": "", 812 | "interval": 900, 813 | "status": 1, 814 | "create_datetime": 1462565497, 815 | "monitor_group": 0, 816 | "is_group_main": 0, 817 | "logs": [ 818 | { 819 | "type": 98, 820 | "datetime": 1463540297, 821 | "duration": 1054134 822 | } 823 | ] 824 | }, 825 | { 826 | "id": 777749809, 827 | "friendly_name": "monitor-85", 828 | "url": "http://www.monitor.com", 829 | "type": 1, 830 | "sub_type": "", 831 | "keyword_type": "", 832 | "keyword_value": "", 833 | "http_username": "", 834 | "http_password": "", 835 | "port": "", 836 | "interval": 900, 837 | "status": 1, 838 | "create_datetime": 1462565497, 839 | "monitor_group": 0, 840 | "is_group_main": 0, 841 | "logs": [ 842 | { 843 | "type": 98, 844 | "datetime": 1463540297, 845 | "duration": 1054134 846 | } 847 | ] 848 | }, 849 | { 850 | "id": 777749809, 851 | "friendly_name": "monitor-86", 852 | "url": "http://www.monitor.com", 853 | "type": 1, 854 | "sub_type": "", 855 | "keyword_type": "", 856 | "keyword_value": "", 857 | "http_username": "", 858 | "http_password": "", 859 | "port": "", 860 | "interval": 900, 861 | "status": 1, 862 | "create_datetime": 1462565497, 863 | "monitor_group": 0, 864 | "is_group_main": 0, 865 | "logs": [ 866 | { 867 | "type": 98, 868 | "datetime": 1463540297, 869 | "duration": 1054134 870 | } 871 | ] 872 | }, 873 | { 874 | "id": 777749809, 875 | "friendly_name": "monitor-87", 876 | "url": "http://www.monitor.com", 877 | "type": 1, 878 | "sub_type": "", 879 | "keyword_type": "", 880 | "keyword_value": "", 881 | "http_username": "", 882 | "http_password": "", 883 | "port": "", 884 | "interval": 900, 885 | "status": 1, 886 | "create_datetime": 1462565497, 887 | "monitor_group": 0, 888 | "is_group_main": 0, 889 | "logs": [ 890 | { 891 | "type": 98, 892 | "datetime": 1463540297, 893 | "duration": 1054134 894 | } 895 | ] 896 | }, 897 | { 898 | "id": 777749809, 899 | "friendly_name": "monitor-88", 900 | "url": "http://www.monitor.com", 901 | "type": 1, 902 | "sub_type": "", 903 | "keyword_type": "", 904 | "keyword_value": "", 905 | "http_username": "", 906 | "http_password": "", 907 | "port": "", 908 | "interval": 900, 909 | "status": 1, 910 | "create_datetime": 1462565497, 911 | "monitor_group": 0, 912 | "is_group_main": 0, 913 | "logs": [ 914 | { 915 | "type": 98, 916 | "datetime": 1463540297, 917 | "duration": 1054134 918 | } 919 | ] 920 | }, 921 | { 922 | "id": 777749809, 923 | "friendly_name": "monitor-89", 924 | "url": "http://www.monitor.com", 925 | "type": 1, 926 | "sub_type": "", 927 | "keyword_type": "", 928 | "keyword_value": "", 929 | "http_username": "", 930 | "http_password": "", 931 | "port": "", 932 | "interval": 900, 933 | "status": 1, 934 | "create_datetime": 1462565497, 935 | "monitor_group": 0, 936 | "is_group_main": 0, 937 | "logs": [ 938 | { 939 | "type": 98, 940 | "datetime": 1463540297, 941 | "duration": 1054134 942 | } 943 | ] 944 | }, 945 | { 946 | "id": 777749809, 947 | "friendly_name": "monitor-90", 948 | "url": "http://www.monitor.com", 949 | "type": 1, 950 | "sub_type": "", 951 | "keyword_type": "", 952 | "keyword_value": "", 953 | "http_username": "", 954 | "http_password": "", 955 | "port": "", 956 | "interval": 900, 957 | "status": 1, 958 | "create_datetime": 1462565497, 959 | "monitor_group": 0, 960 | "is_group_main": 0, 961 | "logs": [ 962 | { 963 | "type": 98, 964 | "datetime": 1463540297, 965 | "duration": 1054134 966 | } 967 | ] 968 | }, 969 | { 970 | "id": 777749809, 971 | "friendly_name": "monitor-91", 972 | "url": "http://www.monitor.com", 973 | "type": 1, 974 | "sub_type": "", 975 | "keyword_type": "", 976 | "keyword_value": "", 977 | "http_username": "", 978 | "http_password": "", 979 | "port": "", 980 | "interval": 900, 981 | "status": 1, 982 | "create_datetime": 1462565497, 983 | "monitor_group": 0, 984 | "is_group_main": 0, 985 | "logs": [ 986 | { 987 | "type": 98, 988 | "datetime": 1463540297, 989 | "duration": 1054134 990 | } 991 | ] 992 | }, 993 | { 994 | "id": 777749809, 995 | "friendly_name": "monitor-92", 996 | "url": "http://www.monitor.com", 997 | "type": 1, 998 | "sub_type": "", 999 | "keyword_type": "", 1000 | "keyword_value": "", 1001 | "http_username": "", 1002 | "http_password": "", 1003 | "port": "", 1004 | "interval": 900, 1005 | "status": 1, 1006 | "create_datetime": 1462565497, 1007 | "monitor_group": 0, 1008 | "is_group_main": 0, 1009 | "logs": [ 1010 | { 1011 | "type": 98, 1012 | "datetime": 1463540297, 1013 | "duration": 1054134 1014 | } 1015 | ] 1016 | }, 1017 | { 1018 | "id": 777749809, 1019 | "friendly_name": "monitor-93", 1020 | "url": "http://www.monitor.com", 1021 | "type": 1, 1022 | "sub_type": "", 1023 | "keyword_type": "", 1024 | "keyword_value": "", 1025 | "http_username": "", 1026 | "http_password": "", 1027 | "port": "", 1028 | "interval": 900, 1029 | "status": 1, 1030 | "create_datetime": 1462565497, 1031 | "monitor_group": 0, 1032 | "is_group_main": 0, 1033 | "logs": [ 1034 | { 1035 | "type": 98, 1036 | "datetime": 1463540297, 1037 | "duration": 1054134 1038 | } 1039 | ] 1040 | }, 1041 | { 1042 | "id": 777749809, 1043 | "friendly_name": "monitor-94", 1044 | "url": "http://www.monitor.com", 1045 | "type": 1, 1046 | "sub_type": "", 1047 | "keyword_type": "", 1048 | "keyword_value": "", 1049 | "http_username": "", 1050 | "http_password": "", 1051 | "port": "", 1052 | "interval": 900, 1053 | "status": 1, 1054 | "create_datetime": 1462565497, 1055 | "monitor_group": 0, 1056 | "is_group_main": 0, 1057 | "logs": [ 1058 | { 1059 | "type": 98, 1060 | "datetime": 1463540297, 1061 | "duration": 1054134 1062 | } 1063 | ] 1064 | }, 1065 | { 1066 | "id": 777749809, 1067 | "friendly_name": "monitor-95", 1068 | "url": "http://www.monitor.com", 1069 | "type": 1, 1070 | "sub_type": "", 1071 | "keyword_type": "", 1072 | "keyword_value": "", 1073 | "http_username": "", 1074 | "http_password": "", 1075 | "port": "", 1076 | "interval": 900, 1077 | "status": 1, 1078 | "create_datetime": 1462565497, 1079 | "monitor_group": 0, 1080 | "is_group_main": 0, 1081 | "logs": [ 1082 | { 1083 | "type": 98, 1084 | "datetime": 1463540297, 1085 | "duration": 1054134 1086 | } 1087 | ] 1088 | }, 1089 | { 1090 | "id": 777749809, 1091 | "friendly_name": "monitor-96", 1092 | "url": "http://www.monitor.com", 1093 | "type": 1, 1094 | "sub_type": "", 1095 | "keyword_type": "", 1096 | "keyword_value": "", 1097 | "http_username": "", 1098 | "http_password": "", 1099 | "port": "", 1100 | "interval": 900, 1101 | "status": 1, 1102 | "create_datetime": 1462565497, 1103 | "monitor_group": 0, 1104 | "is_group_main": 0, 1105 | "logs": [ 1106 | { 1107 | "type": 98, 1108 | "datetime": 1463540297, 1109 | "duration": 1054134 1110 | } 1111 | ] 1112 | }, 1113 | { 1114 | "id": 777749809, 1115 | "friendly_name": "monitor-97", 1116 | "url": "http://www.monitor.com", 1117 | "type": 1, 1118 | "sub_type": "", 1119 | "keyword_type": "", 1120 | "keyword_value": "", 1121 | "http_username": "", 1122 | "http_password": "", 1123 | "port": "", 1124 | "interval": 900, 1125 | "status": 1, 1126 | "create_datetime": 1462565497, 1127 | "monitor_group": 0, 1128 | "is_group_main": 0, 1129 | "logs": [ 1130 | { 1131 | "type": 98, 1132 | "datetime": 1463540297, 1133 | "duration": 1054134 1134 | } 1135 | ] 1136 | }, 1137 | { 1138 | "id": 777749809, 1139 | "friendly_name": "monitor-98", 1140 | "url": "http://www.monitor.com", 1141 | "type": 1, 1142 | "sub_type": "", 1143 | "keyword_type": "", 1144 | "keyword_value": "", 1145 | "http_username": "", 1146 | "http_password": "", 1147 | "port": "", 1148 | "interval": 900, 1149 | "status": 1, 1150 | "create_datetime": 1462565497, 1151 | "monitor_group": 0, 1152 | "is_group_main": 0, 1153 | "logs": [ 1154 | { 1155 | "type": 98, 1156 | "datetime": 1463540297, 1157 | "duration": 1054134 1158 | } 1159 | ] 1160 | }, 1161 | { 1162 | "id": 777749809, 1163 | "friendly_name": "monitor-99", 1164 | "url": "http://www.monitor.com", 1165 | "type": 1, 1166 | "sub_type": "", 1167 | "keyword_type": "", 1168 | "keyword_value": "", 1169 | "http_username": "", 1170 | "http_password": "", 1171 | "port": "", 1172 | "interval": 900, 1173 | "status": 1, 1174 | "create_datetime": 1462565497, 1175 | "monitor_group": 0, 1176 | "is_group_main": 0, 1177 | "logs": [ 1178 | { 1179 | "type": 98, 1180 | "datetime": 1463540297, 1181 | "duration": 1054134 1182 | } 1183 | ] 1184 | }, 1185 | { 1186 | "id": 777749809, 1187 | "friendly_name": "monitor-100", 1188 | "url": "http://www.monitor.com", 1189 | "type": 1, 1190 | "sub_type": "", 1191 | "keyword_type": "", 1192 | "keyword_value": "", 1193 | "http_username": "", 1194 | "http_password": "", 1195 | "port": "", 1196 | "interval": 900, 1197 | "status": 1, 1198 | "create_datetime": 1462565497, 1199 | "monitor_group": 0, 1200 | "is_group_main": 0, 1201 | "logs": [ 1202 | { 1203 | "type": 98, 1204 | "datetime": 1463540297, 1205 | "duration": 1054134 1206 | } 1207 | ] 1208 | } 1209 | ] 1210 | } --------------------------------------------------------------------------------