diff --git a/handlers/api_test.go b/handlers/api_test.go
new file mode 100644
index 00000000..f182644f
--- /dev/null
+++ b/handlers/api_test.go
@@ -0,0 +1,510 @@
+package handlers
+
+import (
+ "fmt"
+ _ "github.com/hunterlong/statping/notifiers"
+ "github.com/hunterlong/statping/source"
+ "github.com/hunterlong/statping/utils"
+ "github.com/stretchr/testify/assert"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strings"
+ "testing"
+)
+
+const (
+ serverDomain = "http://localhost:8080"
+)
+
+var (
+ dir string
+)
+
+func init() {
+ source.Assets()
+ utils.InitLogs()
+ dir = utils.Directory
+}
+
+func TestResetDatabase(t *testing.T) {
+ Clean()
+}
+
+func TestFailedHTTPServer(t *testing.T) {
+ err := RunHTTPServer("missinghost", 0)
+ assert.Error(t, err)
+}
+
+func TestSetupRoutes(t *testing.T) {
+
+ form := url.Values{}
+ form.Add("db_host", "")
+ form.Add("db_user", "")
+ form.Add("db_password", "")
+ form.Add("db_database", "")
+ form.Add("db_connection", "sqlite")
+ form.Add("db_port", "")
+ form.Add("project", "Tester")
+ form.Add("username", "admin")
+ form.Add("password", "password123")
+ form.Add("sample_data", "on")
+ form.Add("description", "This is an awesome test")
+ form.Add("domain", "http://localhost:8080")
+ form.Add("email", "info@statping.com")
+
+ tests := []HTTPTest{
+ {
+ Name: "Statping Setup Check",
+ URL: "/setup",
+ Method: "GET",
+ ExpectedStatus: 200,
+ },
+ {
+ Name: "Statping Run Setup",
+ URL: "/setup",
+ Method: "POST",
+ Body: form.Encode(),
+ ExpectedStatus: 303,
+ HttpHeaders: []string{"Content-Type=application/x-www-form-urlencoded"},
+ ExpectedFiles: []string{utils.Directory + "/config.yml", utils.Directory + "/statup.db"},
+ }}
+
+ for _, v := range tests {
+ t.Run(v.Name, func(t *testing.T) {
+ body, t, err := RunHTTPTest(v, t)
+ assert.Nil(t, err)
+ if err != nil {
+ t.FailNow()
+ }
+ t.Logf("Test %v got: %v\n", v.Name, string(body))
+ })
+ }
+}
+
+func TestMainApiRoutes(t *testing.T) {
+ tests := []HTTPTest{
+ {
+ Name: "Statping Details",
+ URL: "/api",
+ Method: "GET",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`"name":"Tester","description":"This is an awesome test"`},
+ }}
+
+ for _, v := range tests {
+ t.Run(v.Name, func(t *testing.T) {
+ body, t, err := RunHTTPTest(v, t)
+ assert.Nil(t, err)
+ if err != nil {
+ t.FailNow()
+ }
+ t.Logf("Test %v got: %v\n", v.Name, string(body))
+ })
+ }
+}
+
+func TestApiServiceRoutes(t *testing.T) {
+ tests := []HTTPTest{
+ {
+ Name: "Statping All Services",
+ URL: "/api/services",
+ Method: "GET",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`"id":1,"name":"Google","domain":"https://google.com"`},
+ },
+ {
+ Name: "Statping Service 1",
+ URL: "/api/services/1",
+ Method: "GET",
+ ExpectedContains: []string{`"id":1,"name":"Google","domain":"https://google.com"`},
+ ExpectedStatus: 200,
+ },
+ {
+ Name: "Statping Service 1 Data",
+ URL: "/api/services/1/data",
+ Method: "GET",
+ Body: "",
+ ExpectedStatus: 200,
+ },
+ {
+ Name: "Statping Service 1 Failures",
+ URL: "/api/services/1/failures",
+ Method: "GET",
+ Body: "",
+ ExpectedStatus: 200,
+ },
+ {
+ Name: "Statping Reorder Services",
+ URL: "/api/services/reorder",
+ Method: "POST",
+ Body: `[{"service":1,"order":1},{"service":5,"order":2},{"service":2,"order":3},{"service":3,"order":4},{"service":4,"order":5}]`,
+ ExpectedStatus: 200,
+ HttpHeaders: []string{"Content-Type=application/json"},
+ },
+ {
+ Name: "Statping Create Service",
+ URL: "/api/services",
+ HttpHeaders: []string{"Content-Type=application/json"},
+ Method: "POST",
+ Body: `{
+ "name": "New Service",
+ "domain": "https://statping.com",
+ "expected": "",
+ "expected_status": 200,
+ "check_interval": 30,
+ "type": "http",
+ "method": "GET",
+ "post_data": "",
+ "port": 0,
+ "timeout": 30,
+ "order_id": 0
+}`,
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`"status":"success","type":"service","method":"create"`},
+ },
+ {
+ Name: "Statping Update Service",
+ URL: "/api/services/1",
+ HttpHeaders: []string{"Content-Type=application/json"},
+ Method: "POST",
+ Body: `{
+ "name": "Updated New Service",
+ "domain": "https://google.com",
+ "expected": "",
+ "expected_status": 200,
+ "check_interval": 60,
+ "type": "http",
+ "method": "GET",
+ "post_data": "",
+ "port": 0,
+ "timeout": 10,
+ "order_id": 0
+}`,
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`"status":"success","type":"service","method":"update"`},
+ },
+ {
+ Name: "Statping Delete Service",
+ URL: "/api/services/1",
+ Method: "DELETE",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`"status":"success","type":"service","method":"delete"`},
+ }}
+
+ for _, v := range tests {
+ t.Run(v.Name, func(t *testing.T) {
+ body, t, err := RunHTTPTest(v, t)
+ assert.Nil(t, err)
+ if err != nil {
+ t.FailNow()
+ }
+ t.Logf("Test %v got: %v\n", v.Name, string(body))
+ })
+ }
+}
+
+func TestGroupAPIRoutes(t *testing.T) {
+ tests := []HTTPTest{
+ {
+ Name: "Statping Groups",
+ URL: "/api/groups",
+ Method: "GET",
+ ExpectedStatus: 200,
+ },
+ {
+ Name: "Statping View Group",
+ URL: "/api/groups/1",
+ Method: "GET",
+ ExpectedStatus: 200,
+ },
+ {
+ Name: "Statping Create Group",
+ URL: "/api/groups",
+ HttpHeaders: []string{"Content-Type=application/json"},
+ Body: `{
+ "name": "New Group",
+ "public": true
+}`,
+ Method: "POST",
+ ExpectedStatus: 200,
+ },
+ {
+ Name: "Statping Delete Group",
+ URL: "/api/groups/1",
+ Method: "DELETE",
+ ExpectedStatus: 200,
+ }}
+
+ for _, v := range tests {
+ t.Run(v.Name, func(t *testing.T) {
+ body, t, err := RunHTTPTest(v, t)
+ assert.Nil(t, err)
+ if err != nil {
+ t.FailNow()
+ }
+ t.Logf("Test %v got: %v\n", v.Name, string(body))
+ })
+ }
+}
+
+func TestApiUsersRoutes(t *testing.T) {
+ tests := []HTTPTest{
+ {
+ Name: "Statping All Users",
+ URL: "/api/users",
+ Method: "GET",
+ ExpectedStatus: 200,
+ }, {
+ Name: "Statping Create User",
+ URL: "/api/users",
+ HttpHeaders: []string{"Content-Type=application/json"},
+ Method: "POST",
+ Body: `{
+ "username": "adminuser2",
+ "email": "info@adminemail.com",
+ "password": "passsword123",
+ "admin": true
+}`,
+ ExpectedStatus: 200,
+ }, {
+ Name: "Statping View User",
+ URL: "/api/users/1",
+ Method: "GET",
+ ExpectedStatus: 200,
+ }, {
+ Name: "Statping Update User",
+ URL: "/api/users/1",
+ Method: "POST",
+ Body: `{
+ "username": "adminupdated",
+ "email": "info@email.com",
+ "password": "password12345",
+ "admin": true
+}`,
+ ExpectedStatus: 200,
+ }, {
+ Name: "Statping Delete User",
+ URL: "/api/users/1",
+ Method: "DELETE",
+ ExpectedStatus: 200,
+ }}
+
+ for _, v := range tests {
+ t.Run(v.Name, func(t *testing.T) {
+ body, t, err := RunHTTPTest(v, t)
+ assert.Nil(t, err)
+ if err != nil {
+ t.FailNow()
+ }
+ t.Logf("Test %v got: %v\n", v.Name, string(body))
+ })
+ }
+}
+
+func TestApiNotifiersRoutes(t *testing.T) {
+ tests := []HTTPTest{
+ {
+ Name: "Statping Notifiers",
+ URL: "/api/notifiers",
+ Method: "GET",
+ ExpectedStatus: 200,
+ }, {
+ Name: "Statping Mobile Notifier",
+ URL: "/api/notifier/mobile",
+ Method: "GET",
+ ExpectedStatus: 200,
+ }, {
+ Name: "Statping Update Notifier",
+ URL: "/api/notifier/mobile",
+ Method: "POST",
+ Body: `{
+ "method": "mobile",
+ "var1": "ExponentPushToken[ToBadIWillError123456]",
+ "enabled": true,
+ "limits": 55
+}`,
+ ExpectedStatus: 200,
+ }}
+
+ for _, v := range tests {
+ t.Run(v.Name, func(t *testing.T) {
+ body, t, err := RunHTTPTest(v, t)
+ assert.Nil(t, err)
+ if err != nil {
+ t.FailNow()
+ }
+ t.Logf("Test %v got: %v\n", v.Name, string(body))
+ })
+ }
+}
+
+func TestMessagesApiRoutes(t *testing.T) {
+ tests := []HTTPTest{
+ {
+ Name: "Statping Messages",
+ URL: "/api/messages",
+ Method: "GET",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`"id":1,"title":"Routine Downtime"`},
+ }, {
+ Name: "Statping Create Message",
+ URL: "/api/messages",
+ Method: "POST",
+ Body: `{
+ "title": "API Message",
+ "description": "This is an example a upcoming message for a service!",
+ "start_on": "2022-11-17T03:28:16.323797-08:00",
+ "end_on": "2022-11-17T05:13:16.323798-08:00",
+ "service": 1,
+ "notify_users": true,
+ "notify_method": "email",
+ "notify_before": 6,
+ "notify_before_scale": "hour"
+}`,
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`"status":"success","type":"message","method":"create"`},
+ },
+ {
+ Name: "Statping View Message",
+ URL: "/api/messages/1",
+ Method: "GET",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`"id":1,"title":"Routine Downtime"`},
+ }, {
+ Name: "Statping Update Message",
+ URL: "/api/messages/1",
+ Method: "POST",
+ Body: `{
+ "title": "Updated Message",
+ "description": "This message was updated",
+ "start_on": "2022-11-17T03:28:16.323797-08:00",
+ "end_on": "2022-11-17T05:13:16.323798-08:00",
+ "service": 1,
+ "notify_users": true,
+ "notify_method": "email",
+ "notify_before": 3,
+ "notify_before_scale": "hour"
+}`,
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`"status":"success","type":"message","method":"update"`},
+ },
+ {
+ Name: "Statping Delete Message",
+ URL: "/api/messages/1",
+ Method: "DELETE",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`"status":"success","type":"message","method":"delete"`},
+ }}
+
+ for _, v := range tests {
+ t.Run(v.Name, func(t *testing.T) {
+ body, t, err := RunHTTPTest(v, t)
+ assert.Nil(t, err)
+ if err != nil {
+ t.FailNow()
+ }
+ t.Logf("Test %v got: %v\n", v.Name, string(body))
+ })
+ }
+}
+
+func TestApiCheckinRoutes(t *testing.T) {
+ tests := []HTTPTest{
+ {
+ Name: "Statping Checkins",
+ URL: "/api/checkins",
+ Method: "GET",
+ ExpectedStatus: 200,
+ }, {
+ Name: "Statping Create Checkin",
+ URL: "/api/checkin",
+ Method: "POST",
+ Body: `{
+ "service_id": 2,
+ "name": "Server Checkin",
+ "interval": 900,
+ "grace": 60
+}`,
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`"status":"success","type":"checkin","method":"create"`},
+ },
+ {
+ Name: "Statping Checkins",
+ URL: "/api/checkins",
+ Method: "GET",
+ ExpectedStatus: 200,
+ }}
+
+ for _, v := range tests {
+ t.Run(v.Name, func(t *testing.T) {
+ body, t, err := RunHTTPTest(v, t)
+ assert.Nil(t, err)
+ if err != nil {
+ t.FailNow()
+ }
+ t.Logf("Test %v got: %v\n", v.Name, string(body))
+ })
+ }
+}
+
+// HTTPTest contains all the parameters for a HTTP Unit Test
+type HTTPTest struct {
+ Name string
+ URL string
+ Method string
+ Body string
+ ExpectedStatus int
+ ExpectedContains []string
+ HttpHeaders []string
+ ExpectedFiles []string
+}
+
+// RunHTTPTest accepts a HTTPTest type to execute the HTTP request
+func RunHTTPTest(test HTTPTest, t *testing.T) (string, *testing.T, error) {
+ req, err := http.NewRequest(test.Method, serverDomain+test.URL, strings.NewReader(test.Body))
+ if err != nil {
+ assert.Nil(t, err)
+ return "", t, err
+ }
+ if len(test.HttpHeaders) != 0 {
+ for _, v := range test.HttpHeaders {
+ splits := strings.Split(v, "=")
+ req.Header.Set(splits[0], splits[1])
+ }
+ }
+ rr := httptest.NewRecorder()
+ Router().ServeHTTP(rr, req)
+ if err != nil {
+ assert.Nil(t, err)
+ return "", t, err
+ }
+ body, err := ioutil.ReadAll(rr.Result().Body)
+ if err != nil {
+ assert.Nil(t, err)
+ return "", t, err
+ }
+ stringBody := string(body)
+ if test.ExpectedStatus != rr.Result().StatusCode {
+ assert.Equal(t, test.ExpectedStatus, rr.Result().StatusCode)
+ return stringBody, t, fmt.Errorf("status code %v does not match %v", rr.Result().StatusCode, test.ExpectedStatus)
+ }
+ if len(test.ExpectedContains) != 0 {
+ for _, v := range test.ExpectedContains {
+ assert.Contains(t, stringBody, v)
+ }
+ }
+ if len(test.ExpectedFiles) != 0 {
+ for _, v := range test.ExpectedFiles {
+ assert.FileExists(t, v)
+ }
+ }
+ return stringBody, t, err
+}
+
+func Clean() {
+ utils.DeleteFile(dir + "/config.yml")
+ utils.DeleteFile(dir + "/statup.db")
+ utils.DeleteDirectory(dir + "/logs")
+}
diff --git a/handlers/dashboard_test.go b/handlers/dashboard_test.go
new file mode 100644
index 00000000..c721a715
--- /dev/null
+++ b/handlers/dashboard_test.go
@@ -0,0 +1,113 @@
+package handlers
+
+import (
+ "github.com/stretchr/testify/assert"
+ "net/url"
+ "testing"
+)
+
+func TestGenericRoutes(t *testing.T) {
+
+ form := url.Values{}
+ form.Add("username", "admin")
+ form.Add("password", "password123")
+
+ form2 := url.Values{}
+ form2.Add("username", "admin")
+ form2.Add("password", "wrongpassword")
+
+ tests := []HTTPTest{
+ {
+ Name: "Statping Index",
+ URL: "/",
+ Method: "GET",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{
+ `
Tester Status`,
+ `")
-}
-
-func TestMissingServiceViewHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/service/99999999", nil)
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 404, rr.Code)
-}
-
-func TestServiceChartHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/charts.js", 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, "var ctx_1")
- assert.Contains(t, body, "var ctx_2")
- assert.Contains(t, body, "var ctx_3")
- assert.Contains(t, body, "var ctx_4")
- assert.Contains(t, body, "var ctx_5")
-}
-
-func TestDashboardHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/dashboard", 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, "Statping | Dashboard")
- assert.Contains(t, body, "")
-}
-
-func TestLoginHandler(t *testing.T) {
- form := url.Values{}
- form.Add("username", "admin")
- form.Add("password", "password123")
- req, err := http.NewRequest("POST", "/dashboard", 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)
-}
-
-func TestBadLoginHandler(t *testing.T) {
- form := url.Values{}
- form.Add("username", "admin")
- form.Add("password", "wrongpassword")
- req, err := http.NewRequest("POST", "/dashboard", 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)
- body := rr.Body.String()
- assert.Contains(t, body, "Incorrect login information submitted, try again.")
- assert.Equal(t, 200, rr.Code)
-}
-
-func TestServicesHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/services", 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, "Statping | Services")
- //assert.Contains(t, body, "️")
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestUsersHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/users", 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, "Statping | Users")
- assert.Contains(t, body, "admin | ")
- assert.NotContains(t, body, "changedusername | ")
- assert.Contains(t, body, "")
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestUsersEditHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/user/1", 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, "Statping | admin")
- assert.Contains(t, body, "User admin
")
- assert.Contains(t, body, "value=\"info@statping.com\"")
- //assert.Contains(t, body, "️")
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestSettingsHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/settings", 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, "Statping | Settings")
- //assert.Contains(t, body, "️")
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestHelpHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/help", 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, "Statping | Help")
- //assert.Contains(t, body, "️")
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestServicesHandler2(t *testing.T) {
- req, err := http.NewRequest("GET", "/services", 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, "Statping | Services")
- assert.Contains(t, body, "JSON Users Test")
- assert.Contains(t, body, "JSON API Tester")
- //assert.Contains(t, body, "️")
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestViewHTTPServicesHandler(t *testing.T) {
- 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, "Google DNS Status")
- //assert.Contains(t, body, "️")
-}
-
-func TestViewTCPServicesHandler(t *testing.T) {
- 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, "Google DNS Status")
- //assert.Contains(t, body, "️")
-}
-
-func TestServicesDeleteFailuresHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/service/5/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 TestFailingServicesDeleteFailuresHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/service/5/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 TestLogsHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/logs", 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, "Statping | Logs")
- //assert.Contains(t, body, "️")
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestLogsLineHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/logs/line", nil)
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- body := rr.Body.String()
- assert.Equal(t, 200, rr.Code)
- assert.NotEmpty(t, body)
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestSaveSettingsHandler(t *testing.T) {
- form := url.Values{}
- form.Add("project", "Awesome Status")
- form.Add("description", "These tests can probably be better")
- req, err := http.NewRequest("POST", "/settings", 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 TestViewSettingsHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/settings", 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, "Statping | Settings")
- assert.Contains(t, body, "Awesome Status")
- //assert.Contains(t, body, "️")
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestSaveAssetsHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/settings/build", nil)
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 303, rr.Code)
- assert.FileExists(t, utils.Directory+"/assets/css/base.css")
- assert.DirExists(t, utils.Directory+"/assets")
- assert.True(t, source.UsingAssets(dir))
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestDeleteAssetsHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/settings/delete_assets", nil)
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 303, rr.Code)
- assert.False(t, source.UsingAssets(dir))
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestPrometheusHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/metrics", nil)
- req.Header.Set("Authorization", core.CoreApp.ApiSecret)
- 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, "statping_total_services 5")
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestUpdateNotificationHandler(t *testing.T) {
- t.SkipNow()
- data := `{"limits": 7, "enabled": true, "method": "email", "host": "smtp.emailserver.com", "username": "exampleuser", "password": "password123", "port": 543, "var1": "info@betatude.com", "var2": "sendto@gmail.com"}`
- rr, err := httpRequestAPI(t, "POST", "/api/notifier/email", strings.NewReader(data))
- assert.Nil(t, err)
- body := rr.Body.String()
- var obj apiResponse
- formatJSON(body, &obj)
- assert.Equal(t, 200, rr.Code)
- assert.Equal(t, "success", obj.Status)
-}
-
-func TestViewNotificationSettingsHandler(t *testing.T) {
- t.SkipNow()
- req, err := http.NewRequest("GET", "/settings", 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, "Statping | Settings")
- assert.Contains(t, body, `value="exampleuser" id="smtp_username"`)
- assert.Contains(t, body, `value="587" id="smtp_port"`)
- assert.Contains(t, body, `value="info@betatude.com" id="outgoing_email_address"`)
- assert.Contains(t, body, `value="sendto@gmail.com" id="send_alerts_to"`)
- assert.Contains(t, body, `id="limits_per_hour_email" value="7"`)
- assert.Contains(t, body, `id="switch-email" checked`)
- //assert.Contains(t, body, "️")
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestSaveFooterHandler(t *testing.T) {
- form := url.Values{}
- form.Add("footer", "Created by Hunter Long")
- req, err := http.NewRequest("POST", "/settings", 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))
-
- req, err = http.NewRequest("GET", "/", 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, "Created by Hunter Long")
-}
-
-func TestError404Handler(t *testing.T) {
- req, err := http.NewRequest("GET", "/404me", nil)
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 404, rr.Code)
-}
-
-func TestLogoutHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/logout", nil)
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 303, rr.Code)
-}
-
-func TestBuildAssetsHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/settings/build", nil)
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 303, rr.Code)
- assert.True(t, isRouteAuthenticated(req))
- assert.FileExists(t, "../assets/scss/base.scss")
-}
-
-func TestSaveSassHandler(t *testing.T) {
- base := source.OpenAsset(utils.Directory, "scss/base.scss")
- vars := source.OpenAsset(utils.Directory, "scss/variables.scss")
-
- form := url.Values{}
- form.Add("theme", base+"\n .test_design { color: $test-design; }")
- form.Add("variables", vars+"\n $test-design: #ffffff; ")
- req, err := http.NewRequest("POST", "/settings/css", 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))
-
- newBase := source.OpenAsset(utils.Directory, "css/base.css")
- assert.Contains(t, newBase, ".test_design {")
-}
-
-func TestReorderServiceHandler(t *testing.T) {
- data := `[{id: 1, order: 3},{id: 2, order: 2},{id: 3, order: 1}]"`
- req, err := http.NewRequest("POST", "/api/services/reorder", strings.NewReader(data))
- req.Header.Set("Content-Type", "application/json")
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 200, rr.Code)
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestExportHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/settings/export", nil)
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 200, rr.Code)
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func isRouteAuthenticated(req *http.Request) bool {
- os.Setenv("GO_ENV", "production")
- rr := httptest.NewRecorder()
- req.Header.Set("Authorization", "badkey")
- Router().ServeHTTP(rr, req)
- code := rr.Code
- if code == 200 {
- os.Setenv("GO_ENV", "test")
- return false
- }
- os.Setenv("GO_ENV", "test")
- return true
-}
diff --git a/handlers/messages_test.go b/handlers/messages_test.go
new file mode 100644
index 00000000..052cc332
--- /dev/null
+++ b/handlers/messages_test.go
@@ -0,0 +1,35 @@
+package handlers
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestMessageRoutes(t *testing.T) {
+ tests := []HTTPTest{
+ {
+ Name: "Messages",
+ URL: "/messages",
+ Method: "GET",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`Statping Messages`},
+ },
+ {
+ Name: "Message 2",
+ URL: "/message/2",
+ Method: "GET",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`Statping | Server Reboot`},
+ }}
+
+ for _, v := range tests {
+ t.Run(v.Name, func(t *testing.T) {
+ body, t, err := RunHTTPTest(v, t)
+ assert.Nil(t, err)
+ if err != nil {
+ t.FailNow()
+ }
+ t.Logf("Test %v got: %v\n", v.Name, string(body))
+ })
+ }
+}
diff --git a/handlers/route_api_test.go b/handlers/route_api_test.go
deleted file mode 100644
index 2cc37cf4..00000000
--- a/handlers/route_api_test.go
+++ /dev/null
@@ -1,327 +0,0 @@
-// Statping
-// Copyright (C) 2018. Hunter Long and the project contributors
-// Written by Hunter Long and the project contributors
-//
-// https://github.com/hunterlong/statping
-//
-// The licenses for most software and other practical works are designed
-// to take away your freedom to share and change the works. By contrast,
-// the GNU General Public License is intended to guarantee your freedom to
-// share and change all versions of a program--to make sure it remains free
-// software for all its users.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-
-package handlers
-
-import (
- "encoding/json"
- "github.com/hunterlong/statping/core"
- "github.com/hunterlong/statping/source"
- "github.com/hunterlong/statping/types"
- "github.com/hunterlong/statping/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"}`
-)
-
-var (
- dir string
-)
-
-func init() {
- dir = utils.Directory
- utils.InitLogs()
- source.Assets()
-}
-
-func loadDatabase() {
- core.NewCore()
- core.LoadConfigFile(dir)
- core.Configs = &core.DbConfig{
- DbConn: "sqlite",
- Location: dir,
- }
- core.CoreApp.DbConnection = "sqlite"
- core.Configs.Connect(false, utils.Directory)
- core.CoreApp.Version = "DEV"
- core.Configs.Save()
-}
-
-func createDatabase() {
- core.Configs.DropDatabase()
- core.Configs.CreateDatabase()
-}
-
-func resetDatabase() {
- core.Configs.DropDatabase()
- core.Configs.CreateDatabase()
- core.InsertLargeSampleData()
-}
-
-func Clean() {
- utils.DeleteFile(dir + "/config.yml")
- utils.DeleteFile(dir + "/statup.db")
- utils.DeleteDirectory(dir + "/assets")
- utils.DeleteDirectory(dir + "/logs")
-}
-
-func TestInit(t *testing.T) {
- //Clean()
- //loadDatabase()
- //resetDatabase()
- //loadDatabase()
- //core.SelectCore()
- //core.InsertNotifierDB()
- //core.CoreApp.SelectAllServices(false)
- //core.CoreApp.Notifications = notifier.Load()
-}
-
-func formatJSON(res string, out interface{}) {
- json.Unmarshal([]byte(res), &out)
-}
-
-func TestApiIndexHandler(t *testing.T) {
- rr, err := httpRequestAPI(t, "GET", "/api", nil)
- assert.Nil(t, err)
- body := rr.Body.String()
- var obj types.Core
- formatJSON(body, &obj)
- assert.Equal(t, 200, rr.Code)
- assert.Equal(t, "Awesome Status", obj.Name)
- assert.Equal(t, "sqlite", obj.DbConnection)
-}
-
-func TestApiAllServicesHandlerHandler(t *testing.T) {
- rr, err := httpRequestAPI(t, "GET", "/api/services", nil)
- assert.Nil(t, err)
- body := rr.Body.String()
- 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) {
- rr, err := httpRequestAPI(t, "GET", "/api/services/1", nil)
- assert.Nil(t, err)
- body := rr.Body.String()
- 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) {
- 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)
- var obj apiResponse
- formatJSON(body, &obj)
- t.Log(body)
- object := obj.Output.(map[string]interface{})
- assert.Equal(t, 200, rr.Code)
- assert.Equal(t, "Google Website", object["name"])
- assert.Equal(t, "https://google.com", object["domain"])
-}
-
-func TestApiUpdateServiceHandler(t *testing.T) {
- data := `{
- "name": "Updated Service",
- "domain": "https://google.com",
- "expected": "",
- "expected_status": 200,
- "check_interval": 60,
- "type": "http",
- "method": "GET",
- "post_data": "",
- "port": 0,
- "timeout": 10,
- "order_id": 0}`
- rr, err := httpRequestAPI(t, "POST", "/api/services/1", strings.NewReader(data))
- 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, "success", obj.Status)
- assert.Equal(t, "update", obj.Method)
- assert.Equal(t, int64(1), obj.Id)
-}
-
-func TestApiDeleteServiceHandler(t *testing.T) {
- rr, err := httpRequestAPI(t, "DELETE", "/api/services/2", nil)
- assert.Nil(t, err)
- body := rr.Body.String()
- var obj apiResponse
- formatJSON(body, &obj)
- assert.Equal(t, 200, rr.Code)
- assert.Equal(t, "delete", obj.Method)
- assert.Equal(t, "success", obj.Status)
-}
-
-func TestApiAllUsersHandler(t *testing.T) {
- rr, err := httpRequestAPI(t, "GET", "/api/users", nil)
- assert.Nil(t, err)
- body := rr.Body.String()
- assert.Equal(t, 200, rr.Code)
- var obj []types.User
- formatJSON(body, &obj)
- assert.Equal(t, true, obj[0].Admin.Bool)
- assert.Equal(t, "admin", obj[0].Username)
-}
-
-func TestApiCreateUserHandler(t *testing.T) {
- data := `{
- "username": "admin2",
- "email": "info@email.com",
- "password": "password123",
- "admin": true}`
- rr, err := httpRequestAPI(t, "POST", "/api/users", strings.NewReader(data))
- assert.Nil(t, err)
- body := rr.Body.String()
- var obj apiResponse
- formatJSON(body, &obj)
- t.Log(body)
- assert.Equal(t, 200, rr.Code)
- assert.Contains(t, "create", obj.Method)
- assert.Contains(t, "success", obj.Status)
-}
-
-func TestApiViewUserHandler(t *testing.T) {
- rr, err := httpRequestAPI(t, "GET", "/api/users/1", nil)
- assert.Nil(t, err)
- body := rr.Body.String()
- assert.Equal(t, 200, rr.Code)
- var obj types.User
- formatJSON(body, &obj)
- t.Log(body)
- assert.Equal(t, "admin", obj.Username)
- assert.Equal(t, true, obj.Admin.Bool)
-}
-
-func TestApiUpdateUserHandler(t *testing.T) {
- data := `{
- "username": "adminupdated",
- "password": "password123",
- "admin": true}`
- rr, err := httpRequestAPI(t, "POST", "/api/users/1", strings.NewReader(data))
- assert.Nil(t, err)
- body := rr.Body.String()
- var obj apiResponse
- formatJSON(body, &obj)
- t.Log(body)
- assert.Equal(t, 200, rr.Code)
- assert.Equal(t, "update", obj.Method)
- assert.Equal(t, "success", obj.Status)
- assert.Equal(t, int64(1), obj.Id)
-}
-
-func TestApiDeleteUserHandler(t *testing.T) {
- rr, err := httpRequestAPI(t, "DELETE", "/api/users/1", nil)
- assert.Nil(t, err)
- body := rr.Body.String()
- var obj apiResponse
- formatJSON(body, &obj)
- assert.Equal(t, 200, rr.Code)
- assert.Equal(t, "delete", obj.Method)
- assert.Equal(t, "success", obj.Status)
-}
-
-func TestApiServiceDataHandler(t *testing.T) {
- grouping := []string{"minute", "hour", "day", "week"}
- for _, g := range grouping {
- params := "?start=0&end=999999999999&group=" + g
- rr, err := httpRequestAPI(t, "GET", "/api/services/5/data"+params, nil)
- assert.Nil(t, err)
- body := rr.Body.String()
- var obj core.DateScanObj
- formatJSON(body, &obj)
- assert.Equal(t, 200, rr.Code)
- assert.NotZero(t, len(obj.Array))
- }
-}
-
-func TestApiRenewHandler(t *testing.T) {
- api := core.CoreApp.ApiKey
- secret := core.CoreApp.ApiSecret
- rr, err := httpRequestAPI(t, "GET", "/api/renew", nil)
- assert.Nil(t, err)
- body := rr.Body.String()
- var obj apiResponse
- formatJSON(body, &obj)
- assert.Equal(t, 303, rr.Code)
- assert.NotEqual(t, api, core.CoreApp.ApiKey)
- assert.NotEqual(t, secret, core.CoreApp.ApiSecret)
-}
-
-func TestApiCheckinHandler(t *testing.T) {
- t.SkipNow()
- service := core.SelectService(1)
- checkin := service.AllCheckins()
- rr, err := httpRequestAPI(t, "GET", "/api/checkin/"+checkin[0].ApiKey, nil)
- assert.Nil(t, err)
- body := rr.Body.String()
- var obj apiResponse
- formatJSON(body, &obj)
- assert.Equal(t, 200, rr.Code)
-}
-
-func TestApiCreateCheckinHandler(t *testing.T) {
- data := `{
- "service_id": 1,
- "name": "New API Checkin",
- "interval": 60,
- "grace": 30}`
- rr, err := httpRequestAPI(t, "POST", "/api/checkin", strings.NewReader(data))
- assert.Nil(t, err)
- body := rr.Body.String()
- var obj apiResponse
- formatJSON(body, &obj)
- t.Log(body)
- assert.Equal(t, 200, rr.Code)
- assert.Equal(t, "create", obj.Method)
- assert.Equal(t, "success", obj.Status)
- assert.Equal(t, int64(1), obj.Id)
-}
-
-func TestApiAllCheckinsHandler(t *testing.T) {
- rr, err := httpRequestAPI(t, "GET", "/api/checkins", nil)
- assert.Nil(t, err)
- body := rr.Body.String()
- assert.Equal(t, 200, rr.Code)
- var obj []types.Checkin
- formatJSON(body, &obj)
- t.Log(body)
- assert.Equal(t, int(1), len(obj))
- assert.Equal(t, int64(1), obj[0].Id)
- assert.Equal(t, int64(1), obj[0].ServiceId)
- assert.Equal(t, "New API Checkin", obj[0].Name)
-}
-
-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
- }
- if body != nil {
- req.Header.Set("Content-Type", "application/json")
- }
- req.Header.Set("Authorization", core.CoreApp.ApiSecret)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Nil(t, err)
- return rr, err
-}
diff --git a/handlers/routes.go b/handlers/routes.go
index c2cd6075..7ac87a7f 100644
--- a/handlers/routes.go
+++ b/handlers/routes.go
@@ -40,17 +40,18 @@ func Router() *mux.Router {
indexHandler := http.FileServer(http.Dir(dir + "/assets/"))
r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(http.Dir(dir+"/assets/css"))))
r.PathPrefix("/font/").Handler(http.StripPrefix("/font/", http.FileServer(http.Dir(dir+"/assets/font"))))
+ r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(http.Dir(dir+"/assets/js"))))
r.PathPrefix("/robots.txt").Handler(indexHandler)
r.PathPrefix("/favicon.ico").Handler(indexHandler)
r.PathPrefix("/banner.png").Handler(indexHandler)
} else {
r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(source.CssBox.HTTPBox())))
r.PathPrefix("/font/").Handler(http.StripPrefix("/font/", http.FileServer(source.FontBox.HTTPBox())))
+ r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(source.JsBox.HTTPBox())))
r.PathPrefix("/robots.txt").Handler(http.FileServer(source.TmplBox.HTTPBox()))
r.PathPrefix("/favicon.ico").Handler(http.FileServer(source.TmplBox.HTTPBox()))
r.PathPrefix("/banner.png").Handler(http.FileServer(source.TmplBox.HTTPBox()))
}
- r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(source.JsBox.HTTPBox())))
r.Handle("/charts.js", http.HandlerFunc(renderServiceChartsHandler))
r.Handle("/setup", http.HandlerFunc(setupHandler)).Methods("GET")
r.Handle("/setup", http.HandlerFunc(processSetupHandler)).Methods("POST")
diff --git a/handlers/services_test.go b/handlers/services_test.go
new file mode 100644
index 00000000..f6a4d77a
--- /dev/null
+++ b/handlers/services_test.go
@@ -0,0 +1,35 @@
+package handlers
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestServiceRoutes(t *testing.T) {
+ tests := []HTTPTest{
+ {
+ Name: "Services",
+ URL: "/services",
+ Method: "GET",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`Statping | Services`},
+ },
+ {
+ Name: "Services 2",
+ URL: "/service/2",
+ Method: "GET",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`Statping Github Status`},
+ }}
+
+ for _, v := range tests {
+ t.Run(v.Name, func(t *testing.T) {
+ body, t, err := RunHTTPTest(v, t)
+ assert.Nil(t, err)
+ if err != nil {
+ t.FailNow()
+ }
+ t.Logf("Test %v got: %v\n", v.Name, string(body))
+ })
+ }
+}
diff --git a/handlers/users_test.go b/handlers/users_test.go
new file mode 100644
index 00000000..3a1b6a5f
--- /dev/null
+++ b/handlers/users_test.go
@@ -0,0 +1,35 @@
+package handlers
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestUserRoutes(t *testing.T) {
+ tests := []HTTPTest{
+ {
+ Name: "Users",
+ URL: "/users",
+ Method: "GET",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`Statping | Users`},
+ },
+ {
+ Name: "User 2",
+ URL: "/user/2",
+ Method: "GET",
+ ExpectedStatus: 200,
+ ExpectedContains: []string{`Statping | adminuser2`},
+ }}
+
+ for _, v := range tests {
+ t.Run(v.Name, func(t *testing.T) {
+ body, t, err := RunHTTPTest(v, t)
+ assert.Nil(t, err)
+ if err != nil {
+ t.FailNow()
+ }
+ t.Logf("Test %v got: %v\n", v.Name, string(body))
+ })
+ }
+}
diff --git a/source/css/base.css b/source/css/base.css
index da1b43c3..e10f4f01 100644
--- a/source/css/base.css
+++ b/source/css/base.css
@@ -7,54 +7,66 @@
/* Mobile Service Container */
HTML, BODY {
background-color: #fcfcfc;
- padding-bottom: 10px; }
+ padding-bottom: 10px;
+}
.container {
padding-top: 20px;
padding-bottom: 25px;
max-width: 860px;
- box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; }
+ box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
+}
.header-title {
- color: #464646; }
+ color: #464646;
+}
.header-desc {
- color: #939393; }
+ color: #939393;
+}
.btn {
- border-radius: 0.2rem; }
+ border-radius: 0.2rem;
+}
.online_list .badge {
- margin-top: 0.2rem; }
+ margin-top: 0.2rem;
+}
.navbar {
- margin-bottom: 30px; }
+ margin-bottom: 30px;
+}
.btn-sm {
line-height: 1.3;
- font-size: 0.75rem; }
+ font-size: 0.75rem;
+}
.view_service_btn {
position: absolute;
bottom: -40px;
- right: 40px; }
+ right: 40px;
+}
.service_lower_info {
position: absolute;
bottom: -40px;
left: 40px;
color: #d1ffca;
- font-size: 0.85rem; }
+ font-size: 0.85rem;
+}
.lg_number {
font-size: 2.3rem;
font-weight: bold;
display: block;
- color: #4f4f4f; }
+ color: #4f4f4f;
+}
.stats_area {
text-align: center;
- color: #a5a5a5; }
+ color: #a5a5a5;
+}
.lower_canvas {
height: 3.4rem;
@@ -62,112 +74,144 @@ HTML, BODY {
background-color: #48d338;
padding: 15px 10px;
margin-left: 0px !important;
- margin-right: 0px !important; }
+ margin-right: 0px !important;
+}
.lower_canvas SPAN {
font-size: 1rem;
- color: #fff; }
+ color: #fff;
+}
.footer {
text-decoration: none;
- margin-top: 20px; }
+ margin-top: 20px;
+}
.footer A {
color: #8d8d8d;
- text-decoration: none; }
+ text-decoration: none;
+}
.footer A:HOVER {
- color: #6d6d6d; }
+ color: #6d6d6d;
+}
.badge {
color: white;
- border-radius: 0.2rem; }
+ border-radius: 0.2rem;
+}
.btn-group {
- height: 25px; }
- .btn-group A {
- padding: 0.1rem .75rem;
- font-size: 0.8rem; }
+ height: 25px;
+}
+.btn-group A {
+ padding: 0.1rem 0.75rem;
+ font-size: 0.8rem;
+}
.card-body .badge {
- color: #fff; }
+ color: #fff;
+}
.nav-pills .nav-link {
- border-radius: 0.2rem; }
+ border-radius: 0.2rem;
+}
.form-control {
- border-radius: 0.2rem; }
+ border-radius: 0.2rem;
+}
.card {
background-color: #ffffff;
- border: 1px solid rgba(0, 0, 0, 0.125); }
+ border: 1px solid rgba(0, 0, 0, 0.125);
+}
.card-body {
- overflow: hidden; }
+ overflow: hidden;
+}
.card-body H4 A {
color: #444444;
- text-decoration: none; }
+ text-decoration: none;
+}
.chart-container {
position: relative;
height: 170px;
- width: 100%; }
+ width: 100%;
+}
.service-chart-container {
position: relative;
height: 400px;
- width: 100%; }
+ width: 100%;
+}
.btn-primary {
background-color: #3e9bff;
border-color: #006fe6;
- color: white; }
- .btn-primary.dyn-dark {
- background-color: #32a825 !important;
- border-color: #2c9320 !important; }
- .btn-primary.dyn-light {
- background-color: #75de69 !important;
- border-color: #88e37e !important; }
+ color: white;
+}
+.btn-primary.dyn-dark {
+ background-color: #32a825 !important;
+ border-color: #2c9320 !important;
+}
+.btn-primary.dyn-light {
+ background-color: #75de69 !important;
+ border-color: #88e37e !important;
+}
.btn-success {
- background-color: #47d337; }
- .btn-success.dyn-dark {
- background-color: #32a825 !important;
- border-color: #2c9320 !important; }
- .btn-success.dyn-light {
- background-color: #75de69 !important;
- border-color: #88e37e !important; }
+ background-color: #47d337;
+}
+.btn-success.dyn-dark {
+ background-color: #32a825 !important;
+ border-color: #2c9320 !important;
+}
+.btn-success.dyn-light {
+ background-color: #75de69 !important;
+ border-color: #88e37e !important;
+}
.btn-danger {
- background-color: #dd3545; }
- .btn-danger.dyn-dark {
- background-color: #b61f2d !important;
- border-color: #a01b28 !important; }
- .btn-danger.dyn-light {
- background-color: #e66975 !important;
- border-color: #e97f89 !important; }
+ background-color: #dd3545;
+}
+.btn-danger.dyn-dark {
+ background-color: #b61f2d !important;
+ border-color: #a01b28 !important;
+}
+.btn-danger.dyn-light {
+ background-color: #e66975 !important;
+ border-color: #e97f89 !important;
+}
.bg-success {
- background-color: #47d337 !important; }
+ background-color: #47d337 !important;
+}
.bg-danger {
- background-color: #dd3545 !important; }
+ background-color: #dd3545 !important;
+}
.bg-success .dyn-dark {
- background-color: #35b027 !important; }
+ background-color: #35b027 !important;
+}
.bg-danger .dyn-dark {
- background-color: #bf202f !important; }
+ background-color: #bf202f !important;
+}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
- background-color: #13a00d; }
+ background-color: #13a00d;
+}
.nav-pills A {
- color: #424242; }
+ color: #424242;
+}
.nav-pills I {
- margin-right: 10px; }
+ margin-right: 10px;
+}
.CodeMirror {
/* Bootstrap Settings */
@@ -187,23 +231,26 @@ HTML, BODY {
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+ transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
/* Code Mirror Settings */
font-family: monospace;
position: relative;
overflow: hidden;
- height: 80vh; }
+ height: 80vh;
+}
.CodeMirror-focused {
/* Bootstrap Settings */
border-color: #66afe9;
outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
- transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; }
+ transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+}
.switch {
font-size: 1rem;
- position: relative; }
+ position: relative;
+}
.switch input {
position: absolute;
@@ -214,7 +261,8 @@ HTML, BODY {
clip: rect(0 0 0 0);
clip-path: inset(50%);
overflow: hidden;
- padding: 0; }
+ padding: 0;
+}
.switch input + label {
position: relative;
@@ -227,23 +275,26 @@ HTML, BODY {
outline: none;
user-select: none;
vertical-align: middle;
- text-indent: calc(calc(calc(2.375rem * .8) * 2) + .5rem); }
+ text-indent: calc(calc(calc(2.375rem * .8) * 2) + .5rem);
+}
.switch input + label::before,
.switch input + label::after {
- content: '';
+ content: "";
position: absolute;
top: 0;
left: 0;
width: calc(calc(2.375rem * .8) * 2);
bottom: 0;
- display: block; }
+ display: block;
+}
.switch input + label::before {
right: 0;
background-color: #dee2e6;
border-radius: calc(2.375rem * .8);
- transition: 0.2s all; }
+ transition: 0.2s all;
+}
.switch input + label::after {
top: 2px;
@@ -252,120 +303,154 @@ HTML, BODY {
height: calc(calc(2.375rem * .8) - calc(2px * 2));
border-radius: 50%;
background-color: white;
- transition: 0.2s all; }
+ transition: 0.2s all;
+}
.switch input:checked + label::before {
- background-color: #08d; }
+ background-color: #08d;
+}
.switch input:checked + label::after {
- margin-left: calc(2.375rem * .8); }
+ margin-left: calc(2.375rem * .8);
+}
.switch input:focus + label::before {
outline: none;
- box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25); }
+ box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25);
+}
.switch input:disabled + label {
color: #868e96;
- cursor: not-allowed; }
+ cursor: not-allowed;
+}
.switch input:disabled + label::before {
- background-color: #e9ecef; }
+ background-color: #e9ecef;
+}
.switch.switch-sm {
- font-size: 0.875rem; }
+ font-size: 0.875rem;
+}
.switch.switch-sm input + label {
min-width: calc(calc(1.9375rem * .8) * 2);
height: calc(1.9375rem * .8);
line-height: calc(1.9375rem * .8);
- text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem); }
+ text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem);
+}
.switch.switch-sm input + label::before {
- width: calc(calc(1.9375rem * .8) * 2); }
+ width: calc(calc(1.9375rem * .8) * 2);
+}
.switch.switch-sm input + label::after {
width: calc(calc(1.9375rem * .8) - calc(2px * 2));
- height: calc(calc(1.9375rem * .8) - calc(2px * 2)); }
+ height: calc(calc(1.9375rem * .8) - calc(2px * 2));
+}
.switch.switch-sm input:checked + label::after {
- margin-left: calc(1.9375rem * .8); }
+ margin-left: calc(1.9375rem * .8);
+}
.switch.switch-lg {
- font-size: 1.25rem; }
+ font-size: 1.25rem;
+}
.switch.switch-lg input + label {
min-width: calc(calc(3rem * .8) * 2);
height: calc(3rem * .8);
line-height: calc(3rem * .8);
- text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem); }
+ text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem);
+}
.switch.switch-lg input + label::before {
- width: calc(calc(3rem * .8) * 2); }
+ width: calc(calc(3rem * .8) * 2);
+}
.switch.switch-lg input + label::after {
width: calc(calc(3rem * .8) - calc(2px * 2));
- height: calc(calc(3rem * .8) - calc(2px * 2)); }
+ height: calc(calc(3rem * .8) - calc(2px * 2));
+}
.switch.switch-lg input:checked + label::after {
- margin-left: calc(3rem * .8); }
+ margin-left: calc(3rem * .8);
+}
.switch + .switch {
- margin-left: 1rem; }
+ margin-left: 1rem;
+}
@keyframes pulse_animation {
0% {
- transform: scale(1); }
+ transform: scale(1);
+ }
30% {
- transform: scale(1); }
+ transform: scale(1);
+ }
40% {
- transform: scale(1.02); }
+ transform: scale(1.02);
+ }
50% {
- transform: scale(1); }
+ transform: scale(1);
+ }
60% {
- transform: scale(1); }
+ transform: scale(1);
+ }
70% {
- transform: scale(1.05); }
+ transform: scale(1.05);
+ }
80% {
- transform: scale(1); }
+ transform: scale(1);
+ }
100% {
- transform: scale(1); } }
+ transform: scale(1);
+ }
+}
.pulse {
animation-name: pulse_animation;
animation-duration: 1500ms;
transform-origin: 70% 70%;
animation-iteration-count: infinite;
- animation-timing-function: linear; }
+ animation-timing-function: linear;
+}
@keyframes glow-grow {
0% {
opacity: 0;
- transform: scale(1); }
+ transform: scale(1);
+ }
80% {
- opacity: 1; }
+ opacity: 1;
+ }
100% {
transform: scale(2);
- opacity: 0; } }
+ opacity: 0;
+ }
+}
.pulse-glow {
animation-name: glow-grown;
animation-duration: 100ms;
transform-origin: 70% 30%;
animation-iteration-count: infinite;
- animation-timing-function: linear; }
+ animation-timing-function: linear;
+}
.pulse-glow:before,
.pulse-glow:after {
position: absolute;
- content: '';
+ content: "";
height: 0.5rem;
width: 1.75rem;
top: 1.2rem;
right: 2.15rem;
border-radius: 0;
box-shadow: 0 0 7px #47d337;
- animation: glow-grow 2s ease-out infinite; }
+ animation: glow-grow 2s ease-out infinite;
+}
.sortable_drag {
- background-color: #0000000f; }
+ background-color: #0000000f;
+}
.drag_icon {
cursor: move;
@@ -379,106 +464,132 @@ HTML, BODY {
margin-right: 5px;
margin-left: -10px;
text-align: center;
- color: #b1b1b1; }
+ color: #b1b1b1;
+}
/* (Optional) Apply a "closed-hand" cursor during drag operation. */
.drag_icon:active {
cursor: grabbing;
cursor: -moz-grabbing;
- cursor: -webkit-grabbing; }
+ cursor: -webkit-grabbing;
+}
.switch_btn {
float: right;
margin: -1px 0px 0px 0px;
- display: block; }
+ display: block;
+}
#start_container {
position: absolute;
z-index: 99999;
- margin-top: 20px; }
+ margin-top: 20px;
+}
#end_container {
position: absolute;
z-index: 99999;
margin-top: 20px;
- right: 0; }
+ right: 0;
+}
.pointer {
- cursor: pointer; }
+ cursor: pointer;
+}
.jumbotron {
- background-color: white; }
+ background-color: white;
+}
@media (max-width: 767px) {
HTML, BODY {
- background-color: #fcfcfc; }
+ background-color: #fcfcfc;
+ }
.sm-container {
margin-top: 0px !important;
- padding: 0 !important; }
+ padding: 0 !important;
+ }
.list-group-item H5 {
- font-size: 0.9rem; }
+ font-size: 0.9rem;
+ }
.container {
padding: 0px !important;
- padding-top: 15px !important; }
+ padding-top: 15px !important;
+ }
.group_header {
- margin-left: 15px; }
+ margin-left: 15px;
+ }
.navbar {
margin-left: 0px;
margin-top: 0px;
width: 100%;
- margin-bottom: 0; }
+ margin-bottom: 0;
+ }
.btn-sm {
line-height: 0.9rem;
- font-size: 0.65rem; }
+ font-size: 0.65rem;
+ }
.full-col-12 {
padding-left: 0px;
- padding-right: 0px; }
+ padding-right: 0px;
+ }
.card {
border: 0;
border-radius: 0rem;
padding: 0;
- background-color: #ffffff; }
+ background-color: #ffffff;
+ }
.card-body {
font-size: 10pt;
- padding: 0px 10px; }
+ padding: 0px 10px;
+ }
.lg_number {
- font-size: 7.8vw; }
+ font-size: 7.8vw;
+ }
.stats_area {
margin-top: 1.5rem !important;
- margin-bottom: 1.5rem !important; }
+ margin-bottom: 1.5rem !important;
+ }
.stats_area .col-4 {
padding-left: 0;
padding-right: 0;
- font-size: 0.6rem; }
+ font-size: 0.6rem;
+ }
.list-group-item {
border-top: 1px solid #e4e4e4;
- border: 0px; }
+ border: 0px;
+ }
.list-group-item:first-child {
border-top-left-radius: 0;
- border-top-right-radius: 0; }
+ border-top-right-radius: 0;
+ }
.list-group-item:last-child {
border-bottom-right-radius: 0;
- border-bottom-left-radius: 0; }
+ border-bottom-left-radius: 0;
+ }
.list-group-item P {
- font-size: 0.7rem; }
+ font-size: 0.7rem;
+ }
.service-chart-container {
- height: 200px; } }
+ height: 200px;
+ }
+}
/*# sourceMappingURL=base.css.map */
diff --git a/source/source_test.go b/source/source_test.go
index d07b1488..7604aae7 100644
--- a/source/source_test.go
+++ b/source/source_test.go
@@ -40,8 +40,8 @@ func TestCore_UsingAssets(t *testing.T) {
func TestCreateAssets(t *testing.T) {
assert.Nil(t, CreateAllAssets(dir))
assert.True(t, UsingAssets(dir))
- assert.FileExists(t, "../assets/css/base.css")
- assert.FileExists(t, "../assets/scss/base.scss")
+ assert.FileExists(t, "./assets/css/base.css")
+ assert.FileExists(t, "./assets/scss/base.scss")
}
func TestCompileSASS(t *testing.T) {
diff --git a/source/wiki.go b/source/wiki.go
index 4ede29c3..58fa01da 100644
--- a/source/wiki.go
+++ b/source/wiki.go
@@ -1,6 +1,6 @@
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
-// 2019-01-10 12:48:42.676069 -0800 PST m=+0.785435129
+// 2019-01-14 17:21:53.045328 -0800 PST m=+0.787121391
//
// This contains the most recently Markdown source for the Statping Wiki.
package source
diff --git a/utils/utils_test.go b/utils/utils_test.go
index 7bf71155..4cb5501f 100644
--- a/utils/utils_test.go
+++ b/utils/utils_test.go
@@ -31,11 +31,7 @@ func TestCreateLog(t *testing.T) {
func TestInitLogs(t *testing.T) {
assert.Nil(t, InitLogs())
- assert.FileExists(t, "../logs/statup.log")
-}
-
-func TestFileExists(t *testing.T) {
- assert.True(t, FileExists("../logs/statup.log"))
+ assert.FileExists(t, "./logs/statup.log")
}
func TestDir(t *testing.T) {
diff --git a/version.txt b/version.txt
index 63106728..9d8e01cc 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-0.80.36
+0.80.37