Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export const columns: ColumnDef<Subscriber>[] = [
const flavor =
sub.channelType === "webhook"
? detectFlavorBadge(sub.webhookUrl)
: null;
: sub.channelType === "slack"
? "Slack"
: null;

return (
<div className="flex items-center gap-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,17 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
statusPage?.pageComponentGroups ?? [],
);

const editDefaults = isVendorAdded
? {
channelType: sub.channelType,
name: sub.name ?? "",
email: sub.email ?? "",
webhookUrl: sub.webhookUrl ?? "",
headers: parseHeaders(sub.channelConfig),
componentIds: sub.components.map((c) => c.id),
}
: undefined;
const editDefaults =
isVendorAdded && sub.channelType !== "slack"
? {
channelType: sub.channelType,
name: sub.name ?? "",
email: sub.email ?? "",
webhookUrl: sub.webhookUrl ?? "",
headers: parseHeaders(sub.channelConfig),
componentIds: sub.components.map((c) => c.id),
}
: undefined;

const actions = [
...(isVendorAdded
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
Expand Down
16 changes: 16 additions & 0 deletions apps/private-location/buf.gen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# buf.gen.yaml defines a local generation template.
# For details, see https://buf.build/docs/configuration/v2/buf-gen-yaml
version: v2
managed:
enabled: true

#
# This is not beautiful but while in dev let's generate it twice
plugins:
- local: protoc-gen-go
out: gen

- local: protoc-gen-connect-go
out: ger
opt:
- paths=source_relative
8 changes: 8 additions & 0 deletions apps/private-location/buf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: v2

modules:
- path: .
name: buf.build/openstatus/private-location
lint:
use:
- STANDARD
60 changes: 60 additions & 0 deletions apps/private-location/cmd/probe/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"context"
"fmt"

"os"
"os/signal"
"syscall"
"time"

"github.com/openstatushq/openstatus/apps/private-location/internal/scheduler"
)

const (
configRefreshInterval = 1 * time.Minute
)


func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Graceful shutdown on interrupt
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
cancel()
}()

monitorManager := scheduler.MonitorManager{
HttpMonitors: make(map[string]*v1.HTTPMonitor),
MonitorChannels: make(map[string]chan bool),
}

apiKey := getEnv("OPENSTATUS_KEY", "key")

configTicker := time.NewTicker(configRefreshInterval)
defer configTicker.Stop()
monitorManager.UpdateMonitors(apiKey)
for {
select {
case <-ctx.Done():
return
case <-configTicker.C:
fmt.Println("fetching monitors")
monitorManager.UpdateMonitors(apiKey)
}
}
}

func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}

// UpdateMonitors fetches the latest monitors and starts/stops jobs as needed
179 changes: 179 additions & 0 deletions apps/private-location/internal/job/http_job.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package job

import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/cenkalti/backoff/v5"
"github.com/google/uuid"

v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1"
"github.com/openstatushq/openstatus/apps/checker/request"
)

func HTTPJob(monitor *v1.HTTPMonitor) (*HttpPingData, error) {
ctx := context.Background()

retry := monitor.Retry
if retry == 0 {
retry = 3
}

requestClient := &http.Client{
Timeout: time.Duration(monitor.Timeout) * time.Millisecond,
}
defer requestClient.CloseIdleConnections()

if !monitor.FollowRedirects {
requestClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
} else {
requestClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return http.ErrUseLastResponse
}
return nil
}
}

var degradedAfter int64
if monitor.DegradedAt != nil {
degradedAfter = *monitor.DegradedAt
}

headers := make([]struct {
Key string `json:"key"`
Value string `json:"value"`
}, 0)
if monitor.Headers != nil {
for _, header := range monitor.Headers {
headers = append(headers, struct {
Key string `json:"key"`
Value string `json:"value"`
}{
Key: header.Key,
Value: header.Value,
})
}
}

req := request.HttpCheckerRequest{
URL: monitor.Url,
MonitorID: monitor.Id,
Method: monitor.Method,
Body: monitor.Body,
Retry: monitor.Retry,
Timeout: monitor.Timeout,
DegradedAfter: degradedAfter,
FollowRedirects: monitor.FollowRedirects,
Headers: headers,
}

var called int


op := func() (*HttpPingData, error) {
called++
res, err := checker.Http(ctx, requestClient, req)
if err != nil {
return nil, fmt.Errorf("unable to ping: %w", err)
}

timingBytes, err := json.Marshal(res.Timing)
if err != nil {
return nil, fmt.Errorf("error while parsing timing data %s: %w", req.URL, err)
}
headersBytes, err := json.Marshal(res.Headers)
if err != nil {
return nil, fmt.Errorf("error while parsing headers %s: %w", req.URL, err)
}
id, err := uuid.NewV7()
if err != nil {
return nil, fmt.Errorf("error while generating uuid: %w", err)
}

status := statusCode(res.Status)
isSuccessful := status.IsSuccessful()
if len(monitor.HeaderAssertions) > 0 {
headersAsString, err := json.Marshal(res.Headers)
if err != nil {
return nil, fmt.Errorf("error while parsing headers %s: %w", req.URL, err)
}
for _, assertion := range monitor.HeaderAssertions {
assert := assertions.HeaderTarget{
Comparator: request.StringComparator(assertion.Comparator.String()),
Target: assertion.Target,
Key: assertion.Key,
}
assert.HeaderEvaluate(string(headersAsString))
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
}
}

if len(monitor.StatusCodeAssertions) > 0 {
for _, assertion := range monitor.StatusCodeAssertions {
assert := assertions.StatusTarget{
Comparator: request.NumberComparator(assertion.Comparator.String()),
Target: assertion.Target,
}
isSuccessful = isSuccessful && assert.StatusEvaluate(int64(res.Status))
}
}
if len(monitor.BodyAssertions) > 0 {
for _, assertion := range monitor.BodyAssertions {
assert := assertions.StringTargetType{
Comparator: request.StringComparator(assertion.Comparator.String()),
Target: assertion.Target,
}
isSuccessful = isSuccessful && assert.StringEvaluate(res.Body)
}
}

requestStatus := "success"
if !isSuccessful {
requestStatus = "error"
} else if req.DegradedAfter > 0 && res.Latency > req.DegradedAfter {
requestStatus = "degraded"
}

data := HttpPingData{
ID: id.String(),
Latency: res.Latency,
StatusCode: res.Status,
Timestamp: res.Timestamp,
CronTimestamp: req.CronTimestamp,
URL: req.URL,
Method: req.Method,
Timing: string(timingBytes),
Headers: string(headersBytes),
Body: "",
RequestStatus: requestStatus,
Error: 0,
}

if isSuccessful {
if req.DegradedAfter != 0 && res.Latency > req.DegradedAfter {
data.Body = res.Body
}
} else {
data.Error = 1
if called < int(retry) {
return nil, fmt.Errorf("unable to ping: %v with status %v", res, res.Status)
}
}



fmt.Println(data)
return &data, nil
}

resp, err := backoff.Retry(context.TODO(), op, backoff.WithMaxTries(uint(retry)), backoff.WithBackOff(backoff.NewExponentialBackOff()))
if err != nil {
return nil, err
}
return resp, nil
}
25 changes: 25 additions & 0 deletions apps/private-location/internal/job/job.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package job

type statusCode int

func (s statusCode) IsSuccessful() bool {
return s >= 200 && s < 300
}

type HttpPingData struct {
ID string `json:"id"`
URL string `json:"url"`
Method string `json:"method"`
Region string `json:"region"`
Message string `json:"message,omitempty"`
Timing string `json:"timing,omitempty"`
Headers string `json:"headers,omitempty"`
Assertions string `json:"assertions"`
Body string `json:"body,omitempty"`
RequestStatus string `json:"requestStatus,omitempty"`
Latency int64 `json:"latency"`
CronTimestamp int64 `json:"cronTimestamp"`
Timestamp int64 `json:"timestamp"`
StatusCode int `json:"statusCode,omitempty"`
Error uint8 `json:"error"`
}
45 changes: 45 additions & 0 deletions apps/private-location/internal/job/monitors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package job
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated

type Monitor struct {
ID int `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Periodicity string `json:"periodicity"`
Description string `json:"description"`
Method string `json:"method"`
Regions []string `json:"regions"`
Active bool `json:"active"`
Public bool `json:"public"`
Timeout int `json:"timeout"`
DegradedAfter int `json:"degraded_after,omitempty"`
Body string `json:"body"`
Headers []Header `json:"headers,omitempty"`
Assertions []Assertion `json:"assertions,omitempty"`
Retry int `json:"retry"`
JobType string `json:"jobType"`
}

type Header struct {
Key string `json:"key"`
Value string `json:"value"`
}

type Assertion struct {
Type string `json:"type"`
Compare string `json:"compare"`
Key string `json:"key"`
Target any `json:"target"`
}

type Timing struct {
DnsStart int64 `json:"dnsStart"`
DnsDone int64 `json:"dnsDone"`
ConnectStart int64 `json:"connectStart"`
ConnectDone int64 `json:"connectDone"`
TlsHandshakeStart int64 `json:"tlsHandshakeStart"`
TlsHandshakeDone int64 `json:"tlsHandshakeDone"`
FirstByteStart int64 `json:"firstByteStart"`
FirstByteDone int64 `json:"firstByteDone"`
TransferStart int64 `json:"transferStart"`
TransferDone int64 `json:"transferDone"`
}
Loading
Loading