diff --git a/.gitignore b/.gitignore index 397f1f7..d8d9592 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ dist/ node_modules/ .parcel-cache/ static/ +hostmonitor diff --git a/core/printer.go b/core/printer.go index 599c8b2..0f4d115 100644 --- a/core/printer.go +++ b/core/printer.go @@ -48,7 +48,7 @@ func (p *Printer) ToTable(results *Store) { p.t.AppendRow(table.Row{ r.Id, r.Tcp, - r.HttpStatus, + r.HttpResponse, r.Duration, }) return true diff --git a/core/store.go b/core/store.go index 81168b8..fe575d6 100644 --- a/core/store.go +++ b/core/store.go @@ -22,11 +22,12 @@ func (s *Store) AddOrUpdate(res TestResult) { } else { prev := existing.(TestResult) s.results.Store(res.Id, TestResult{ - Id: prev.Id, - InProgress: true, - Tcp: prev.Tcp, - HttpStatus: prev.HttpStatus, - Duration: prev.Duration, + Id: prev.Id, + InProgress: true, + Tcp: prev.Tcp, + HttpResponse: prev.HttpResponse, + Duration: prev.Duration, + Status: prev.Status, }) } } else { diff --git a/core/test-result.go b/core/test-result.go new file mode 100644 index 0000000..f114c4f --- /dev/null +++ b/core/test-result.go @@ -0,0 +1,19 @@ +package core + +import "net/url" + +const ( + StatusOK = "OK" + StatusErr = "Error" + StatusErrResponse = "ErrorResponse" +) + +type TestResult struct { + Id string `json:"id"` + InProgress bool `json:"inProgress"` + url url.URL + Tcp string `json:"tcp"` + HttpResponse string `json:"httpResponse"` + Duration string `json:"duration"` + Status string `json:"status"` +} diff --git a/core/tester.go b/core/tester.go index 431fbd5..6ad95f2 100644 --- a/core/tester.go +++ b/core/tester.go @@ -9,15 +9,6 @@ import ( "time" ) -type TestResult struct { - Id string `json:"id"` - InProgress bool `json:"inProgress"` - url url.URL - Tcp string `json:"tcp"` - HttpStatus string `json:"httpStatus"` - Duration string `json:"duration"` -} - type Tester struct { requestTimeout time.Duration testInterval time.Duration @@ -40,23 +31,25 @@ func (t *Tester) Test(url *url.URL) { case <-t.quit: return default: + tcp := "-" t.out <- TestResult{ Id: url.String(), InProgress: true, - HttpStatus: "Testing...", + Tcp: tcp, } - var pass int if url.Scheme == "tcp" { - pass = t.tcp(url) + pass := t.tcp(url) + tcp = fmt.Sprintf("%d/10", pass) } - status, duration := t.http(url) + response, duration, status := t.http(url) t.out <- TestResult{ - Id: url.String(), - url: *url, - Tcp: fmt.Sprintf("%d/10", pass), - HttpStatus: status, - Duration: strconv.FormatInt(duration.Milliseconds(), 10) + "ms", + Id: url.String(), + url: *url, + Tcp: tcp, + HttpResponse: response, + Duration: strconv.FormatInt(duration.Milliseconds(), 10) + "ms", + Status: status, } time.Sleep(t.testInterval) } @@ -76,18 +69,25 @@ func (t *Tester) tcp(url *url.URL) int { return pass } -func (t *Tester) http(url *url.URL) (status string, duration time.Duration) { +func (t *Tester) http(url *url.URL) (statusMessage string, duration time.Duration, status string) { tp := NewTransport(t.requestTimeout) client := http.Client{Transport: tp, Timeout: t.requestTimeout} addr := strings.Replace(url.String(), "tcp", "http", 1) res, err := client.Get(addr) duration = tp.Duration() if err == nil { - status = res.Status + statusMessage = res.Status + if res.StatusCode >= 500 { + status = StatusErrResponse + } else { + status = StatusOK + } } else if duration >= t.requestTimeout { - status = "TIMEOUT" + statusMessage = "TIMEOUT" + status = StatusErrResponse } else { - status = formatError(err, url) + statusMessage = formatError(err, url) + status = StatusErr } return diff --git a/hostmonitor b/hostmonitor deleted file mode 100755 index ad92f1d..0000000 Binary files a/hostmonitor and /dev/null differ diff --git a/web/client/src/main.tsx b/web/client/src/main.tsx index 984866f..09fd0e4 100644 --- a/web/client/src/main.tsx +++ b/web/client/src/main.tsx @@ -1,5 +1,6 @@ import { lift } from '@grammarly/focal' import { + Chip, Paper, Table, TableBody, @@ -14,23 +15,14 @@ import { Stream } from './stream' const Body = lift(TableBody) -const getBg = (item: Stream.Item) => { - switch (true) { - case item.httpStatus.includes('OK'): - return '#a1ffc3' - case item.httpStatus.includes('connection refused'): - return '#ebffa1' - case item.httpStatus.includes('TIMEOUT'): - return '#fc8672' - default: - return undefined - } -} +const Tcp = ({ item }: { item: Stream.Item }) => ( + +) export const Main = () => (
- +
Address @@ -47,14 +39,16 @@ export const Main = () => ( key={item.id} sx={{ '&:last-child td, &:last-child th': { border: 0 }, - background: getBg(item) + background: Stream.Item.getStatusColor(item) }} > - - {item.id} + + {item.id} + + + - {item.tcp} - {item.httpStatus} + {item.httpResponse} {item.duration} )) diff --git a/web/client/src/stream.ts b/web/client/src/stream.ts index 83f7380..a5f272f 100644 --- a/web/client/src/stream.ts +++ b/web/client/src/stream.ts @@ -1,17 +1,68 @@ +import { pipe } from 'fp-ts/es6/function' +import { isNumber } from 'fp-ts/es6/number' +import * as Ord from 'fp-ts/es6/Ord' import { fromEvent, map, mergeMap, Observable, retryWhen, scan, take, timer } from 'rxjs' import { WebSocketSubject } from 'rxjs/webSocket' +import * as A from 'fp-ts/es6/Array' +import { string } from 'fp-ts' export type Stream = Observable export namespace Stream { const RETRY_DELAY = 5000 export interface Item { - id: string - inProgress: boolean - tcp: string - httpStatus: string - duration: string + readonly id: string + readonly inProgress: boolean + readonly tcp: string + readonly httpResponse: string + readonly duration: string + readonly status: Stream.Item.Status } + + export namespace Item { + export enum Status { + StatusOK = 'OK', + StatusErr = 'Error', + StatusErrResponse = 'ErrorResponse' + } + + export const ord: Ord.Ord = pipe( + Ord.contramap(x => x.status)(string.Ord), + Ord.reverse + ) + + export const getStatusColor = (item: Stream.Item) => { + switch (item.status) { + case Stream.Item.Status.StatusOK: + return '#a1ffc3' + case Stream.Item.Status.StatusErr: + return '#ebffa1' + case Stream.Item.Status.StatusErrResponse: + return '#fc8672' + default: + return undefined + } + } + + export const getTcpStatus = (item: Stream.Item) => { + const success = +item.tcp.split('/')[0] + if (isNumber(success)) { + switch (true) { + case success >= 6: + return 'success' + case success < 6 && success > 3: + return 'warning' + case success <= 3: + return 'error' + default: + return 'default' + } + } + + return 'default' + } + } + export const create = () => { const sock = new WebSocketSubject(`ws://${location.host}/ws`) return sock.pipe( @@ -31,7 +82,7 @@ export namespace Stream { (a, c) => (a.set(c.id, c), a), new Map() ), - map(items => [...items.values()]) + map(items => pipe([...items.values()], A.sort(Stream.Item.ord))) ) } } diff --git a/web/server.go b/web/server.go index fbea28e..3b15c18 100644 --- a/web/server.go +++ b/web/server.go @@ -68,7 +68,6 @@ Loop: s.store.ForEach(func(res core.TestResult) bool { err := c.WriteJSON(res) if err != nil { - log.Println("Error sending a message to client", err.Error()) close <- true return false }