mirror of https://github.com/statping/statping
parent
23de9e16cc
commit
2eb5444a4e
2
Makefile
2
Makefile
|
@ -1,4 +1,4 @@
|
|||
VERSION=0.79.6
|
||||
VERSION=0.79.7
|
||||
BINARY_NAME=statup
|
||||
GOPATH:=$(GOPATH)
|
||||
GOCMD=go
|
||||
|
|
|
@ -110,7 +110,6 @@ func TestRunAll(t *testing.T) {
|
|||
})
|
||||
t.Run(dbt+" Select Core", func(t *testing.T) {
|
||||
RunSelectCoreMYQL(t, dbt)
|
||||
t.Log(core.CoreApp)
|
||||
})
|
||||
t.Run(dbt+" Select Services", func(t *testing.T) {
|
||||
RunSelectAllMysqlServices(t)
|
||||
|
@ -556,6 +555,7 @@ func RunUsersHandler(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
route.ServeHTTP(rr, req)
|
||||
t.Log(rr.Body.String())
|
||||
assert.True(t, strings.Contains(rr.Body.String(), "<title>Statup | Users</title>"))
|
||||
assert.True(t, strings.Contains(rr.Body.String(), "footer"))
|
||||
assert.True(t, handlers.IsAuthenticated(req))
|
||||
|
@ -566,6 +566,7 @@ func RunUserViewHandler(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
route.ServeHTTP(rr, req)
|
||||
t.Log(rr.Body.String())
|
||||
assert.True(t, strings.Contains(rr.Body.String(), "<title>Statup | testadmin</title>"))
|
||||
assert.True(t, strings.Contains(rr.Body.String(), "footer"))
|
||||
assert.True(t, handlers.IsAuthenticated(req))
|
||||
|
|
|
@ -32,6 +32,13 @@ func ReturnUser(u *types.User) *user {
|
|||
return &user{u}
|
||||
}
|
||||
|
||||
// CountUsers returns the amount of users
|
||||
func CountUsers() int64 {
|
||||
var amount int64
|
||||
usersDB().Count(&amount)
|
||||
return amount
|
||||
}
|
||||
|
||||
// SelectUser returns the user based on the user's ID.
|
||||
func SelectUser(id int64) (*user, error) {
|
||||
var user user
|
||||
|
|
174
handlers/api.go
174
handlers/api.go
|
@ -17,6 +17,7 @@ package handlers
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/hunterlong/statup/core"
|
||||
|
@ -30,9 +31,10 @@ import (
|
|||
|
||||
type apiResponse struct {
|
||||
Status string `json:"status"`
|
||||
Object string `json:"type"`
|
||||
Id int64 `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Object string `json:"type,omitempty"`
|
||||
Id int64 `json:"id,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func apiIndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -63,7 +65,8 @@ func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
|
|||
core.CoreApp.ApiSecret = utils.NewSHA1Hash(40)
|
||||
core.CoreApp, err = core.UpdateCore(core.CoreApp)
|
||||
if err != nil {
|
||||
utils.Log(3, err)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
||||
}
|
||||
|
@ -84,7 +87,7 @@ func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
|
|||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||
if service == nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
sendErrorJson(errors.New("service data not found"), w, r)
|
||||
return
|
||||
}
|
||||
fields := parseGet(r)
|
||||
|
@ -106,7 +109,7 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
|
|||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||
if service == nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
sendErrorJson(errors.New("service not found"), w, r)
|
||||
return
|
||||
}
|
||||
fields := parseGet(r)
|
||||
|
@ -127,7 +130,7 @@ func apiServiceHandler(w http.ResponseWriter, r *http.Request) {
|
|||
vars := mux.Vars(r)
|
||||
service := core.SelectServicer(utils.StringInt(vars["id"]))
|
||||
if service == nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
sendErrorJson(errors.New("service not found"), w, r)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -144,13 +147,13 @@ func apiCreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
|||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&service)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
newService := core.ReturnService(service)
|
||||
_, err = newService.Create(true)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
@ -165,7 +168,7 @@ func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||
if service == nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
sendErrorJson(errors.New("service not found"), w, r)
|
||||
return
|
||||
}
|
||||
var updatedService *types.Service
|
||||
|
@ -175,7 +178,7 @@ func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
service = core.ReturnService(updatedService)
|
||||
err := service.Update(true)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
service.Check(true)
|
||||
|
@ -191,12 +194,12 @@ func apiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||
if service == nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
sendErrorJson(errors.New("service not found"), w, r)
|
||||
return
|
||||
}
|
||||
err := service.Delete()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
output := apiResponse{
|
||||
|
@ -223,113 +226,6 @@ func apiAllServicesHandler(w http.ResponseWriter, r *http.Request) {
|
|||
json.NewEncoder(w).Encode(servicesOut)
|
||||
}
|
||||
|
||||
func apiUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAPIAuthorized(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
user, err := core.SelectUser(utils.StringInt(vars["id"]))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
|
||||
func apiUserUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAPIAuthorized(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
user, err := core.SelectUser(utils.StringInt(vars["id"]))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
var updateUser *types.User
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
decoder.Decode(&updateUser)
|
||||
updateUser.Id = user.Id
|
||||
user = core.ReturnUser(updateUser)
|
||||
err = user.Update()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
|
||||
func apiUserDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAPIAuthorized(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
user, err := core.SelectUser(utils.StringInt(vars["id"]))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
err = user.Delete()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
output := apiResponse{
|
||||
Object: "user",
|
||||
Method: "delete",
|
||||
Id: user.Id,
|
||||
Status: "success",
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(output)
|
||||
}
|
||||
|
||||
func apiAllUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAPIAuthorized(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
users, err := core.SelectAllUsers()
|
||||
if err != nil {
|
||||
utils.Log(3, err)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(users)
|
||||
}
|
||||
|
||||
func apiCreateUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAPIAuthorized(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
var user *types.User
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&user)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
newUser := core.ReturnUser(user)
|
||||
uId, err := newUser.Create()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
output := apiResponse{
|
||||
Object: "user",
|
||||
Method: "create",
|
||||
Id: uId,
|
||||
Status: "success",
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(output)
|
||||
}
|
||||
|
||||
func apiNotifierGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAPIAuthorized(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
|
@ -338,7 +234,7 @@ func apiNotifierGetHandler(w http.ResponseWriter, r *http.Request) {
|
|||
vars := mux.Vars(r)
|
||||
_, notifierObj, err := notifier.SelectNotifier(vars["notifier"])
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("%v notifier was not found", vars["notifier"]), http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
@ -358,7 +254,7 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
notifer, not, err := notifier.SelectNotifier(vars["notifier"])
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("%v notifier was not found", vars["notifier"]), http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -374,7 +270,8 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
_, err = notifier.Update(not, notifer)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Sprintf("issue updating notifier: %v", err))
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
notifier.OnSave(notifer.Method)
|
||||
|
||||
|
@ -389,7 +286,7 @@ func apiAllMessagesHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
messages, err := core.SelectMessages()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("error fetching all messages: %v", err), http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
@ -404,7 +301,7 @@ func apiMessageGetHandler(w http.ResponseWriter, r *http.Request) {
|
|||
vars := mux.Vars(r)
|
||||
message, err := core.SelectMessage(utils.StringInt(vars["id"]))
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("message #%v was not found", vars["id"]), http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
@ -419,12 +316,12 @@ func apiMessageDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||
vars := mux.Vars(r)
|
||||
message, err := core.SelectMessage(utils.StringInt(vars["id"]))
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("message #%v was not found", vars["id"]), http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
err = message.Delete()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("message #%v could not be deleted %v", vars["id"], err), http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -447,14 +344,14 @@ func apiMessageUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
vars := mux.Vars(r)
|
||||
message, err := core.SelectMessage(utils.StringInt(vars["id"]))
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("message #%v was not found", vars["id"]), http.StatusInternalServerError)
|
||||
sendErrorJson(fmt.Errorf("message #%v was not found", vars["id"]), w, r)
|
||||
return
|
||||
}
|
||||
var messageBody *types.Message
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err = decoder.Decode(&messageBody)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -462,7 +359,7 @@ func apiMessageUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
message = core.ReturnMessage(messageBody)
|
||||
_, err = message.Update()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -512,14 +409,23 @@ func apiServiceFailuresHandler(w http.ResponseWriter, r *http.Request) {
|
|||
json.NewEncoder(w).Encode(service.AllFailures())
|
||||
}
|
||||
|
||||
func sendErrorJson(err error, w http.ResponseWriter, r *http.Request) {
|
||||
output := apiResponse{
|
||||
Status: "error",
|
||||
Error: err.Error(),
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(output)
|
||||
}
|
||||
|
||||
func sendUnauthorizedJson(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"error": "unauthorized",
|
||||
"url": r.RequestURI,
|
||||
output := apiResponse{
|
||||
Status: "error",
|
||||
Error: errors.New("not authorized").Error(),
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(data)
|
||||
json.NewEncoder(w).Encode(output)
|
||||
}
|
||||
|
||||
func isAPIAuthorized(r *http.Request) bool {
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statup/core"
|
||||
"github.com/hunterlong/statup/core/notifier"
|
||||
_ "github.com/hunterlong/statup/notifiers"
|
||||
|
@ -175,53 +174,6 @@ func TestServicesHandler(t *testing.T) {
|
|||
assert.True(t, isRouteAuthenticated(req))
|
||||
}
|
||||
|
||||
func TestCreateUserHandler(t *testing.T) {
|
||||
form := url.Values{}
|
||||
form.Add("username", "newuser")
|
||||
form.Add("password", "password123")
|
||||
form.Add("email", "info@okokk.com")
|
||||
form.Add("admin", "on")
|
||||
req, err := http.NewRequest("POST", "/users", strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
assert.Equal(t, 303, rr.Code)
|
||||
assert.True(t, isRouteAuthenticated(req))
|
||||
}
|
||||
|
||||
func TestEditUserHandler(t *testing.T) {
|
||||
form := url.Values{}
|
||||
form.Add("username", "changedusername")
|
||||
form.Add("password", "##########")
|
||||
form.Add("email", "info@okokk.com")
|
||||
form.Add("admin", "on")
|
||||
req, err := http.NewRequest("POST", "/user/2", strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
|
||||
req, err = http.NewRequest("GET", "/users", nil)
|
||||
assert.Nil(t, err)
|
||||
rr = httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
body := rr.Body.String()
|
||||
assert.Contains(t, body, "<td>admin</td>")
|
||||
assert.Contains(t, body, "<td>changedusername</td>")
|
||||
assert.Equal(t, 200, rr.Code)
|
||||
assert.True(t, isRouteAuthenticated(req))
|
||||
}
|
||||
|
||||
func TestDeleteUserHandler(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/user/2/delete", nil)
|
||||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
assert.Equal(t, 303, rr.Code)
|
||||
assert.True(t, isRouteAuthenticated(req))
|
||||
}
|
||||
|
||||
func TestUsersHandler(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/users", nil)
|
||||
assert.Nil(t, err)
|
||||
|
@ -275,48 +227,6 @@ func TestHelpHandler(t *testing.T) {
|
|||
assert.True(t, isRouteAuthenticated(req))
|
||||
}
|
||||
|
||||
func TestCreateHTTPServiceHandler(t *testing.T) {
|
||||
form := url.Values{}
|
||||
form.Add("name", "Crystal Castles - Kept")
|
||||
form.Add("domain", "https://www.youtube.com/watch?v=CfbCLwNlGwU")
|
||||
form.Add("method", "GET")
|
||||
form.Add("expected_status", "200")
|
||||
form.Add("interval", "30")
|
||||
form.Add("port", "")
|
||||
form.Add("timeout", "30")
|
||||
form.Add("check_type", "http")
|
||||
form.Add("post_data", "")
|
||||
|
||||
req, err := http.NewRequest("POST", "/services", strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
assert.Equal(t, 303, rr.Code)
|
||||
assert.True(t, isRouteAuthenticated(req))
|
||||
}
|
||||
|
||||
func TestCreateTCPerviceHandler(t *testing.T) {
|
||||
form := url.Values{}
|
||||
form.Add("name", "Local Postgres")
|
||||
form.Add("domain", "localhost")
|
||||
form.Add("method", "GET")
|
||||
form.Add("expected_status", "")
|
||||
form.Add("interval", "30")
|
||||
form.Add("port", "5432")
|
||||
form.Add("timeout", "30")
|
||||
form.Add("check_type", "tcp")
|
||||
form.Add("post_data", "")
|
||||
|
||||
req, err := http.NewRequest("POST", "/services", strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
assert.Equal(t, 303, rr.Code)
|
||||
assert.True(t, isRouteAuthenticated(req))
|
||||
}
|
||||
|
||||
func TestServicesHandler2(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/services", nil)
|
||||
assert.Nil(t, err)
|
||||
|
@ -325,36 +235,36 @@ func TestServicesHandler2(t *testing.T) {
|
|||
body := rr.Body.String()
|
||||
assert.Equal(t, 200, rr.Code)
|
||||
assert.Contains(t, body, "<title>Statup | Services</title>")
|
||||
assert.Contains(t, body, "Crystal Castles - Kept")
|
||||
assert.Contains(t, body, "Local Postgres")
|
||||
assert.Contains(t, body, "JSON Users Test")
|
||||
assert.Contains(t, body, "JSON API Tester")
|
||||
//assert.Contains(t, body, "</footer>️")
|
||||
assert.True(t, isRouteAuthenticated(req))
|
||||
}
|
||||
|
||||
func TestViewHTTPServicesHandler(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/service/6", nil)
|
||||
req, err := http.NewRequest("GET", "/service/5", nil)
|
||||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
body := rr.Body.String()
|
||||
assert.Equal(t, 200, rr.Code)
|
||||
assert.Contains(t, body, "<title>Crystal Castles - Kept Status</title>")
|
||||
assert.Contains(t, body, "<title>Google DNS Status</title>")
|
||||
//assert.Contains(t, body, "</footer>️")
|
||||
}
|
||||
|
||||
func TestViewTCPServicesHandler(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/service/7", nil)
|
||||
req, err := http.NewRequest("GET", "/service/5", nil)
|
||||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
body := rr.Body.String()
|
||||
assert.Equal(t, 200, rr.Code)
|
||||
assert.Contains(t, body, "<title>Local Postgres Status</title>")
|
||||
assert.Contains(t, body, "<title>Google DNS Status</title>")
|
||||
//assert.Contains(t, body, "</footer>️")
|
||||
}
|
||||
|
||||
func TestServicesDeleteFailuresHandler(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/service/7/delete_failures", nil)
|
||||
req, err := http.NewRequest("GET", "/service/5/delete_failures", nil)
|
||||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
|
@ -363,43 +273,7 @@ func TestServicesDeleteFailuresHandler(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFailingServicesDeleteFailuresHandler(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/service/1/delete_failures", nil)
|
||||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
assert.Equal(t, 303, rr.Code)
|
||||
assert.True(t, isRouteAuthenticated(req))
|
||||
}
|
||||
|
||||
func TestServicesUpdateHandler(t *testing.T) {
|
||||
form := url.Values{}
|
||||
form.Add("name", "The Bravery - An Honest Mistake")
|
||||
form.Add("domain", "https://www.youtube.com/watch?v=O8vzbezVru4")
|
||||
form.Add("method", "GET")
|
||||
form.Add("expected_status", "")
|
||||
form.Add("interval", "30")
|
||||
form.Add("port", "")
|
||||
form.Add("timeout", "15")
|
||||
form.Add("check_type", "http")
|
||||
form.Add("post_data", "")
|
||||
req, err := http.NewRequest("POST", "/service/6", strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
|
||||
req, err = http.NewRequest("GET", "/service/6", nil)
|
||||
assert.Nil(t, err)
|
||||
rr = httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
body := rr.Body.String()
|
||||
assert.Equal(t, 200, rr.Code)
|
||||
assert.Contains(t, body, "<title>The Bravery - An Honest Mistake Status</title>")
|
||||
//assert.Contains(t, body, "</footer>️")
|
||||
}
|
||||
|
||||
func TestDeleteServiceHandler(t *testing.T) {
|
||||
req, err := http.NewRequest("POST", "/service/7/delete", nil)
|
||||
req, err := http.NewRequest("GET", "/service/5/delete_failures", nil)
|
||||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
|
@ -486,7 +360,7 @@ func TestPrometheusHandler(t *testing.T) {
|
|||
Router().ServeHTTP(rr, req)
|
||||
body := rr.Body.String()
|
||||
assert.Equal(t, 200, rr.Code)
|
||||
assert.Contains(t, body, "statup_total_services 6")
|
||||
assert.Contains(t, body, "statup_total_services 5")
|
||||
assert.True(t, isRouteAuthenticated(req))
|
||||
}
|
||||
|
||||
|
@ -622,42 +496,6 @@ func TestExportHandler(t *testing.T) {
|
|||
assert.True(t, isRouteAuthenticated(req))
|
||||
}
|
||||
|
||||
func TestCreateBulkServices(t *testing.T) {
|
||||
domains := []string{
|
||||
"https://status.coinapp.io",
|
||||
"https://demo.statup.io",
|
||||
"https://golang.org",
|
||||
"https://github.com/hunterlong",
|
||||
"https://www.santamonica.com",
|
||||
"https://www.oeschs-die-dritten.ch/en/",
|
||||
"https://etherscan.io",
|
||||
"https://www.youtube.com/watch?v=ipvEIZMMILA",
|
||||
"https://www.youtube.com/watch?v=UdaYVxYF1Ok",
|
||||
"https://www.youtube.com/watch?v=yydZbVoCbn0&t=870s",
|
||||
"http://failingdomainsarenofunatall.com",
|
||||
}
|
||||
for k, d := range domains {
|
||||
form := url.Values{}
|
||||
form.Add("name", fmt.Sprintf("Test Service %v", k))
|
||||
form.Add("domain", d)
|
||||
form.Add("method", "GET")
|
||||
form.Add("expected_status", "200")
|
||||
form.Add("interval", fmt.Sprintf("%v", k+1))
|
||||
form.Add("port", "")
|
||||
form.Add("timeout", "30")
|
||||
form.Add("check_type", "http")
|
||||
form.Add("post_data", "")
|
||||
|
||||
req, err := http.NewRequest("POST", "/services", strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
assert.Nil(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
Router().ServeHTTP(rr, req)
|
||||
assert.Equal(t, 303, rr.Code)
|
||||
assert.True(t, isRouteAuthenticated(req))
|
||||
}
|
||||
}
|
||||
|
||||
func isRouteAuthenticated(req *http.Request) bool {
|
||||
os.Setenv("GO_ENV", "production")
|
||||
rr := httptest.NewRecorder()
|
||||
|
|
|
@ -107,8 +107,8 @@ func TestApiAllServicesHandlerHandler(t *testing.T) {
|
|||
var obj []types.Service
|
||||
formatJSON(body, &obj)
|
||||
assert.Equal(t, 200, rr.Code)
|
||||
assert.Equal(t, "Test Service 9", obj[0].Name)
|
||||
assert.Equal(t, "https://www.youtube.com/watch?v=yydZbVoCbn0&t=870s", obj[0].Domain)
|
||||
assert.Equal(t, "Google", obj[0].Name)
|
||||
assert.Equal(t, "https://google.com", obj[0].Domain)
|
||||
}
|
||||
|
||||
func TestApiServiceHandler(t *testing.T) {
|
||||
|
@ -212,7 +212,6 @@ func TestApiViewUserHandler(t *testing.T) {
|
|||
func TestApiUpdateUserHandler(t *testing.T) {
|
||||
data := `{
|
||||
"username": "adminupdated",
|
||||
"email": "info@email.com",
|
||||
"password": "password123",
|
||||
"admin": true}`
|
||||
rr, err := httpRequestAPI(t, "POST", "/api/users/1", strings.NewReader(data))
|
||||
|
@ -220,6 +219,7 @@ func TestApiUpdateUserHandler(t *testing.T) {
|
|||
body := rr.Body.String()
|
||||
var obj types.User
|
||||
formatJSON(body, &obj)
|
||||
t.Log(body)
|
||||
assert.Equal(t, 200, rr.Code)
|
||||
assert.Equal(t, "adminupdated", obj.Username)
|
||||
assert.Equal(t, true, obj.Admin.Bool)
|
||||
|
|
|
@ -65,17 +65,10 @@ func Router() *mux.Router {
|
|||
|
||||
// USER Routes
|
||||
r.Handle("/users", http.HandlerFunc(usersHandler)).Methods("GET")
|
||||
r.Handle("/users", http.HandlerFunc(createUserHandler)).Methods("POST")
|
||||
r.Handle("/user/{id}", http.HandlerFunc(usersEditHandler)).Methods("GET")
|
||||
r.Handle("/user/{id}", http.HandlerFunc(updateUserHandler)).Methods("POST")
|
||||
r.Handle("/user/{id}/delete", http.HandlerFunc(usersDeleteHandler)).Methods("GET")
|
||||
|
||||
// MESSAGES Routes
|
||||
r.Handle("/messages", http.HandlerFunc(messagesHandler)).Methods("GET")
|
||||
r.Handle("/messages", http.HandlerFunc(createMessageHandler)).Methods("POST")
|
||||
r.Handle("/message/{id}", http.HandlerFunc(viewMessageHandler)).Methods("GET")
|
||||
r.Handle("/message/{id}", http.HandlerFunc(updateMessageHandler)).Methods("POST")
|
||||
r.Handle("/message/{id}/delete", http.HandlerFunc(deleteMessageHandler)).Methods("GET")
|
||||
|
||||
// SETTINGS Routes
|
||||
r.Handle("/settings", http.HandlerFunc(settingsHandler)).Methods("GET")
|
||||
|
@ -89,14 +82,13 @@ func Router() *mux.Router {
|
|||
|
||||
// SERVICE Routes
|
||||
r.Handle("/services", http.HandlerFunc(servicesHandler)).Methods("GET")
|
||||
r.Handle("/services", http.HandlerFunc(createServiceHandler)).Methods("POST")
|
||||
r.Handle("/services/reorder", http.HandlerFunc(reorderServiceHandler)).Methods("POST")
|
||||
r.Handle("/service/{id}", http.HandlerFunc(servicesViewHandler)).Methods("GET")
|
||||
r.Handle("/service/{id}", http.HandlerFunc(servicesUpdateHandler)).Methods("POST")
|
||||
r.Handle("/service/{id}/edit", http.HandlerFunc(servicesViewHandler))
|
||||
r.Handle("/service/{id}/delete", http.HandlerFunc(servicesDeleteHandler))
|
||||
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(servicesDeleteFailuresHandler)).Methods("GET")
|
||||
r.Handle("/service/{id}/checkin", http.HandlerFunc(checkinCreateHandler)).Methods("POST")
|
||||
|
||||
// CHECKIN Routes
|
||||
r.Handle("/checkin/{id}/delete", http.HandlerFunc(checkinDeleteHandler)).Methods("GET")
|
||||
r.Handle("/checkin/{id}", http.HandlerFunc(checkinHitHandler))
|
||||
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/hunterlong/statup/core"
|
||||
"github.com/hunterlong/statup/types"
|
||||
|
@ -45,82 +46,116 @@ func usersEditHandler(w http.ResponseWriter, r *http.Request) {
|
|||
executeResponse(w, r, "user.html", user, nil)
|
||||
}
|
||||
|
||||
func updateUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
func apiUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAPIAuthorized(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
vars := mux.Vars(r)
|
||||
id := utils.StringInt(vars["id"])
|
||||
user, err := core.SelectUser(id)
|
||||
user, err := core.SelectUser(utils.StringInt(vars["id"]))
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Sprintf("user error: %v", err))
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
user.Password = ""
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
|
||||
user.Username = r.PostForm.Get("username")
|
||||
user.Email = r.PostForm.Get("email")
|
||||
isAdmin := r.PostForm.Get("admin") == "on"
|
||||
user.Admin = types.NewNullBool(isAdmin)
|
||||
password := r.PostForm.Get("password")
|
||||
if password != "##########" {
|
||||
user.Password = utils.HashPassword(password)
|
||||
func apiUserUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAPIAuthorized(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
user, err := core.SelectUser(utils.StringInt(vars["id"]))
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
var updateUser *types.User
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
decoder.Decode(&updateUser)
|
||||
updateUser.Id = user.Id
|
||||
user = core.ReturnUser(updateUser)
|
||||
err = user.Update()
|
||||
if err != nil {
|
||||
utils.Log(3, err)
|
||||
}
|
||||
|
||||
fmt.Println(user.Id)
|
||||
fmt.Println(user.Password)
|
||||
fmt.Println(user.Admin.Bool)
|
||||
|
||||
users, _ := core.SelectAllUsers()
|
||||
executeResponse(w, r, "users.html", users, "/users")
|
||||
}
|
||||
|
||||
func createUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
username := r.PostForm.Get("username")
|
||||
password := r.PostForm.Get("password")
|
||||
email := r.PostForm.Get("email")
|
||||
admin := r.PostForm.Get("admin")
|
||||
|
||||
user := core.ReturnUser(&types.User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: email,
|
||||
Admin: types.NewNullBool(admin == "on"),
|
||||
})
|
||||
_, err := user.Create()
|
||||
if err != nil {
|
||||
utils.Log(3, err)
|
||||
}
|
||||
//notifiers.OnNewUser(user)
|
||||
executeResponse(w, r, "users.html", user, "/users")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
|
||||
func usersDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsAuthenticated(r) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
func apiUserDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAPIAuthorized(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["id"])
|
||||
user, _ := core.SelectUser(int64(id))
|
||||
|
||||
users, _ := core.SelectAllUsers()
|
||||
if len(users) == 1 {
|
||||
utils.Log(2, "cannot delete the only user in the system")
|
||||
http.Redirect(w, r, "/users", http.StatusSeeOther)
|
||||
users := core.CountUsers()
|
||||
if users == 1 {
|
||||
sendErrorJson(errors.New("cannot delete the last user"), w, r)
|
||||
return
|
||||
}
|
||||
user.Delete()
|
||||
http.Redirect(w, r, "/users", http.StatusSeeOther)
|
||||
user, err := core.SelectUser(utils.StringInt(vars["id"]))
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
err = user.Delete()
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
output := apiResponse{
|
||||
Object: "user",
|
||||
Method: "delete",
|
||||
Id: user.Id,
|
||||
Status: "success",
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(output)
|
||||
}
|
||||
|
||||
func apiAllUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAPIAuthorized(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
users, err := core.SelectAllUsers()
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(users)
|
||||
}
|
||||
|
||||
func apiCreateUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAPIAuthorized(r) {
|
||||
sendUnauthorizedJson(w, r)
|
||||
return
|
||||
}
|
||||
var user *types.User
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&user)
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
newUser := core.ReturnUser(user)
|
||||
uId, err := newUser.Create()
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
output := apiResponse{
|
||||
Object: "user",
|
||||
Method: "create",
|
||||
Id: uId,
|
||||
Status: "success",
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(output)
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
webhookTestUrl = "http://localhost:5555"
|
||||
webhookTestUrl = "https://demo.statup.com/api/services"
|
||||
webhookMessage = `{"id": "%service.Id","name": "%service.Name","online": "%service.Online","issue": "%failure.Issue"}`
|
||||
apiKey = "application/json"
|
||||
fullMsg string
|
||||
|
@ -99,7 +99,7 @@ func TestWebhookNotifier(t *testing.T) {
|
|||
|
||||
t.Run("webhooker Send", func(t *testing.T) {
|
||||
err := webhook.Send(fullMsg)
|
||||
assert.Nil(t, err)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, len(webhook.Queue), 1)
|
||||
})
|
||||
|
||||
|
|
|
@ -405,6 +405,9 @@ HTML, BODY {
|
|||
.pointer {
|
||||
cursor: pointer; }
|
||||
|
||||
.jumbotron {
|
||||
background-color: white; }
|
||||
|
||||
@media (max-width: 767px) {
|
||||
HTML, BODY {
|
||||
background-color: #fcfcfc; }
|
||||
|
|
|
@ -59,11 +59,23 @@ $('.test_notifier').on('click', function(e) {
|
|||
});
|
||||
|
||||
$('form').submit(function() {
|
||||
var spinner = '<i class="fa fa-spinner fa-spin"></i>';
|
||||
$(this).find('button[type=submit]').prop('disabled', true);
|
||||
$(this).find('button[type=submit]').html(spinner);
|
||||
// Spinner($(this).find('button[type=submit]'))
|
||||
});
|
||||
|
||||
|
||||
function Spinner(btn, off = false) {
|
||||
btn.prop('disabled', !off);
|
||||
if (off) {
|
||||
let pastVal = btn.attr("data-past");
|
||||
btn.text(pastVal);
|
||||
btn.removeAttr("data-past");
|
||||
} else {
|
||||
let pastVal = btn.text();
|
||||
btn.attr("data-past", pastVal);
|
||||
btn.html('<i class="fa fa-spinner fa-spin"></i>');
|
||||
}
|
||||
}
|
||||
|
||||
$('select#service_type').on('change', function() {
|
||||
var selected = $('#service_type option:selected').val();
|
||||
var typeLabel = $('#service_type_label');
|
||||
|
@ -122,6 +134,120 @@ function PingAjaxChart(chart, service, start=0, end=9999999999, group="hour") {
|
|||
});
|
||||
}
|
||||
|
||||
$('.ajax_delete').on('click', function() {
|
||||
let obj = $(this);
|
||||
let id = obj.attr('data-id');
|
||||
let element = obj.attr('data-obj');
|
||||
let url = obj.attr('href');
|
||||
let method = obj.attr('data-method');
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: method,
|
||||
data: JSON.stringify({id: id}),
|
||||
success: function (data) {
|
||||
if (data.status === 'error') {
|
||||
alert(data.error)
|
||||
} else {
|
||||
console.log(data);
|
||||
$('#' + element).remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
return false
|
||||
});
|
||||
|
||||
|
||||
$('form.ajax_form').on('submit', function() {
|
||||
const form = $(this);
|
||||
let values = form.serializeArray();
|
||||
let method = form.attr('method');
|
||||
let action = form.attr('action');
|
||||
let func = form.attr('data-func');
|
||||
let redirect = form.attr('data-redirect');
|
||||
let button = form.find('button[type=submit]');
|
||||
let alerter = form.find('#alerter');
|
||||
var arrayData = [];
|
||||
let newArr = {};
|
||||
Spinner(button);
|
||||
values.forEach(function(k, v) {
|
||||
if (k.name === "password_confirm") {
|
||||
return
|
||||
}
|
||||
if (k.value === "") {
|
||||
return
|
||||
}
|
||||
if (k.value === "on") {
|
||||
k.value = true
|
||||
}
|
||||
if($.isNumeric(k.value)){
|
||||
if (k.name !== "password") {
|
||||
k.value = parseInt(k.value)
|
||||
}
|
||||
}
|
||||
newArr[k.name] = k.value;
|
||||
arrayData.push(newArr)
|
||||
});
|
||||
let sendData = JSON.stringify(newArr);
|
||||
console.log('sending '+method.toUpperCase()+' '+action+':', newArr);
|
||||
$.ajax({
|
||||
url: action,
|
||||
type: method,
|
||||
data: sendData,
|
||||
success: function (data) {
|
||||
console.log(data)
|
||||
if (data.status === 'error') {
|
||||
let alerter = form.find('#alerter');
|
||||
alerter.html(data.error);
|
||||
alerter.removeClass("d-none");
|
||||
Spinner(button, true);
|
||||
} else {
|
||||
Spinner(button, true);
|
||||
if (func) {
|
||||
let fn = window[func];
|
||||
if (typeof fn === "function") fn({form: newArr, data: data});
|
||||
}
|
||||
if (redirect) {
|
||||
window.location.href = redirect;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
function CreateService(output) {
|
||||
console.log('creating service', output)
|
||||
let form = output.form;
|
||||
let data = output.data;
|
||||
let objTbl = `<tr id="service_${data.id}">
|
||||
<td><span class="drag_icon d-none d-md-inline"><i class="fas fa-bars"></i></span> ${form.name}</td>
|
||||
<td class="d-none d-md-table-cell">${data.online}<span class="badge badge-success">ONLINE</span></td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<a href="/service/${data.id}" class="btn btn-outline-secondary"><i class="fas fa-chart-area"></i> View</a>
|
||||
<a href="/api/services/${data.id}" class="ajax_delete btn btn-danger confirm-btn" data-method="DELETE" data-obj="service_${data.id}" data-id="${data.id}"><i class="fas fa-times"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
$('#services_table').append(objTbl);
|
||||
}
|
||||
|
||||
function CreateUser(output) {
|
||||
console.log('creating user', output)
|
||||
let form = output.form;
|
||||
let data = output.data;
|
||||
let objTbl = `<tr id="user_${data.id}">
|
||||
<td>${form.username}</td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<a href="/user/${data.id}" class="btn btn-outline-secondary"><i class="fas fa-user-edit"></i> Edit</a>
|
||||
<a href="/api/users/${data.id}" class="ajax_delete btn btn-danger confirm-btn" data-method="DELETE" data-obj="user_${data.id}" data-id="${data.id}"><i class="fas fa-times"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
$('#users_table').append(objTbl);
|
||||
}
|
||||
|
||||
$('select#service_check_type').on('change', function() {
|
||||
var selected = $('#service_check_type option:selected').val();
|
||||
if (selected === 'POST') {
|
||||
|
|
|
@ -468,4 +468,8 @@ HTML,BODY {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jumbotron {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@import 'mobile';
|
||||
|
|
|
@ -199,7 +199,7 @@ func CopyAllToPublic(box *rice.Box, folder string) error {
|
|||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
filePath := fmt.Sprintf("%v%v", folder, path)
|
||||
filePath := fmt.Sprintf("%v/%v", folder, path)
|
||||
SaveAsset(file, utils.Directory, filePath)
|
||||
return nil
|
||||
})
|
||||
|
|
|
@ -19,7 +19,14 @@
|
|||
</div>
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h3>Services</h3>
|
||||
{{if eq (len CoreApp.Services) 0}}
|
||||
<div class="jumbotron jumbotron-fluid">
|
||||
<div class="text-center">
|
||||
<h1 class="display-4">No Services!</h1>
|
||||
<a class="lead">You don't have any websites or applications being monitored by your Statup server. <p><a href="/services" class="btn btn-secondary mt-3">Add Service</a></p></p>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="list-group mb-5 mt-3">
|
||||
{{ range Services }}
|
||||
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
|
||||
|
@ -29,7 +36,8 @@
|
|||
</div>
|
||||
<p class="mb-1">{{.SmallText}}</p>
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{ range Services }}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{{define "form_message"}}
|
||||
{{$message := .}}
|
||||
<form action="{{if ne .Id 0}}/message/{{.Id}}{{else}}/messages{{end}}" method="POST">
|
||||
{{if ne .Id 0}}
|
||||
<form class="ajax_form" action="/api/messages/{{.Id}}" data-redirect="/messages" method="POST">
|
||||
{{else}}
|
||||
<form class="ajax_form" action="/api/messages" data-redirect="/messages" method="POST">
|
||||
{{end}}
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-4 col-form-label">Title</label>
|
||||
<div class="col-sm-8">
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
{{define "form_service"}}
|
||||
<form action="{{if ne .Id 0}}/service/{{.Id}}{{else}}/services{{end}}" method="POST">
|
||||
{{if ne .Id 0}}
|
||||
<form class="ajax_form" action="/api/services/{{.Id}}" data-redirect="/services" method="POST">
|
||||
{{else}}
|
||||
<form class="ajax_form" action="/api/services" data-redirect="/services" method="POST">
|
||||
{{end}}
|
||||
<div class="form-group row">
|
||||
<label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
|
||||
<div class="col-sm-8">
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
{{define "form_user"}}
|
||||
<form action="{{if ne .Id 0}}/user/{{.Id}}{{else}}/users{{end}}" method="POST">
|
||||
{{if ne .Id 0}}
|
||||
<form class="ajax_form" action="/api/users/{{.Id}}" data-redirect="/users" method="POST">
|
||||
{{else}}
|
||||
<form class="ajax_form" action="/api/users" data-redirect="/users" method="POST">
|
||||
{{end}}
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-4 col-form-label">Username</label>
|
||||
<div class="col-6 col-md-4">
|
||||
|
@ -21,13 +25,13 @@
|
|||
<div class="form-group row">
|
||||
<label for="password" class="col-sm-4 col-form-label">Password</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="password" name="password" class="form-control" id="password" value="##########" placeholder="Password" required>
|
||||
<input type="password" name="password" class="form-control" id="password" {{if ne .Id 0}}value="##########"{{end}} placeholder="Password" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="password_confirm" class="col-sm-4 col-form-label">Confirm Password</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="password" name="password_confirm" class="form-control" id="password_confirm" value="##########" placeholder="Confirm Password" required>
|
||||
<input type="password" name="password_confirm" class="form-control" id="password_confirm" {{if ne .Id 0}}value="##########"{{end}} placeholder="Confirm Password" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
|
@ -35,5 +39,6 @@
|
|||
<button type="submit" class="btn btn-primary btn-block">{{if ne .Id 0}}Update User{{else}}Create User{{end}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
|
||||
</form>
|
||||
{{end}}
|
||||
{{end}}
|
|
@ -17,9 +17,6 @@ You can change multiple settings in your Statup instance.
|
|||
# Users
|
||||
Users can access the Statup Dashboard to add, remove, and view services.
|
||||
|
||||
# Notifications
|
||||
|
||||
|
||||
# Plugins
|
||||
Creating a plugin for Statup is not that difficult, if you know a little bit of Go Language you can create any type of application to be embedded into the Status framework.
|
||||
Checkout the example plugin that includes all the interfaces, information, and custom HTTP routing at <a href="https://github.com/hunterlong/statup_plugin">https://github.com/hunterlong/statup_plugin</a>.
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<tr id="message_{{.Id}}">
|
||||
<td>{{.Title}}</td>
|
||||
<td>{{if .Service}}<a href="/service/{{.Service.Id}}">{{.Service.Name}}</a>{{end}}</td>
|
||||
<td>{{Duration 0}}</td>
|
||||
<td class="text-right" id="message_{{.Id}}">
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<a href="/message/{{.Id}}" class="btn btn-outline-secondary"><i class="fas fa-exclamation-triangle"></i> Edit</a>
|
||||
<a href="/message/{{.Id}}/delete" class="btn btn-danger confirm-btn" data-id="message_{{.Id}}"><i class="fas fa-times"></i></a>
|
||||
<a href="/api/messages/{{.Id}}" class="ajax_delete btn btn-danger confirm-btn" data-method="DELETE" data-obj="message_{{.Id}}" data-id="{{.Id}}"><i class="fas fa-times"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
<div class="col-12">
|
||||
|
||||
{{if ne (len .) 0}}
|
||||
<h3>Services</h3>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -15,21 +15,22 @@
|
|||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="sortable">
|
||||
<tbody class="sortable" id="services_table">
|
||||
{{range .}}
|
||||
<tr id="{{.Id}}">
|
||||
<tr id="service_{{.Id}}">
|
||||
<td><span class="drag_icon d-none d-md-inline"><i class="fas fa-bars"></i></span> {{.Name}}</td>
|
||||
<td class="d-none d-md-table-cell">{{if .Online}}<span class="badge badge-success">ONLINE</span>{{else}}<span class="badge badge-danger">OFFLINE</span>{{end}} </td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<a href="/service/{{.Id}}" class="btn btn-outline-secondary"><i class="fas fa-chart-area"></i> View</a>
|
||||
<a href="/service/{{.Id}}/delete" class="btn btn-danger confirm-btn"><i class="fas fa-times"></i></a>
|
||||
<a href="/api/services/{{.Id}}" class="ajax_delete btn btn-danger confirm-btn" data-method="DELETE" data-obj="service_{{.Id}}" data-id="{{.Id}}"><i class="fas fa-times"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
|
||||
<h3>Create Service</h3>
|
||||
|
||||
|
|
|
@ -127,7 +127,12 @@
|
|||
<div class="tab-pane" id="v-pills-style" role="tabpanel" aria-labelledby="v-pills-style-tab">
|
||||
|
||||
{{if not .UsingAssets }}
|
||||
<a href="/settings/build" class="btn btn-primary btn-block"{{if USE_CDN}} disabled{{end}}>Enable Local Assets</a>
|
||||
<div class="jumbotron jumbotron-fluid">
|
||||
<div class="text-center col-12">
|
||||
<h1 class="display-5">Enable Local Assets</h1>
|
||||
<a class="lead">Customize your status page design by enabling local assets. This will create a 'assets' directory containing all CSS.<p><a href="/settings/build" class="btn btn-primary mt-3"{{if USE_CDN}} disabled{{end}}>Enable Local Assets</a></p></p>
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
<form method="POST" action="/settings/css">
|
||||
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
|
||||
|
|
|
@ -11,14 +11,14 @@
|
|||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody id="users_table">
|
||||
{{range .}}
|
||||
<tr>
|
||||
<tr id="user_{{.Id}}">
|
||||
<td>{{.Username}}</td>
|
||||
<td class="text-right" id="user_{{.Id}}">
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<a href="/user/{{.Id}}" class="btn btn-outline-secondary"><i class="fas fa-user-edit"></i> Edit</a>
|
||||
<a href="/user/{{.Id}}/delete" class="btn btn-danger confirm-btn" data-id="user_{{.Id}}"><i class="fas fa-times"></i></a>
|
||||
<a href="/api/users/{{.Id}}" class="ajax_delete btn btn-danger confirm-btn" data-method="DELETE" data-obj="user_{{.Id}}" data-id="{{.Id}}"><i class="fas fa-times"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -22,12 +22,12 @@ import (
|
|||
// User is the main struct for Users
|
||||
type User struct {
|
||||
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
||||
Username string `gorm:"type:varchar(100);unique;column:username;" json:"username"`
|
||||
Password string `gorm:"column:password" json:"-"`
|
||||
Email string `gorm:"type:varchar(100);unique;column:email" json:"-"`
|
||||
ApiKey string `gorm:"column:api_key" json:"api_key"`
|
||||
Username string `gorm:"type:varchar(100);unique;column:username;" json:"username,omitempty"`
|
||||
Password string `gorm:"column:password" json:"password,omitempty"`
|
||||
Email string `gorm:"type:varchar(100);unique;column:email" json:"email,omitempty"`
|
||||
ApiKey string `gorm:"column:api_key" json:"api_key,omitempty"`
|
||||
ApiSecret string `gorm:"column:api_secret" json:"-"`
|
||||
Admin NullBool `gorm:"column:administrator" json:"admin"`
|
||||
Admin NullBool `gorm:"column:administrator" json:"admin,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
UserInterface `gorm:"-" json:"-"`
|
||||
|
|
Loading…
Reference in New Issue