pull/99/head
Hunter Long 2018-11-25 04:56:09 +01:00
parent eac1b0568c
commit 439ffc293e
22 changed files with 226 additions and 230 deletions

View File

@ -27,7 +27,6 @@ import (
"github.com/hunterlong/statup/utils" "github.com/hunterlong/statup/utils"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"io/ioutil" "io/ioutil"
"net/http"
"net/http/httptest" "net/http/httptest"
"time" "time"
) )
@ -35,7 +34,9 @@ import (
// catchCLI will run functions based on the commands sent to Statup // catchCLI will run functions based on the commands sent to Statup
func catchCLI(args []string) error { func catchCLI(args []string) error {
dir := utils.Directory dir := utils.Directory
utils.InitLogs() if err := utils.InitLogs(); err != nil {
return err
}
source.Assets() source.Assets()
loadDotEnvs() loadDotEnvs()
@ -50,22 +51,21 @@ func catchCLI(args []string) error {
} }
return errors.New("end") return errors.New("end")
case "assets": case "assets":
err := source.CreateAllAssets(dir) var err error
if err != nil { if err = source.CreateAllAssets(dir); err != nil {
return err return err
} else {
return errors.New("end")
} }
return errors.New("end")
case "sass": case "sass":
err := source.CompileSASS(dir) if err := source.CompileSASS(dir); err != nil {
if err == nil {
return errors.New("end")
}
return err return err
}
return errors.New("end")
case "update": case "update":
gitCurrent, err := checkGithubUpdates() var err error
if err != nil { var gitCurrent githubResponse
return nil if gitCurrent, err = checkGithubUpdates(); err != nil {
return err
} }
fmt.Printf("Statup Version: v%v\nLatest Version: %v\n", VERSION, gitCurrent.TagName) fmt.Printf("Statup Version: v%v\nLatest Version: %v\n", VERSION, gitCurrent.TagName)
if VERSION != gitCurrent.TagName[1:] { if VERSION != gitCurrent.TagName[1:] {
@ -73,10 +73,7 @@ func catchCLI(args []string) error {
} else { } else {
fmt.Printf("You have the latest version of Statup!\n") fmt.Printf("You have the latest version of Statup!\n")
} }
if err == nil {
return errors.New("end") return errors.New("end")
}
return nil
case "test": case "test":
cmd := args[1] cmd := args[1]
switch cmd { switch cmd {
@ -84,19 +81,17 @@ func catchCLI(args []string) error {
plugin.LoadPlugins() plugin.LoadPlugins()
} }
return errors.New("end") return errors.New("end")
case "export": case "static":
var err error var err error
fmt.Printf("Statup v%v Exporting Static 'index.html' page...\n", VERSION) fmt.Printf("Statup v%v Exporting Static 'index.html' page...\n", VERSION)
utils.InitLogs() utils.InitLogs()
core.Configs, err = core.LoadConfigFile(dir) if core.Configs, err = core.LoadConfigFile(dir); err != nil {
if err != nil {
utils.Log(4, "config.yml file not found") utils.Log(4, "config.yml file not found")
return err return err
} }
indexSource := ExportIndexHTML() indexSource := ExportIndexHTML()
core.CloseDB() core.CloseDB()
err = utils.SaveFile(dir+"/index.html", indexSource) if err = utils.SaveFile(dir+"/index.html", indexSource); err != nil {
if err != nil {
utils.Log(4, err) utils.Log(4, err)
return err return err
} }
@ -104,6 +99,40 @@ func catchCLI(args []string) error {
case "help": case "help":
HelpEcho() HelpEcho()
return errors.New("end") return errors.New("end")
case "export":
var err error
var data []byte
if err := utils.InitLogs(); err != nil {
return err
}
if core.Configs, err = core.LoadConfigFile(dir); err != nil {
return err
}
if err = core.Configs.Connect(false, dir); err != nil {
return err
}
if data, err = core.ExportSettings(); err != nil {
return fmt.Errorf("could not export settings: %v", err.Error())
}
if err = utils.SaveFile(dir+"/statup-export.json", data); err != nil {
return fmt.Errorf("could not write file statup-export.json: %v", err.Error())
}
return errors.New("end")
case "import":
var err error
var data []byte
if len(args) != 2 {
return fmt.Errorf("did not include a JSON file to import\nstatup import filename.json")
}
filename := args[1]
if data, err = ioutil.ReadFile(filename); err != nil {
return err
}
var exportData core.ExportData
if err = json.Unmarshal(data, &exportData); err != nil {
return err
}
return errors.New("end")
case "run": case "run":
utils.Log(1, "Running 1 time and saving to database...") utils.Log(1, "Running 1 time and saving to database...")
RunOnce() RunOnce()
@ -175,11 +204,13 @@ func HelpEcho() {
fmt.Println(" statup version - Returns the current version of Statup") fmt.Println(" statup version - Returns the current version of Statup")
fmt.Println(" statup run - Check all services 1 time and then quit") fmt.Println(" statup run - Check all services 1 time and then quit")
fmt.Println(" statup assets - Dump all assets used locally to be edited.") fmt.Println(" statup assets - Dump all assets used locally to be edited.")
fmt.Println(" statup export - Exports the index page as a static HTML for pushing") fmt.Println(" statup static - Creates a static HTML file of the index page")
fmt.Println(" statup sass - Compile .scss files into the css directory") fmt.Println(" statup sass - Compile .scss files into the css directory")
fmt.Println(" statup test plugins - Test all plugins for required information") fmt.Println(" statup test plugins - Test all plugins for required information")
fmt.Println(" statup env - Show all environment variables being used for Statup") fmt.Println(" statup env - Show all environment variables being used for Statup")
fmt.Println(" statup update - Attempts to update to the latest version") fmt.Println(" statup update - Attempts to update to the latest version")
fmt.Println(" statup export - Exports your Statup settings to a 'statup-export.json' file.")
fmt.Println(" statup import <file> - Imports settings from a previously saved JSON file.")
fmt.Println(" statup help - Shows the user basic information about Statup") fmt.Println(" statup help - Shows the user basic information about Statup")
fmt.Printf("Flags:\n") fmt.Printf("Flags:\n")
fmt.Println(" -ip 127.0.0.1 - Run HTTP server on specific IP address (default: localhost)") fmt.Println(" -ip 127.0.0.1 - Run HTTP server on specific IP address (default: localhost)")
@ -190,7 +221,7 @@ func HelpEcho() {
fmt.Println(" DB_HOST - Database hostname or IP address") fmt.Println(" DB_HOST - Database hostname or IP address")
fmt.Println(" DB_USER - Database username") fmt.Println(" DB_USER - Database username")
fmt.Println(" DB_PASS - Database password") fmt.Println(" DB_PASS - Database password")
fmt.Println(" DB_PORT - Database port (5432, 3306, ...") fmt.Println(" DB_PORT - Database port (5432, 3306, ...)")
fmt.Println(" DB_DATABASE - Database connection's database name") fmt.Println(" DB_DATABASE - Database connection's database name")
fmt.Println(" GO_ENV - Run Statup in testmode, will bypass HTTP authentication (if set as 'true')") fmt.Println(" GO_ENV - Run Statup in testmode, will bypass HTTP authentication (if set as 'true')")
fmt.Println(" NAME - Set a name for the Statup status page") fmt.Println(" NAME - Set a name for the Statup status page")
@ -198,19 +229,15 @@ func HelpEcho() {
fmt.Println(" DOMAIN - Set a URL for the Statup status page") fmt.Println(" DOMAIN - Set a URL for the Statup status page")
fmt.Println(" ADMIN_USER - Username for administrator account (default: admin)") fmt.Println(" ADMIN_USER - Username for administrator account (default: admin)")
fmt.Println(" ADMIN_PASS - Password for administrator account (default: admin)") fmt.Println(" ADMIN_PASS - Password for administrator account (default: admin)")
fmt.Println(" SASS - Set the absolute path to the sass binary location")
fmt.Println(" * You can insert environment variables into a '.env' file in root directory.") fmt.Println(" * You can insert environment variables into a '.env' file in root directory.")
fmt.Println("Give Statup a Star at https://github.com/hunterlong/statup") fmt.Println("Give Statup a Star at https://github.com/hunterlong/statup")
} }
func checkGithubUpdates() (githubResponse, error) { func checkGithubUpdates() (githubResponse, error) {
var gitResp githubResponse var gitResp githubResponse
response, err := http.Get("https://api.github.com/repos/hunterlong/statup/releases/latest") url := "https://api.github.com/repos/hunterlong/statup/releases/latest"
if err != nil { contents, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(10*time.Second))
return githubResponse{}, err
}
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
if err != nil { if err != nil {
return githubResponse{}, err return githubResponse{}, err
} }

View File

@ -61,9 +61,9 @@ func TestHelpCommand(t *testing.T) {
} }
func TestExportCommand(t *testing.T) { func TestExportCommand(t *testing.T) {
cmd := helperCommand(nil, "export") cmd := helperCommand(nil, "static")
var got = make(chan string) var got = make(chan string)
commandAndSleep(cmd, time.Duration(4*time.Second), got) commandAndSleep(cmd, time.Duration(10*time.Second), got)
gg, _ := <-got gg, _ := <-got
t.Log(gg) t.Log(gg)
assert.Contains(t, gg, "Exporting Static 'index.html' page...") assert.Contains(t, gg, "Exporting Static 'index.html' page...")
@ -72,10 +72,12 @@ func TestExportCommand(t *testing.T) {
} }
func TestUpdateCommand(t *testing.T) { func TestUpdateCommand(t *testing.T) {
c := testcli.Command("statup", "update") cmd := helperCommand(nil, "version")
c.Run() var got = make(chan string)
assert.True(t, c.StdoutContains("Statup Version: "+VERSION)) commandAndSleep(cmd, time.Duration(10*time.Second), got)
assert.True(t, c.StdoutContains("Latest Version:")) gg, _ := <-got
t.Log(gg)
assert.Contains(t, gg, "Statup")
} }
func TestAssetsCommand(t *testing.T) { func TestAssetsCommand(t *testing.T) {

View File

@ -74,13 +74,11 @@ func main() {
} }
} }
utils.Log(1, fmt.Sprintf("Starting Statup v%v", VERSION)) utils.Log(1, fmt.Sprintf("Starting Statup v%v", VERSION))
defer core.CloseDB()
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt) signal.Notify(c, os.Interrupt)
go func() { go func() {
<-c <-c
core.CloseDB()
os.Exit(1) os.Exit(1)
}() }()

View File

@ -68,7 +68,7 @@ func RunInit(db string, t *testing.T) {
func TestRunAll(t *testing.T) { func TestRunAll(t *testing.T) {
//t.Parallel() //t.Parallel()
databases := []string{"sqlite", "postgres", "mysql"} databases := []string{"postgres", "sqlite", "mysql"}
if os.Getenv("ONLY_DB") != "" { if os.Getenv("ONLY_DB") != "" {
databases = []string{os.Getenv("ONLY_DB")} databases = []string{os.Getenv("ONLY_DB")}
} }
@ -89,6 +89,8 @@ func TestRunAll(t *testing.T) {
}) })
t.Run(dbt+" Drop Database", func(t *testing.T) { t.Run(dbt+" Drop Database", func(t *testing.T) {
assert.NotNil(t, core.Configs) assert.NotNil(t, core.Configs)
assert.NotNil(t, core.DbSession)
assert.Nil(t, core.DbSession.DB().Ping())
RunDropDatabase(t) RunDropDatabase(t)
}) })
t.Run(dbt+" Connect to Database Again", func(t *testing.T) { t.Run(dbt+" Connect to Database Again", func(t *testing.T) {
@ -209,14 +211,13 @@ func TestRunAll(t *testing.T) {
RunSettingsHandler(t) RunSettingsHandler(t)
}) })
t.Run(dbt+" Cleanup", func(t *testing.T) { t.Run(dbt+" Cleanup", func(t *testing.T) {
core.Configs.Close() //core.CloseDB()
core.DbSession = nil
if dbt == "mssql" { if dbt == "mssql" {
os.Setenv("DB_DATABASE", "root") os.Setenv("DB_DATABASE", "root")
os.Setenv("DB_PASS", "password123") os.Setenv("DB_PASS", "password123")
os.Setenv("DB_PORT", "1433") os.Setenv("DB_PORT", "1433")
} }
//Clean() Clean()
}) })
//<-done //<-done

View File

@ -69,7 +69,7 @@ func usersDB() *gorm.DB {
// checkinDB returns the Checkin records for a service // checkinDB returns the Checkin records for a service
func checkinDB() *gorm.DB { func checkinDB() *gorm.DB {
return DbSession.Table("checkins").Model(&types.Checkin{}) return DbSession.Model(&types.Checkin{})
} }
// checkinHitsDB returns the Checkin Hits records for a service // checkinHitsDB returns the Checkin Hits records for a service
@ -100,11 +100,6 @@ func CloseDB() {
} }
} }
// Close shutsdown the database connection
func (db *DbConfig) Close() error {
return DbSession.DB().Close()
}
// AfterFind for Core will set the timezone // AfterFind for Core will set the timezone
func (c *Core) AfterFind() (err error) { func (c *Core) AfterFind() (err error) {
c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone) c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)

View File

@ -17,7 +17,9 @@ package core
import ( import (
"bytes" "bytes"
"encoding/json"
"github.com/hunterlong/statup/source" "github.com/hunterlong/statup/source"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils" "github.com/hunterlong/statup/utils"
"html/template" "html/template"
) )
@ -42,3 +44,28 @@ func ExportChartsJs() string {
result := tpl.String() result := tpl.String()
return result return result
} }
type ExportData struct {
Core *types.Core `json:"core"`
Services []types.ServiceInterface `json:"services"`
Messages []*types.Message `json:"messages"`
Checkins []*Checkin `json:"checkins"`
Users []*User `json:"users"`
Notifiers []types.AllNotifiers `json:"notifiers"`
}
func ExportSettings() ([]byte, error) {
users, err := SelectAllUsers()
if err != nil {
return nil, err
}
data := ExportData{
Core: CoreApp.Core,
Notifiers: CoreApp.Notifications,
Checkins: AllCheckins(),
Users: users,
Services: CoreApp.Services,
}
export, err := json.Marshal(data)
return export, err
}

View File

@ -80,7 +80,9 @@ type NotificationForm struct {
DbField string `json:"field"` // true variable key for input DbField string `json:"field"` // true variable key for input
SmallText string `json:"small_text"` // insert small text under a html input SmallText string `json:"small_text"` // insert small text under a html input
Required bool `json:"required"` // require this input on the html form Required bool `json:"required"` // require this input on the html form
Hidden bool `json:"hidden"` // hide this form element from end user IsHidden bool `json:"hidden"` // hide this form element from end user
IsList bool `json:"list"` // make this form element a comma separated list
IsSwitch bool `json:"switch"` // make the notifier a boolean true/false switch
} }
// NotificationLog contains the normalized message from previously sent notifications // NotificationLog contains the normalized message from previously sent notifications

View File

@ -217,7 +217,7 @@ func (s *Service) DowntimeText() string {
// Dbtimestamp will return a SQL query for grouping by date // Dbtimestamp will return a SQL query for grouping by date
func Dbtimestamp(group string, column string) string { func Dbtimestamp(group string, column string) string {
var seconds int64 seconds := 3600
switch group { switch group {
case "minute": case "minute":
seconds = 60 seconds = 60
@ -268,7 +268,10 @@ func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group st
return &DateScanObj{[]DateScan{}} return &DateScanObj{[]DateScan{}}
} }
model = model.Order("timeframe asc", false).Group("timeframe") model = model.Order("timeframe asc", false).Group("timeframe")
rows, _ := model.Rows() rows, err := model.Debug().Rows()
if err != nil {
utils.Log(3, fmt.Errorf("issue fetching service chart data: %v", err))
}
for rows.Next() { for rows.Next() {
var gd DateScan var gd DateScan

View File

@ -18,6 +18,7 @@ package handlers
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"github.com/hunterlong/statup/core" "github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/core/notifier" "github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
@ -62,6 +63,7 @@ func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
} }
func sendErrorJson(err error, w http.ResponseWriter, r *http.Request) { func sendErrorJson(err error, w http.ResponseWriter, r *http.Request) {
utils.Log(2, fmt.Errorf("sending error response for %v: %v", r.URL.String(), err.Error()))
output := apiResponse{ output := apiResponse{
Status: "error", Status: "error",
Error: err.Error(), Error: err.Error(),

View File

@ -17,11 +17,9 @@ package handlers
import ( import (
"bytes" "bytes"
"encoding/json"
"github.com/hunterlong/statup/core" "github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/core/notifier" "github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/source" "github.com/hunterlong/statup/source"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils" "github.com/hunterlong/statup/utils"
"net/http" "net/http"
"strconv" "strconv"
@ -99,15 +97,6 @@ func logsLineHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
type exportData struct {
Core *types.Core `json:"core"`
Services []types.ServiceInterface `json:"services"`
Messages []*types.Message `json:"messages"`
Checkins []*core.Checkin `json:"checkins"`
Users []*core.User `json:"users"`
Notifiers []types.AllNotifiers `json:"notifiers"`
}
func exportHandler(w http.ResponseWriter, r *http.Request) { func exportHandler(w http.ResponseWriter, r *http.Request) {
if !IsAuthenticated(r) { if !IsAuthenticated(r) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
@ -120,17 +109,7 @@ func exportHandler(w http.ResponseWriter, r *http.Request) {
notifiers = append(notifiers, notifier.Select()) notifiers = append(notifiers, notifier.Select())
} }
users, _ := core.SelectAllUsers() export, _ := core.ExportSettings()
data := exportData{
Core: core.CoreApp.Core,
Notifiers: core.CoreApp.Notifications,
Checkins: core.AllCheckins(),
Users: users,
Services: core.CoreApp.Services,
}
export, _ := json.Marshal(data)
mime := http.DetectContentType(export) mime := http.DetectContentType(export)
fileSize := len(string(export)) fileSize := len(string(export))

View File

@ -16,13 +16,9 @@
package notifiers package notifiers
import ( import (
"errors"
"github.com/hunterlong/statup/core/notifier" "github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils" "github.com/hunterlong/statup/utils"
"io"
"os"
"os/exec"
"time" "time"
) )
@ -69,59 +65,10 @@ func init() {
} }
func runCommand(app, cmd string) (string, string, error) { func runCommand(app, cmd string) (string, string, error) {
testCmd := exec.Command(app, "-c", cmd) outStr, errStr, err := utils.Command(cmd)
var stdout, stderr []byte
var errStdout, errStderr error
stdoutIn, _ := testCmd.StdoutPipe()
stderrIn, _ := testCmd.StderrPipe()
testCmd.Start()
go func() {
stdout, errStdout = copyAndCapture(os.Stdout, stdoutIn)
}()
go func() {
stderr, errStderr = copyAndCapture(os.Stderr, stderrIn)
}()
err := testCmd.Wait()
if err != nil {
return "", "", err
}
if errStdout != nil || errStderr != nil {
return "", "", errors.New("failed to capture stdout or stderr")
}
outStr, errStr := string(stdout), string(stderr)
return outStr, errStr, err return outStr, errStr, err
} }
// copyAndCapture captures the response from a terminal command
func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) {
var out []byte
buf := make([]byte, 1024, 1024)
for {
n, err := r.Read(buf[:])
if n > 0 {
d := buf[:n]
out = append(out, d...)
_, err := w.Write(d)
if err != nil {
return out, err
}
}
if err != nil {
// Read returns io.EOF at the end of file, which is not an error for us
if err == io.EOF {
err = nil
}
return out, err
}
}
}
func (u *commandLine) Select() *notifier.Notification { func (u *commandLine) Select() *notifier.Notification {
return u.Notification return u.Notification
} }

View File

@ -22,8 +22,8 @@ import (
"fmt" "fmt"
"github.com/hunterlong/statup/core/notifier" "github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
"io/ioutil" "github.com/hunterlong/statup/utils"
"net/http" "strings"
"time" "time"
) )
@ -59,15 +59,9 @@ func init() {
// Send will send a HTTP Post to the discord API. It accepts type: []byte // Send will send a HTTP Post to the discord API. It accepts type: []byte
func (u *discord) Send(msg interface{}) error { func (u *discord) Send(msg interface{}) error {
message := msg.(string) message := msg.(string)
req, _ := http.NewRequest("POST", discorder.GetValue("host"), bytes.NewBuffer([]byte(message))) _, err := utils.HttpRequest(discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err return err
} }
return resp.Body.Close()
}
func (u *discord) Select() *notifier.Notification { func (u *discord) Select() *notifier.Notification {
return u.Notification return u.Notification
@ -101,15 +95,7 @@ func (u *discord) OnSave() error {
func (u *discord) OnTest() error { func (u *discord) OnTest() error {
outError := errors.New("Incorrect discord URL, please confirm URL is correct") outError := errors.New("Incorrect discord URL, please confirm URL is correct")
message := `{"content": "Testing the discord notifier"}` message := `{"content": "Testing the discord notifier"}`
req, _ := http.NewRequest("POST", discorder.Host, bytes.NewBuffer([]byte(message))) contents, err := utils.HttpRequest(discorder.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(message)), time.Duration(10*time.Second))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
contents, _ := ioutil.ReadAll(resp.Body)
if string(contents) == "" { if string(contents) == "" {
return nil return nil
} }

View File

@ -26,12 +26,12 @@ import (
) )
var ( var (
EMAIL_HOST = os.Getenv("EMAIL_HOST") EMAIL_HOST string
EMAIL_USER = os.Getenv("EMAIL_USER") EMAIL_USER string
EMAIL_PASS = os.Getenv("EMAIL_PASS") EMAIL_PASS string
EMAIL_OUTGOING = os.Getenv("EMAIL_OUTGOING") EMAIL_OUTGOING string
EMAIL_SEND_TO = os.Getenv("EMAIL_SEND_TO") EMAIL_SEND_TO string
EMAIL_PORT = utils.StringInt(os.Getenv("EMAIL_PORT")) EMAIL_PORT int64
) )
var testEmail *emailOutgoing var testEmail *emailOutgoing
@ -42,7 +42,7 @@ func init() {
EMAIL_PASS = os.Getenv("EMAIL_PASS") EMAIL_PASS = os.Getenv("EMAIL_PASS")
EMAIL_OUTGOING = os.Getenv("EMAIL_OUTGOING") EMAIL_OUTGOING = os.Getenv("EMAIL_OUTGOING")
EMAIL_SEND_TO = os.Getenv("EMAIL_SEND_TO") EMAIL_SEND_TO = os.Getenv("EMAIL_SEND_TO")
EMAIL_PORT = utils.StringInt(os.Getenv("EMAIL_PORT")) EMAIL_PORT = utils.ToInt(os.Getenv("EMAIL_PORT"))
emailer.Host = EMAIL_HOST emailer.Host = EMAIL_HOST
emailer.Username = EMAIL_USER emailer.Username = EMAIL_USER

View File

@ -20,9 +20,9 @@ import (
"github.com/hunterlong/statup/core/notifier" "github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils" "github.com/hunterlong/statup/utils"
"net/http"
"net/url" "net/url"
"strings" "strings"
"time"
) )
const ( const (
@ -59,22 +59,12 @@ func init() {
// Send will send a HTTP Post with the Authorization to the notify-api.line.me server. It accepts type: string // Send will send a HTTP Post with the Authorization to the notify-api.line.me server. It accepts type: string
func (u *lineNotifier) Send(msg interface{}) error { func (u *lineNotifier) Send(msg interface{}) error {
message := msg.(string) message := msg.(string)
client := new(http.Client)
v := url.Values{} v := url.Values{}
v.Set("message", message) v.Set("message", message)
req, err := http.NewRequest("POST", "https://notify-api.line.me/api/notify", strings.NewReader(v.Encode())) headers := []string{fmt.Sprintf("Authorization=Bearer %v", u.GetValue("api_secret"))}
if err != nil { _, err := utils.HttpRequest("https://notify-api.line.me/api/notify", "POST", "application/x-www-form-urlencoded", headers, strings.NewReader(v.Encode()), time.Duration(10*time.Second))
return err return err
} }
req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", u.GetValue("api_secret")))
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
_, err = client.Do(req)
if err != nil {
return err
}
return nil
}
func (u *lineNotifier) Select() *notifier.Notification { func (u *lineNotifier) Select() *notifier.Notification {
return u.Notification return u.Notification

View File

@ -43,7 +43,7 @@ var mobile = &mobilePush{&notifier.Notification{
Title: "Device Identifiers", Title: "Device Identifiers",
Placeholder: "A list of your mobile device push notification ID's.", Placeholder: "A list of your mobile device push notification ID's.",
DbField: "var1", DbField: "var1",
Hidden: true, IsHidden: true,
}}}, }}},
} }

View File

@ -21,8 +21,8 @@ import (
"fmt" "fmt"
"github.com/hunterlong/statup/core/notifier" "github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
"io/ioutil" "github.com/hunterlong/statup/utils"
"net/http" "strings"
"text/template" "text/template"
"time" "time"
) )
@ -85,28 +85,16 @@ func init() {
// Send will send a HTTP Post to the slack webhooker API. It accepts type: string // Send will send a HTTP Post to the slack webhooker API. It accepts type: string
func (u *slack) Send(msg interface{}) error { func (u *slack) Send(msg interface{}) error {
message := msg.(string) message := msg.(string)
client := new(http.Client) _, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second))
res, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(message)))
if err != nil {
return err return err
} }
defer res.Body.Close()
//contents, _ := ioutil.ReadAll(res.Body)
return nil
}
func (u *slack) Select() *notifier.Notification { func (u *slack) Select() *notifier.Notification {
return u.Notification return u.Notification
} }
func (u *slack) OnTest() error { func (u *slack) OnTest() error {
client := new(http.Client) contents, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(`{"text":"testing message"}`)), time.Duration(10*time.Second))
res, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(`{"text":"testing message"}`)))
if err != nil {
return err
}
defer res.Body.Close()
contents, _ := ioutil.ReadAll(res.Body)
if string(contents) != "ok" { if string(contents) != "ok" {
return errors.New("The slack response was incorrect, check the URL") return errors.New("The slack response was incorrect, check the URL")
} }

View File

@ -22,8 +22,6 @@ import (
"github.com/hunterlong/statup/core/notifier" "github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils" "github.com/hunterlong/statup/utils"
"io/ioutil"
"net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
@ -84,32 +82,21 @@ func (u *twilio) Select() *notifier.Notification {
func (u *twilio) Send(msg interface{}) error { func (u *twilio) Send(msg interface{}) error {
message := msg.(string) message := msg.(string)
twilioUrl := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%v/Messages.json", u.GetValue("api_key")) twilioUrl := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%v/Messages.json", u.GetValue("api_key"))
client := &http.Client{}
v := url.Values{} v := url.Values{}
v.Set("To", "+"+u.Var1) v.Set("To", "+"+u.Var1)
v.Set("From", "+"+u.Var2) v.Set("From", "+"+u.Var2)
v.Set("Body", message) v.Set("Body", message)
rb := *strings.NewReader(v.Encode()) rb := *strings.NewReader(v.Encode())
req, err := http.NewRequest("POST", twilioUrl, &rb)
if err != nil { contents, err := utils.HttpRequest(twilioUrl, "POST", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second))
return err
}
req.SetBasicAuth(u.ApiKey, u.ApiSecret)
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
contents, _ := ioutil.ReadAll(res.Body)
success, _ := twilioSuccess(contents) success, _ := twilioSuccess(contents)
if !success { if !success {
errorOut := twilioError(contents) errorOut := twilioError(contents)
out := fmt.Sprintf("Error code %v - %v", errorOut.Code, errorOut.Message) out := fmt.Sprintf("Error code %v - %v", errorOut.Code, errorOut.Message)
return errors.New(out) return errors.New(out)
} }
return nil return err
} }
// OnFailure will trigger failing service // OnFailure will trigger failing service

View File

@ -59,7 +59,14 @@
<div class="form-group row"> <div class="form-group row">
<label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label> <label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="text" name="notify_before" class="form-control" id="notify_before" value="{{.NotifyBefore}}"> <div class="form-inline">
<input type="number" name="notify_before_scale" class="col-4 form-control" id="notify_before" value="{{.NotifyBefore.Int64}}" >
<select class="ml-2 col-7 form-control" name="notify_before_scale" id="notify_before_scale">
<option value="minute"{{if ne .Id 0}} selected{{end}}>Minutes</option>
<option value="hour">Hours</option>
<option value="day">Days</option>
</select>
</div>
</div> </div>
</div> </div>

View File

@ -59,7 +59,7 @@
<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_code" class="col-sm-4 col-form-label">Expected Status Code</label> <label for="service_response_code" class="col-sm-4 col-form-label">Expected Status Code</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="number" name="expected_status" class="form-control" value="{{if ne .ExpectedStatus 0}}{{.ExpectedStatus}}{{end}}" placeholder="200" id="service_response_code"> <input type="number" name="expected_status" class="form-control" value="{{if ne .ExpectedStatus 0}}{{.ExpectedStatus}}{{else}}200{{end}}" placeholder="200" id="service_response_code">
<small class="form-text text-muted">A status code of 200 is success, or view all the <a target="_blank" href="https://www.restapitutorial.com/httpstatuscodes.html">HTTP Status Codes</a></small> <small class="form-text text-muted">A status code of 200 is success, or view all the <a target="_blank" href="https://www.restapitutorial.com/httpstatuscodes.html">HTTP Status Codes</a></small>
</div> </div>
</div> </div>
@ -90,6 +90,15 @@
<small class="form-text text-muted">You can also drag and drop services to reorder on the Services tab.</small> <small class="form-text text-muted">You can also drag and drop services to reorder on the Services tab.</small>
</div> </div>
</div> </div>
<div class="form-group row">
<label for="order" class="col-sm-4 col-form-label">Notifications</label>
<div class="col-8 mt-1">
<span class="switch float-left">
<input type="checkbox" name="allow_notifications" class="switch" id="switch-service" {{if eq .Id 0}}checked{{end}}{{if .AllowNotifications.Bool}}checked{{end}}>
<label for="switch-service">Allow notifications to be sent for this service</label>
</span>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<div class="{{if ne .Id 0}}col-6{{else}}col-12{{end}}"> <div class="{{if ne .Id 0}}col-6{{else}}col-12{{end}}">
<button type="submit" class="btn btn-success btn-block">{{if ne .Id 0}}Update Service{{else}}Create Service{{end}}</button> <button type="submit" class="btn btn-success btn-block">{{if ne .Id 0}}Update Service{{else}}Create Service{{end}}</button>

View File

@ -940,8 +940,12 @@
"exec": [ "exec": [
"pm.test(\"Create Message\", function () {", "pm.test(\"Create Message\", function () {",
" var jsonData = pm.response.json();", " var jsonData = pm.response.json();",
" pm.expect(jsonData.output.title).to.eql(\"API Message\");", " var object = jsonData.output;",
" pm.expect(jsonData.output.service).to.eql(1);", " pm.expect(object.title).to.eql(\"API Message\");",
" pm.expect(object.description).to.eql(\"This is an example a upcoming message for a service!\");",
" pm.expect(object.service).to.eql(1);",
" pm.expect(object.notify_before).to.eql(6);",
" pm.expect(object.notify_before_scale).to.eql(\"hour\");",
"});" "});"
], ],
"type": "text/javascript" "type": "text/javascript"
@ -960,7 +964,7 @@
], ],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\n \"title\": \"API Message\",\n \"description\": \"This is an example a upcoming message for a service!\",\n \"start_on\": \"2022-11-17T03:28:16.323797-08:00\",\n \"end_on\": \"2022-11-17T05:13:16.323798-08:00\",\n \"service\": 1,\n \"notify_users\": null,\n \"notify_method\": \"\",\n \"notify_before\": 0\n}" "raw": "{\n \"title\": \"API Message\",\n \"description\": \"This is an example a upcoming message for a service!\",\n \"start_on\": \"2022-11-17T03:28:16.323797-08:00\",\n \"end_on\": \"2022-11-17T05:13:16.323798-08:00\",\n \"service\": 1,\n \"notify_users\": true,\n \"notify_method\": \"email\",\n \"notify_before\": 6,\n \"notify_before_scale\": \"hour\"\n}"
}, },
"url": { "url": {
"raw": "{{endpoint}}/api/messages", "raw": "{{endpoint}}/api/messages",
@ -981,7 +985,7 @@
{ {
"listen": "test", "listen": "test",
"script": { "script": {
"id": "abbb5178-9613-418c-b5ee-be2d6b4fdb8f", "id": "c30cc333-53f4-4e9a-9c32-958c905ec163",
"exec": [ "exec": [
"pm.test(\"View Message\", function () {", "pm.test(\"View Message\", function () {",
" var jsonData = pm.response.json();", " var jsonData = pm.response.json();",
@ -1020,13 +1024,16 @@
{ {
"listen": "test", "listen": "test",
"script": { "script": {
"id": "a0403c03-0838-4fd2-9cce-aebaf8a128c3", "id": "e9dd78cc-0f38-4516-bf82-38dd3451b2e7",
"exec": [ "exec": [
"pm.test(\"Update Message\", function () {", "pm.test(\"Update Message\", function () {",
" var jsonData = pm.response.json();", " var jsonData = pm.response.json();",
" pm.expect(jsonData.status).to.eql(\"success\");", " var object = jsonData.output;",
" pm.expect(jsonData.method).to.eql(\"update\");", " pm.expect(object.title).to.eql(\"Updated Message\");",
" pm.expect(jsonData.id).to.eql(1);", " pm.expect(object.description).to.eql(\"This message was updated\");",
" pm.expect(object.service).to.eql(1);",
" pm.expect(object.notify_before).to.eql(3);",
" pm.expect(object.notify_before_scale).to.eql(\"hour\");",
"});" "});"
], ],
"type": "text/javascript" "type": "text/javascript"
@ -1045,7 +1052,7 @@
], ],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\n \"title\": \"Routine Downtime\",\n \"description\": \"This is an example a upcoming message for a service!\",\n \"start_on\": \"2055-11-17T03:28:16.323797-08:00\",\n \"end_on\": \"2055-11-17T05:13:16.323798-08:00\",\n \"service\": 2,\n \"notify_users\": true,\n \"notify_method\": \"email\",\n \"notify_before\": 900\n}" "raw": "{\n \"title\": \"Updated Message\",\n \"description\": \"This message was updated\",\n \"start_on\": \"2022-11-17T03:28:16.323797-08:00\",\n \"end_on\": \"2022-11-17T05:13:16.323798-08:00\",\n \"service\": 1,\n \"notify_users\": true,\n \"notify_method\": \"email\",\n \"notify_before\": 3,\n \"notify_before_scale\": \"hour\"\n}"
}, },
"url": { "url": {
"raw": "{{endpoint}}/api/messages/1", "raw": "{{endpoint}}/api/messages/1",

View File

@ -29,7 +29,8 @@ type Message struct {
ServiceId int64 `gorm:"index;column:service" json:"service"` ServiceId int64 `gorm:"index;column:service" json:"service"`
NotifyUsers NullBool `gorm:"column:notify_users" json:"notify_users"` NotifyUsers NullBool `gorm:"column:notify_users" json:"notify_users"`
NotifyMethod string `gorm:"column:notify_method" json:"notify_method"` NotifyMethod string `gorm:"column:notify_method" json:"notify_method"`
NotifyBefore time.Duration `gorm:"column:notify_before" json:"notify_before"` NotifyBefore NullInt64 `gorm:"column:notify_before" json:"notify_before"`
NotifyBeforeScale string `gorm:"column:notify_before_scale" json:"notify_before_scale"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at" json:"created_at"` CreatedAt time.Time `gorm:"column:created_at" json:"created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" json:"updated_at"` UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" json:"updated_at"`
} }

View File

@ -16,11 +16,13 @@
package utils package utils
import ( import (
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"github.com/ararog/timeago" "github.com/ararog/timeago"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http"
"os" "os"
"os/exec" "os/exec"
"regexp" "regexp"
@ -221,3 +223,39 @@ func SaveFile(filename string, data []byte) error {
err := ioutil.WriteFile(filename, data, 0644) err := ioutil.WriteFile(filename, data, 0644)
return err return err
} }
// HttpRequest is a global function to send a HTTP request
func HttpRequest(url, method string, content interface{}, headers []string, body io.Reader, timeout time.Duration) ([]byte, error) {
var err error
var contentType string
if content != nil {
contentType = content.(string)
}
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
TLSHandshakeTimeout: timeout,
}
client := &http.Client{
Transport: transport,
Timeout: timeout,
}
var response *http.Response
response.Header.Set("User-Agent", "Statup")
for _, h := range headers {
keyVal := strings.Split(h, "=")
response.Header.Add(keyVal[0], keyVal[1])
}
if method == "POST" {
response, err = client.Post(url, contentType, body)
} else {
response, err = client.Get(url)
}
if err != nil {
return nil, err
}
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
return contents, err
}