mirror of https://github.com/statping/statping
password fix - Messages for service - fixed null bool/string - better date lib
parent
52756ee359
commit
6c28635763
|
@ -165,7 +165,7 @@ func (s *Service) checkHttp(record bool) *Service {
|
||||||
|
|
||||||
var response *http.Response
|
var response *http.Response
|
||||||
if s.Method == "POST" {
|
if s.Method == "POST" {
|
||||||
response, err = client.Post(s.Domain, "application/json", bytes.NewBuffer([]byte(s.PostData)))
|
response, err = client.Post(s.Domain, "application/json", bytes.NewBuffer([]byte(s.PostData.String)))
|
||||||
} else {
|
} else {
|
||||||
response, err = client.Get(s.Domain)
|
response, err = client.Get(s.Domain)
|
||||||
}
|
}
|
||||||
|
@ -190,11 +190,11 @@ func (s *Service) checkHttp(record bool) *Service {
|
||||||
s.LastResponse = string(contents)
|
s.LastResponse = string(contents)
|
||||||
s.LastStatusCode = response.StatusCode
|
s.LastStatusCode = response.StatusCode
|
||||||
|
|
||||||
if s.Expected != "" {
|
if s.Expected.String != "" {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log(2, err)
|
utils.Log(2, err)
|
||||||
}
|
}
|
||||||
match, err := regexp.MatchString(s.Expected, string(contents))
|
match, err := regexp.MatchString(s.Expected.String, string(contents))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log(2, err)
|
utils.Log(2, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-yaml/yaml"
|
"github.com/go-yaml/yaml"
|
||||||
|
@ -71,9 +72,8 @@ func LoadUsingEnv() (*DbConfig, error) {
|
||||||
CoreApp.Name = os.Getenv("NAME")
|
CoreApp.Name = os.Getenv("NAME")
|
||||||
CoreApp.Domain = os.Getenv("DOMAIN")
|
CoreApp.Domain = os.Getenv("DOMAIN")
|
||||||
CoreApp.DbConnection = Configs.DbConn
|
CoreApp.DbConnection = Configs.DbConn
|
||||||
if os.Getenv("USE_CDN") == "true" {
|
CoreApp.UseCdn = sql.NullBool{os.Getenv("USE_CDN") == "true", true}
|
||||||
CoreApp.UseCdn = true
|
|
||||||
}
|
|
||||||
err := Configs.Connect(true, utils.Directory)
|
err := Configs.Connect(true, utils.Directory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log(4, err)
|
utils.Log(4, err)
|
||||||
|
@ -94,7 +94,7 @@ func LoadUsingEnv() (*DbConfig, error) {
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
Password: "admin",
|
Password: "admin",
|
||||||
Email: "info@admin.com",
|
Email: "info@admin.com",
|
||||||
Admin: true,
|
Admin: sql.NullBool{true, true},
|
||||||
})
|
})
|
||||||
_, err := admin.Create()
|
_, err := admin.Create()
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/hunterlong/statup/core/notifier"
|
"github.com/hunterlong/statup/core/notifier"
|
||||||
"github.com/hunterlong/statup/source"
|
"github.com/hunterlong/statup/source"
|
||||||
|
@ -147,9 +148,7 @@ func SelectCore() (*Core, error) {
|
||||||
}
|
}
|
||||||
CoreApp.DbConnection = Configs.DbConn
|
CoreApp.DbConnection = Configs.DbConn
|
||||||
CoreApp.Version = VERSION
|
CoreApp.Version = VERSION
|
||||||
if os.Getenv("USE_CDN") == "true" {
|
CoreApp.UseCdn = sql.NullBool{os.Getenv("USE_CDN") == "true", true}
|
||||||
CoreApp.UseCdn = true
|
|
||||||
}
|
|
||||||
return CoreApp, db.Error
|
return CoreApp, db.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,11 @@ func checkinDB() *gorm.DB {
|
||||||
return DbSession.Model(&types.Checkin{})
|
return DbSession.Model(&types.Checkin{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// messagesDb returns the Checkin records for a service
|
||||||
|
func messagesDb() *gorm.DB {
|
||||||
|
return DbSession.Model(&types.Message{})
|
||||||
|
}
|
||||||
|
|
||||||
// checkinHitsDB returns the 'hits' from the Checkin record
|
// checkinHitsDB returns the 'hits' from the Checkin record
|
||||||
func checkinHitsDB() *gorm.DB {
|
func checkinHitsDB() *gorm.DB {
|
||||||
return DbSession.Model(&types.CheckinHit{})
|
return DbSession.Model(&types.CheckinHit{})
|
||||||
|
@ -329,6 +334,7 @@ func (db *DbConfig) DropDatabase() error {
|
||||||
err = DbSession.DropTableIfExists("hits")
|
err = DbSession.DropTableIfExists("hits")
|
||||||
err = DbSession.DropTableIfExists("services")
|
err = DbSession.DropTableIfExists("services")
|
||||||
err = DbSession.DropTableIfExists("users")
|
err = DbSession.DropTableIfExists("users")
|
||||||
|
err = DbSession.DropTableIfExists("messages")
|
||||||
return err.Error
|
return err.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,6 +349,7 @@ func (db *DbConfig) CreateDatabase() error {
|
||||||
err = DbSession.CreateTable(&types.Hit{})
|
err = DbSession.CreateTable(&types.Hit{})
|
||||||
err = DbSession.CreateTable(&types.Service{})
|
err = DbSession.CreateTable(&types.Service{})
|
||||||
err = DbSession.CreateTable(&types.User{})
|
err = DbSession.CreateTable(&types.User{})
|
||||||
|
err = DbSession.CreateTable(&types.Message{})
|
||||||
utils.Log(1, "Statup Database Created")
|
utils.Log(1, "Statup Database Created")
|
||||||
return err.Error
|
return err.Error
|
||||||
}
|
}
|
||||||
|
@ -361,7 +368,7 @@ func (db *DbConfig) MigrateDatabase() error {
|
||||||
if tx.Error != nil {
|
if tx.Error != nil {
|
||||||
return tx.Error
|
return tx.Error
|
||||||
}
|
}
|
||||||
tx = tx.AutoMigrate(&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Checkin{}, &types.CheckinHit{}, ¬ifier.Notification{}).Table("core").AutoMigrate(&types.Core{})
|
tx = tx.AutoMigrate(&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Message{}, &types.Checkin{}, &types.CheckinHit{}, ¬ifier.Notification{}).Table("core").AutoMigrate(&types.Core{})
|
||||||
if tx.Error != nil {
|
if tx.Error != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
utils.Log(3, fmt.Sprintf("Statup Database could not be migrated: %v", tx.Error))
|
utils.Log(3, fmt.Sprintf("Statup Database could not be migrated: %v", tx.Error))
|
||||||
|
|
|
@ -17,6 +17,7 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hunterlong/statup/source"
|
"github.com/hunterlong/statup/source"
|
||||||
"github.com/hunterlong/statup/utils"
|
"github.com/hunterlong/statup/utils"
|
||||||
|
@ -32,7 +33,7 @@ func ExportIndexHTML() string {
|
||||||
source.Assets()
|
source.Assets()
|
||||||
injectDatabase()
|
injectDatabase()
|
||||||
CoreApp.SelectAllServices(false)
|
CoreApp.SelectAllServices(false)
|
||||||
CoreApp.UseCdn = true
|
CoreApp.UseCdn = sql.NullBool{true, true}
|
||||||
for _, srv := range CoreApp.Services {
|
for _, srv := range CoreApp.Services {
|
||||||
service := srv.(*Service)
|
service := srv.(*Service)
|
||||||
service.Check(true)
|
service.Check(true)
|
||||||
|
@ -60,7 +61,7 @@ func ExportIndexHTML() string {
|
||||||
return CoreApp
|
return CoreApp
|
||||||
},
|
},
|
||||||
"USE_CDN": func() bool {
|
"USE_CDN": func() bool {
|
||||||
return CoreApp.UseCdn
|
return CoreApp.UseCdn.Bool
|
||||||
},
|
},
|
||||||
"underscore": func(html string) string {
|
"underscore": func(html string) string {
|
||||||
return utils.UnderScoreString(html)
|
return utils.UnderScoreString(html)
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Statup
|
||||||
|
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||||
|
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||||
|
//
|
||||||
|
// https://github.com/hunterlong/statup
|
||||||
|
//
|
||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hunterlong/statup/types"
|
||||||
|
"github.com/hunterlong/statup/utils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
*types.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectServiceMessages returns all messages for a service
|
||||||
|
func SelectServiceMessages(id int64) []*Message {
|
||||||
|
var message []*Message
|
||||||
|
messagesDb().Where("service = ?", id).Limit(10).Find(&message)
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReturnMessage will convert *types.Message to *core.Message
|
||||||
|
func ReturnMessage(m *types.Message) *Message {
|
||||||
|
return &Message{m}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectMessages returns all messages
|
||||||
|
func SelectMessages() ([]*Message, error) {
|
||||||
|
var messages []*Message
|
||||||
|
db := messagesDb().Find(&messages).Order("id desc")
|
||||||
|
return messages, db.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectMessage returns a Message based on the ID passed
|
||||||
|
func SelectMessage(id int64) (*Message, error) {
|
||||||
|
var message Message
|
||||||
|
db := messagesDb().Where("id = ?", id).Find(&message)
|
||||||
|
return &message, db.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create will create a Message and insert it into the database
|
||||||
|
func (m *Message) Create() (int64, error) {
|
||||||
|
m.CreatedAt = time.Now().UTC()
|
||||||
|
db := messagesDb().Create(m)
|
||||||
|
if db.Error != nil {
|
||||||
|
utils.Log(3, fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error))
|
||||||
|
return 0, db.Error
|
||||||
|
}
|
||||||
|
return m.Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete will delete a Message from database
|
||||||
|
func (m *Message) Delete() error {
|
||||||
|
db := messagesDb().Delete(m)
|
||||||
|
return db.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update will update a Message in the database
|
||||||
|
func (m *Message) Update() (*Message, error) {
|
||||||
|
db := messagesDb().Update(m)
|
||||||
|
if db.Error != nil {
|
||||||
|
utils.Log(3, fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error))
|
||||||
|
return nil, db.Error
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hunterlong/statup/types"
|
"github.com/hunterlong/statup/types"
|
||||||
"github.com/hunterlong/statup/utils"
|
"github.com/hunterlong/statup/utils"
|
||||||
|
@ -60,11 +61,11 @@ func InsertSampleData() error {
|
||||||
Name: "JSON API Tester",
|
Name: "JSON API Tester",
|
||||||
Domain: "https://jsonplaceholder.typicode.com/posts",
|
Domain: "https://jsonplaceholder.typicode.com/posts",
|
||||||
ExpectedStatus: 201,
|
ExpectedStatus: 201,
|
||||||
Expected: `(title)": "((\\"|[statup])*)"`,
|
Expected: utils.NullString(`(title)": "((\\"|[statup])*)"`),
|
||||||
Interval: 30,
|
Interval: 30,
|
||||||
Type: "http",
|
Type: "http",
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
PostData: `{ "title": "statup", "body": "bar", "userId": 19999 }`,
|
PostData: utils.NullString(`{ "title": "statup", "body": "bar", "userId": 19999 }`),
|
||||||
Timeout: 30,
|
Timeout: 30,
|
||||||
Order: 4,
|
Order: 4,
|
||||||
})
|
})
|
||||||
|
@ -155,7 +156,7 @@ func insertSampleCore() error {
|
||||||
Domain: "http://localhost:8080",
|
Domain: "http://localhost:8080",
|
||||||
Version: "test",
|
Version: "test",
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UseCdn: false,
|
UseCdn: sql.NullBool{false, true},
|
||||||
}
|
}
|
||||||
query := coreDB().Create(core)
|
query := coreDB().Create(core)
|
||||||
return query.Error
|
return query.Error
|
||||||
|
@ -167,14 +168,14 @@ func insertSampleUsers() {
|
||||||
Username: "testadmin",
|
Username: "testadmin",
|
||||||
Password: "password123",
|
Password: "password123",
|
||||||
Email: "info@betatude.com",
|
Email: "info@betatude.com",
|
||||||
Admin: true,
|
Admin: sql.NullBool{true, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
u3 := ReturnUser(&types.User{
|
u3 := ReturnUser(&types.User{
|
||||||
Username: "testadmin2",
|
Username: "testadmin2",
|
||||||
Password: "password123",
|
Password: "password123",
|
||||||
Email: "info@adminhere.com",
|
Email: "info@adminhere.com",
|
||||||
Admin: true,
|
Admin: sql.NullBool{true, true},
|
||||||
})
|
})
|
||||||
|
|
||||||
u2.Create()
|
u2.Create()
|
||||||
|
|
|
@ -394,6 +394,12 @@ func (s *Service) Create(check bool) (int64, error) {
|
||||||
return s.Id, nil
|
return s.Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Messages returns all Messages for a Service
|
||||||
|
func (s *Service) Messages() []*Message {
|
||||||
|
messages := SelectServiceMessages(s.Id)
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
// ServicesCount returns the amount of services inside the []*core.Services slice
|
// ServicesCount returns the amount of services inside the []*core.Services slice
|
||||||
func (c *Core) ServicesCount() int {
|
func (c *Core) ServicesCount() int {
|
||||||
return len(c.Services)
|
return len(c.Services)
|
||||||
|
|
|
@ -35,7 +35,7 @@ func ReturnUser(u *types.User) *user {
|
||||||
// SelectUser returns the user based on the user's ID.
|
// SelectUser returns the user based on the user's ID.
|
||||||
func SelectUser(id int64) (*user, error) {
|
func SelectUser(id int64) (*user, error) {
|
||||||
var user user
|
var user user
|
||||||
err := usersDB().First(&user, id)
|
err := usersDB().Where("id = ?", id).First(&user)
|
||||||
return &user, err.Error
|
return &user, err.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,6 @@ func (u *user) Delete() error {
|
||||||
|
|
||||||
// Update will update the user's record in database
|
// Update will update the user's record in database
|
||||||
func (u *user) Update() error {
|
func (u *user) Update() error {
|
||||||
u.Password = utils.HashPassword(u.Password)
|
|
||||||
u.ApiKey = utils.NewSHA1Hash(5)
|
u.ApiKey = utils.NewSHA1Hash(5)
|
||||||
u.ApiSecret = utils.NewSHA1Hash(10)
|
u.ApiSecret = utils.NewSHA1Hash(10)
|
||||||
return usersDB().Update(u).Error
|
return usersDB().Update(u).Error
|
||||||
|
|
|
@ -108,8 +108,11 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap
|
||||||
"len": func(g []types.ServiceInterface) int {
|
"len": func(g []types.ServiceInterface) int {
|
||||||
return len(g)
|
return len(g)
|
||||||
},
|
},
|
||||||
|
"IsNil": func(g interface{}) bool {
|
||||||
|
return g == nil
|
||||||
|
},
|
||||||
"USE_CDN": func() bool {
|
"USE_CDN": func() bool {
|
||||||
return core.CoreApp.UseCdn
|
return core.CoreApp.UseCdn.Bool
|
||||||
},
|
},
|
||||||
"Type": func(g interface{}) []string {
|
"Type": func(g interface{}) []string {
|
||||||
fooType := reflect.TypeOf(g)
|
fooType := reflect.TypeOf(g)
|
||||||
|
@ -163,6 +166,9 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap
|
||||||
"NewCheckin": func() *types.Checkin {
|
"NewCheckin": func() *types.Checkin {
|
||||||
return new(types.Checkin)
|
return new(types.Checkin)
|
||||||
},
|
},
|
||||||
|
"NewMessage": func() *types.Message {
|
||||||
|
return new(types.Message)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +182,7 @@ func executeResponse(w http.ResponseWriter, r *http.Request, file string, data i
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
templates := []string{"base.html", "head.html", "nav.html", "footer.html", "scripts.html", "form_service.html", "form_notifier.html", "form_user.html", "form_checkin.html"}
|
templates := []string{"base.html", "head.html", "nav.html", "footer.html", "scripts.html", "form_service.html", "form_notifier.html", "form_user.html", "form_checkin.html", "form_message.html"}
|
||||||
|
|
||||||
javascripts := []string{"charts.js", "chart_index.js"}
|
javascripts := []string{"charts.js", "chart_index.js"}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/hunterlong/statup/core"
|
"github.com/hunterlong/statup/core"
|
||||||
"github.com/hunterlong/statup/types"
|
"github.com/hunterlong/statup/types"
|
||||||
|
@ -108,7 +109,7 @@ func DesktopInit(ip string, port int) {
|
||||||
Username: config.Username,
|
Username: config.Username,
|
||||||
Password: config.Password,
|
Password: config.Password,
|
||||||
Email: config.Email,
|
Email: config.Email,
|
||||||
Admin: true,
|
Admin: sql.NullBool{true, true},
|
||||||
})
|
})
|
||||||
admin.Create()
|
admin.Create()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
// Statup
|
||||||
|
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||||
|
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||||
|
//
|
||||||
|
// https://github.com/hunterlong/statup
|
||||||
|
//
|
||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/hunterlong/statup/core"
|
||||||
|
"github.com/hunterlong/statup/types"
|
||||||
|
"github.com/hunterlong/statup/utils"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func messagesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !IsAuthenticated(r) {
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
messages, _ := core.SelectMessages()
|
||||||
|
executeResponse(w, r, "messages.html", messages, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !IsAuthenticated(r) {
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id := utils.StringInt(vars["id"])
|
||||||
|
message, err := core.SelectMessage(id)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(w, r, "/messages", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.Delete()
|
||||||
|
http.Redirect(w, r, "/messages", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func viewMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !IsAuthenticated(r) {
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id := utils.StringInt(vars["id"])
|
||||||
|
message, err := core.SelectMessage(id)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
executeResponse(w, r, "message.html", message, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !IsAuthenticated(r) {
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id := utils.StringInt(vars["id"])
|
||||||
|
message, err := core.SelectMessage(id)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
title := r.PostForm.Get("title")
|
||||||
|
description := r.PostForm.Get("description")
|
||||||
|
notifyMethod := r.PostForm.Get("notify_method")
|
||||||
|
notifyUsers := r.PostForm.Get("notify_users")
|
||||||
|
startOn := r.PostForm.Get("start_on")
|
||||||
|
endOn := r.PostForm.Get("end_on")
|
||||||
|
notifyBefore := r.PostForm.Get("notify_before")
|
||||||
|
serviceId := utils.StringInt(r.PostForm.Get("service_id"))
|
||||||
|
|
||||||
|
start, err := time.Parse(utils.FlatpickrTime, startOn)
|
||||||
|
if err != nil {
|
||||||
|
utils.Log(3, err)
|
||||||
|
}
|
||||||
|
end, _ := time.Parse(utils.FlatpickrTime, endOn)
|
||||||
|
before, _ := time.ParseDuration(notifyBefore)
|
||||||
|
|
||||||
|
message.Title = title
|
||||||
|
message.Description = description
|
||||||
|
message.NotifyUsers = utils.NullBool(notifyUsers == "on")
|
||||||
|
message.NotifyMethod = notifyMethod
|
||||||
|
message.StartOn = start.UTC()
|
||||||
|
message.EndOn = end.UTC()
|
||||||
|
message.NotifyBefore = before
|
||||||
|
message.ServiceId = serviceId
|
||||||
|
|
||||||
|
message.Update()
|
||||||
|
executeResponse(w, r, "messages.html", message, "/messages")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !IsAuthenticated(r) {
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
title := r.PostForm.Get("title")
|
||||||
|
description := r.PostForm.Get("description")
|
||||||
|
notifyMethod := r.PostForm.Get("notify_method")
|
||||||
|
notifyUsers := r.PostForm.Get("notify_users")
|
||||||
|
startOn := r.PostForm.Get("start_on")
|
||||||
|
endOn := r.PostForm.Get("end_on")
|
||||||
|
notifyBefore := r.PostForm.Get("notify_before")
|
||||||
|
serviceId := utils.StringInt(r.PostForm.Get("service_id"))
|
||||||
|
|
||||||
|
start, _ := time.Parse(utils.FlatpickrTime, startOn)
|
||||||
|
end, _ := time.Parse(utils.FlatpickrTime, endOn)
|
||||||
|
before, _ := time.ParseDuration(notifyBefore)
|
||||||
|
|
||||||
|
message := core.ReturnMessage(&types.Message{
|
||||||
|
Title: title,
|
||||||
|
Description: description,
|
||||||
|
StartOn: start.UTC(),
|
||||||
|
EndOn: end.UTC(),
|
||||||
|
ServiceId: serviceId,
|
||||||
|
NotifyUsers: utils.NullBool(notifyUsers == "on"),
|
||||||
|
NotifyMethod: notifyMethod,
|
||||||
|
NotifyBefore: before,
|
||||||
|
})
|
||||||
|
_, err := message.Create()
|
||||||
|
if err != nil {
|
||||||
|
utils.Log(3, fmt.Sprintf("Error creating message %v", err))
|
||||||
|
}
|
||||||
|
messages, _ := core.SelectMessages()
|
||||||
|
executeResponse(w, r, "messages.html", messages, "/messages")
|
||||||
|
}
|
|
@ -70,6 +70,13 @@ func Router() *mux.Router {
|
||||||
r.Handle("/user/{id}", http.HandlerFunc(updateUserHandler)).Methods("POST")
|
r.Handle("/user/{id}", http.HandlerFunc(updateUserHandler)).Methods("POST")
|
||||||
r.Handle("/user/{id}/delete", http.HandlerFunc(usersDeleteHandler)).Methods("GET")
|
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
|
// SETTINGS Routes
|
||||||
r.Handle("/settings", http.HandlerFunc(settingsHandler)).Methods("GET")
|
r.Handle("/settings", http.HandlerFunc(settingsHandler)).Methods("GET")
|
||||||
r.Handle("/settings", http.HandlerFunc(saveSettingsHandler)).Methods("POST")
|
r.Handle("/settings", http.HandlerFunc(saveSettingsHandler)).Methods("POST")
|
||||||
|
|
|
@ -104,12 +104,12 @@ func createServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
Name: name,
|
Name: name,
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
Method: method,
|
Method: method,
|
||||||
Expected: expected,
|
Expected: utils.NullString(expected),
|
||||||
ExpectedStatus: status,
|
ExpectedStatus: status,
|
||||||
Interval: interval,
|
Interval: interval,
|
||||||
Type: checkType,
|
Type: checkType,
|
||||||
Port: port,
|
Port: port,
|
||||||
PostData: postData,
|
PostData: utils.NullString(postData),
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
Order: order,
|
Order: order,
|
||||||
})
|
})
|
||||||
|
@ -139,8 +139,11 @@ func servicesDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
func servicesViewHandler(w http.ResponseWriter, r *http.Request) {
|
func servicesViewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
fields := parseGet(r)
|
fields := parseGet(r)
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
startField := utils.StringInt(fields.Get("start"))
|
startField := utils.StringInt(fields.Get("start"))
|
||||||
endField := utils.StringInt(fields.Get("end"))
|
endField := utils.StringInt(fields.Get("end"))
|
||||||
|
group := r.Form.Get("group")
|
||||||
serv := core.SelectService(utils.StringInt(vars["id"]))
|
serv := core.SelectService(utils.StringInt(vars["id"]))
|
||||||
if serv == nil {
|
if serv == nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
@ -156,15 +159,18 @@ func servicesViewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if endField != 0 {
|
if endField != 0 {
|
||||||
end = time.Unix(endField, 0)
|
end = time.Unix(endField, 0)
|
||||||
}
|
}
|
||||||
|
if group == "" {
|
||||||
|
group = "hour"
|
||||||
|
}
|
||||||
|
|
||||||
data := core.GraphDataRaw(serv, start, end, "hour", "latency")
|
data := core.GraphDataRaw(serv, start, end, group, "latency")
|
||||||
|
|
||||||
out := struct {
|
out := struct {
|
||||||
Service *core.Service
|
Service *core.Service
|
||||||
Start int64
|
Start string
|
||||||
End int64
|
End string
|
||||||
Data string
|
Data string
|
||||||
}{serv, start.Unix(), end.Unix(), data.ToString()}
|
}{serv, start.Format(utils.FlatpickrReadable), end.Format(utils.FlatpickrReadable), data.ToString()}
|
||||||
|
|
||||||
executeResponse(w, r, "service.html", out, nil)
|
executeResponse(w, r, "service.html", out, nil)
|
||||||
}
|
}
|
||||||
|
@ -193,11 +199,11 @@ func servicesUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
service.Domain = domain
|
service.Domain = domain
|
||||||
service.Method = method
|
service.Method = method
|
||||||
service.ExpectedStatus = status
|
service.ExpectedStatus = status
|
||||||
service.Expected = expected
|
service.Expected = utils.NullString(expected)
|
||||||
service.Interval = interval
|
service.Interval = interval
|
||||||
service.Type = checkType
|
service.Type = checkType
|
||||||
service.Port = port
|
service.Port = port
|
||||||
service.PostData = postData
|
service.PostData = utils.NullString(postData)
|
||||||
service.Timeout = timeout
|
service.Timeout = timeout
|
||||||
service.Order = order
|
service.Order = order
|
||||||
|
|
||||||
|
|
|
@ -56,8 +56,8 @@ func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
app.Style = style
|
app.Style = style
|
||||||
}
|
}
|
||||||
footer := r.PostForm.Get("footer")
|
footer := r.PostForm.Get("footer")
|
||||||
if footer != app.Footer {
|
if footer != app.Footer.String {
|
||||||
app.Footer = footer
|
app.Footer = utils.NullString(footer)
|
||||||
}
|
}
|
||||||
domain := r.PostForm.Get("domain")
|
domain := r.PostForm.Get("domain")
|
||||||
if domain != app.Domain {
|
if domain != app.Domain {
|
||||||
|
@ -67,7 +67,7 @@ func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
timeFloat, _ := strconv.ParseFloat(timezone, 10)
|
timeFloat, _ := strconv.ParseFloat(timezone, 10)
|
||||||
app.Timezone = float32(timeFloat)
|
app.Timezone = float32(timeFloat)
|
||||||
|
|
||||||
app.UseCdn = (r.PostForm.Get("enable_cdn") == "on")
|
app.UseCdn = utils.NullBool(r.PostForm.Get("enable_cdn") == "on")
|
||||||
core.CoreApp, _ = core.UpdateCore(app)
|
core.CoreApp, _ = core.UpdateCore(app)
|
||||||
//notifiers.OnSettingsSaved(core.CoreApp.ToCore())
|
//notifiers.OnSettingsSaved(core.CoreApp.ToCore())
|
||||||
executeResponse(w, r, "settings.html", core.CoreApp, "/settings")
|
executeResponse(w, r, "settings.html", core.CoreApp, "/settings")
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"github.com/hunterlong/statup/core"
|
"github.com/hunterlong/statup/core"
|
||||||
"github.com/hunterlong/statup/types"
|
"github.com/hunterlong/statup/types"
|
||||||
"github.com/hunterlong/statup/utils"
|
"github.com/hunterlong/statup/utils"
|
||||||
|
@ -113,7 +114,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
Username: config.Username,
|
Username: config.Username,
|
||||||
Password: config.Password,
|
Password: config.Password,
|
||||||
Email: config.Email,
|
Email: config.Email,
|
||||||
Admin: true,
|
Admin: sql.NullBool{true, true},
|
||||||
})
|
})
|
||||||
admin.Create()
|
admin.Create()
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/hunterlong/statup/core"
|
"github.com/hunterlong/statup/core"
|
||||||
|
@ -52,8 +53,8 @@ func updateUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id, _ := strconv.Atoi(vars["id"])
|
id := utils.StringInt(vars["id"])
|
||||||
user, err := core.SelectUser(int64(id))
|
user, err := core.SelectUser(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log(3, fmt.Sprintf("user error: %v", err))
|
utils.Log(3, fmt.Sprintf("user error: %v", err))
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
@ -62,12 +63,21 @@ func updateUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
user.Username = r.PostForm.Get("username")
|
user.Username = r.PostForm.Get("username")
|
||||||
user.Email = r.PostForm.Get("email")
|
user.Email = r.PostForm.Get("email")
|
||||||
user.Admin = (r.PostForm.Get("admin") == "on")
|
isAdmin := r.PostForm.Get("admin") == "on"
|
||||||
|
user.Admin = sql.NullBool{isAdmin, true}
|
||||||
password := r.PostForm.Get("password")
|
password := r.PostForm.Get("password")
|
||||||
if password != "##########" {
|
if password != "##########" {
|
||||||
user.Password = utils.HashPassword(password)
|
user.Password = utils.HashPassword(password)
|
||||||
}
|
}
|
||||||
user.Update()
|
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()
|
users, _ := core.SelectAllUsers()
|
||||||
executeResponse(w, r, "users.html", users, "/users")
|
executeResponse(w, r, "users.html", users, "/users")
|
||||||
}
|
}
|
||||||
|
@ -87,7 +97,7 @@ func createUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: password,
|
Password: password,
|
||||||
Email: email,
|
Email: email,
|
||||||
Admin: (admin == "on"),
|
Admin: sql.NullBool{admin == "on", true},
|
||||||
})
|
})
|
||||||
_, err := user.Create()
|
_, err := user.Create()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -246,7 +246,7 @@ func (u *email) OnTest() error {
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Timeout: 20,
|
Timeout: 20,
|
||||||
LastStatusCode: 200,
|
LastStatusCode: 200,
|
||||||
Expected: "test example",
|
Expected: utils.NullString("test example"),
|
||||||
LastResponse: "<html>this is an example response</html>",
|
LastResponse: "<html>this is an example response</html>",
|
||||||
CreatedAt: time.Now().Add(-24 * time.Hour),
|
CreatedAt: time.Now().Add(-24 * time.Hour),
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,7 +152,7 @@ func (w *webhooker) OnTest() error {
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Timeout: 20,
|
Timeout: 20,
|
||||||
LastStatusCode: 404,
|
LastStatusCode: 404,
|
||||||
Expected: "test example",
|
Expected: utils.NullString("test example"),
|
||||||
LastResponse: "<html>this is an example response</html>",
|
LastResponse: "<html>this is an example response</html>",
|
||||||
CreatedAt: time.Now().Add(-24 * time.Hour),
|
CreatedAt: time.Now().Add(-24 * time.Hour),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
@charset "UTF-8";
|
|
||||||
/* Index Page */
|
/* Index Page */
|
||||||
/* Status Container */
|
/* Status Container */
|
||||||
/* Button Colors */
|
/* Button Colors */
|
||||||
|
@ -406,186 +405,6 @@ HTML, BODY {
|
||||||
.pointer {
|
.pointer {
|
||||||
cursor: pointer; }
|
cursor: pointer; }
|
||||||
|
|
||||||
/*!
|
|
||||||
* Pikaday
|
|
||||||
* Copyright © 2014 David Bushell | BSD & MIT license | http://dbushell.com/
|
|
||||||
*/
|
|
||||||
.pika-single {
|
|
||||||
z-index: 9999;
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
color: #333;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-bottom-color: #bbb;
|
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
|
|
||||||
.pika-single.is-hidden {
|
|
||||||
display: none; }
|
|
||||||
.pika-single.is-bound {
|
|
||||||
position: absolute;
|
|
||||||
box-shadow: 0 5px 15px -5px rgba(0, 0, 0, 0.5); }
|
|
||||||
|
|
||||||
.pika-single {
|
|
||||||
*zoom: 1; }
|
|
||||||
.pika-single:before, .pika-single:after {
|
|
||||||
content: " ";
|
|
||||||
display: table; }
|
|
||||||
.pika-single:after {
|
|
||||||
clear: both; }
|
|
||||||
|
|
||||||
.pika-lendar {
|
|
||||||
float: left;
|
|
||||||
width: 240px;
|
|
||||||
margin: 8px; }
|
|
||||||
|
|
||||||
.pika-title {
|
|
||||||
position: relative;
|
|
||||||
text-align: center; }
|
|
||||||
.pika-title select {
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 9998;
|
|
||||||
margin: 0;
|
|
||||||
left: 0;
|
|
||||||
top: 5px;
|
|
||||||
filter: alpha(opacity=0);
|
|
||||||
opacity: 0; }
|
|
||||||
|
|
||||||
.pika-label {
|
|
||||||
display: inline-block;
|
|
||||||
*display: inline;
|
|
||||||
position: relative;
|
|
||||||
z-index: 9999;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 0;
|
|
||||||
padding: 5px 3px;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
background-color: #fff; }
|
|
||||||
|
|
||||||
.pika-prev,
|
|
||||||
.pika-next {
|
|
||||||
display: block;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
outline: none;
|
|
||||||
border: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 20px;
|
|
||||||
height: 30px;
|
|
||||||
text-indent: 20px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: transparent;
|
|
||||||
background-position: center center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 75% 75%;
|
|
||||||
opacity: .5;
|
|
||||||
*position: absolute;
|
|
||||||
*top: 0; }
|
|
||||||
.pika-prev:hover,
|
|
||||||
.pika-next:hover {
|
|
||||||
opacity: 1; }
|
|
||||||
.pika-prev.is-disabled,
|
|
||||||
.pika-next.is-disabled {
|
|
||||||
cursor: default;
|
|
||||||
opacity: .2; }
|
|
||||||
|
|
||||||
.pika-prev,
|
|
||||||
.is-rtl .pika-next {
|
|
||||||
float: left;
|
|
||||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==");
|
|
||||||
*left: 0; }
|
|
||||||
|
|
||||||
.pika-next,
|
|
||||||
.is-rtl .pika-prev {
|
|
||||||
float: right;
|
|
||||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=");
|
|
||||||
*right: 0; }
|
|
||||||
|
|
||||||
.pika-select {
|
|
||||||
display: inline-block;
|
|
||||||
*display: inline; }
|
|
||||||
|
|
||||||
.pika-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0;
|
|
||||||
border: 0; }
|
|
||||||
.pika-table th,
|
|
||||||
.pika-table td {
|
|
||||||
width: 14.285714285714286%;
|
|
||||||
padding: 0; }
|
|
||||||
.pika-table th {
|
|
||||||
color: #999;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 25px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center; }
|
|
||||||
.pika-table abbr {
|
|
||||||
border-bottom: none;
|
|
||||||
cursor: help; }
|
|
||||||
|
|
||||||
.pika-button {
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
outline: none;
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 15px;
|
|
||||||
text-align: right;
|
|
||||||
background: #f5f5f5; }
|
|
||||||
.is-today .pika-button {
|
|
||||||
color: #33aaff;
|
|
||||||
font-weight: bold; }
|
|
||||||
.is-selected .pika-button {
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
background: #33aaff;
|
|
||||||
box-shadow: inset 0 1px 3px #178fe5;
|
|
||||||
border-radius: 3px; }
|
|
||||||
.is-disabled .pika-button, .is-outside-current-month .pika-button {
|
|
||||||
color: #999;
|
|
||||||
opacity: .3; }
|
|
||||||
.is-disabled .pika-button {
|
|
||||||
pointer-events: none;
|
|
||||||
cursor: default; }
|
|
||||||
.pika-button:hover {
|
|
||||||
color: #fff;
|
|
||||||
background: #ff8000;
|
|
||||||
box-shadow: none;
|
|
||||||
border-radius: 3px; }
|
|
||||||
.pika-button .is-selection-disabled {
|
|
||||||
pointer-events: none;
|
|
||||||
cursor: default; }
|
|
||||||
|
|
||||||
.pika-week {
|
|
||||||
font-size: 11px;
|
|
||||||
color: #999; }
|
|
||||||
|
|
||||||
.is-inrange .pika-button {
|
|
||||||
background: #D5E9F7; }
|
|
||||||
|
|
||||||
.is-startrange .pika-button {
|
|
||||||
color: #fff;
|
|
||||||
background: #6CB31D;
|
|
||||||
box-shadow: none;
|
|
||||||
border-radius: 3px; }
|
|
||||||
|
|
||||||
.is-endrange .pika-button {
|
|
||||||
color: #fff;
|
|
||||||
background: #33aaff;
|
|
||||||
box-shadow: none;
|
|
||||||
border-radius: 3px; }
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
HTML, BODY {
|
HTML, BODY {
|
||||||
background-color: #fcfcfc; }
|
background-color: #fcfcfc; }
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1257
source/js/pikaday.js
1257
source/js/pikaday.js
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,147 @@
|
||||||
|
/* flatpickr v4.5.2, @license MIT */
|
||||||
|
(function (global, factory) {
|
||||||
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||||
|
typeof define === 'function' && define.amd ? define(factory) :
|
||||||
|
(global.rangePlugin = factory());
|
||||||
|
}(this, (function () { 'use strict';
|
||||||
|
|
||||||
|
function rangePlugin(config) {
|
||||||
|
if (config === void 0) {
|
||||||
|
config = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return function (fp) {
|
||||||
|
var dateFormat = "",
|
||||||
|
secondInput,
|
||||||
|
_secondInputFocused,
|
||||||
|
_prevDates;
|
||||||
|
|
||||||
|
var createSecondInput = function createSecondInput() {
|
||||||
|
if (config.input) {
|
||||||
|
secondInput = config.input instanceof Element ? config.input : window.document.querySelector(config.input);
|
||||||
|
} else {
|
||||||
|
secondInput = fp._input.cloneNode();
|
||||||
|
secondInput.removeAttribute("id");
|
||||||
|
secondInput._flatpickr = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secondInput.value) {
|
||||||
|
var parsedDate = fp.parseDate(secondInput.value);
|
||||||
|
if (parsedDate) fp.selectedDates.push(parsedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
secondInput.setAttribute("data-fp-omit", "");
|
||||||
|
|
||||||
|
fp._bind(secondInput, ["focus", "click"], function () {
|
||||||
|
if (fp.selectedDates[1]) {
|
||||||
|
fp.latestSelectedDateObj = fp.selectedDates[1];
|
||||||
|
|
||||||
|
fp._setHoursFromDate(fp.selectedDates[1]);
|
||||||
|
|
||||||
|
fp.jumpToDate(fp.selectedDates[1]);
|
||||||
|
}
|
||||||
|
_secondInputFocused = true;
|
||||||
|
fp.isOpen = false;
|
||||||
|
fp.open(undefined, secondInput);
|
||||||
|
});
|
||||||
|
|
||||||
|
fp._bind(fp._input, ["focus", "click"], function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
fp.isOpen = false;
|
||||||
|
fp.open();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fp.config.allowInput) fp._bind(secondInput, "keydown", function (e) {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
fp.setDate([fp.selectedDates[0], secondInput.value], true, dateFormat);
|
||||||
|
secondInput.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!config.input) fp._input.parentNode && fp._input.parentNode.insertBefore(secondInput, fp._input.nextSibling);
|
||||||
|
};
|
||||||
|
|
||||||
|
var plugin = {
|
||||||
|
onParseConfig: function onParseConfig() {
|
||||||
|
fp.config.mode = "range";
|
||||||
|
dateFormat = fp.config.altInput ? fp.config.altFormat : fp.config.dateFormat;
|
||||||
|
},
|
||||||
|
onReady: function onReady() {
|
||||||
|
createSecondInput();
|
||||||
|
fp.config.ignoredFocusElements.push(secondInput);
|
||||||
|
|
||||||
|
if (fp.config.allowInput) {
|
||||||
|
fp._input.removeAttribute("readonly");
|
||||||
|
|
||||||
|
secondInput.removeAttribute("readonly");
|
||||||
|
} else {
|
||||||
|
secondInput.setAttribute("readonly", "readonly");
|
||||||
|
}
|
||||||
|
|
||||||
|
fp._bind(fp._input, "focus", function () {
|
||||||
|
fp.latestSelectedDateObj = fp.selectedDates[0];
|
||||||
|
|
||||||
|
fp._setHoursFromDate(fp.selectedDates[0]);
|
||||||
|
_secondInputFocused = false;
|
||||||
|
fp.jumpToDate(fp.selectedDates[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fp.config.allowInput) fp._bind(fp._input, "keydown", function (e) {
|
||||||
|
if (e.key === "Enter") fp.setDate([fp._input.value, fp.selectedDates[1]], true, dateFormat);
|
||||||
|
});
|
||||||
|
fp.setDate(fp.selectedDates, false);
|
||||||
|
plugin.onValueUpdate(fp.selectedDates);
|
||||||
|
},
|
||||||
|
onPreCalendarPosition: function onPreCalendarPosition() {
|
||||||
|
if (_secondInputFocused) {
|
||||||
|
fp._positionElement = secondInput;
|
||||||
|
setTimeout(function () {
|
||||||
|
fp._positionElement = fp._input;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChange: function onChange() {
|
||||||
|
if (!fp.selectedDates.length) {
|
||||||
|
setTimeout(function () {
|
||||||
|
if (fp.selectedDates.length) return;
|
||||||
|
secondInput.value = "";
|
||||||
|
_prevDates = [];
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_secondInputFocused) {
|
||||||
|
setTimeout(function () {
|
||||||
|
secondInput.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDestroy: function onDestroy() {
|
||||||
|
if (!config.input) secondInput.parentNode && secondInput.parentNode.removeChild(secondInput);
|
||||||
|
},
|
||||||
|
onValueUpdate: function onValueUpdate(selDates) {
|
||||||
|
if (!secondInput) return;
|
||||||
|
_prevDates = !_prevDates || selDates.length >= _prevDates.length ? selDates.concat() : _prevDates;
|
||||||
|
|
||||||
|
if (_prevDates.length > selDates.length) {
|
||||||
|
var newSelectedDate = selDates[0];
|
||||||
|
var newDates = _secondInputFocused ? [_prevDates[0], newSelectedDate] : [newSelectedDate, _prevDates[1]];
|
||||||
|
fp.setDate(newDates, false);
|
||||||
|
_prevDates = newDates.concat();
|
||||||
|
}
|
||||||
|
|
||||||
|
var _fp$selectedDates$map = fp.selectedDates.map(function (d) {
|
||||||
|
return fp.formatDate(d, dateFormat);
|
||||||
|
});
|
||||||
|
|
||||||
|
var _fp$selectedDates$map2 = _fp$selectedDates$map[0];
|
||||||
|
fp._input.value = _fp$selectedDates$map2 === void 0 ? "" : _fp$selectedDates$map2;
|
||||||
|
var _fp$selectedDates$map3 = _fp$selectedDates$map[1];
|
||||||
|
secondInput.value = _fp$selectedDates$map3 === void 0 ? "" : _fp$selectedDates$map3;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return rangePlugin;
|
||||||
|
|
||||||
|
})));
|
|
@ -468,6 +468,4 @@ HTML,BODY {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@import './pikaday';
|
|
||||||
|
|
||||||
@import './mobile';
|
@import './mobile';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{ define "footer"}}
|
{{ define "footer"}}
|
||||||
<div class="footer text-center mb-4">
|
<div class="footer text-center mb-4">
|
||||||
{{ if CoreApp.Footer}}
|
{{ if CoreApp.Footer.String }}
|
||||||
{{ CoreApp.Footer }}
|
{{ CoreApp.Footer.String }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a href="https://github.com/hunterlong/statup" target="_blank">Statup {{VERSION}} made with <i class="text-danger fas fa-heart"></i></a> | <a href="/dashboard">Dashboard</a>
|
<a href="https://github.com/hunterlong/statup" target="_blank">Statup {{VERSION}} made with <i class="text-danger fas fa-heart"></i></a> | <a href="/dashboard">Dashboard</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
{{define "form_message"}}
|
||||||
|
{{$message := .}}
|
||||||
|
<form action="{{if ne .Id 0}}/message/{{.Id}}{{else}}/messages{{end}}" method="POST">
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="username" class="col-sm-4 col-form-label">Title</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input type="text" name="title" class="form-control" value="{{.Title}}" id="title" placeholder="Message Title" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="username" class="col-sm-4 col-form-label">Description</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<textarea rows="5" name="description" class="form-control" id="description" required>{{.Description}}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-4 col-form-label">Message Date Range</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input type="text" name="start_on" class="form-control form-control-plaintext" id="start_on" value="{{.StartOn}}" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input type="text" name="end_on" class="form-control form-control-plaintext" id="end_on" value="{{.EndOn}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="service_id" class="col-sm-4 col-form-label">Service</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<select class="form-control" name="service_id" id="service_id">
|
||||||
|
<option value="0" {{if eq (ToString .ServiceId) "0"}}selected{{end}}>Global Message</option>
|
||||||
|
{{range Services}}
|
||||||
|
{{$s := .Select}}
|
||||||
|
<option value="{{$s.Id}}" {{if eq $message.ServiceId $s.Id}}selected{{end}}>{{$s.Name}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="notify_method" class="col-sm-4 col-form-label">Notification Method</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input type="text" name="notify_method" class="form-control" id="notify_method" value="{{.NotifyUsers.Bool}}" placeholder="email">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input type="text" name="notify_before" class="form-control" id="notify_before" value="{{.NotifyBefore}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block">{{if ne .Id 0}}Update Message{{else}}Create Message{{end}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
|
@ -41,14 +41,14 @@
|
||||||
<div class="form-group row{{if ne .Method "POST"}} d-none{{end}}">
|
<div class="form-group row{{if ne .Method "POST"}} d-none{{end}}">
|
||||||
<label for="post_data" class="col-sm-4 col-form-label">Optional Post Data (JSON)</label>
|
<label for="post_data" class="col-sm-4 col-form-label">Optional Post Data (JSON)</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<textarea name="post_data" class="form-control" id="post_data" rows="3" autocapitalize="false" spellcheck="false" placeholder='{"data": { "method": "success", "id": 148923 } }'>{{.PostData}}</textarea>
|
<textarea name="post_data" class="form-control" id="post_data" rows="3" autocapitalize="false" spellcheck="false" placeholder='{"data": { "method": "success", "id": 148923 } }'>{{.PostData.String}}</textarea>
|
||||||
<small class="form-text text-muted">Insert a JSON string to send data to the endpoint.</small>
|
<small class="form-text text-muted">Insert a JSON string to send data to the endpoint.</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row{{if (eq .Type "tcp") or (eq .Type "udp")}} d-none{{end}}">
|
<div class="form-group row{{if (eq .Type "tcp") or (eq .Type "udp")}} d-none{{end}}">
|
||||||
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
|
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<textarea name="expected" class="form-control" id="service_response" rows="3" autocapitalize="false" spellcheck="false" placeholder='(method)": "((\\"|[success])*)"'>{{.Expected}}</textarea>
|
<textarea name="expected" class="form-control" id="service_response" rows="3" autocapitalize="false" spellcheck="false" placeholder='(method)": "((\\"|[success])*)"'>{{.Expected.String}}</textarea>
|
||||||
<small class="form-text text-muted">You can use plain text or insert <a target="_blank" href="https://regex101.com/r/I5bbj9/1">Regex</a> to validate the response</small>
|
<small class="form-text text-muted">You can use plain text or insert <a target="_blank" href="https://regex101.com/r/I5bbj9/1">Regex</a> to validate the response</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -60,16 +60,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row{{if (ne .Type "tcp") and (ne .Type "udp")}} d-none{{end}}">
|
<div class="form-group row{{if (ne .Type "tcp") and (ne .Type "udp")}} d-none{{end}}">
|
||||||
<label id="service_type_label" for="service_port" class="col-sm-4 col-form-label">TCP Port</label>
|
<label for="port" class="col-sm-4 col-form-label">TCP Port</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<input type="number" name="port" class="form-control" value="{{if ne .Port 0}}{{.Port}}{{end}}" id="service_port" placeholder="8080">
|
<input type="number" name="port" class="form-control" value="{{if ne .Port 0}}{{.Port}}{{end}}" id="service_port" placeholder="8080">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
|
<label for="interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<input type="number" name="interval" class="form-control" value="{{if ne .Interval 0}}{{.Interval}}{{else}}60{{end}}" min="1" id="service_interval" required>
|
<input type="number" name="interval" class="form-control" value="{{if ne .Interval 0}}{{.Interval}}{{else}}60{{end}}" min="1" id="service_interval" required>
|
||||||
<small id="emailHelp" class="form-text text-muted">10,000+ will be checked in Microseconds (1 millisecond = 1000 microseconds).</small>
|
<small id="interval" class="form-text text-muted">10,000+ will be checked in Microseconds (1 millisecond = 1000 microseconds).</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 col-md-4">
|
<div class="col-6 col-md-4">
|
||||||
<span class="switch">
|
<span class="switch">
|
||||||
<input type="checkbox" name="admin" class="switch" id="switch-normal"{{if .Admin}} checked{{end}}>
|
<input type="checkbox" name="admin" class="switch" id="switch-normal"{{if .Admin.Bool}} checked{{end}}>
|
||||||
<label for="switch-normal">Administrator</label>
|
<label for="switch-normal">Administrator</label>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
{{define "title"}}Statup | {{.Title}}{{end}}
|
||||||
|
{{define "content"}}
|
||||||
|
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||||
|
{{template "nav"}}
|
||||||
|
<div class="col-12">
|
||||||
|
<h3>Message {{.Title}}</h3>
|
||||||
|
{{template "form_message" .}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{define "extra_css"}}
|
||||||
|
<link rel="stylesheet" href="/css/flatpickr.min.css">
|
||||||
|
{{end}}
|
||||||
|
{{define "extra_scripts"}}
|
||||||
|
<script src="/js/flatpickr.js"></script>
|
||||||
|
<script src="/js/rangePlugin.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$("#start_on").flatpickr({
|
||||||
|
enableTime: true,
|
||||||
|
dateFormat: "Y-m-d H:i",
|
||||||
|
minDate: "today",
|
||||||
|
"plugins": [new rangePlugin({ input: "#end_on"})]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{end}}
|
|
@ -0,0 +1,52 @@
|
||||||
|
{{define "title"}}Statup Messages{{end}}
|
||||||
|
{{define "content"}}
|
||||||
|
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||||
|
{{template "nav"}}
|
||||||
|
<div class="col-12">
|
||||||
|
<h3>Messages</h3>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Title</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.Title}}</td>
|
||||||
|
<td class="text-right" id="message_{{.Id}}">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Create Message</h3>
|
||||||
|
|
||||||
|
{{template "form_message" NewMessage}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{define "extra_css"}}
|
||||||
|
<link rel="stylesheet" href="/css/flatpickr.min.css">
|
||||||
|
{{end}}
|
||||||
|
{{define "extra_scripts"}}
|
||||||
|
<script src="/js/flatpickr.js"></script>
|
||||||
|
<script src="/js/rangePlugin.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$("#start_on").flatpickr({
|
||||||
|
enableTime: true,
|
||||||
|
dateFormat: "Y-m-d H:i",
|
||||||
|
minDate: "today",
|
||||||
|
"plugins": [new rangePlugin({ input: "#end_on"})]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{end}}
|
|
@ -16,6 +16,9 @@
|
||||||
<li class="nav-item{{ if eq URL "/users" }} active{{ end }}">
|
<li class="nav-item{{ if eq URL "/users" }} active{{ end }}">
|
||||||
<a class="nav-link" href="/users">Users</a>
|
<a class="nav-link" href="/users">Users</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item{{ if eq URL "/messages" }} active{{ end }}">
|
||||||
|
<a class="nav-link" href="/messages">Messages</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item{{ if eq URL "/settings" }} active{{ end }}">
|
<li class="nav-item{{ if eq URL "/settings" }} active{{ end }}">
|
||||||
<a class="nav-link" href="/settings">Settings</a>
|
<a class="nav-link" href="/settings">Settings</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -46,11 +46,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="service_date_form" class="col-12 mt-2 mb-3">
|
<form id="service_date_form" class="col-12 mt-2 mb-3">
|
||||||
<span id="start_date" class="text-muted small float-left pointer">{{FromUnix .Start}}</span>
|
<input type="text" class="d-none" name="start" id="service_start" data-input>
|
||||||
<span id="end_date" class="text-muted small float-right pointer" style="position: absolute;right: 0;">{{FromUnix .End}}</span>
|
<span data-toggle title="toggle" id="start_date" class="text-muted small float-left pointer mt-2">{{.Start}} to {{.End}}</span>
|
||||||
<input type="hidden" name="start" class="form-control" id="service_start" spellcheck="false">
|
<button type="submit" class="btn btn-light btn-sm mt-2">Set Timeframe</button>
|
||||||
<input type="hidden" name="end" class="form-control" id="service_end" spellcheck="false">
|
<input type="text" class="d-none" name="end" id="service_end" data-input>
|
||||||
<button type="submit" class="btn btn-light btn-block btn-sm mt-2">Set Timeframe</button>
|
|
||||||
<div id="start_container"></div>
|
<div id="start_container"></div>
|
||||||
<div id="end_container"></div>
|
<div id="end_container"></div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -59,6 +59,15 @@
|
||||||
<div class="col-12 small text-center mt-3 text-muted">{{$s.DowntimeText}}</div>
|
<div class="col-12 small text-center mt-3 text-muted">{{$s.DowntimeText}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{if $s.Messages}}
|
||||||
|
{{range $s.Messages}}
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
<h3>{{.Title}}</h3>
|
||||||
|
<span>{{safe .Description}}</span>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{ if $s.LimitedFailures }}
|
{{ if $s.LimitedFailures }}
|
||||||
<div class="list-group mt-3 mb-4">
|
<div class="list-group mt-3 mb-4">
|
||||||
{{ range $s.LimitedFailures }}
|
{{ range $s.LimitedFailures }}
|
||||||
|
@ -137,14 +146,15 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{define "extra_scripts"}}
|
{{define "extra_css"}}
|
||||||
{{if USE_CDN}}
|
<link rel="stylesheet" href="/css/flatpickr.min.css">
|
||||||
<script src="https://assets.statup.io/pikaday.js"></script>
|
|
||||||
{{ else }}
|
|
||||||
<script src="/js/pikaday.js"></script>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{define "extra_scripts"}}
|
||||||
{{$s := .Service}}
|
{{$s := .Service}}
|
||||||
|
<script src="/js/flatpickr.js"></script>
|
||||||
|
<script src="/js/rangePlugin.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
var ctx = document.getElementById("service").getContext('2d');
|
var ctx = document.getElementById("service").getContext('2d');
|
||||||
|
|
||||||
|
@ -208,45 +218,29 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var startPick = new Pikaday({
|
|
||||||
field: $('#service_start')[0],
|
|
||||||
bound: false,
|
|
||||||
trigger: $("#start_date"),
|
|
||||||
container: $("#start_container")[0],
|
|
||||||
maxDate: new Date(),
|
|
||||||
onSelect: function(date) {
|
|
||||||
$('#service_start')[0].value = Math.round(date.getTime() / 1000);
|
|
||||||
this.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var endPick = new Pikaday({
|
|
||||||
field: $('#service_end')[0],
|
|
||||||
bound: false,
|
|
||||||
trigger: $("#end_date"),
|
|
||||||
container: $("#end_container")[0],
|
|
||||||
maxDate: new Date(),
|
|
||||||
onSelect: function(date) {
|
|
||||||
$('#service_end')[0].value = Math.round(date.getTime() / 1000);
|
|
||||||
this.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
startPick.setDate(new Date({{.Start}}* 1000));
|
|
||||||
endPick.setDate(new Date({{.End}}* 1000));
|
|
||||||
startPick.hide();
|
|
||||||
endPick.hide();
|
|
||||||
|
|
||||||
$("#start_date").click(function(e) {
|
|
||||||
startPick.show()
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#end_date").click(function(e) {
|
|
||||||
endPick.show()
|
|
||||||
});
|
|
||||||
|
|
||||||
AjaxChart(chartdata,{{$s.Id}},{{.Start}},{{.End}},"hour");
|
AjaxChart(chartdata,{{$s.Id}},{{.Start}},{{.End}},"hour");
|
||||||
|
|
||||||
|
let startDate = $("#service_start").flatpickr({
|
||||||
|
enableTime: false,
|
||||||
|
static: true,
|
||||||
|
altInput: true,
|
||||||
|
altFormat: "U",
|
||||||
|
maxDate: "today",
|
||||||
|
dateFormat: "F j, Y",
|
||||||
|
onChange: function(selectedDates, dateStr, instance) {
|
||||||
|
var one = Math.round((new Date(selectedDates[0])).getTime() / 1000);
|
||||||
|
var two = Math.round((new Date(selectedDates[1])).getTime() / 1000);
|
||||||
|
$("#service_start").val(one);
|
||||||
|
$("#service_end").val(two);
|
||||||
|
$("#start_date").html(dateStr);
|
||||||
|
},
|
||||||
|
"plugins": [new rangePlugin({ input: "#service_end"})]
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#start_date").click(function(e) {
|
||||||
|
startDate.open()
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="footer">Custom Footer</label>
|
<label for="footer">Custom Footer</label>
|
||||||
<textarea rows="4" name="footer" class="form-control" id="footer">{{ .Footer }}</textarea>
|
<textarea rows="4" name="footer" class="form-control" id="footer">{{ .Footer.String }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,11 +33,11 @@ type Core struct {
|
||||||
ApiKey string `gorm:"column:api_key" json:"-"`
|
ApiKey string `gorm:"column:api_key" json:"-"`
|
||||||
ApiSecret string `gorm:"column:api_secret" json:"-"`
|
ApiSecret string `gorm:"column:api_secret" json:"-"`
|
||||||
Style string `gorm:"not null;column:style" json:"style,omitempty"`
|
Style string `gorm:"not null;column:style" json:"style,omitempty"`
|
||||||
Footer string `gorm:"not null;column:footer" json:"footer,omitempty"`
|
Footer sql.NullString `gorm:"not null;column:footer" json:"footer"`
|
||||||
Domain string `gorm:"not null;column:domain" json:"domain,omitempty"`
|
Domain string `gorm:"not null;column:domain" json:"domain"`
|
||||||
Version string `gorm:"column:version" json:"version"`
|
Version string `gorm:"column:version" json:"version"`
|
||||||
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
|
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
|
||||||
UseCdn bool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
|
UseCdn sql.NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
|
||||||
Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"`
|
Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Statup
|
||||||
|
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||||
|
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||||
|
//
|
||||||
|
// https://github.com/hunterlong/statup
|
||||||
|
//
|
||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message is for creating Announcements, Alerts and other messages for the end users
|
||||||
|
type Message struct {
|
||||||
|
Id int64 `gorm:"primary_key;column:id"`
|
||||||
|
Title string `gorm:"column:title"`
|
||||||
|
Description string `gorm:"column:description"`
|
||||||
|
StartOn time.Time `gorm:"column:start_on"`
|
||||||
|
EndOn time.Time `gorm:"column:end_on"`
|
||||||
|
ServiceId int64 `gorm:"index;column:service"`
|
||||||
|
NotifyUsers sql.NullBool `gorm:"column:notify_users"`
|
||||||
|
NotifyMethod string `gorm:"column:notify_method"`
|
||||||
|
NotifyBefore time.Duration `gorm:"column:notify_before"`
|
||||||
|
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,15 +25,16 @@ type Service struct {
|
||||||
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
||||||
Name string `gorm:"column:name" json:"name"`
|
Name string `gorm:"column:name" json:"name"`
|
||||||
Domain string `gorm:"column:domain" json:"domain"`
|
Domain string `gorm:"column:domain" json:"domain"`
|
||||||
Expected string `gorm:"not null;column:expected" json:"expected"`
|
Expected sql.NullString `gorm:"not null;column:expected" json:"expected"`
|
||||||
ExpectedStatus int `gorm:"default:200;column:expected_status" json:"expected_status"`
|
ExpectedStatus int `gorm:"default:200;column:expected_status" json:"expected_status"`
|
||||||
Interval int `gorm:"default:30;column:check_interval" json:"check_interval"`
|
Interval int `gorm:"default:30;column:check_interval" json:"check_interval"`
|
||||||
Type string `gorm:"column:check_type" json:"type"`
|
Type string `gorm:"column:check_type" json:"type"`
|
||||||
Method string `gorm:"column:method" json:"method"`
|
Method string `gorm:"column:method" json:"method"`
|
||||||
PostData string `gorm:"not null;column:post_data" json:"post_data"`
|
PostData sql.NullString `gorm:"not null;column:post_data" json:"post_data"`
|
||||||
Port int `gorm:"not null;column:port" json:"port"`
|
Port int `gorm:"not null;column:port" json:"port"`
|
||||||
Timeout int `gorm:"default:30;column:timeout" json:"timeout"`
|
Timeout int `gorm:"default:30;column:timeout" json:"timeout"`
|
||||||
Order int `gorm:"default:0;column:order_id" json:"order_id"`
|
Order int `gorm:"default:0;column:order_id" json:"order_id"`
|
||||||
|
AllowNotifications sql.NullBool `gorm:"default:false;column:allow_notifications" json:"allow_notifications"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||||
Online bool `gorm:"-" json:"online"`
|
Online bool `gorm:"-" json:"online"`
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ type User struct {
|
||||||
Email string `gorm:"type:varchar(100);unique;column:email" json:"-"`
|
Email string `gorm:"type:varchar(100);unique;column:email" json:"-"`
|
||||||
ApiKey string `gorm:"column:api_key" json:"api_key"`
|
ApiKey string `gorm:"column:api_key" json:"api_key"`
|
||||||
ApiSecret string `gorm:"column:api_secret" json:"-"`
|
ApiSecret string `gorm:"column:api_secret" json:"-"`
|
||||||
Admin bool `gorm:"column:administrator" json:"admin"`
|
Admin sql.NullBool `gorm:"column:administrator" json:"admin"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||||
UserInterface `gorm:"-" json:"-"`
|
UserInterface `gorm:"-" json:"-"`
|
||||||
|
|
|
@ -20,6 +20,12 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FlatpickrTime = "2006-01-02 15:04"
|
||||||
|
FlatpickrDay = "2006-01-02"
|
||||||
|
FlatpickrReadable = "Mon, 02 Jan 2006"
|
||||||
|
)
|
||||||
|
|
||||||
// FormatDuration converts a time.Duration into a string
|
// FormatDuration converts a time.Duration into a string
|
||||||
func FormatDuration(d time.Duration) string {
|
func FormatDuration(d time.Duration) string {
|
||||||
var out string
|
var out string
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/ararog/timeago"
|
"github.com/ararog/timeago"
|
||||||
|
@ -43,6 +44,20 @@ func init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NullString(s string) sql.NullString {
|
||||||
|
return sql.NullString{s, true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NullBool(s bool) sql.NullBool {
|
||||||
|
return sql.NullBool{s, true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringPoint(s string) *string {
|
||||||
|
val := new(string)
|
||||||
|
*val = s
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
// StringInt converts a string to an int64
|
// StringInt converts a string to an int64
|
||||||
func StringInt(s string) int64 {
|
func StringInt(s string) int64 {
|
||||||
num, _ := strconv.Atoi(s)
|
num, _ := strconv.Atoi(s)
|
||||||
|
|
Loading…
Reference in New Issue