restructure the API and update the admin script

This commit is contained in:
ngn
2025-01-04 00:00:10 +03:00
parent 03586da8df
commit 26e8909998
34 changed files with 1699 additions and 983 deletions

105
api/status/service.go Normal file
View File

@ -0,0 +1,105 @@
package status
import (
"net/http"
"net/http/httptrace"
"net/url"
"time"
"github.com/ngn13/website/api/database"
"github.com/ngn13/website/api/util"
)
const (
STATUS_RES_DOWN = 0 // service is down
STATUS_RES_OK = 1 // service is up
STATUS_RES_SLOW = 2 // service is up, but slow
STATUS_RES_NONE = 3 // service doesn't support status checking/status checking is disabled
)
func (s *Type) check_http_service(service *database.Service) (r uint8, err error) {
var (
req *http.Request
res *http.Response
start time.Time
elapsed time.Duration
)
r = STATUS_RES_NONE
if req, err = http.NewRequest("GET", service.CheckURL, nil); err != nil {
return
}
trace := &httptrace.ClientTrace{
GetConn: func(_ string) { start = time.Now() },
GotFirstResponseByte: func() { elapsed = time.Since(start) },
}
http.DefaultClient.Timeout = s.timeout
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
res, err = http.DefaultClient.Do(req)
if res != nil {
defer res.Body.Close()
}
if err != nil {
util.Debg("marking service \"%s\" as down (%s)", service.Name, err.Error())
err = nil
r = STATUS_RES_DOWN
} else if res.StatusCode != 200 {
util.Debg("marking service \"%s\" as down (status code %d)", service.Name, res.StatusCode)
r = STATUS_RES_DOWN
} else if elapsed.Microseconds() > s.limit.Microseconds() {
r = STATUS_RES_SLOW
} else {
r = STATUS_RES_OK
}
return
}
func (s *Type) check_service(service *database.Service) error {
var (
res uint8
url *url.URL
err error
)
if s.disabled || service.CheckURL == "" {
err = nil
goto fail
}
if url, err = url.Parse(service.CheckURL); err != nil {
return err
}
switch url.Scheme {
case "https":
if res, err = s.check_http_service(service); err != nil {
goto fail
}
case "http":
if res, err = s.check_http_service(service); err != nil {
goto fail
}
default:
// unsupported protocol
err = nil
goto fail
}
service.CheckTime = uint64(time.Now().Unix())
service.CheckRes = res
return nil
fail:
service.CheckTime = 0
service.CheckRes = STATUS_RES_NONE
return err
}

139
api/status/status.go Normal file
View File

@ -0,0 +1,139 @@
package status
import (
"fmt"
"time"
"github.com/ngn13/website/api/config"
"github.com/ngn13/website/api/database"
"github.com/ngn13/website/api/util"
)
type Type struct {
conf *config.Type
db *database.Type
ticker *time.Ticker
updateChan chan int
closeChan chan int
disabled bool
timeout time.Duration
limit time.Duration
}
func (s *Type) check() {
var (
services []database.Service
service database.Service
err error
)
for s.db.ServiceNext(&service) {
services = append(services, service)
}
for i := range services {
if err = s.check_service(&services[i]); err != nil {
util.Fail("failed to check the service status for \"%s\": %s", services[i].Name, err.Error())
}
if err = s.db.ServiceUpdate(&services[i]); err != nil {
util.Fail("failed to update service status for \"%s\": %s", services[i].Name, err.Error())
}
}
}
func (s *Type) loop() {
s.check()
for {
select {
case <-s.closeChan:
close(s.updateChan)
s.ticker.Stop()
s.closeChan <- 0
return
case <-s.updateChan:
s.check()
case <-s.ticker.C:
s.check()
}
}
}
func (s *Type) Setup(conf *config.Type, db *database.Type) error {
var (
dur time.Duration
iv, to, lm string
err error
)
iv = conf.GetStr("interval")
to = conf.GetStr("timeout")
lm = conf.GetStr("limit")
if iv == "" || to == "" || lm == "" {
s.disabled = true
return nil
}
if dur, err = util.GetDuration(iv); err != nil {
return err
}
if s.timeout, err = util.GetDuration(iv); err != nil {
return err
}
if s.limit, err = util.GetDuration(iv); err != nil {
return err
}
s.conf = conf
s.db = db
s.ticker = time.NewTicker(dur)
s.updateChan = make(chan int)
s.closeChan = make(chan int)
s.disabled = false
return nil
}
func (s *Type) Run() error {
if s.ticker == nil || s.updateChan == nil || s.closeChan == nil {
return fmt.Errorf("you either didn't call Setup() or you called it and it failed")
}
if s.disabled {
go s.check()
return nil
}
go s.loop()
return nil
}
func (s *Type) Check() {
if !s.disabled {
s.updateChan <- 0
}
}
func (s *Type) Stop() {
// tell loop() to stop
s.closeChan <- 0
// wait till loop() stops
for {
select {
case <-s.closeChan:
close(s.closeChan)
return
}
}
}