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
|
||||
|
||||
import (
|
||||
"status/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg, err := ReadConfig("config.json")
|
||||
if err != nil {
|
||||
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