forked from Team-Ortix/StatusApp
Compare commits
16 Commits
Author | SHA1 | Date |
---|---|---|
Luther Wen Xu | 99232eb17f | |
Luther Wen Xu | 2d158a5bd0 | |
Luther Wen Xu | 3bb5187e39 | |
Luther Wen Xu | 62e4645b63 | |
Luther Wen Xu | 2dd0ad173b | |
Luther Wen Xu | ed0e61cf79 | |
Luther Wen Xu | 2560637c21 | |
Luther Wen Xu | 84501beb23 | |
Luther Wen Xu | 6d5d07e2e5 | |
Luther Wen Xu | 237ce4ee2a | |
Luther Wen Xu | 05395032be | |
Luther Wen Xu | 81e74807f4 | |
Luther Wen Xu | 5f14a5b943 | |
Luther Wen Xu | f6a8187f4e | |
Luther Wen Xu | 3e40b6ecdb | |
Luther Wen Xu | 64c096860a |
@ -0,0 +1,68 @@
|
|||||||
|
kind: pipeline
|
||||||
|
name: Build Application
|
||||||
|
steps:
|
||||||
|
- name: Build
|
||||||
|
image: golang:1.14
|
||||||
|
commands:
|
||||||
|
- go build ./...
|
||||||
|
volumes:
|
||||||
|
- name: deps
|
||||||
|
path: /go
|
||||||
|
- name: Test
|
||||||
|
image: golang:1.14
|
||||||
|
commands:
|
||||||
|
- go test ./...
|
||||||
|
volumes:
|
||||||
|
- name: deps
|
||||||
|
path: /go
|
||||||
|
- name: Lint
|
||||||
|
image: golangci/golangci-lint:v1.25.0
|
||||||
|
commands:
|
||||||
|
- golangci-lint run -v
|
||||||
|
volumes:
|
||||||
|
- name: deps
|
||||||
|
path: /go
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: deps
|
||||||
|
temp: {}
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
name: Build Docker Image
|
||||||
|
steps:
|
||||||
|
- name: Build Docker Image
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
registry: docker.teamortix.com
|
||||||
|
username: droneci
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
repo: docker.teamortix.com/teamortix/status
|
||||||
|
tags: test
|
||||||
|
purge: true
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
exclude:
|
||||||
|
- tag
|
||||||
|
depends_on:
|
||||||
|
- Build Application
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
name: Continuous Deployment
|
||||||
|
steps:
|
||||||
|
- name: Deploy Docker Image
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
registry: docker.teamortix.com
|
||||||
|
username: droneci
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
repo: docker.teamortix.com/teamortix/status
|
||||||
|
auto_tag: true
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- tag
|
||||||
|
depends_on:
|
||||||
|
- Build Application
|
@ -0,0 +1,10 @@
|
|||||||
|
FROM golang:1.14.2 AS build
|
||||||
|
WORKDIR /root/
|
||||||
|
COPY . .
|
||||||
|
RUN go build -o Status .
|
||||||
|
|
||||||
|
FROM golang:1.14.2
|
||||||
|
WORKDIR /root/
|
||||||
|
COPY --from=build /root/Status .
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["./Status"]
|
@ -1,9 +1,18 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"status/server"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cfg, err := ReadConfig("config.json")
|
cfg, err := ReadConfig("config.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
checkLoop(cfg.Targets)
|
go checkLoop(cfg.Targets)
|
||||||
|
validServices := make([]string, 0, len(cfg.Targets))
|
||||||
|
for _, v := range cfg.Targets {
|
||||||
|
validServices = append(validServices, v.ServiceName)
|
||||||
|
}
|
||||||
|
server.StartServer(cfg.Port, validServices)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"status/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeJSON(w http.ResponseWriter, a interface{}) {
|
||||||
|
bytes, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, err = w.Write(bytes)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func allStatus(w http.ResponseWriter, req *http.Request) {
|
||||||
|
status := getStatus()
|
||||||
|
writeJSON(w, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dayHistory(w http.ResponseWriter, req *http.Request) {
|
||||||
|
keys, ok := req.URL.Query()["service_name"]
|
||||||
|
if !ok || len(keys) == 0 || len(keys[0]) < 1 {
|
||||||
|
http.Error(w, "`service_name` not provided", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(keys) >= 2 {
|
||||||
|
http.Error(w, "Only one `service_name` allowed", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
service_name := keys[0]
|
||||||
|
if !validTarget[service_name] {
|
||||||
|
http.Error(w, "Invalid `service_name` requested", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keys, ok = req.URL.Query()["interval"]
|
||||||
|
var interval = 5
|
||||||
|
if len(keys) >= 2 {
|
||||||
|
http.Error(w, "Only one `interval` allowed", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok && len(keys) == 1 && len(keys[0]) >= 1 {
|
||||||
|
var err error
|
||||||
|
interval, err = strconv.Atoi(keys[0])
|
||||||
|
if err != nil || interval < 1 || interval > 1440 {
|
||||||
|
http.Error(w, "Invalid `interval` provided. Must be integer in [1, 1440]", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if service_name == "all" {
|
||||||
|
service_name = ""
|
||||||
|
}
|
||||||
|
entries := db.GetFromDB(service_name, time.Now().Add(time.Duration(-24)*time.Hour), time.Now())
|
||||||
|
if interval == 1 {
|
||||||
|
writeJSON(w, entries)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filteredEntries := make([]db.PingEntry, 0, len(entries)/interval)
|
||||||
|
for i := 0; i < len(entries); i += interval {
|
||||||
|
filteredEntries = append(filteredEntries, entries[i])
|
||||||
|
}
|
||||||
|
writeJSON(w, filteredEntries)
|
||||||
|
}
|
||||||
|
|
||||||
|
func weekHistory(w http.ResponseWriter, req *http.Request) {
|
||||||
|
history(w, req, 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
func monthHistory(w http.ResponseWriter, req *http.Request) {
|
||||||
|
history(w, req, 31)
|
||||||
|
}
|
||||||
|
|
||||||
|
func yearHistory(w http.ResponseWriter, req *http.Request) {
|
||||||
|
history(w, req, 366)
|
||||||
|
}
|
||||||
|
|
||||||
|
func history(w http.ResponseWriter, req *http.Request, dayCount int) {
|
||||||
|
keys, ok := req.URL.Query()["service_name"]
|
||||||
|
if !ok || len(keys) == 0 || len(keys[0]) < 1 {
|
||||||
|
http.Error(w, "`service_name` not provided", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(keys) >= 2 {
|
||||||
|
http.Error(w, "Only one `service_name` allowed", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !validTarget[keys[0]] {
|
||||||
|
http.Error(w, "Invalid `service_name` requested", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entries := getHistoryDay(keys[0], dayCount)
|
||||||
|
writeJSON(w, entries)
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"status/check"
|
||||||
|
"status/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stat struct {
|
||||||
|
StartTime time.Time `json:"start_time"`
|
||||||
|
TotalPing int64 `json:"total_ping"`
|
||||||
|
SampleCount int `json:"sample_count"`
|
||||||
|
SuccessCount int `json:"success_count"`
|
||||||
|
FailCount int `json:"fail_count"`
|
||||||
|
Summary check.Result `json:"summary"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var dayAggregation = make(map[string]map[time.Time]stat)
|
||||||
|
|
||||||
|
func convertToResult(successCount, totalCount int) check.Result {
|
||||||
|
if successCount >= totalCount*99/100 {
|
||||||
|
return check.Online
|
||||||
|
}
|
||||||
|
if successCount >= totalCount*9/10 {
|
||||||
|
return check.Unstable
|
||||||
|
}
|
||||||
|
return check.Offline
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDay(serviceName string, day time.Time) stat {
|
||||||
|
if _, ok := dayAggregation[serviceName]; !ok {
|
||||||
|
dayAggregation[serviceName] = make(map[time.Time]stat)
|
||||||
|
}
|
||||||
|
if cached, ok := dayAggregation[serviceName][day]; ok {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
entries := db.GetFromDB(serviceName, day.AddDate(0, 0, -1), day)
|
||||||
|
result := stat{
|
||||||
|
StartTime: day,
|
||||||
|
}
|
||||||
|
for _, v := range entries {
|
||||||
|
result.SampleCount++
|
||||||
|
result.TotalPing += v.Ping
|
||||||
|
switch v.Status {
|
||||||
|
case check.Online:
|
||||||
|
result.SuccessCount++
|
||||||
|
case check.Offline:
|
||||||
|
result.FailCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.Summary = convertToResult(result.SuccessCount, result.SampleCount)
|
||||||
|
dayAggregation[serviceName][day] = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func today() time.Time {
|
||||||
|
now := time.Now()
|
||||||
|
return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"status/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getStatus() map[string]db.PingEntry {
|
||||||
|
entries := db.GetFromDB("", time.Now().Add(time.Duration(-2)*time.Minute), time.Now())
|
||||||
|
result := make(map[string]db.PingEntry)
|
||||||
|
for _, v := range entries {
|
||||||
|
if v.Time.Before(result[v.ServiceName].Time) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result[v.ServiceName] = v
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHistoryDay(serviceName string, dayCount int) []stat {
|
||||||
|
currentDay := today()
|
||||||
|
result := make([]stat, 0)
|
||||||
|
for i := 0; i >= -dayCount+1; i-- {
|
||||||
|
dayData := getDay(serviceName, currentDay.AddDate(0, 0, i))
|
||||||
|
if dayData.SampleCount > 0 {
|
||||||
|
result = append(result, dayData)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
validTarget = make(map[string]bool)
|
||||||
|
)
|
||||||
|
|
||||||
|
func apiEndpoints() {
|
||||||
|
http.HandleFunc("/api/latest", allStatus)
|
||||||
|
http.HandleFunc("/api/history/day", dayHistory)
|
||||||
|
http.HandleFunc("/api/history/week", weekHistory)
|
||||||
|
http.HandleFunc("/api/history/month", monthHistory)
|
||||||
|
http.HandleFunc("/api/history/year", yearHistory)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartServer(port int, validTargets []string) {
|
||||||
|
for _, v := range validTargets {
|
||||||
|
validTarget[v] = true
|
||||||
|
}
|
||||||
|
apiEndpoints()
|
||||||
|
err := http.ListenAndServe(":"+strconv.Itoa(port), nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("ListenAndServe returned an error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue