Skip to content

Commit

Permalink
Implment the incident frontend, service api and update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
eirsyl committed Oct 1, 2017
1 parent 5161289 commit f8cadd1
Show file tree
Hide file tree
Showing 11 changed files with 355 additions and 121 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
# Statuspage
> Framework used to create a personalized statuspage.
> Self hosted status page written in golang!
![Dashboard](screenshot.png?raw=true "Dashboard")

This is a small status page project with Postgres as the backing datastore.
There should be no need to change the go code to customize the page.
We use environment variables for configuration, this also includes the logo.

## Todo

* API authorization
* API payload validation
* Incident API
* Customize page with env variables
8 changes: 5 additions & 3 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
version: '2'

services:
redis:
image: redis:latest
postgres:
image: postgres:9.5
ports:
- '127.0.0.1:6379:6379'
- '127.0.0.1:5432:5432'
environment:
- POSTGRES_USER=statuspage
84 changes: 49 additions & 35 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,70 @@ package main

import (
"github.com/gin-gonic/gin"
"net/http"
"github.com/eirsyl/statuspage/src"
"github.com/go-redis/redis"
"github.com/eirsyl/statuspage/src/routes"
"os"
"strconv"
"github.com/go-pg/pg"
"runtime"
"log"
)

func main() {

redisAddr := os.Getenv("REDIS_ADDRESS")
redisPassword := os.Getenv("REDIS_PASSWORD")
redisDB := os.Getenv("REDIS_DB")
ConfigRuntime()

redisDBInt, err := strconv.Atoi(redisDB)
if err != nil {
log.Print("Could not parse Redis DB, using 0 as default.")
redisDBInt = 0
}

db := redis.NewClient(&redis.Options{
Addr: redisAddr,
Password: redisPassword,
DB: redisDBInt,
})

services := src.Services{}
services.Initialize(*db)

incidents := src.Incidents{}
incidents.Initialize(*db)
gin.SetMode(gin.ReleaseMode)

router := gin.Default()
router.Use(State())

router.Static("/static", "./static")
router.LoadHTMLGlob("templates/*")

router.GET("/", func(c *gin.Context) {
router.GET("/", routes.Dashboard)

res, err := services.GetServices(true)
if err != nil {
panic(err)
}

c.HTML(http.StatusOK, "index.tmpl", gin.H{
"owner": "Abakus",
"services": src.AggregateServices(res),
"mostCriticalStatus": src.MostCriticalStatus(res),
})
})
router.GET("/api/services", routes.ServiceList)
router.POST("/api/services", routes.ServicePost)
router.GET("/api/services/:id", routes.ServiceGet)
router.PATCH("/api/services/:id", routes.ServicePatch)
router.DELETE("/api/services/:id", routes.ServiceDelete)

router.Run()

}

func ConfigRuntime() {
nuCPU := runtime.NumCPU()
runtime.GOMAXPROCS(nuCPU)
log.Printf("Running with %d CPUs\n", nuCPU)
}

func State() gin.HandlerFunc {
pgAddr := os.Getenv("POSTGRES_ADDRESS")
pgUser := os.Getenv("POSTGRES_USER")
pgPassword := os.Getenv("POSTGRES_PASSWORD")
pgDB := os.Getenv("POSTGRES_DB")

db := pg.Connect(&pg.Options{
Addr: pgAddr,
User: pgUser,
Password: pgPassword,
Database: pgDB,
})

if err := src.CreateSchema(db); err != nil {
panic(err)
}

services := src.Services{}
services.Initialize(*db)

incidents := src.Incidents{}
incidents.Initialize(*db)

return func(c *gin.Context) {
c.Set("services", services)
c.Set("incidents", incidents)
c.Next()
}
}
Binary file added screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 40 additions & 11 deletions src/incidents.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,65 @@
package src

import (
"github.com/go-redis/redis"
"time"
"github.com/go-pg/pg"
)

type Incident struct {
Id string
Id int64
Time time.Time
Title string
Resolved time.Time
Updates []*IncidentUpdate
}

type IncidentUpdate struct {
Id string
Incident string
Id int64
Time time.Time
IncidentId int64
Status string
Message string
}

type Incidents struct {
db redis.Client
db pg.DB
}

func (i *Incidents) Initialize(db redis.Client) {
func (i *Incidents) Initialize(db pg.DB) {
i.db = db
}

func (i *Incidents) CreateIncident(incident Incident) error {
return nil
func (i *Incidents) InsertIncident(incident Incident) error {
if incident.Time.IsZero() {
now := time.Now()
incident.Time = now
}
err := i.db.Insert(&incident)
return err
}

func (i *Incidents) InsertIncidentUpdate(incident int64, update IncidentUpdate) error {
update.IncidentId = incident

if update.Time.IsZero() {
now := time.Now()
update.Time = now
}

err := i.db.Insert(&update)
return err
}

func (i *Incidents) CreateIncidentUpdate(incident string, update IncidentUpdate) error {
return nil
func (i *Incidents) GetLatestIncidents() ([]Incident, error) {
to := time.Now()
from := to.Add(-14 * 24 * time.Hour).Truncate(24 * time.Hour)

var incidents []Incident

err := i.db.Model(&incidents).
Column("incident.*", "Updates").
Where("time > ?", from).
Where("time < ?", to).
Select()

return incidents, err
}
102 changes: 102 additions & 0 deletions src/routes/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package routes

import (
"github.com/gin-gonic/gin"
"github.com/eirsyl/statuspage/src"
"net/http"
"strconv"
)

func ServiceList(c *gin.Context) {
services, _ := c.Keys["services"].(src.Services)

s, err := services.GetServices(true)
if err != nil {
panic(err)
}

c.JSON(http.StatusOK, s)
}

func ServicePost(c *gin.Context) {
var service src.Service
err := c.BindJSON(&service)
if err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}

services, _ := c.Keys["services"].(src.Services)

err = services.InsertService(service)
if err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}

c.JSON(http.StatusCreated, service)
}

func ServiceGet(c *gin.Context) {
serviceId := c.Param("id")
id, err := strconv.Atoi(serviceId)
if err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}

services, _ := c.Keys["services"].(src.Services)

s, err := services.GetService(int64(id))
if err != nil {
c.AbortWithStatus(http.StatusNotFound)
return
}

c.JSON(http.StatusOK, s)
}

func ServicePatch(c *gin.Context) {
serviceId := c.Param("id")
id, err := strconv.Atoi(serviceId)
if err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}

var service src.Service
err = c.BindJSON(&service)
if err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}

services, _ := c.Keys["services"].(src.Services)

err = services.UpdateService(int64(id), service)
if err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}

c.JSON(http.StatusCreated, service)
}

func ServiceDelete(c *gin.Context) {
serviceId := c.Param("id")
id, err := strconv.Atoi(serviceId)
if err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}

services, _ := c.Keys["services"].(src.Services)

err = services.DeleteService(int64(id))
if err != nil {
c.AbortWithStatus(http.StatusNotFound)
return
}

c.AbortWithStatus(http.StatusNoContent)
}
29 changes: 29 additions & 0 deletions src/routes/dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package routes

import (
"github.com/gin-gonic/gin"
"github.com/eirsyl/statuspage/src"
"net/http"
)

func Dashboard(c *gin.Context) {
services, _ := c.Keys["services"].(src.Services)
incidents, _ := c.Keys["incidents"].(src.Incidents)

res, err := services.GetServices(true)
if err != nil {
panic(err)
}

inc, err := incidents.GetLatestIncidents()
if err != nil {
panic(err)
}

c.HTML(http.StatusOK, "index.tmpl", gin.H{
"owner": "Abakus",
"services": src.AggregateServices(res),
"mostCriticalStatus": src.MostCriticalStatus(res),
"incidents": src.AggregateIncidents(inc),
})
}
Loading

0 comments on commit f8cadd1

Please sign in to comment.