From 9e9c29df31331b3f45e4267153165de93065cd2d Mon Sep 17 00:00:00 2001 From: Hunter Long <Info@socialeck.com> Date: Sun, 1 Jul 2018 03:24:35 -0700 Subject: [PATCH] core initial setup fixes - Slack integration - template updates --- .travis.yml | 2 +- Dockerfile | 2 +- README.md | 4 ++++ core/assets.go | 13 +++++++++--- core/checker.go | 21 +++++++++++++++---- core/communication.go | 21 +++++++++++++++++++ core/core.go | 7 +++++++ core/database.go | 12 ++++++----- core/events.go | 11 +++++++++- core/services.go | 7 +++++-- core/setup.go | 43 +++++++++++++++++++++++---------------- handlers/dashboard.go | 3 +-- handlers/routes.go | 1 + handlers/services.go | 28 ++++++++++++------------- handlers/settings.go | 18 ++++++++++++++++ handlers/setup.go | 13 +++++++----- main.go | 2 +- main_test.go | 12 +++++------ source/tmpl/service.html | 10 +++++++-- source/tmpl/services.html | 6 ++++++ source/tmpl/settings.html | 35 +++++++++++++++++++++++-------- 21 files changed, 198 insertions(+), 73 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3c43baed..19e032d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ services: env: global: - - VERSION=0.28.4 + - VERSION=0.28.5 - DB_HOST=localhost - DB_USER=travis - DB_PASS= diff --git a/Dockerfile b/Dockerfile index 187f9043..54d3f1c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:latest -ENV VERSION=v0.28.4 +ENV VERSION=v0.28.5 RUN apk --no-cache add libstdc++ ca-certificates RUN wget -q https://github.com/hunterlong/statup/releases/download/$VERSION/statup-linux-alpine.tar.gz && \ diff --git a/README.md b/README.md index 86bbd77c..31b95d5e 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,10 @@ Whether you're a Docker fan-boy or a AWS EC2 master, Statup gives you multiple o Running on an EC2 server might be the most cost effective way to host your own Statup Status Page. The server runs on the smallest EC2 instance (t2.nano) AWS has to offer, which only costs around $4.60 USD a month for your dedicated Status Page. Want to run it on your own Docker server? Awesome! Statup has multiple docker-compose.yml files to work with. Statup can automatically create a SSL Certification for your status page. +## Slack Integration +Everyone uses Slack and Statup should also. You can create an [Incoming Webhook](https://api.slack.com/incoming-webhooks) in Slack and insert the URL into the Settings page in Statup. Anytime a service fails, you're channel that you specified on Slack will receive a message. +This is a brand new feature, right now it is sending basic text. With Slack Messaging format, I plan on creating a more detailed message for a cleaner look. + ## Email Nofitications Statup includes email notification via SMTP if your services go offline. diff --git a/core/assets.go b/core/assets.go index 77fac2f5..6269f586 100644 --- a/core/assets.go +++ b/core/assets.go @@ -10,12 +10,19 @@ import ( ) func CopyToPublic(box *rice.Box, folder, file string) { - utils.Log(1, fmt.Sprintf("Copying %v to %v...", file, folder)) + assetFolder := fmt.Sprintf("assets/%v/%v", folder, file) + if folder == "" { + assetFolder = fmt.Sprintf("assets/%v", file) + } + utils.Log(1, fmt.Sprintf("Copying %v to %v", file, assetFolder)) base, err := box.String(file) if err != nil { - utils.Log(3, fmt.Sprintf("Failed to copy %v to %v, %v.", file, folder, err)) + utils.Log(3, fmt.Sprintf("Failed to copy %v to %v, %v.", file, assetFolder, err)) + } + err = ioutil.WriteFile(assetFolder, []byte(base), 0644) + if err != nil { + utils.Log(3, fmt.Sprintf("Failed to write file %v to %v, %v.", file, assetFolder, err)) } - ioutil.WriteFile("assets/"+folder+"/"+file, []byte(base), 0644) } func MakePublicFolder(folder string) { diff --git a/core/checker.go b/core/checker.go index 5f5ce7c8..50b4d50d 100644 --- a/core/checker.go +++ b/core/checker.go @@ -1,6 +1,7 @@ package core import ( + "bytes" "fmt" "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" @@ -61,7 +62,13 @@ func (s *Service) Check() *Service { client := http.Client{ Timeout: 30 * time.Second, } - response, err := client.Get(s.Domain) + + var response *http.Response + if s.Method == "POST" { + response, err = client.Post(s.Domain, "application/json", bytes.NewBuffer([]byte(s.PostData))) + } else { + response, err = client.Get(s.Domain) + } if err != nil { s.Failure(fmt.Sprintf("HTTP Error %v", err)) return s @@ -74,9 +81,15 @@ func (s *Service) Check() *Service { return s } defer response.Body.Close() - contents, _ := ioutil.ReadAll(response.Body) + contents, err := ioutil.ReadAll(response.Body) + if err != nil { + utils.Log(2, err) + } if s.Expected != "" { - match, _ := regexp.MatchString(s.Expected, string(contents)) + match, err := regexp.MatchString(s.Expected, string(contents)) + if err != nil { + utils.Log(2, err) + } if !match { s.LastResponse = string(contents) s.LastStatusCode = response.StatusCode @@ -119,5 +132,5 @@ func (s *Service) Failure(issue string) { utils.Log(2, fmt.Sprintf("Service %v Failing: %v", s.Name, issue)) s.CreateFailure(data) //SendFailureEmail(s) - OnFailure(s) + OnFailure(s, data) } diff --git a/core/communication.go b/core/communication.go index 87b8160b..0460bcd8 100644 --- a/core/communication.go +++ b/core/communication.go @@ -1,8 +1,11 @@ package core import ( + "bytes" + "fmt" "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" + "net/http" "time" ) @@ -83,3 +86,21 @@ func SelectCommunication(id int64) *Communication { } return nil } + +func SendSlackMessage(msg string) error { + fullMessage := fmt.Sprintf("{\"text\":\"%v\"}", msg) + utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook: %v", fullMessage)) + slack := SelectCommunication(2) + if slack == nil { + utils.Log(3, fmt.Sprintf("Slack communication database entry was not found.")) + return nil + } + client := http.Client{ + Timeout: 15 * time.Second, + } + _, err := client.Post(slack.Host, "application/json", bytes.NewBuffer([]byte(fullMessage))) + if err != nil { + utils.Log(3, err) + } + return err +} diff --git a/core/core.go b/core/core.go index 1ed1ae95..540ba08b 100644 --- a/core/core.go +++ b/core/core.go @@ -70,6 +70,13 @@ func (c Core) BaseSASS() string { return OpenAsset("scss/base.scss") } +func (c Core) MobileSASS() string { + if !UsingAssets { + return "" + } + return OpenAsset("scss/mobile.scss") +} + func (c Core) AllOnline() bool { for _, s := range CoreApp.Services { if !s.Online { diff --git a/core/database.go b/core/database.go index 826ed6cb..745240e0 100644 --- a/core/database.go +++ b/core/database.go @@ -92,12 +92,12 @@ func (c *DbConfig) Save() error { var err error config, err := os.Create("config.yml") if err != nil { - utils.Log(2, err) + utils.Log(4, err) return err } data, err := yaml.Marshal(c) if err != nil { - utils.Log(2, err) + utils.Log(3, err) return err } config.WriteString(string(data)) @@ -105,12 +105,12 @@ func (c *DbConfig) Save() error { Configs, err = LoadConfig() if err != nil { - utils.Log(2, err) + utils.Log(3, err) return err } err = DbConnection(Configs.Connection) if err != nil { - utils.Log(2, err) + utils.Log(4, err) return err } DropDatabase() @@ -124,9 +124,11 @@ func (c *DbConfig) Save() error { ApiSecret: utils.NewSHA1Hash(16), Domain: c.Domain, } - col := DbSession.Collection("core") _, err = col.Insert(newCore) + if err == nil { + CoreApp = newCore + } return err } diff --git a/core/events.go b/core/events.go index 2fef5746..48bd15c6 100644 --- a/core/events.go +++ b/core/events.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "github.com/fatih/structs" "github.com/hunterlong/statup/plugin" "upper.io/db.v3/lib/sqlbuilder" @@ -18,10 +19,18 @@ func OnSuccess(s *Service) { } } -func OnFailure(s *Service) { +func OnFailure(s *Service, f FailureData) { for _, p := range AllPlugins { p.OnFailure(structs.Map(s)) } + slack := SelectCommunication(2) + if slack == nil { + return + } + if slack.Enabled { + msg := fmt.Sprintf("Service %v is currently offline! Issue: %v", s.Name, f.Issue) + SendSlackMessage(msg) + } } func OnSettingsSaved(c *Core) { diff --git a/core/services.go b/core/services.go index d4237338..a30c3892 100644 --- a/core/services.go +++ b/core/services.go @@ -195,13 +195,16 @@ func (u *Service) Delete() error { return err } -func (u *Service) Update(s *Service) { +func (u *Service) Update(s *Service) *Service { + s.CreatedAt = time.Now() res := serviceCol().Find("id", u.Id) err := res.Update(s) if err != nil { utils.Log(3, fmt.Sprintf("Failed to update service %v. %v", u.Name, err)) } + *u = *s OnUpdateService(u) + return u } func (u *Service) Create() (int64, error) { @@ -213,7 +216,7 @@ func (u *Service) Create() (int64, error) { } u.Id = uuid.(int64) CoreApp.Services = append(CoreApp.Services, u) - go u.CheckQueue() + //go u.CheckQueue() OnNewService(u) return uuid.(int64), err } diff --git a/core/setup.go b/core/setup.go index 53c87362..65835378 100644 --- a/core/setup.go +++ b/core/setup.go @@ -1,7 +1,6 @@ package core import ( - "fmt" "github.com/hunterlong/statup/utils" "os" ) @@ -13,6 +12,12 @@ func InsertDefaultComms() { Enabled: false, } Create(emailer) + slack := &Communication{ + Method: "slack", + Removable: false, + Enabled: false, + } + Create(slack) } func DeleteConfig() { @@ -27,41 +32,43 @@ type ErrorResponse struct { } func LoadSampleData() error { - fmt.Println("Inserting Sample Data...") + utils.Log(1, "Inserting Sample Data...") s1 := &Service{ Name: "Google", Domain: "https://google.com", ExpectedStatus: 200, Interval: 10, Port: 0, - Type: "https", + Type: "http", Method: "GET", } s2 := &Service{ - Name: "Statup.io", - Domain: "https://statup.io", + Name: "Statup Github", + Domain: "https://github.com/hunterlong/statup", ExpectedStatus: 200, - Interval: 15, + Interval: 30, Port: 0, - Type: "https", + Type: "http", Method: "GET", } s3 := &Service{ - Name: "Statup.io SSL Check", - Domain: "https://statup.io", + Name: "JSON Users Test", + Domain: "https://jsonplaceholder.typicode.com/users", ExpectedStatus: 200, - Interval: 15, + Interval: 60, Port: 443, - Type: "tcp", + Type: "http", + Method: "GET", } s4 := &Service{ - Name: "Github Failing Check", - Domain: "https://github.com/thisisnotausernamemaybeitis", - ExpectedStatus: 200, - Interval: 15, - Port: 0, - Type: "https", - Method: "GET", + Name: "JSON API Tester", + Domain: "https://jsonplaceholder.typicode.com/posts", + ExpectedStatus: 201, + Expected: `(title)": "((\\"|[statup])*)"`, + Interval: 30, + Type: "http", + Method: "POST", + PostData: `{ "title": "statup", "body": "bar", "userId": 19999 }`, } s1.Create() s2.Create() diff --git a/handlers/dashboard.go b/handlers/dashboard.go index 881a2525..00eebcae 100644 --- a/handlers/dashboard.go +++ b/handlers/dashboard.go @@ -49,8 +49,7 @@ func LogoutHandler(w http.ResponseWriter, r *http.Request) { } func HelpHandler(w http.ResponseWriter, r *http.Request) { - auth := IsAuthenticated(r) - if !auth { + if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) return } diff --git a/handlers/routes.go b/handlers/routes.go index 9e55cf1c..5e83a0c7 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -33,6 +33,7 @@ func Router() *mux.Router { r.Handle("/settings/css", http.HandlerFunc(SaveSASSHandler)).Methods("POST") r.Handle("/settings/build", http.HandlerFunc(SaveAssetsHandler)).Methods("GET") r.Handle("/settings/email", http.HandlerFunc(SaveEmailSettingsHandler)).Methods("POST") + r.Handle("/settings/slack", http.HandlerFunc(SaveSlackSettingsHandler)).Methods("POST") r.Handle("/plugins/download/{name}", http.HandlerFunc(PluginsDownloadHandler)) r.Handle("/plugins/{name}/save", http.HandlerFunc(PluginSavedHandler)).Methods("POST") r.Handle("/help", http.HandlerFunc(HelpHandler)) diff --git a/handlers/services.go b/handlers/services.go index 5d8ab589..93fe525b 100644 --- a/handlers/services.go +++ b/handlers/services.go @@ -10,8 +10,7 @@ import ( ) func ServicesHandler(w http.ResponseWriter, r *http.Request) { - auth := IsAuthenticated(r) - if !auth { + if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -19,8 +18,7 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) { } func CreateServiceHandler(w http.ResponseWriter, r *http.Request) { - auth := IsAuthenticated(r) - if !auth { + if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -34,6 +32,7 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) { interval, _ := strconv.Atoi(r.PostForm.Get("interval")) port, _ := strconv.Atoi(r.PostForm.Get("port")) checkType := r.PostForm.Get("check_type") + postData := r.PostForm.Get("post_data") service := &core.Service{ Name: name, @@ -44,17 +43,18 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) { Interval: interval, Type: checkType, Port: port, + PostData: postData, } _, err := service.Create() if err != nil { - go service.CheckQueue() + utils.Log(3, fmt.Sprintf("Error starting %v check routine. %v", service.Name, err)) } + go service.CheckQueue() http.Redirect(w, r, "/services", http.StatusSeeOther) } func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) { - auth := IsAuthenticated(r) - if !auth { + if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -89,8 +89,7 @@ func ServicesBadgeHandler(w http.ResponseWriter, r *http.Request) { } func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) { - auth := IsAuthenticated(r) - if !auth { + if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -105,7 +104,9 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) { interval, _ := strconv.Atoi(r.PostForm.Get("interval")) port, _ := strconv.Atoi(r.PostForm.Get("port")) checkType := r.PostForm.Get("check_type") + postData := r.PostForm.Get("post_data") serviceUpdate := &core.Service{ + Id: service.Id, Name: name, Domain: domain, Method: method, @@ -114,14 +115,14 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) { Interval: interval, Type: checkType, Port: port, + PostData: postData, } - service.Update(serviceUpdate) + service = service.Update(serviceUpdate) ExecuteResponse(w, r, "service.html", service) } func ServicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) { - auth := IsAuthenticated(r) - if !auth { + if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -134,8 +135,7 @@ func ServicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) { } func CheckinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) { - auth := IsAuthenticated(r) - if !auth { + if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) return } diff --git a/handlers/settings.go b/handlers/settings.go index d47266e1..f444c406 100644 --- a/handlers/settings.go +++ b/handlers/settings.go @@ -113,3 +113,21 @@ func SaveEmailSettingsHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/settings", http.StatusSeeOther) } + +func SaveSlackSettingsHandler(w http.ResponseWriter, r *http.Request) { + auth := IsAuthenticated(r) + if !auth { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + slack := core.SelectCommunication(2) + r.ParseForm() + slack.Host = r.PostForm.Get("host") + slack.Enabled = true + if slack.Host == "" { + slack.Enabled = false + } + core.Update(slack) + core.SendSlackMessage("This is a test from Statup!") + http.Redirect(w, r, "/settings", http.StatusSeeOther) +} diff --git a/handlers/setup.go b/handlers/setup.go index 23109a90..05cdc011 100644 --- a/handlers/setup.go +++ b/handlers/setup.go @@ -7,7 +7,6 @@ import ( "net/http" "os" "strconv" - "time" ) func SetupHandler(w http.ResponseWriter, r *http.Request) { @@ -75,7 +74,11 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) { } err := config.Save() if err != nil { - utils.Log(2, err) + utils.Log(4, err) + } + + if err != nil { + utils.Log(3, err) config.Error = err SetupResponseError(w, r, config) return @@ -83,7 +86,7 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) { core.Configs, err = core.LoadConfig() if err != nil { - utils.Log(2, err) + utils.Log(3, err) config.Error = err SetupResponseError(w, r, config) return @@ -91,7 +94,7 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) { err = core.DbConnection(core.Configs.Connection) if err != nil { - utils.Log(2, err) + utils.Log(3, err) core.DeleteConfig() config.Error = err SetupResponseError(w, r, config) @@ -112,8 +115,8 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) { go core.LoadSampleData() } + core.SelectCore() http.Redirect(w, r, "/", http.StatusSeeOther) - time.Sleep(2 * time.Second) //mainProcess() } diff --git a/main.go b/main.go index 570d7d78..73391311 100644 --- a/main.go +++ b/main.go @@ -29,7 +29,7 @@ func main() { CatchCLI(os.Args) os.Exit(0) } - utils.Log(1, fmt.Sprintf("Starting Statup v%v\n", VERSION)) + utils.Log(1, fmt.Sprintf("Starting Statup v%v", VERSION)) RenderBoxes() core.HasAssets() diff --git a/main_test.go b/main_test.go index bd3f2b56..569ef619 100644 --- a/main_test.go +++ b/main_test.go @@ -196,11 +196,11 @@ func TestService_Create(t *testing.T) { } func TestService_Check(t *testing.T) { - service := core.SelectService(2) + service := core.SelectService(1) assert.NotNil(t, service) - assert.Equal(t, "Statup.io", service.Name) + assert.Equal(t, "Google", service.Name) out := service.Check() - assert.Equal(t, false, out.Online) + assert.Equal(t, true, out.Online) } func TestService_AvgTime(t *testing.T) { @@ -226,12 +226,12 @@ func TestService_GraphData(t *testing.T) { func TestBadService_Create(t *testing.T) { service := &core.Service{ - Name: "bad service", + Name: "Bad Service", Domain: "https://9839f83h72gey2g29278hd2od2d.com", ExpectedStatus: 200, Interval: 10, Port: 0, - Type: "https", + Type: "http", Method: "GET", } id, err := service.Create() @@ -242,7 +242,7 @@ func TestBadService_Create(t *testing.T) { func TestBadService_Check(t *testing.T) { service := core.SelectService(4) assert.NotNil(t, service) - assert.Equal(t, "Github Failing Check", service.Name) + assert.Equal(t, "JSON API Tester", service.Name) } func TestService_Hits(t *testing.T) { diff --git a/source/tmpl/service.html b/source/tmpl/service.html index 44fa0980..c31398d6 100644 --- a/source/tmpl/service.html +++ b/source/tmpl/service.html @@ -101,11 +101,17 @@ <label for="service_check_type" class="col-sm-4 col-form-label">Service Check Type</label> <div class="col-sm-8"> <select name="method" class="form-control" id="service_check_type" value="{{.Method}}"> - <option value="GET" selected>GET</option> - <option value="POST">POST</option> + <option value="GET" {{if eq .Method "GET"}}selected{{end}}>GET</option> + <option value="POST" {{if eq .Method "POST"}}selected{{end}}>POST</option> </select> </div> </div> + <div class="form-group row"> + <label for="post_data" class="col-sm-4 col-form-label">Post Data (JSON)</label> + <div class="col-sm-8"> + <textarea name="post_data" class="form-control" id="post_data" rows="3">{{.PostData}}</textarea> + </div> + </div> <div class="form-group row"> <label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label> <div class="col-sm-8"> diff --git a/source/tmpl/services.html b/source/tmpl/services.html index eefa8d07..ced73e8b 100644 --- a/source/tmpl/services.html +++ b/source/tmpl/services.html @@ -77,6 +77,12 @@ </select> </div> </div> + <div class="form-group row"> + <label for="post_data" class="col-sm-4 col-form-label">Post Data (JSON)</label> + <div class="col-sm-8"> + <textarea name="post_data" class="form-control" id="post_data" rows="3"></textarea> + </div> + </div> <div class="form-group row"> <label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label> <div class="col-sm-8"> diff --git a/source/tmpl/settings.html b/source/tmpl/settings.html index 62574805..36ffe08b 100644 --- a/source/tmpl/settings.html +++ b/source/tmpl/settings.html @@ -28,6 +28,7 @@ <a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">Settings</a> <a class="nav-link" id="v-pills-style-tab" data-toggle="pill" href="#v-pills-style" role="tab" aria-controls="v-pills-style" aria-selected="false">Theme Editor</a> <a class="nav-link" id="v-pills-email-tab" data-toggle="pill" href="#v-pills-email" role="tab" aria-controls="v-pills-email" aria-selected="true">Email Settings</a> + <a class="nav-link" id="v-pills-slack-tab" data-toggle="pill" href="#v-pills-slack" role="tab" aria-controls="v-pills-slack" aria-selected="true">Slack Updates</a> {{ range .Communications }} {{ end }} @@ -107,34 +108,34 @@ {{end}} </div> - {{ range .Communications }} - <div class="tab-pane fade" id="v-pills-{{ .Method }}" role="tabpanel" aria-labelledby="v-pills-{{ .Method }}-tab"> + {{ with $c := index .Communications 0 }} + <div class="tab-pane fade" id="v-pills-{{ $c.Method }}" role="tabpanel" aria-labelledby="v-pills-{{ $c.Method }}-tab"> - <form method="POST" action="/settings/{{ .Method }}"> + <form method="POST" action="/settings/{{ $c.Method }}"> <div class="form-group"> <label for="host">SMTP Host</label> - <input type="text" name="host" class="form-control" value="{{ .Host }}" id="host" placeholder="Great Uptime"> + <input type="text" name="host" class="form-control" value="{{ $c.Host }}" id="host" placeholder="Great Uptime"> </div> <div class="form-group"> <label for="username">SMTP Username</label> - <input type="text" name="username" class="form-control" value="{{ .Username }}" id="username" placeholder="Great Uptime"> + <input type="text" name="username" class="form-control" value="{{ $c.Username }}" id="username" placeholder="Great Uptime"> </div> <div class="form-group"> <label for="password">SMTP Password</label> - <input type="password" name="password" class="form-control" value="{{ .Password }}" id="password"> + <input type="password" name="password" class="form-control" value="{{ $c.Password }}" id="password"> </div> <div class="form-group"> <label for="port">SMTP Port</label> - <input type="number" name="port" class="form-control" value="{{ .Port }}" id="port" placeholder="587"> + <input type="number" name="port" class="form-control" value="{{ $c.Port }}" id="port" placeholder="587"> </div> <div class="form-group"> <label for="address">Outgoing Email Address</label> - <input type="text" name="address" class="form-control" value="{{ .Var1 }}" id="address" placeholder="noreply@domain.com"> + <input type="text" name="address" class="form-control" value="{{ $c.Var1 }}" id="address" placeholder="noreply@domain.com"> </div> <div class="form-group"> @@ -149,6 +150,24 @@ </div> {{ end }} + +{{ with $c := index .Communications 1 }} + <div class="tab-pane fade" id="v-pills-{{ $c.Method }}" role="tabpanel" aria-labelledby="v-pills-{{ $c.Method }}-tab"> + + <form method="POST" action="/settings/{{ $c.Method }}"> + + <div class="form-group"> + <label for="host">Slack Webhook URL</label> + <input type="text" name="host" class="form-control" value="{{ $c.Host }}" id="host" placeholder="https://hooks.slack.com/services/TJIIDSJIFJ/729FJSDF/hua463asda9af79"> + </div> + + <button type="submit" class="btn btn-primary btn-block">Save Slack Settings</button> + + </form> + + </div> +{{ end }} + <div class="tab-pane fade" id="v-pills-browse" role="tabpanel" aria-labelledby="v-pills-browse-tab"> {{ range .Repos }} <div class="card col-6" style="width: 18rem;">