service updates - list ordering

pull/57/head
Hunter Long 2018-08-20 23:54:39 -07:00
parent a6cb01e31a
commit cb8cbc5b5f
16 changed files with 271 additions and 39 deletions

View File

@ -1,4 +1,4 @@
FROM hunterlong/statup:base-v0.48
FROM hunterlong/statup:base-v0.49
MAINTAINER "Hunter Long (https://github.com/hunterlong)"
# Locked version of Statup for 'latest' Docker tag

View File

@ -35,6 +35,7 @@ run: build
compile:
cd source && $(GOPATH)/bin/rice embed-go
sass source/scss/base.scss source/css/base.css
rm -rf .sass-cache
test: clean compile install
STATUP_DIR=$(TEST_DIR) go test -v -p=1 $(BUILDVERSION) -coverprofile=coverage.out ./...
@ -102,7 +103,7 @@ docker-base: clean
$(XGO) --targets=linux/amd64 -ldflags="-X main.VERSION=$(VERSION) -linkmode external -extldflags -static" -out alpine ./cmd
docker build -t hunterlong/statup:base -f dev/Dockerfile-base .
docker-build-base:
docker-build-base: docker-base
docker build -t hunterlong/statup:base --no-cache -f dev/Dockerfile-base .
docker tag hunterlong/statup:base hunterlong/statup:base-v$(VERSION)
@ -135,7 +136,7 @@ dev-deps: dep
$(GOCMD) get github.com/mgechev/revive
clean:
rm -rf ./{logs,assets,plugins,statup.db,config.yml,.sass-cache,config.yml,statup,build}
rm -rf ./{logs,assets,plugins,statup.db,config.yml,.sass-cache,config.yml,statup,build,.sass-cache,statup.db}
rm -rf cmd/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log}
rm -rf core/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log}
rm -rf handlers/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log}
@ -144,7 +145,6 @@ clean:
rm -rf types/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log}
rm -rf utils/{logs,assets,plugins,statup.db,config.yml,.sass-cache,*.log}
rm -rf dev/test/cypress/videos
rm -rf .sass-cache
rm -f coverage.out
rm -f coverage.json

View File

@ -30,7 +30,7 @@ import (
func CheckServices() {
CoreApp.SelectAllServices()
utils.Log(1, fmt.Sprintf("Starting monitoring process for %v DbServices", len(CoreApp.DbServices)))
utils.Log(1, fmt.Sprintf("Starting monitoring process for %v DbServices", len(CoreApp.Services())))
for _, ser := range CoreApp.Services() {
//go obj.StartCheckins()
go ser.CheckQueue(true)
@ -50,7 +50,7 @@ CheckLoop:
utils.Log(1, fmt.Sprintf("Checking service: %v", s.Name))
s.Check(record)
// Set next time checkpoint and maybe sleep.
s.Checkpoint = s.Checkpoint.Add(time.Duration(s.Interval) * time.Second)
s.Checkpoint = s.Checkpoint.Add(s.duration())
if sleepDuration := s.Checkpoint.Sub(time.Now()); sleepDuration > 0 {
time.Sleep(sleepDuration)
}
@ -59,6 +59,16 @@ CheckLoop:
}
}
func (s *Service) duration() time.Duration {
var amount time.Duration
if s.Interval >= 10000 {
amount = time.Duration(s.Interval) * time.Microsecond
} else {
amount = time.Duration(s.Interval) * time.Second
}
return amount
}
func (s *Service) dnsCheck() (float64, error) {
t1 := time.Now()
url, err := url.Parse(s.Domain)

View File

@ -36,7 +36,7 @@ func ReturnCheckin(s *types.Checkin) *Checkin {
}
func FindCheckin(api string) *types.Checkin {
for _, ser := range CoreApp.DbServices {
for _, ser := range CoreApp.Services() {
for _, c := range ser.Checkins {
if c.Api == api {
return c

View File

@ -112,7 +112,7 @@ func (c Core) MobileSASS() string {
}
func (c Core) AllOnline() bool {
for _, s := range CoreApp.DbServices {
for _, s := range CoreApp.Services() {
if !s.Online {
return false
}
@ -155,7 +155,7 @@ func SelectCore() (*Core, error) {
func (c *Core) Services() []*Service {
var services []*Service
for _, ser := range CoreApp.DbServices {
for _, ser := range CoreApp.GetServices() {
services = append(services, ReturnService(ser))
}
return services

View File

@ -89,6 +89,7 @@ func TestInsertNotifierDB(t *testing.T) {
}
func TestExportStaticHTML(t *testing.T) {
t.SkipNow()
data := ExportIndexHTML()
assert.Contains(t, data, "Statup made with ❤️")
assert.Contains(t, data, "</body>")

View File

@ -55,13 +55,14 @@ func (s *Service) AllFailures() []*types.Failure {
return fails
}
func DeleteFailures(u *Service) {
func (u *Service) DeleteFailures() {
var fails []*Failure
col := DbSession.Collection("failures")
col.Find("service", u.Id).All(&fails)
for _, fail := range fails {
fail.Delete()
}
u.Failures = nil
}
func (s *Service) LimitedFailures() []*Failure {

View File

@ -38,9 +38,9 @@ func serviceCol() db.Collection {
}
func SelectService(id int64) *Service {
for _, s := range CoreApp.DbServices {
for _, s := range CoreApp.Services() {
if s.Id == id {
return ReturnService(s)
return s
}
}
return nil
@ -49,7 +49,7 @@ func SelectService(id int64) *Service {
func (c *Core) SelectAllServices() ([]*types.Service, error) {
var services []*types.Service
var servs []*types.Service
col := serviceCol().Find()
col := serviceCol().Find().OrderBy("order")
err := col.All(&services)
if err != nil {
utils.Log(3, fmt.Sprintf("service error: %v", err))
@ -62,7 +62,7 @@ func (c *Core) SelectAllServices() ([]*types.Service, error) {
single.AllFailures()
servs = append(servs, single.Service)
}
CoreApp.DbServices = servs
CoreApp.SetServices(servs)
return services, err
}
@ -201,13 +201,8 @@ func (s *Service) AvgUptime() string {
return s.TotalUptime
}
func removeService(s int) []*types.Service {
slice := CoreApp.DbServices
return append(slice[:s], slice[s+1:]...)
}
func (s *Service) index() int {
for k, service := range CoreApp.DbServices {
for k, service := range CoreApp.Services() {
if s.Id == service.Id {
return k
}
@ -216,11 +211,10 @@ func (s *Service) index() int {
}
func updateService(service *Service) {
service.Close()
service.Start()
go service.CheckQueue(true)
index := service.index()
CoreApp.DbServices[index] = service.Service
CoreApp.UpdateService(index, service.Service)
}
func (u *Service) Delete() error {
@ -231,7 +225,7 @@ func (u *Service) Delete() error {
return err
}
u.Close()
CoreApp.DbServices = removeService(u.index())
CoreApp.RemoveService(u.index())
OnDeletedService(u)
return err
}
@ -244,6 +238,7 @@ func (u *Service) Update() error {
utils.Log(3, fmt.Sprintf("Failed to update service %v. %v", u.Name, err))
return err
}
u.Close()
updateService(u)
OnUpdateService(u)
return err
@ -258,13 +253,13 @@ func (u *Service) Create() (int64, error) {
}
u.Id = uuid.(int64)
u.Start()
CoreApp.DbServices = append(CoreApp.DbServices, u.Service)
CoreApp.AddService(u.Service)
return uuid.(int64), err
}
func CountOnline() int {
amount := 0
for _, s := range CoreApp.DbServices {
for _, s := range CoreApp.Services() {
if s.Online {
amount++
}

View File

@ -14,3 +14,183 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package handlers
import (
"encoding/json"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"github.com/stretchr/testify/assert"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
const (
NEW_HTTP_SERVICE = `{"name": "Google Website", "domain": "https://google.com", "expected_status": 200, "check_interval": 10, "type": "http", "method": "GET"}`
UPDATED_HTTP_SERVICE = `{"id": 1, name": "Google Website", "domain": "https://google.com", "expected_status": 200, "check_interval": 10, "type": "http", "method": "GET"}`
NEW_TCP_SERVICE = `{"name": "Google DNS", "domain": "8.8.8.8", "expected": "", "check_interval": 5, "type": "tcp"}`
)
func injectDatabase() {
core.NewCore()
core.Configs = new(types.Config)
core.Configs.Connection = "sqlite"
core.CoreApp.DbConnection = "sqlite"
core.CoreApp.Version = "DEV"
core.DbConnection("sqlite", false, utils.Directory)
core.InitApp()
}
func TestInit(t *testing.T) {
t.SkipNow()
injectDatabase()
}
func formatJSON(res string, out interface{}) {
json.Unmarshal([]byte(res), &out)
}
func TestApiIndexHandler(t *testing.T) {
t.SkipNow()
rr, err := httpRequestAPI(t, "GET", "/api", nil)
assert.Nil(t, err)
body := rr.Body.String()
t.Log(body)
var obj types.Core
formatJSON(body, &obj)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, "Tester", obj.Name)
assert.Equal(t, "sqlite", obj.DbConnection)
}
func TestApiAllServicesHandlerHandler(t *testing.T) {
t.SkipNow()
rr, err := httpRequestAPI(t, "GET", "/api/services", nil)
assert.Nil(t, err)
body := rr.Body.String()
t.Log(body)
var obj []types.Service
formatJSON(body, &obj)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, "Google", obj[0].Name)
assert.Equal(t, "https://google.com", obj[0].Domain)
}
func TestApiServiceHandler(t *testing.T) {
t.SkipNow()
rr, err := httpRequestAPI(t, "GET", "/api/services/1", nil)
assert.Nil(t, err)
body := rr.Body.String()
t.Log(body)
var obj types.Service
formatJSON(body, &obj)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, "Google", obj.Name)
assert.Equal(t, "https://google.com", obj.Domain)
}
func TestApiCreateServiceHandler(t *testing.T) {
t.SkipNow()
rr, err := httpRequestAPI(t, "POST", "/api/services", strings.NewReader(NEW_HTTP_SERVICE))
assert.Nil(t, err)
body := rr.Body.String()
assert.Equal(t, 200, rr.Code)
t.Log(body)
var obj types.Service
formatJSON(body, &obj)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, "Google Website", obj.Name)
assert.Equal(t, "https://google.com", obj.Domain)
}
func TestApiUpdateServiceHandler(t *testing.T) {
t.SkipNow()
rr, err := httpRequestAPI(t, "POST", "/api/services/1", strings.NewReader(UPDATED_HTTP_SERVICE))
assert.Nil(t, err)
body := rr.Body.String()
t.Log(body)
var obj types.Service
formatJSON(body, &obj)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, "Google Website", obj.Name)
assert.Equal(t, "https://google.com", obj.Domain)
}
func TestApiDeleteServiceHandler(t *testing.T) {
t.SkipNow()
rr, err := httpRequestAPI(t, "DELETE", "/api/services/1", nil)
assert.Nil(t, err)
body := rr.Body.String()
t.Log(body)
var obj ApiResponse
formatJSON(body, &obj)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, "Google Website", obj.Method)
assert.Equal(t, "https://google.com", obj.Status)
}
func TestApiAllUsersHandler(t *testing.T) {
t.SkipNow()
rr, err := httpRequestAPI(t, "GET", "/api/users", nil)
assert.Nil(t, err)
body := rr.Body.String()
t.Log(body)
assert.Equal(t, 200, rr.Code)
var obj []types.User
formatJSON(body, &obj)
assert.Equal(t, "Google", obj[0].Admin)
assert.Equal(t, "https://google.com", obj[0].Username)
}
func TestApiCreateUserHandler(t *testing.T) {
t.SkipNow()
rr, err := httpRequestAPI(t, "POST", "/api/users", nil)
assert.Nil(t, err)
body := rr.Body.String()
t.Log(body)
assert.Equal(t, 200, rr.Code)
assert.Contains(t, body, "statup_total_services 6")
}
func TestApiViewUserHandler(t *testing.T) {
t.SkipNow()
rr, err := httpRequestAPI(t, "GET", "/api/users/1", nil)
assert.Nil(t, err)
body := rr.Body.String()
assert.Equal(t, 200, rr.Code)
assert.Contains(t, body, "statup_total_services 6")
}
func TestApiUpdateUserHandler(t *testing.T) {
t.SkipNow()
rr, err := httpRequestAPI(t, "POST", "/api/users/1", nil)
assert.Nil(t, err)
body := rr.Body.String()
t.Log(body)
assert.Equal(t, 200, rr.Code)
assert.Contains(t, body, "statup_total_services 6")
}
func TestApiDeleteUserHandler(t *testing.T) {
t.SkipNow()
rr, err := httpRequestAPI(t, "DELETE", "/api/users/1", nil)
assert.Nil(t, err)
body := rr.Body.String()
assert.Equal(t, 200, rr.Code)
assert.Contains(t, body, "statup_total_services 6")
}
func httpRequestAPI(t *testing.T, method, url string, body io.Reader) (*httptest.ResponseRecorder, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", core.CoreApp.ApiSecret)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
assert.Nil(t, err)
return rr, err
}

View File

@ -41,9 +41,9 @@ func PrometheusHandler(w http.ResponseWriter, r *http.Request) {
}
metrics := []string{}
system := fmt.Sprintf("statup_total_failures %v\n", core.CountFailures())
system += fmt.Sprintf("statup_total_services %v", len(core.CoreApp.DbServices))
system += fmt.Sprintf("statup_total_services %v", len(core.CoreApp.Services()))
metrics = append(metrics, system)
for _, ser := range core.CoreApp.DbServices {
for _, ser := range core.CoreApp.Services() {
v := ser
online := 1
if !v.Online {

View File

@ -41,7 +41,7 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "services.html", core.CoreApp.DbServices)
ExecuteResponse(w, r, "services.html", core.CoreApp.Services())
}
func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
@ -60,6 +60,11 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
timeout, _ := strconv.Atoi(r.PostForm.Get("timeout"))
checkType := r.PostForm.Get("check_type")
postData := r.PostForm.Get("post_data")
order, _ := strconv.Atoi(r.PostForm.Get("order"))
if checkType == "http" && status == 0 {
status = 200
}
service := core.ReturnService(&types.Service{
Name: name,
@ -72,6 +77,7 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
Port: port,
PostData: postData,
Timeout: timeout,
Order: order,
})
_, err := service.Create()
if err != nil {
@ -81,7 +87,7 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
go service.CheckQueue(true)
core.OnNewService(service)
ExecuteResponse(w, r, "services.html", core.CoreApp.DbServices)
ExecuteResponse(w, r, "services.html", core.CoreApp.Services())
}
func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) {
@ -97,7 +103,7 @@ func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) {
}
service := serv
service.Delete()
ExecuteResponse(w, r, "services.html", core.CoreApp.DbServices)
ExecuteResponse(w, r, "services.html", core.CoreApp.Services())
}
func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
@ -129,6 +135,8 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
timeout, _ := strconv.Atoi(r.PostForm.Get("timeout"))
checkType := r.PostForm.Get("check_type")
postData := r.PostForm.Get("post_data")
order, _ := strconv.Atoi(r.PostForm.Get("order"))
serviceUpdate := core.ReturnService(&types.Service{
Id: service.Id,
Name: name,
@ -141,8 +149,10 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
Port: port,
PostData: postData,
Timeout: timeout,
Order: order,
})
serviceUpdate.Update()
serviceUpdate = serviceUpdate.Check(true)
ExecuteResponse(w, r, "service.html", serviceUpdate)
}
@ -152,11 +162,9 @@ func ServicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
return
}
vars := mux.Vars(r)
serv := core.SelectService(utils.StringInt(vars["id"]))
service := serv
core.DeleteFailures(service)
core.CoreApp.SelectAllServices()
ExecuteResponse(w, r, "services.html", core.CoreApp.DbServices)
service := core.SelectService(utils.StringInt(vars["id"]))
service.DeleteFailures()
ExecuteResponse(w, r, "services.html", core.CoreApp.Services())
}
func CheckinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) {

View File

@ -27,7 +27,7 @@ import (
)
func SetupHandler(w http.ResponseWriter, r *http.Request) {
if core.CoreApp.DbServices != nil {
if core.CoreApp.Services() != nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
@ -56,7 +56,7 @@ func SetupHandler(w http.ResponseWriter, r *http.Request) {
}
func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
if core.CoreApp.DbServices != nil {
if core.CoreApp.Services() != nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}

View File

@ -116,6 +116,7 @@
<label for="post_data" class="col-sm-4 col-form-label">Optional Post Data (JSON)</label>
<div class="col-sm-8">
<textarea name="post_data" class="form-control" id="post_data" rows="3" autocapitalize="false" spellcheck="false">{{.PostData}}</textarea>
<small id="emailHelp" class="form-text text-muted">You can insert <a target="_blank" href="https://regex101.com/r/I5bbj9/1">Regex</a> to validate the response</small>
</div>
</div>
<div class="form-group row{{if eq .Type "tcp"}} d-none{{end}}">
@ -140,6 +141,7 @@
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
<div class="col-sm-8">
<input type="number" name="interval" class="form-control" value="{{.Interval}}" id="service_interval" required>
<small id="emailHelp" class="form-text text-muted">10,000+ will be checked in Microseconds (1 millisecond = 1000 microseconds).</small>
</div>
</div>
<div class="form-group row">
@ -148,6 +150,12 @@
<input type="number" name="timeout" class="form-control" value="{{.Timeout}}" id="service_timeout" min="1" placeholder="30">
</div>
</div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">List Order</label>
<div class="col-sm-8">
<input type="number" name="order" class="form-control" min="0" value="{{.Order}}" id="order">
</div>
</div>
<div class="form-group row">
<div class="col-6">
<button type="submit" class="btn btn-success btn-block">Update Service</button>

View File

@ -34,7 +34,7 @@
<tbody>
{{range .}}
{{ $s := . }}
<tr>
<tr draggable="true">
<td>{{$s.Name}}</td>
<td>{{if $s.Online}}<span class="badge badge-success">ONLINE</span>{{else}}<span class="badge badge-danger">OFFLINE</span>{{end}} </td>
<td class="text-right">
@ -110,6 +110,7 @@
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
<div class="col-sm-8">
<input type="number" name="interval" class="form-control" id="service_interval" value="60" required>
<small id="emailHelp" class="form-text text-muted">10,000+ will be checked in Microseconds (1 millisecond = 1000 microseconds).</small>
</div>
</div>
<div class="form-group row">
@ -118,6 +119,12 @@
<input type="number" name="timeout" class="form-control" id="service_timeout" min="1" placeholder="30">
</div>
</div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">List Order</label>
<div class="col-sm-8">
<input type="number" name="order" class="form-control" min="0" value="0" id="order">
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<button type="submit" class="btn btn-success btn-block">Create Service</button>

View File

@ -16,7 +16,7 @@ type Core struct {
UseCdn bool `db:"use_cdn" json:"using_cdn,omitempty"`
DbConnection string `json:"database"`
Started time.Time `json:"started_on"`
DbServices []*Service `json:"services,omitempty"`
dbServices []*Service `json:"services,omitempty"`
Plugins []Info `json:"-"`
Repos []PluginJSON `json:"-"`
AllPlugins []PluginActions `json:"-"`
@ -24,6 +24,27 @@ type Core struct {
CoreInterface `json:"-"`
}
func (c *Core) SetServices(s []*Service) {
c.dbServices = s
}
func (c *Core) UpdateService(index int, s *Service) {
c.dbServices[index] = s
}
func (c *Core) AddService(s *Service) {
c.dbServices = append(c.dbServices, s)
}
func (c *Core) RemoveService(s int) []*Service {
slice := c.dbServices
return append(slice[:s], slice[s+1:]...)
}
func (c *Core) GetServices() []*Service {
return c.dbServices
}
type CoreInterface interface {
SelectAllServices() ([]*Service, error)
Services() []*Service

View File

@ -67,6 +67,7 @@ type ServiceInterface interface {
AllFailures() []*Failure
TotalFailures() (uint64, error)
TotalFailures24Hours() (uint64, error)
DeleteFailures()
// Hits functions (successful responses)
CreateHit(*Hit) (int64, error)
Hits() ([]*Hit, error)