From ddb2a10a7cfc8d297e3d653a16c27f40e056f137 Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Thu, 4 Oct 2018 21:28:38 -0700 Subject: [PATCH] checkins - installer shell script --- core/checkin.go | 26 ++-- core/database.go | 20 ++- core/sample.go | 4 +- core/services.go | 8 +- dev/aws/statup-install | 246 ++++++++++++++++++++++++++++++++++ handlers/handlers.go | 14 +- handlers/routes.go | 2 +- handlers/services.go | 11 +- source/tmpl/form_checkin.html | 19 ++- source/tmpl/service.html | 33 +++-- types/service.go | 1 - utils/time.go | 78 +++++++++++ 12 files changed, 391 insertions(+), 71 deletions(-) create mode 100755 dev/aws/statup-install create mode 100644 utils/time.go diff --git a/core/checkin.go b/core/checkin.go index 7d3dcda8..5d929495 100644 --- a/core/checkin.go +++ b/core/checkin.go @@ -20,6 +20,7 @@ import ( "github.com/ararog/timeago" "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" + "github.com/jinzhu/gorm" "time" ) @@ -49,17 +50,17 @@ func SelectCheckin(api string) *Checkin { return &checkin } -func (u Checkin) Period() time.Duration { +func (u *Checkin) Period() time.Duration { duration, _ := time.ParseDuration(fmt.Sprintf("%vs", u.Interval)) return duration } -func (u Checkin) Grace() time.Duration { +func (u *Checkin) Grace() time.Duration { duration, _ := time.ParseDuration(fmt.Sprintf("%vs", u.GracePeriod)) return duration } -func (u Checkin) Expected() time.Duration { +func (u *Checkin) Expected() time.Duration { last := u.Last().CreatedAt now := time.Now() lastDir := now.Sub(last) @@ -67,7 +68,7 @@ func (u Checkin) Expected() time.Duration { return sub } -func (u Checkin) Last() CheckinHit { +func (u *Checkin) Last() CheckinHit { var hit CheckinHit checkinHitsDB().Where("checkin = ?", u.Id).Last(&hit) return hit @@ -79,13 +80,18 @@ func (u *Checkin) Hits() []CheckinHit { return checkins } -func (u *Checkin) Create() (int64, error) { - if u.CreatedAt.IsZero() { - u.CreatedAt = time.Now() +func (u *Checkin) Update() (int64, error) { + fmt.Println("updating: ", u.Id, u.ApiKey) + exists := checkinDB().Find(&u).RecordNotFound() + var row *gorm.DB + if !exists { + row = checkinDB().Update(&u) + } else { + u.ApiKey = utils.RandomString(7) + row = checkinDB().Create(&u) } - u.ApiKey = utils.NewSHA1Hash(7) - row := checkinDB().Create(u) - if row.Error == nil { + fmt.Println("found: ", exists, u.Id, u.Service, u.Interval, u.GracePeriod, u.ApiKey) + if row.Error != nil { utils.Log(2, row.Error) return 0, row.Error } diff --git a/core/database.go b/core/database.go index 37652595..45c3b473 100644 --- a/core/database.go +++ b/core/database.go @@ -126,27 +126,37 @@ func (u *Hit) BeforeCreate() (err error) { } func (u *Failure) BeforeCreate() (err error) { - u.CreatedAt = time.Now().UTC() + if u.CreatedAt.IsZero() { + u.CreatedAt = time.Now().UTC() + } return } func (u *User) BeforeCreate() (err error) { - u.CreatedAt = time.Now().UTC() + if u.CreatedAt.IsZero() { + u.CreatedAt = time.Now().UTC() + } return } func (u *Service) BeforeCreate() (err error) { - u.CreatedAt = time.Now().UTC() + if u.CreatedAt.IsZero() { + u.CreatedAt = time.Now().UTC() + } return } func (u *Checkin) BeforeCreate() (err error) { - u.CreatedAt = time.Now().UTC() + if u.CreatedAt.IsZero() { + u.CreatedAt = time.Now().UTC() + } return } func (u *CheckinHit) BeforeCreate() (err error) { - u.CreatedAt = time.Now().UTC() + if u.CreatedAt.IsZero() { + u.CreatedAt = time.Now().UTC() + } return } diff --git a/core/sample.go b/core/sample.go index 1399dcd6..c98f612d 100644 --- a/core/sample.go +++ b/core/sample.go @@ -89,14 +89,14 @@ func InsertSampleData() error { Interval: 300, GracePeriod: 300, }) - checkin1.Create() + checkin1.Update() checkin2 := ReturnCheckin(&types.Checkin{ Service: s2.Id, Interval: 900, GracePeriod: 300, }) - checkin2.Create() + checkin2.Update() checkTime := time.Now().Add(-24 * time.Hour) for i := 0; i <= 60; i++ { diff --git a/core/services.go b/core/services.go index 58c19fa0..0f9b8536 100644 --- a/core/services.go +++ b/core/services.go @@ -49,10 +49,10 @@ func SelectService(id int64) *Service { return nil } -func (s *Service) Checkin() Checkin { - var hits Checkin - servicesDB().Where("service = ?", s.Id).First(&hits) - return hits +func (s *Service) Checkin() *Checkin { + var checkin types.Checkin + checkinDB().Find(&checkin, "service = ?", s.Id) + return &Checkin{&checkin} } // SelectAllServices returns a slice of *core.Service to be store on []*core.Services, should only be called once on startup. diff --git a/dev/aws/statup-install b/dev/aws/statup-install new file mode 100755 index 00000000..3e745ede --- /dev/null +++ b/dev/aws/statup-install @@ -0,0 +1,246 @@ +#!/usr/bin/env bash +VERSION="3.4.0" +VERBOSE=false +SSLOPTION=false +AWSREGION="us-west-2" +US_W_1="ami-7be8a103" +US_W_2="ami-7be8a103" +US_E_1="ami-7be8a103" +US_E_2="ami-7be8a103" +AWS_CLI=$(which aws) +AWS_ECS="$AWS_CLI --output json" + +function usage() { +cat < /dev/null 2>&1 || { + echo "Some of the required software is not installed:" + echo " please install $1" >&2; + exit 4; + } +} + +function setAWSPresets { + if [ -z ${AWS_DEFAULT_REGION+x} ]; + then unset AWS_DEFAULT_REGION + else + AWS_ECS="$AWS_ECS --region $AWS_DEFAULT_REGION" + fi + if [ -z ${AWS_PROFILE+x} ]; + then unset AWS_PROFILE + else + AWS_ECS="$AWS_ECS --profile $AWS_PROFILE" + fi +} + +function awsAskRegion { + if [ -z ${AWS_DEFAULT_REGION+x} ]; then + read -p "Enter the AWS Region: " AWSREGION + else + AWSREGION=$AWS_DEFAULT_REGION + fi +} + +function askEC2Name { + read -p "Enter the Name for EC2 Instance: " SERVERNAME +} + +function askSSLOption { + read -p "Do you want to install a SSL certificate? (y/N):" SSLOPTION +} + +function askSSLDomain { + read -p "Enter the Domain to attach the SSL certificate on: " SSLDOMAIN +} + +function askSSLEmail { + read -p "Enter the Email for Lets Encrypt: " SSLEMAIL +} + +function askEC2KeyName { + read -p "Enter the Keypair for EC2 Instance: " EC2KEYNAME +} + +function askSecurityName { + read -p "Enter a name for the new Security Group: " EC2SECGROUP +} + +function awsSecurityGroup { + echo "Running task: Creating Security Group"; + GROUPID=`$AWS_ECS ec2 create-security-group --group-name "$EC2SECGROUP" --description "Statup HTTP Server on port 80 and 443" | jq -r .GroupId` + echo "Created new security group: $GROUPID"; + awsAuthSecurityGroup +} + +function awsAuthSecurityGroup { + $AWS_ECS ec2 authorize-security-group-ingress --group-id $GROUPID --protocol tcp --port 80 --cidr 0.0.0.0/0 + $AWS_ECS ec2 authorize-security-group-ingress --group-id $GROUPID --protocol tcp --port 443 --cidr 0.0.0.0/0 + echo "Authorize security group to be open on ports 80 and 443"; +} + +function awsCreateEC2 { + NEW_SRV=`$AWS_ECS ec2 run-instances --image-id $US_W_2 --count 1 --instance-type t2.nano --key-name $EC2KEYNAME --security-group-ids $GROUPID` + INSTANCE_ID=`echo $NEW_SRV | jq .Instances[0].InstanceId` + EC2_STATUS=`echo $NEW_SRV | .Instances[0].StateReason.Message` + echo "New EC2 instance created: $INSTANCE_ID with status $EC2_STATUS"; +} + +function ec2TaskComplete { + echo "New EC2 instance is ready! $INSTANCE_ID with status $EC2_STATUS"; + echo "Instance ID: $INSTANCE_ID with status $EC2_STATUS"; + echo "Public DNS: $EC2_DNS"; + if [ $SSLOPTION == "y" ]; then + echo "Now you have to add a CNAME DNS record on $SSLDOMAIN pointing to $EC2_DNS" + fi +} + +function checkEC2Instance { + SRV_INFO=`$AWS_ECS ec2 describe-instances --instance-ids $INSTANCE_ID` + EC2_STATUS=$(echo "${SRV_INFO}" | jq .Reservations[0].Instances[0].State.Name) + EC2_DNS=$(echo "${SRV_INFO}" | jq .Reservations[0].Instances[0].PublicDnsName) + EC2_STATUS=$(echo "${SRV_INFO}" | jq .Reservations[0].Instances[0].State.Name) + if [ $EC2_STATUS == '"pending"' ]; then + echo "EC2 instance is still being created: $INSTANCE_ID"; + sleep 3 + checkEC2Instance + fi +} + +function awsTest { + INSTANCE_ID="i-0768e3d5ba00897af" + checkEC2Instance + ec2TaskComplete +} + +function awsTask { + setAWSPresets + askEC2Name + awsAskRegion + askSecurityName + askEC2KeyName + askSSLOption + if [ $SSLOPTION == "y" ]; then + askSSLDomain + askSSLEmail + fi + awsSecurityGroup + awsCreateEC2 + checkEC2Instance + ec2TaskComplete +} + +function localTask { + echo "installing locally" +} + +function dockerTask { + echo "installing docker" +} + +function dockerComposeTask { + echo "installing docker compose" +} + +if [ "$BASH_SOURCE" == "$0" ]; then + set -o errexit + set -o pipefail + set -u + set -e + # If no args are provided, display usage information + if [ $# == 0 ]; then usage; fi + + COMMD=$1 + + # Loop through arguments, two at a time for key and value + while [[ $# -gt 0 ]] + do + key="$1" + + case $key in + -k|--aws-access-key) + AWS_ACCESS_KEY_ID="$2" + shift # past argument + ;; + -s|--aws-secret-key) + AWS_SECRET_ACCESS_KEY="$2" + shift # past argument + ;; + -r|--region) + AWS_DEFAULT_REGION="$2" + shift # past argument + ;; + -x|--verbose) + VERBOSE=true + ;; + -v|--version) + echo ${VERSION} + echo "$2" + usage + exit 2 + exit 0 + ;; + *) + ;; + esac + shift # past argument or value + done + + if [ $VERBOSE == true ]; then + set -x + fi + + case $COMMD in + aws) + require aws + require jq + awsTask + exit 0 + ;; + awstest) + require aws + require jq + awsTest + exit 0 + ;; + docker) + require docker + dockerTask + exit 0 + ;; + docker-compose) + require docker-compose + dockerComposeTask + exit 0 + ;; + local) + localTask + shift # past argument + ;; + *) + ;; + esac + shift # past argument or value + fi + exit 0 + +fi diff --git a/handlers/handlers.go b/handlers/handlers.go index 435abe76..b29a4ee5 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -141,20 +141,8 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap return utils.Timestamp(t).Ago() }, "Duration": func(t time.Duration) string { - var out string duration, _ := time.ParseDuration(fmt.Sprintf("%vs", t.Seconds())) - if duration.Minutes() < 1 { - out = fmt.Sprintf("%0.0f second", duration.Seconds()) - if duration.Seconds() >= 2 { - out += "s" - } - } else { - out = fmt.Sprintf("%0.0f minute", duration.Minutes()) - if duration.Minutes() >= 2 { - out += "s" - } - } - return out + return utils.FormatDuration(duration) }, "ToUnix": func(t time.Time) int64 { return t.UTC().Unix() diff --git a/handlers/routes.go b/handlers/routes.go index b1fab9e2..a8dd39db 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -53,7 +53,6 @@ func Router() *mux.Router { r.Handle("/dashboard", http.HandlerFunc(dashboardHandler)).Methods("GET") r.Handle("/dashboard", http.HandlerFunc(loginHandler)).Methods("POST") r.Handle("/logout", http.HandlerFunc(logoutHandler)) - r.Handle("/checkin/{id}", http.HandlerFunc(checkinUpdateHandler)) r.Handle("/plugins/download/{name}", http.HandlerFunc(pluginsDownloadHandler)) r.Handle("/plugins/{name}/save", http.HandlerFunc(pluginSavedHandler)).Methods("POST") r.Handle("/help", http.HandlerFunc(helpHandler)) @@ -87,6 +86,7 @@ func Router() *mux.Router { r.Handle("/service/{id}/delete", http.HandlerFunc(servicesDeleteHandler)) r.Handle("/service/{id}/delete_failures", http.HandlerFunc(servicesDeleteFailuresHandler)).Methods("GET") r.Handle("/service/{id}/checkin", http.HandlerFunc(checkinCreateUpdateHandler)).Methods("POST") + r.Handle("/checkin/{id}", http.HandlerFunc(checkinUpdateHandler)) // API SERVICE Routes r.Handle("/api/services", http.HandlerFunc(apiAllServicesHandler)).Methods("GET") diff --git a/handlers/services.go b/handlers/services.go index 1f7345eb..ac42709f 100644 --- a/handlers/services.go +++ b/handlers/services.go @@ -261,15 +261,12 @@ func checkinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) { } vars := mux.Vars(r) service := core.SelectService(utils.StringInt(vars["id"])) - + checkin := service.Checkin() interval := utils.StringInt(r.PostForm.Get("interval")) grace := utils.StringInt(r.PostForm.Get("grace")) - checkin := core.ReturnCheckin(&types.Checkin{ - Service: service.Id, - Interval: interval, - GracePeriod: grace, - }) - checkin.Create() + checkin.Interval = interval + checkin.GracePeriod = grace + checkin.Update() executeResponse(w, r, "service.html", service, fmt.Sprintf("/service/%v", service.Id)) } diff --git a/source/tmpl/form_checkin.html b/source/tmpl/form_checkin.html index e87f8cd9..5f6f2310 100644 --- a/source/tmpl/form_checkin.html +++ b/source/tmpl/form_checkin.html @@ -1,17 +1,16 @@ {{define "form_checkin"}} -
+
-
- - +
+ +
-
- - +
+ +
-
-
-
+
+
diff --git a/source/tmpl/service.html b/source/tmpl/service.html index 8886a325..ef11eacd 100644 --- a/source/tmpl/service.html +++ b/source/tmpl/service.html @@ -77,15 +77,7 @@ {{if Auth}} -
- -

Edit Service

- - {{template "form_service" $s}} - -
- -
+

Last Response

@@ -96,28 +88,33 @@
+
+

Edit Service

+ {{template "form_service" $s}} +
+

Service Checkin

- - - - - - + + + + + + {{$ch := $s.Checkin}} - + - +
CheckinReport Period
Grace Period
Last SeenExpected
CheckinReport Period
Grace Period
Last SeenExpected
{{CoreApp.Domain}}/checkin/{{$ch.ApiKey}}every {{Duration $ch.Period}}
{{Duration $ch.Grace}} grace period
every {{Duration $ch.Period}}
after {{Duration $ch.Grace}}
{{Ago $ch.Last.CreatedAt}} {{ if lt $ch.Expected 0}}{{Duration $ch.Expected}} ago{{else}}in {{Duration $ch.Expected}}{{end}}
- {{template "form_checkin" $s}} + {{template "form_checkin" $ch}}
diff --git a/types/service.go b/types/service.go index 459846d9..7328a962 100644 --- a/types/service.go +++ b/types/service.go @@ -46,7 +46,6 @@ type Service struct { LastStatusCode int `gorm:"-" json:"status_code"` LastOnline time.Time `gorm:"-" json:"last_online"` Failures []interface{} `gorm:"-" json:"failures,omitempty"` - //Checkins []*Checkin `gorm:"-" json:"checkins,omitempty"` } type ServiceInterface interface { diff --git a/utils/time.go b/utils/time.go new file mode 100644 index 00000000..c0688840 --- /dev/null +++ b/utils/time.go @@ -0,0 +1,78 @@ +// Statup +// Copyright (C) 2018. Hunter Long and the project contributors +// Written by Hunter Long 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 . + +package utils + +import ( + "fmt" + "time" +) + +func FormatDuration(d time.Duration) string { + var out string + if d.Hours() >= 24 { + out = fmt.Sprintf("%0.0f day", d.Hours()/24) + if (d.Hours() / 24) >= 2 { + out += "s" + } + return out + } else if d.Hours() >= 1 { + out = fmt.Sprintf("%0.0f hour", d.Hours()) + if d.Hours() >= 2 { + out += "s" + } + return out + } else if d.Minutes() >= 1 { + out = fmt.Sprintf("%0.0f minute", d.Minutes()) + if d.Minutes() >= 2 { + out += "s" + } + return out + } else if d.Seconds() >= 1 { + out = fmt.Sprintf("%0.0f second", d.Seconds()) + if d.Seconds() >= 2 { + out += "s" + } + return out + } else if rev(d.Hours()) >= 24 { + out = fmt.Sprintf("%0.0f day", rev(d.Hours()/24)) + if rev(d.Hours()/24) >= 2 { + out += "s" + } + return out + } else if rev(d.Hours()) >= 1 { + out = fmt.Sprintf("%0.0f hour", rev(d.Hours())) + if rev(d.Hours()) >= 2 { + out += "s" + } + return out + } else if rev(d.Minutes()) >= 1 { + out = fmt.Sprintf("%0.0f minute", rev(d.Minutes())) + if rev(d.Minutes()) >= 2 { + out += "s" + } + return out + } else { + out = fmt.Sprintf("%0.0f second", rev(d.Seconds())) + if rev(d.Seconds()) >= 2 { + out += "s" + } + return out + } +} + +func rev(f float64) float64 { + return f * -1 +}