checkins - installer shell script

pull/78/head
Hunter Long 2018-10-04 21:28:38 -07:00
parent 9a6c91a997
commit ddb2a10a7c
12 changed files with 391 additions and 71 deletions

View File

@ -20,6 +20,7 @@ import (
"github.com/ararog/timeago" "github.com/ararog/timeago"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils" "github.com/hunterlong/statup/utils"
"github.com/jinzhu/gorm"
"time" "time"
) )
@ -49,17 +50,17 @@ func SelectCheckin(api string) *Checkin {
return &checkin return &checkin
} }
func (u Checkin) Period() time.Duration { func (u *Checkin) Period() time.Duration {
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", u.Interval)) duration, _ := time.ParseDuration(fmt.Sprintf("%vs", u.Interval))
return duration return duration
} }
func (u Checkin) Grace() time.Duration { func (u *Checkin) Grace() time.Duration {
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", u.GracePeriod)) duration, _ := time.ParseDuration(fmt.Sprintf("%vs", u.GracePeriod))
return duration return duration
} }
func (u Checkin) Expected() time.Duration { func (u *Checkin) Expected() time.Duration {
last := u.Last().CreatedAt last := u.Last().CreatedAt
now := time.Now() now := time.Now()
lastDir := now.Sub(last) lastDir := now.Sub(last)
@ -67,7 +68,7 @@ func (u Checkin) Expected() time.Duration {
return sub return sub
} }
func (u Checkin) Last() CheckinHit { func (u *Checkin) Last() CheckinHit {
var hit CheckinHit var hit CheckinHit
checkinHitsDB().Where("checkin = ?", u.Id).Last(&hit) checkinHitsDB().Where("checkin = ?", u.Id).Last(&hit)
return hit return hit
@ -79,13 +80,18 @@ func (u *Checkin) Hits() []CheckinHit {
return checkins return checkins
} }
func (u *Checkin) Create() (int64, error) { func (u *Checkin) Update() (int64, error) {
if u.CreatedAt.IsZero() { fmt.Println("updating: ", u.Id, u.ApiKey)
u.CreatedAt = time.Now() 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) fmt.Println("found: ", exists, u.Id, u.Service, u.Interval, u.GracePeriod, u.ApiKey)
row := checkinDB().Create(u) if row.Error != nil {
if row.Error == nil {
utils.Log(2, row.Error) utils.Log(2, row.Error)
return 0, row.Error return 0, row.Error
} }

View File

@ -126,27 +126,37 @@ func (u *Hit) BeforeCreate() (err error) {
} }
func (u *Failure) BeforeCreate() (err error) { func (u *Failure) BeforeCreate() (err error) {
u.CreatedAt = time.Now().UTC() if u.CreatedAt.IsZero() {
u.CreatedAt = time.Now().UTC()
}
return return
} }
func (u *User) BeforeCreate() (err error) { func (u *User) BeforeCreate() (err error) {
u.CreatedAt = time.Now().UTC() if u.CreatedAt.IsZero() {
u.CreatedAt = time.Now().UTC()
}
return return
} }
func (u *Service) BeforeCreate() (err error) { func (u *Service) BeforeCreate() (err error) {
u.CreatedAt = time.Now().UTC() if u.CreatedAt.IsZero() {
u.CreatedAt = time.Now().UTC()
}
return return
} }
func (u *Checkin) BeforeCreate() (err error) { func (u *Checkin) BeforeCreate() (err error) {
u.CreatedAt = time.Now().UTC() if u.CreatedAt.IsZero() {
u.CreatedAt = time.Now().UTC()
}
return return
} }
func (u *CheckinHit) BeforeCreate() (err error) { func (u *CheckinHit) BeforeCreate() (err error) {
u.CreatedAt = time.Now().UTC() if u.CreatedAt.IsZero() {
u.CreatedAt = time.Now().UTC()
}
return return
} }

View File

@ -89,14 +89,14 @@ func InsertSampleData() error {
Interval: 300, Interval: 300,
GracePeriod: 300, GracePeriod: 300,
}) })
checkin1.Create() checkin1.Update()
checkin2 := ReturnCheckin(&types.Checkin{ checkin2 := ReturnCheckin(&types.Checkin{
Service: s2.Id, Service: s2.Id,
Interval: 900, Interval: 900,
GracePeriod: 300, GracePeriod: 300,
}) })
checkin2.Create() checkin2.Update()
checkTime := time.Now().Add(-24 * time.Hour) checkTime := time.Now().Add(-24 * time.Hour)
for i := 0; i <= 60; i++ { for i := 0; i <= 60; i++ {

View File

@ -49,10 +49,10 @@ func SelectService(id int64) *Service {
return nil return nil
} }
func (s *Service) Checkin() Checkin { func (s *Service) Checkin() *Checkin {
var hits Checkin var checkin types.Checkin
servicesDB().Where("service = ?", s.Id).First(&hits) checkinDB().Find(&checkin, "service = ?", s.Id)
return hits return &Checkin{&checkin}
} }
// SelectAllServices returns a slice of *core.Service to be store on []*core.Services, should only be called once on startup. // SelectAllServices returns a slice of *core.Service to be store on []*core.Services, should only be called once on startup.

246
dev/aws/statup-install Executable file
View File

@ -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 <<EOM
##### Statup Installer #####
A simple shell script that will help you install Statup on your local machine, AWS, or Docker.
Commands:
aws Create a new EC2 instance running Statup
docker Start the latest Statup Docker image
docker-compose Create Statup with a Postgres database
Available Flags:
-k | --aws-access-key AWS Access Key ID. May also be set as environment variable AWS_ACCESS_KEY_ID
-s | --aws-secret-key AWS Secret Access Key. May also be set as environment variable AWS_SECRET_ACCESS_KEY
-r | --region AWS Region Name. May also be set as environment variable AWS_DEFAULT_REGION
-v | --version Print out the current version of this tool
-x | --verbose Verbose output
Visit the github repo at: https://github.com/hunterlong/statup
EOM
exit 3
}
# Check requirements
function require() {
command -v "$1" > /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

View File

@ -141,20 +141,8 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap
return utils.Timestamp(t).Ago() return utils.Timestamp(t).Ago()
}, },
"Duration": func(t time.Duration) string { "Duration": func(t time.Duration) string {
var out string
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", t.Seconds())) duration, _ := time.ParseDuration(fmt.Sprintf("%vs", t.Seconds()))
if duration.Minutes() < 1 { return utils.FormatDuration(duration)
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
}, },
"ToUnix": func(t time.Time) int64 { "ToUnix": func(t time.Time) int64 {
return t.UTC().Unix() return t.UTC().Unix()

View File

@ -53,7 +53,6 @@ func Router() *mux.Router {
r.Handle("/dashboard", http.HandlerFunc(dashboardHandler)).Methods("GET") r.Handle("/dashboard", http.HandlerFunc(dashboardHandler)).Methods("GET")
r.Handle("/dashboard", http.HandlerFunc(loginHandler)).Methods("POST") r.Handle("/dashboard", http.HandlerFunc(loginHandler)).Methods("POST")
r.Handle("/logout", http.HandlerFunc(logoutHandler)) r.Handle("/logout", http.HandlerFunc(logoutHandler))
r.Handle("/checkin/{id}", http.HandlerFunc(checkinUpdateHandler))
r.Handle("/plugins/download/{name}", http.HandlerFunc(pluginsDownloadHandler)) r.Handle("/plugins/download/{name}", http.HandlerFunc(pluginsDownloadHandler))
r.Handle("/plugins/{name}/save", http.HandlerFunc(pluginSavedHandler)).Methods("POST") r.Handle("/plugins/{name}/save", http.HandlerFunc(pluginSavedHandler)).Methods("POST")
r.Handle("/help", http.HandlerFunc(helpHandler)) 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", http.HandlerFunc(servicesDeleteHandler))
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(servicesDeleteFailuresHandler)).Methods("GET") r.Handle("/service/{id}/delete_failures", http.HandlerFunc(servicesDeleteFailuresHandler)).Methods("GET")
r.Handle("/service/{id}/checkin", http.HandlerFunc(checkinCreateUpdateHandler)).Methods("POST") r.Handle("/service/{id}/checkin", http.HandlerFunc(checkinCreateUpdateHandler)).Methods("POST")
r.Handle("/checkin/{id}", http.HandlerFunc(checkinUpdateHandler))
// API SERVICE Routes // API SERVICE Routes
r.Handle("/api/services", http.HandlerFunc(apiAllServicesHandler)).Methods("GET") r.Handle("/api/services", http.HandlerFunc(apiAllServicesHandler)).Methods("GET")

View File

@ -261,15 +261,12 @@ func checkinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) {
} }
vars := mux.Vars(r) vars := mux.Vars(r)
service := core.SelectService(utils.StringInt(vars["id"])) service := core.SelectService(utils.StringInt(vars["id"]))
checkin := service.Checkin()
interval := utils.StringInt(r.PostForm.Get("interval")) interval := utils.StringInt(r.PostForm.Get("interval"))
grace := utils.StringInt(r.PostForm.Get("grace")) grace := utils.StringInt(r.PostForm.Get("grace"))
checkin := core.ReturnCheckin(&types.Checkin{ checkin.Interval = interval
Service: service.Id, checkin.GracePeriod = grace
Interval: interval, checkin.Update()
GracePeriod: grace,
})
checkin.Create()
executeResponse(w, r, "service.html", service, fmt.Sprintf("/service/%v", service.Id)) executeResponse(w, r, "service.html", service, fmt.Sprintf("/service/%v", service.Id))
} }

View File

@ -1,17 +1,16 @@
{{define "form_checkin"}} {{define "form_checkin"}}
<form action="/service/{{.Id}}/checkin" method="POST"> <form action="/checkin/{{.Id}}" method="POST">
<div class="form-group row"> <div class="form-group row">
<div class="col-md-12 col-sm-12"> <div class="col-md-4 col-sm-4">
<label for="checkin_interval" class="col-sm-4 col-form-label">Check Interval (in seconds)</label> <label for="checkin_interval" class="col-form-label">Check Interval (in seconds)</label>
<input type="number" name="interval" class="form-control" id="checkin_interval" value="30" placeholder="60"> <input type="number" name="interval" class="form-control" id="checkin_interval" value="{{.Interval}}" placeholder="60">
</div> </div>
<div class="col-md-12 col-sm-12"> <div class="col-md-4 col-sm-4">
<label for="grace_period" class="col-sm-4 col-form-label">Grace Period </label> <label for="grace_period" class="col-form-label">Grace Period</label>
<input type="number" name="grace" class="form-control" id="grace_period" value="30" placeholder="60"> <input type="number" name="grace" class="form-control" id="grace_period" value="{{.GracePeriod}}" placeholder="60">
</div> </div>
</div> <div class="col-md-4 col-sm-4">
<div class="form-group row"> <label for="checkin_interval" class="col-form-label"></label>
<div class="col-sm-10">
<button type="submit" class="btn btn-success d-block">Save Checkin</button> <button type="submit" class="btn btn-success d-block">Save Checkin</button>
</div> </div>
</div> </div>

View File

@ -77,15 +77,7 @@
{{if Auth}} {{if Auth}}
<div class="col-12 mt-4"> <div class="col-12 mt-4{{if ne $s.Type "http"}} d-none{{end}}">
<h3>Edit Service</h3>
{{template "form_service" $s}}
</div>
<div class="col-12 mt-4{{if eq $s.Type "tcp"}} d-none{{end}}">
<h3>Last Response</h3> <h3>Last Response</h3>
<textarea rows="8" class="form-control" readonly>{{ $s.LastResponse }}</textarea> <textarea rows="8" class="form-control" readonly>{{ $s.LastResponse }}</textarea>
<div class="form-group row mt-2"> <div class="form-group row mt-2">
@ -96,28 +88,33 @@
</div> </div>
</div> </div>
<div class="col-12 mt-4">
<h3>Edit Service</h3>
{{template "form_service" $s}}
</div>
<div class="col-12 mt-4"> <div class="col-12 mt-4">
<h3>Service Checkin</h3> <h3>Service Checkin</h3>
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th scope="col">Checkin</th> <th scope="col">Checkin</th>
<th scope="col">Report Period<br>Grace Period</th> <th scope="col">Report Period<br>Grace Period</th>
<th scope="col">Last Seen</th> <th scope="col">Last Seen</th>
<th scope="col">Expected</th> <th scope="col">Expected</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{$ch := $s.Checkin}} {{$ch := $s.Checkin}}
<tr> <tr class="{{ if lt $ch.Expected 0}}bg-danger text-white{{else}}bg-light{{end}}">
<td>{{CoreApp.Domain}}/checkin/{{$ch.ApiKey}}</td> <td>{{CoreApp.Domain}}/checkin/{{$ch.ApiKey}}</td>
<td>every {{Duration $ch.Period}}<br>{{Duration $ch.Grace}} grace period</td> <td>every {{Duration $ch.Period}}<br>after {{Duration $ch.Grace}}</td>
<td>{{Ago $ch.Last.CreatedAt}}</td> <td>{{Ago $ch.Last.CreatedAt}}</td>
<td>{{ if lt $ch.Expected 0}}{{Duration $ch.Expected}} ago{{else}}in {{Duration $ch.Expected}}{{end}}</td> <td>{{ if lt $ch.Expected 0}}{{Duration $ch.Expected}} ago{{else}}in {{Duration $ch.Expected}}{{end}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
{{template "form_checkin" $s}} {{template "form_checkin" $ch}}
</div> </div>

View File

@ -46,7 +46,6 @@ type Service struct {
LastStatusCode int `gorm:"-" json:"status_code"` LastStatusCode int `gorm:"-" json:"status_code"`
LastOnline time.Time `gorm:"-" json:"last_online"` LastOnline time.Time `gorm:"-" json:"last_online"`
Failures []interface{} `gorm:"-" json:"failures,omitempty"` Failures []interface{} `gorm:"-" json:"failures,omitempty"`
//Checkins []*Checkin `gorm:"-" json:"checkins,omitempty"`
} }
type ServiceInterface interface { type ServiceInterface interface {

78
utils/time.go Normal file
View File

@ -0,0 +1,78 @@
// 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 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
}