forked from Team-Ortix/StatusApp
				
			Compare commits
	
		
			16 Commits 
		
	
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
								 | 
						99232eb17f | |
| 
							
							
								
								 | 
						2d158a5bd0 | |
| 
							
							
								
								 | 
						3bb5187e39 | |
| 
							
							
								
								 | 
						62e4645b63 | |
| 
							
							
								
								 | 
						2dd0ad173b | |
| 
							
							
								
								 | 
						ed0e61cf79 | |
| 
							
							
								
								 | 
						2560637c21 | |
| 
							
							
								
								 | 
						84501beb23 | |
| 
							
							
								
								 | 
						6d5d07e2e5 | |
| 
							
							
								
								 | 
						237ce4ee2a | |
| 
							
							
								
								 | 
						05395032be | |
| 
							
							
								
								 | 
						81e74807f4 | |
| 
							
							
								
								 | 
						5f14a5b943 | |
| 
							
							
								
								 | 
						f6a8187f4e | |
| 
							
							
								
								 | 
						3e40b6ecdb | |
| 
							
							
								
								 | 
						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