Openstatus www.openstatus.dev
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

๐Ÿšง ๐ŸŒ ๐Ÿ“Œ ๐Ÿ”’ (#1396)

* ๐Ÿšง

* ci: apply automated fixes

* ci: apply automated fixes (attempt 2/3)

* ci: apply automated fixes (attempt 3/3)

* โœ๏ธ changelog (#1395)

* โœ๏ธ changelog

* ci: apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* wtf: rewrite

* wtf: rewrite

* wtf: rewrite

* fix: x-proxy

* wtf: rewrite

* wtf: rewrite

* wtf: rewrite

* fix: maintenance window minute per day (#1399)

* fix: tracker bar (#1398)

* fix: tracker bar

* ci: apply automated fixes

* chore: include post-mortem

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* chore: relaxed status-event messages

* blog: rss slack feed (#1397)

* blog: rss slack feed

* chore: definition and links

* fix: links

* chore: blog post

* fix: empty monitors list (#1401)

* fix: more stpg stuff (#1400)

* ๐Ÿค—

* ๐Ÿ˜‚

* ๐Ÿ˜ฑ

* ๐Ÿ˜‚

* ๐Ÿš€

* fix: notifications 404 (#1403)

* ๐Ÿบ

* ๐Ÿบ

* fix: format (#1405)

* chore: theme-store package (#1392)

* chore: theme-store package

* fix: tsc

* chore: status-page-configuration

* chore: note

* chore: note

* refactor: configuration token

* chore: docs status page beta (#1380)

* chore: docs status page beta

* chore: cookie note

* chore: update docs

* fix: small stuff (#1406)

* fix: manual validation (#1407)

* fix: status page provider validation

* fix: manual value

* chore: themes subdomain plausible (#1409)

* fix: keep regions chart data after update (#1408)

* fix: keep regions chart data after update

* chore: avoid trigger check if inactive

* chore: add footer description

* chore: form card info variant (#1411)

* fix: redirect monitor create (#1412)

* fix: redirect monitor create

* fix: resolved only order

* chore: docs and changelog

* chore: dx status-page env (#1413)

* chore: dx status-page env

* fix: tsconfig exclude env

* Dx improvment (#1414)

* ๐Ÿค—

* ๐Ÿ˜‚

* fix: og image (#1415)

* ๐ŸŒ koyeb (#1404)

* ๐ŸŒ

* ci: apply automated fixes

* ๐ŸŒ

* ๐Ÿค—

* ๐Ÿค—

* ๐ŸŒ koyeb

* ci: apply automated fixes

* ๐Ÿ”ฅ

* ๐ŸŒ

* ๐Ÿš€

* ci: apply automated fixes

* ๐Ÿ˜‚

* ci: apply automated fixes

* ๐Ÿ˜ญ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ci: apply automated fixes

* ๐Ÿ˜ฑ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ci: apply automated fixes

* hore: form-regions-provider-icon

* fix: code label

* chore: play checker

* fix: tsc

* fix: mock

* fix: tsc and stuff

* fix: dashboard regions

* fix: spacing

* chore: extract icon cloud provider component

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Maximilian Kaske <maximilian@kaske.org>

* ๐Ÿ”ฅ (#1417)

* chore: delete web dashboard (#1416)

* chore: delete web dashboard

* chore: delete ping

* fix: region column (#1418)

* chore: seo (#1419)

* chore: seo

* chore: seo

* chore: seo

* chore: sitemap

* chore: seo

* fix: og

* chore: seo

* chore: seo

* ๐Ÿš‘

* ๐Ÿ› Bug chart missing koyeb (#1420)

* ๐Ÿš‘

* ๐Ÿš‘

* chore: improve speed checker (#1422)

* chore: improve speed checker

* chore: seo content

* refactor: icon cloud provider tooltip

* chore: response logs cloud provider (#1423)

* fix: missing provider info (#1424)

* ๐Ÿš€ Add railway (#1421)

* ๐ŸŒ

* ๐ŸŒ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ci: apply automated fixes

* ๐ŸŒ

* ๐Ÿš€

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ci: apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: region code formatter (#1425)

* ๐Ÿ˜ฑ

* ๐Ÿš€

* ๐Ÿš€

* minor update

* wip

* ci: apply automated fixes

* ๐Ÿค—

* wip

* wip

* ๐Ÿš€

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿงช

* add just

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* should gen

* ๐Ÿ”ฅ

* ci: apply automated fixes

* feat: crud private regions

* feat: apply private regions in monitor form

* ๐Ÿ˜‚

* ๐ŸŽ๏ธ

* for prod

* ๐Ÿ”ฅ

* ๐Ÿš€

* chore: region search

* fix: table region trucate

* new migration

* ๐Ÿ”ฅ

* ๐Ÿ›

* feat: dashboard private locations

* ๐Ÿ”ฅ

* fix: web build

* ๐Ÿ”ฅ

* fix: limit regions

* chore: upgrade dialog based on limit

* wip: docs

* fix: docs

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Maximilian Kaske <maximilian@kaske.org>
Co-authored-by: Maximilian Kaske <56969857+mxkaske@users.noreply.github.com>

+11144 -617
-25
.github/workflows/checker.yml
··· 1 - name: Checker CI 2 - 3 - on: 4 - push: 5 - branches: 6 - - master 7 - tags: 8 - - '*.*.*' 9 - pull_request: 10 - branches: 11 - - '**' 12 - 13 - jobs: 14 - ci: 15 - name: Continuous Integration 16 - runs-on: ubuntu-latest 17 - timeout-minutes: 5 18 - steps: 19 - - uses: actions/checkout@v4 20 - - uses: actions/setup-go@v5 21 - with: 22 - go-version: '>=1.24.0' 23 - - name: Run test 24 - run: go test -timeout 30s -race -count=1 ./... 25 - working-directory: apps/checker
+31
.github/workflows/go-tests.yml
··· 1 + name: Go Tests 2 + 3 + on: 4 + push: 5 + branches: 6 + - master 7 + tags: 8 + - '*.*.*' 9 + pull_request: 10 + branches: 11 + - '**' 12 + paths: 13 + - "apps/checker/**" 14 + - "apps/private-location/**" 15 + 16 + jobs: 17 + ci: 18 + name: Continuous Integration 19 + runs-on: ubuntu-latest 20 + timeout-minutes: 5 21 + steps: 22 + - uses: actions/checkout@v4 23 + - uses: actions/setup-go@v5 24 + with: 25 + go-version: '>=1.25.0' 26 + - name: Run test Checker 27 + run: go test -timeout 30s -race -count=1 ./... 28 + working-directory: apps/checker 29 + - name: Run test Private Location 30 + run: go test -timeout 30s -race -count=1 ./... 31 + working-directory: apps/private-location
+27 -2
.github/workflows/publish-checker.yml
··· 10 10 IMAGE_NAME: checker 11 11 12 12 jobs: 13 - build: 13 + build-checker: 14 14 runs-on: ubuntu-latest 15 15 # Permissions to use OIDC token authentication 16 16 permissions: ··· 35 35 tags: ghcr.io/openstatushq/checker:latest 36 36 platforms: linux/amd64,linux/arm64 37 37 push: true 38 - 38 + build-private-location: 39 + runs-on: ubuntu-latest 40 + # Permissions to use OIDC token authentication 41 + permissions: 42 + contents: read 43 + id-token: write 44 + # Allows pushing to the GitHub Container Registry 45 + packages: write 46 + steps: 47 + - uses: actions/checkout@v3 48 + - uses: depot/setup-action@v1 49 + - name: Log in to the Container registry 50 + uses: docker/login-action@v3 51 + with: 52 + registry: ${{ env.REGISTRY }} 53 + username: ${{ github.repository_owner }} 54 + password: ${{ secrets.GITHUB_TOKEN }} 55 + - name: Build and push 56 + uses: depot/build-push-action@v1 57 + with: 58 + file: private-location.Dockerfile 59 + project: 9cknw183m8 60 + context: apps/checker 61 + tags: ghcr.io/openstatushq/private-location:latest 62 + platforms: linux/amd64,linux/arm64 63 + push: true
+3
apps/README.md
··· 1 + # Apps 2 + 3 + All openstatus apps
+1
apps/checker/.gitignore
··· 1 + tmp
+3 -3
apps/checker/.golangci.yml
··· 1 + version: "2" 2 + 1 3 linters: 2 - enable-all: true 3 - disable-all: false 4 - fast: true 4 + default: fast
+47
apps/checker/.private.air.toml
··· 1 + root = "." 2 + testdata_dir = "testdata" 3 + tmp_dir = "tmp" 4 + 5 + [build] 6 + args_bin = [] 7 + bin = "./tmp/main" 8 + cmd = "go build -o ./tmp/main ./cmd/private/main.go" 9 + delay = 1000 10 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] 11 + exclude_file = [] 12 + exclude_regex = ["_test.go"] 13 + exclude_unchanged = false 14 + follow_symlink = false 15 + full_bin = "" 16 + include_dir = [] 17 + include_ext = ["go", "tpl", "tmpl", "html"] 18 + include_file = [] 19 + kill_delay = "0s" 20 + log = "build-errors.log" 21 + poll = false 22 + poll_interval = 0 23 + post_cmd = [] 24 + pre_cmd = [] 25 + rerun = false 26 + rerun_delay = 500 27 + send_interrupt = false 28 + stop_on_error = false 29 + 30 + 31 + [color] 32 + app = "" 33 + build = "yellow" 34 + main = "magenta" 35 + runner = "green" 36 + watcher = "cyan" 37 + 38 + [log] 39 + main_only = false 40 + time = false 41 + 42 + [misc] 43 + clean_on_exit = false 44 + 45 + [screen] 46 + clear_on_rebuild = false 47 + keep_scroll = true
+47
apps/checker/.probe.air.toml
··· 1 + root = "." 2 + testdata_dir = "testdata" 3 + tmp_dir = "tmp" 4 + 5 + [build] 6 + args_bin = [] 7 + bin = "./tmp/main" 8 + cmd = "go build -o ./tmp/main ./cmd/server/main.go" 9 + delay = 1000 10 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] 11 + exclude_file = [] 12 + exclude_regex = ["_test.go"] 13 + exclude_unchanged = false 14 + follow_symlink = false 15 + full_bin = "" 16 + include_dir = [] 17 + include_ext = ["go", "tpl", "tmpl", "html"] 18 + include_file = [] 19 + kill_delay = "0s" 20 + log = "build-errors.log" 21 + poll = false 22 + poll_interval = 0 23 + post_cmd = [] 24 + pre_cmd = [] 25 + rerun = false 26 + rerun_delay = 500 27 + send_interrupt = false 28 + stop_on_error = false 29 + 30 + 31 + [color] 32 + app = "" 33 + build = "yellow" 34 + main = "magenta" 35 + runner = "green" 36 + watcher = "cyan" 37 + 38 + [log] 39 + main_only = false 40 + time = false 41 + 42 + [misc] 43 + clean_on_exit = false 44 + 45 + [screen] 46 + clear_on_rebuild = false 47 + keep_scroll = true
+1 -1
apps/checker/Dockerfile
··· 18 18 RUN go mod download 19 19 20 20 COPY . . 21 - RUN go build -trimpath -ldflags "-s -w" -o checker ./cmd 21 + RUN go build -trimpath -ldflags "-s -w" -o checker ./cmd/server 22 22 23 23 FROM scratch 24 24
apps/checker/cmd/main.go apps/checker/cmd/server/main.go
+99
apps/checker/cmd/private/main.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "net/http" 7 + 8 + "os" 9 + "os/signal" 10 + "syscall" 11 + "time" 12 + 13 + "connectrpc.com/connect" 14 + "github.com/madflojo/tasks" 15 + "github.com/openstatushq/openstatus/apps/checker/pkg/job" 16 + "github.com/openstatushq/openstatus/apps/checker/pkg/scheduler" 17 + 18 + v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" 19 + ) 20 + 21 + const ( 22 + configRefreshInterval = 1 * time.Minute 23 + ) 24 + 25 + func main() { 26 + ctx, cancel := context.WithCancel(context.Background()) 27 + defer cancel() 28 + 29 + // Graceful shutdown on interrupt 30 + sigChan := make(chan os.Signal, 1) 31 + signal.Notify(sigChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) 32 + go func() { 33 + <-sigChan 34 + cancel() 35 + }() 36 + 37 + s := tasks.New() 38 + defer s.Stop() 39 + 40 + apiKey := getEnv("OPENSTATUS_KEY", "my-secret-key") 41 + 42 + monitorManager := scheduler.MonitorManager{ 43 + Client: getClient(apiKey), 44 + JobRunner: job.NewJobRunner(), 45 + Scheduler: s, 46 + } 47 + configTicker := time.NewTicker(configRefreshInterval) 48 + defer configTicker.Stop() 49 + 50 + monitorManager.UpdateMonitors(ctx) 51 + for { 52 + select { 53 + case <-ctx.Done(): 54 + return 55 + case <-configTicker.C: 56 + fmt.Println("fetching monitors") 57 + monitorManager.UpdateMonitors(ctx) 58 + } 59 + } 60 + } 61 + 62 + func getEnv(key, fallback string) string { 63 + if value, ok := os.LookupEnv(key); ok { 64 + return value 65 + } 66 + return fallback 67 + } 68 + 69 + // UpdateMonitors fetches the latest monitors and starts/stops jobs as needed 70 + 71 + func getClient(apiKey string) v1.PrivateLocationServiceClient { 72 + client := v1.NewPrivateLocationServiceClient( 73 + http.DefaultClient, 74 + "https://openstatus-private-location.fly.dev", 75 + connect.WithHTTPGet(), 76 + connect.WithInterceptors(NewAuthInterceptor(apiKey)), 77 + ) 78 + 79 + return client 80 + } 81 + 82 + func NewAuthInterceptor(token string) connect.UnaryInterceptorFunc { 83 + 84 + interceptor := func(next connect.UnaryFunc) connect.UnaryFunc { 85 + return connect.UnaryFunc(func( 86 + ctx context.Context, 87 + req connect.AnyRequest, 88 + ) (connect.AnyResponse, error) { 89 + if req.Spec().IsClient { 90 + // Send a token with client requests. 91 + req.Header().Set("openstatus-token", token) 92 + } 93 + 94 + return next(ctx, req) 95 + }) 96 + } 97 + return connect.UnaryInterceptorFunc(interceptor) 98 + 99 + }
+12 -3
apps/checker/go.mod
··· 1 1 module github.com/openstatushq/openstatus/apps/checker 2 2 3 - go 1.24.0 3 + go 1.25.2 4 4 5 5 require ( 6 - github.com/cenkalti/backoff/v4 v4.3.0 6 + connectrpc.com/connect v1.19.1 7 7 github.com/gin-gonic/gin v1.9.1 8 8 github.com/google/uuid v1.6.0 9 9 github.com/rs/zerolog v1.32.0 ··· 13 13 go.opentelemetry.io/otel/metric v1.34.0 14 14 go.opentelemetry.io/otel/sdk v1.34.0 15 15 go.opentelemetry.io/otel/sdk/metric v1.34.0 16 + google.golang.org/protobuf v1.36.10 17 + ) 18 + 19 + //replace github.com/openstatushq/openstatus/packages/proto => ./../../packages/proto 20 + 21 + require ( 22 + github.com/cenkalti/backoff/v4 v4.3.0 23 + github.com/cenkalti/backoff/v5 v5.0.3 24 + github.com/madflojo/tasks v1.2.1 16 25 ) 17 26 18 27 require ( ··· 38 47 github.com/modern-go/reflect2 v1.0.2 // indirect 39 48 github.com/pelletier/go-toml/v2 v2.2.0 // indirect 40 49 github.com/pmezard/go-difflib v1.0.0 // indirect 50 + github.com/rs/xid v1.6.0 // indirect 41 51 github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 42 52 github.com/ugorji/go/codec v1.2.12 // indirect 43 53 go.opentelemetry.io/auto/sdk v1.1.0 // indirect ··· 51 61 google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect 52 62 google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect 53 63 google.golang.org/grpc v1.69.4 // indirect 54 - google.golang.org/protobuf v1.36.3 // indirect 55 64 gopkg.in/yaml.v3 v3.0.1 // indirect 56 65 )
+12 -4
apps/checker/go.sum
··· 1 + connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= 2 + connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= 1 3 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 4 github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= 3 5 github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= 4 6 github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= 5 7 github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 6 8 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 9 + github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= 10 + github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 7 11 github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 8 12 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 9 13 github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= ··· 39 43 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 40 44 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 41 45 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 42 - github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 43 - github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 46 + github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 47 + github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 44 48 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 45 49 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 46 50 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= ··· 58 62 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 59 63 github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 60 64 github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 65 + github.com/madflojo/tasks v1.2.1 h1:0HMN1RCVf6yDjrlIbthkET1KCB+gxknQG3/SLO+HHj4= 66 + github.com/madflojo/tasks v1.2.1/go.mod h1:/WMv6u3Xb5eyy+aIM76ildaIT166GOxN/jya9oI7dyo= 61 67 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 62 68 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 63 69 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= ··· 77 83 github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 78 84 github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 79 85 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 86 + github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= 87 + github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= 80 88 github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= 81 89 github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 82 90 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= ··· 133 141 google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= 134 142 google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= 135 143 google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 136 - google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= 137 - google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 144 + google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= 145 + google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 138 146 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 139 147 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 140 148 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+1 -5
apps/checker/handlers/checker.go
··· 11 11 "github.com/google/uuid" 12 12 "github.com/rs/zerolog/log" 13 13 14 - "github.com/openstatushq/openstatus/apps/checker" 14 + "github.com/openstatushq/openstatus/apps/checker/checker" 15 15 "github.com/openstatushq/openstatus/apps/checker/pkg/assertions" 16 16 otelOS "github.com/openstatushq/openstatus/apps/checker/pkg/otel" 17 17 "github.com/openstatushq/openstatus/apps/checker/request" ··· 64 64 return 65 65 } 66 66 } 67 - 68 67 69 68 var req request.HttpCheckerRequest 70 69 if err := c.ShouldBindJSON(&req); err != nil { ··· 147 146 switch req.Status { 148 147 case "active": 149 148 requestStatus = "success" 150 - break 151 149 case "error": 152 150 requestStatus = "error" 153 - break 154 151 case "degraded": 155 152 requestStatus = "degraded" 156 - break 157 153 } 158 154 159 155 data := PingData{
+1 -1
apps/checker/handlers/ping.go
··· 8 8 9 9 "github.com/cenkalti/backoff/v4" 10 10 "github.com/gin-gonic/gin" 11 - "github.com/openstatushq/openstatus/apps/checker" 11 + "github.com/openstatushq/openstatus/apps/checker/checker" 12 12 "github.com/openstatushq/openstatus/apps/checker/request" 13 13 "github.com/rs/zerolog/log" 14 14 )
+4 -6
apps/checker/handlers/tcp.go
··· 10 10 "github.com/cenkalti/backoff/v4" 11 11 "github.com/gin-gonic/gin" 12 12 "github.com/google/uuid" 13 - "github.com/openstatushq/openstatus/apps/checker" 13 + "github.com/openstatushq/openstatus/apps/checker/checker" 14 14 otelOS "github.com/openstatushq/openstatus/apps/checker/pkg/otel" 15 15 "github.com/openstatushq/openstatus/apps/checker/request" 16 16 "github.com/rs/zerolog/log" ··· 96 96 } 97 97 98 98 op := func() error { 99 - res, err := checker.PingTcp(int(req.Timeout), req.URI) 99 + res, err := checker.PingTCP(int(req.Timeout), req.URI) 100 100 101 101 if err != nil { 102 102 return fmt.Errorf("unable to check tcp %s", err) ··· 113 113 switch req.Status { 114 114 case "active": 115 115 requestStatus = "success" 116 - break 117 116 case "error": 118 117 requestStatus = "error" 119 - break 118 + 120 119 case "degraded": 121 120 requestStatus = "degraded" 122 - break 123 121 } 124 122 125 123 id, err := uuid.NewV7() ··· 282 280 op := func() error { 283 281 called++ 284 282 timestamp := time.Now().UTC().UnixMilli() 285 - res, err := checker.PingTcp(int(req.Timeout), req.URI) 283 + res, err := checker.PingTCP(int(req.Timeout), req.URI) 286 284 287 285 if err != nil { 288 286 return fmt.Errorf("unable to check tcp %s", err)
+31 -24
apps/checker/http.go apps/checker/checker/http.go
··· 43 43 Timing Timing `json:"timing"` 44 44 } 45 45 46 + // decodeBase64Body decodes a data URL base64 body if needed 47 + func decodeBase64Body(body string) ([]byte, error) { 48 + data := strings.Split(body, ",") 49 + if len(data) == 2 { 50 + return base64.StdEncoding.DecodeString(data[1]) 51 + } 52 + return nil, fmt.Errorf("invalid base64 data url format") 53 + } 54 + 46 55 // FIXME: This should only return the TCP Timing Data; 47 56 func Http(ctx context.Context, client *http.Client, inputData request.HttpCheckerRequest) (Response, error) { 48 57 logger := log.Ctx(ctx).With().Str("monitor", inputData.URL).Logger() 49 58 50 - b := []byte(inputData.Body) 59 + var bodyBytes []byte 51 60 if inputData.Method == http.MethodPost { 61 + contentType := "" 52 62 for _, header := range inputData.Headers { 53 - if header.Key == "Content-Type" && header.Value == "application/octet-stream" { 54 - // split the body by comma and convert it to bytes it's data url base64 55 - data := strings.Split(inputData.Body, ",") 56 - if len(data) == 2 { 57 - decoded, err := base64.StdEncoding.DecodeString(data[1]) 58 - if err != nil { 59 - return Response{}, fmt.Errorf("error while decoding base64: %w", err) 60 - } 61 - 62 - b = decoded 63 - 64 - } 63 + if header.Key == "Content-Type" { 64 + contentType = header.Value 65 + break 66 + } 67 + } 68 + if contentType == "application/octet-stream" { 69 + decoded, err := decodeBase64Body(inputData.Body) 70 + if err != nil { 71 + return Response{}, fmt.Errorf("error while decoding base64: %w", err) 65 72 } 73 + bodyBytes = decoded 74 + } else { 75 + bodyBytes = []byte(inputData.Body) 66 76 } 77 + } else { 78 + bodyBytes = []byte(inputData.Body) 67 79 } 68 80 69 - req, err := http.NewRequestWithContext(ctx, inputData.Method, inputData.URL, bytes.NewReader(b)) 81 + req, err := http.NewRequestWithContext(ctx, inputData.Method, inputData.URL, bytes.NewReader(bodyBytes)) 70 82 if err != nil { 71 83 logger.Error().Err(err).Msg("error while creating req") 72 84 return Response{}, fmt.Errorf("unable to create req: %w", err) ··· 79 91 } 80 92 81 93 // Maybe we should remove the default post to application JSON 82 - if inputData.Method == http.MethodPost { 83 - head := req.Header 84 - _, ok := head["Content-Type"] 85 - 86 - if !ok { 87 - // by default we set the content type to application/json if it's a POST request 88 - req.Header.Set("Content-Type", "application/json") 89 - } 94 + // Default POST Content-Type 95 + if inputData.Method == http.MethodPost && req.Header.Get("Content-Type") == "" { 96 + req.Header.Set("Content-Type", "application/json") 90 97 } 91 98 92 99 timing := Timing{} ··· 128 135 129 136 logger.Error().Err(err).Msg("error while pinging") 130 137 131 - return Response{}, fmt.Errorf("error with monitorURL %s: %w", inputData.URL, err) 138 + return Response{}, err 132 139 } 133 140 134 141 defer response.Body.Close() ··· 143 150 Timing: timing, 144 151 Timestamp: start.UTC().UnixMilli(), 145 152 Error: fmt.Sprintf("Cannot read response body: %s", err.Error()), 146 - }, fmt.Errorf("error with monitorURL %s: %w", inputData.URL, err) 153 + }, err 147 154 } 148 155 149 156 headers := make(map[string]string)
+1 -1
apps/checker/http_test.go apps/checker/checker/http_test.go
··· 9 9 10 10 "github.com/stretchr/testify/assert" 11 11 12 - "github.com/openstatushq/openstatus/apps/checker" 12 + "github.com/openstatushq/openstatus/apps/checker/checker" 13 13 "github.com/openstatushq/openstatus/apps/checker/request" 14 14 ) 15 15
+20
apps/checker/justfile
··· 1 + build-probe: 2 + go build -o probe ./cmd/server/main.go 3 + 4 + build-private: 5 + go build -o provider ./cmd/private/main.go 6 + 7 + dev-probe: 8 + air -c .probe.air.toml 9 + 10 + 11 + dev-private: 12 + air -c .private.air.toml 13 + 14 + clean: 15 + echo "Cleaning..." 16 + rm -f main 17 + 18 + test: 19 + echo "Running tests..." 20 + go test ./... -v
+176
apps/checker/pkg/job/http_job.go
··· 1 + package job 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + "net/http" 8 + "time" 9 + 10 + "github.com/cenkalti/backoff/v5" 11 + "github.com/google/uuid" 12 + "github.com/openstatushq/openstatus/apps/checker/checker" 13 + "github.com/openstatushq/openstatus/apps/checker/pkg/assertions" 14 + v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" 15 + "github.com/openstatushq/openstatus/apps/checker/request" 16 + ) 17 + 18 + func (jr jobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor) (*HttpPrivateRegionData, error) { 19 + 20 + retry := monitor.Retry 21 + if retry == 0 { 22 + retry = 3 23 + } 24 + 25 + requestClient := &http.Client{ 26 + Timeout: time.Duration(monitor.Timeout) * time.Millisecond, 27 + } 28 + defer requestClient.CloseIdleConnections() 29 + 30 + if !monitor.FollowRedirects { 31 + requestClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { 32 + return http.ErrUseLastResponse 33 + } 34 + } else { 35 + requestClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { 36 + if len(via) >= 10 { 37 + return http.ErrUseLastResponse 38 + } 39 + return nil 40 + } 41 + } 42 + 43 + var degradedAfter int64 44 + if monitor.DegradedAt != nil { 45 + degradedAfter = *monitor.DegradedAt 46 + } 47 + 48 + headers := make([]struct { 49 + Key string `json:"key"` 50 + Value string `json:"value"` 51 + }, 0) 52 + if monitor.Headers != nil { 53 + for _, header := range monitor.Headers { 54 + headers = append(headers, struct { 55 + Key string `json:"key"` 56 + Value string `json:"value"` 57 + }{ 58 + Key: header.Key, 59 + Value: header.Value, 60 + }) 61 + } 62 + } 63 + 64 + req := request.HttpCheckerRequest{ 65 + URL: monitor.Url, 66 + MonitorID: monitor.Id, 67 + Method: monitor.Method, 68 + Body: monitor.Body, 69 + Retry: monitor.Retry, 70 + Timeout: monitor.Timeout, 71 + DegradedAfter: degradedAfter, 72 + FollowRedirects: monitor.FollowRedirects, 73 + Headers: headers, 74 + } 75 + 76 + var called int 77 + 78 + op := func() (*HttpPrivateRegionData, error) { 79 + called++ 80 + res, err := checker.Http(ctx, requestClient, req) 81 + if err != nil { 82 + return nil, fmt.Errorf("unable to ping: %w", err) 83 + } 84 + 85 + timingBytes, err := json.Marshal(res.Timing) 86 + if err != nil { 87 + return nil, fmt.Errorf("error while parsing timing data %s: %w", req.URL, err) 88 + } 89 + headersBytes, err := json.Marshal(res.Headers) 90 + if err != nil { 91 + return nil, fmt.Errorf("error while parsing headers %s: %w", req.URL, err) 92 + } 93 + id, err := uuid.NewV7() 94 + if err != nil { 95 + return nil, fmt.Errorf("error while generating uuid: %w", err) 96 + } 97 + 98 + status := statusCode(res.Status) 99 + isSuccessful := status.IsSuccessful() 100 + if len(monitor.HeaderAssertions) > 0 { 101 + headersAsString, err := json.Marshal(res.Headers) 102 + if err != nil { 103 + return nil, fmt.Errorf("error while parsing headers %s: %w", req.URL, err) 104 + } 105 + for _, assertion := range monitor.HeaderAssertions { 106 + assert := assertions.HeaderTarget{ 107 + Comparator: request.StringComparator(assertion.Comparator.String()), 108 + Target: assertion.Target, 109 + Key: assertion.Key, 110 + } 111 + assert.HeaderEvaluate(string(headersAsString)) 112 + } 113 + } 114 + 115 + if len(monitor.StatusCodeAssertions) > 0 { 116 + for _, assertion := range monitor.StatusCodeAssertions { 117 + assert := assertions.StatusTarget{ 118 + Comparator: request.NumberComparator(assertion.Comparator.String()), 119 + Target: assertion.Target, 120 + } 121 + isSuccessful = isSuccessful && assert.StatusEvaluate(int64(res.Status)) 122 + } 123 + } 124 + if len(monitor.BodyAssertions) > 0 { 125 + for _, assertion := range monitor.BodyAssertions { 126 + assert := assertions.StringTargetType{ 127 + Comparator: request.StringComparator(assertion.Comparator.String()), 128 + Target: assertion.Target, 129 + } 130 + isSuccessful = isSuccessful && assert.StringEvaluate(res.Body) 131 + } 132 + } 133 + 134 + requestStatus := "success" 135 + if !isSuccessful { 136 + requestStatus = "error" 137 + } else if req.DegradedAfter > 0 && res.Latency > req.DegradedAfter { 138 + requestStatus = "degraded" 139 + } 140 + 141 + data := HttpPrivateRegionData{ 142 + ID: id.String(), 143 + Latency: res.Latency, 144 + StatusCode: res.Status, 145 + Timestamp: res.Timestamp, 146 + CronTimestamp: res.Timestamp, 147 + URL: req.URL, 148 + // Method: req.Method, 149 + Timing: string(timingBytes), 150 + Headers: string(headersBytes), 151 + Body: "", 152 + RequestStatus: requestStatus, 153 + // Assertions: assertionAsString, 154 + Error: 0, 155 + } 156 + 157 + if isSuccessful { 158 + if req.DegradedAfter != 0 && res.Latency > req.DegradedAfter { 159 + data.Body = res.Body 160 + } 161 + } else { 162 + data.Error = 1 163 + if called < int(retry) { 164 + return nil, fmt.Errorf("unable to ping: %v with status %v", res, res.Status) 165 + } 166 + } 167 + 168 + return &data, nil 169 + } 170 + 171 + resp, err := backoff.Retry(ctx, op, backoff.WithMaxTries(uint(retry)), backoff.WithBackOff(backoff.NewExponentialBackOff())) 172 + if err != nil { 173 + return nil, err 174 + } 175 + return resp, nil 176 + }
+52
apps/checker/pkg/job/http_job_test.go
··· 1 + package job_test 2 + 3 + import ( 4 + "context" 5 + "testing" 6 + 7 + "github.com/openstatushq/openstatus/apps/checker/pkg/job" 8 + v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" 9 + ) 10 + 11 + // Save original checker.Http for restoration 12 + 13 + func TestHTTPJob_Success(t *testing.T) { 14 + 15 + // Mock checker.Http to simulate success 16 + 17 + monitor := &v1.HTTPMonitor{ 18 + Url: "https://openstat.us", 19 + Method: "GET", 20 + Timeout: 10000, 21 + Retry: 2, 22 + } 23 + 24 + data, err := job.NewJobRunner().HTTPJob(context.Background(), monitor) 25 + if err != nil { 26 + t.Fatalf("expected no error, got %v", err) 27 + } 28 + if data.RequestStatus != "success" { 29 + t.Errorf("expected RequestStatus 'success', got '%s'", data.RequestStatus) 30 + } 31 + if data.Error != 0 { 32 + t.Errorf("expected Error 0, got %d", data.Error) 33 + } 34 + } 35 + 36 + func TestHTTPJob_Failure(t *testing.T) { 37 + 38 + monitor := &v1.HTTPMonitor{ 39 + Url: "https://localhost:1234", 40 + Method: "GET", 41 + Timeout: 1000, 42 + Retry: 1, 43 + } 44 + 45 + data, err := job.NewJobRunner().HTTPJob(context.Background(), monitor) 46 + if err == nil { 47 + t.Fatalf("expected error, got nil") 48 + } 49 + if data != nil { 50 + t.Errorf("expected data to be nil on error, got %+v", data) 51 + } 52 + }
+39
apps/checker/pkg/job/job.go
··· 1 + package job 2 + 3 + import ( 4 + "context" 5 + 6 + v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" 7 + ) 8 + 9 + type statusCode int 10 + 11 + func (s statusCode) IsSuccessful() bool { 12 + return s >= 200 && s < 300 13 + } 14 + 15 + type HttpPrivateRegionData struct { 16 + ID string `json:"id"` 17 + URL string `json:"url"` 18 + Message string `json:"message,omitempty"` 19 + Timing string `json:"timing,omitempty"` 20 + Headers string `json:"headers,omitempty"` 21 + Body string `json:"body,omitempty"` 22 + RequestStatus string `json:"requestStatus,omitempty"` 23 + Latency int64 `json:"latency"` 24 + CronTimestamp int64 `json:"cronTimestamp"` 25 + Timestamp int64 `json:"timestamp"` 26 + StatusCode int `json:"statusCode,omitempty"` 27 + Error uint8 `json:"error"` 28 + } 29 + 30 + type JobRunner interface { 31 + TCPJob(ctx context.Context, monitor *v1.TCPMonitor) (*TCPPrivateRegionData, error) 32 + HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor) (*HttpPrivateRegionData, error) 33 + } 34 + 35 + type jobRunner struct{} 36 + 37 + func NewJobRunner() JobRunner { 38 + return &jobRunner{} 39 + }
+45
apps/checker/pkg/job/monitors.go
··· 1 + package job 2 + 3 + type Monitor struct { 4 + ID int `json:"id"` 5 + Name string `json:"name"` 6 + URL string `json:"url"` 7 + Periodicity string `json:"periodicity"` 8 + Description string `json:"description"` 9 + Method string `json:"method"` 10 + Regions []string `json:"regions"` 11 + Active bool `json:"active"` 12 + Public bool `json:"public"` 13 + Timeout int `json:"timeout"` 14 + DegradedAfter int `json:"degraded_after,omitempty"` 15 + Body string `json:"body"` 16 + Headers []Header `json:"headers,omitempty"` 17 + Assertions []Assertion `json:"assertions,omitempty"` 18 + Retry int `json:"retry"` 19 + JobType string `json:"jobType"` 20 + } 21 + 22 + type Header struct { 23 + Key string `json:"key"` 24 + Value string `json:"value"` 25 + } 26 + 27 + type Assertion struct { 28 + Type string `json:"type"` 29 + Compare string `json:"compare"` 30 + Key string `json:"key"` 31 + Target any `json:"target"` 32 + } 33 + 34 + type Timing struct { 35 + DnsStart int64 `json:"dnsStart"` 36 + DnsDone int64 `json:"dnsDone"` 37 + ConnectStart int64 `json:"connectStart"` 38 + ConnectDone int64 `json:"connectDone"` 39 + TlsHandshakeStart int64 `json:"tlsHandshakeStart"` 40 + TlsHandshakeDone int64 `json:"tlsHandshakeDone"` 41 + FirstByteStart int64 `json:"firstByteStart"` 42 + FirstByteDone int64 `json:"firstByteDone"` 43 + TransferStart int64 `json:"transferStart"` 44 + TransferDone int64 `json:"transferDone"` 45 + }
+114
apps/checker/pkg/job/tcp_job.go
··· 1 + package job 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + 8 + "github.com/cenkalti/backoff/v5" 9 + "github.com/google/uuid" 10 + "github.com/openstatushq/openstatus/apps/checker/checker" 11 + v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" 12 + ) 13 + 14 + // AssertionResult tracks the results of running assertions 15 + type AssertionResult struct { 16 + Type string 17 + Success bool 18 + Message string 19 + } 20 + 21 + // TCPPrivateRegionData represents the result of a TCP monitor check 22 + type TCPPrivateRegionData struct { 23 + ID string `json:"id"` 24 + URI string `json:"uri"` 25 + RequestStatus string `json:"request_status"` 26 + Message string `json:"message"` 27 + Latency int64 `json:"latency"` 28 + Timestamp int64 `json:"timestamp"` 29 + CronTimestamp int64 `json:"cron_timestamp"` 30 + Error int `json:"error"` 31 + Timing string `json:"timing"` 32 + } 33 + 34 + // runAssertions performs all configured assertions for TCP and returns their results 35 + 36 + func (jobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor) (*TCPPrivateRegionData, error) { 37 + retry := monitor.Retry 38 + if retry == 0 { 39 + retry = 3 40 + } 41 + 42 + var degradedAfter int64 43 + if monitor.DegradedAt != nil { 44 + degradedAfter = *monitor.DegradedAt 45 + } 46 + 47 + var called int 48 + 49 + op := func() (*TCPPrivateRegionData, error) { 50 + called++ 51 + res, err := checker.PingTCP(int(monitor.Timeout), monitor.Uri) 52 + if err != nil { 53 + if called < int(retry) { 54 + return nil, fmt.Errorf("TCP connection failed: %w", err) 55 + } 56 + // On final attempt, return the error in the result 57 + id, uuidErr := uuid.NewV7() 58 + if uuidErr != nil { 59 + return nil, fmt.Errorf("failed to generate UUID: %w", uuidErr) 60 + } 61 + 62 + return &TCPPrivateRegionData{ 63 + ID: id.String(), 64 + Latency: 0, 65 + Timestamp: res.TCPStart, 66 + CronTimestamp: res.TCPStart, 67 + URI: monitor.Uri, 68 + RequestStatus: "error", 69 + Error: 1, 70 + Message: err.Error(), 71 + }, nil 72 + } 73 + 74 + latency := res.TCPDone - res.TCPStart 75 + 76 + var requestStatus = "active" 77 + 78 + if degradedAfter > 0 && latency > degradedAfter { 79 + requestStatus = "degraded" 80 + } 81 + 82 + id, err := uuid.NewV7() 83 + if err != nil { 84 + return nil, fmt.Errorf("failed to generate UUID: %w", err) 85 + } 86 + timingAsString, err := json.Marshal(res) 87 + if err != nil { 88 + return nil, fmt.Errorf("error while parsing timing data %s: %w", monitor.Uri, err) 89 + } 90 + 91 + data := &TCPPrivateRegionData{ 92 + ID: id.String(), 93 + Latency: latency, 94 + Timestamp: res.TCPStart, 95 + CronTimestamp: res.TCPStart, 96 + URI: monitor.Uri, 97 + RequestStatus: requestStatus, 98 + Error: 0, 99 + Message: fmt.Sprintf("Successfully connected to %s", monitor.Uri), 100 + Timing: string(timingAsString), 101 + } 102 + 103 + return data, nil 104 + } 105 + 106 + resp, err := backoff.Retry(ctx, op, 107 + backoff.WithMaxTries(uint(retry)), 108 + backoff.WithBackOff(backoff.NewExponentialBackOff()), 109 + ) 110 + if err != nil { 111 + return nil, fmt.Errorf("TCP job failed after %d retries: %w", retry, err) 112 + } 113 + return resp, nil 114 + }
+48
apps/checker/pkg/job/tcp_job_test.go
··· 1 + package job_test 2 + 3 + import ( 4 + "context" 5 + "testing" 6 + 7 + "github.com/openstatushq/openstatus/apps/checker/pkg/job" 8 + v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" 9 + ) 10 + 11 + func TestTCPJob_Success(t *testing.T) { 12 + 13 + monitor := &v1.TCPMonitor{ 14 + Uri: "openstatus.dev:80", 15 + Timeout: 1, 16 + Retry: 1, 17 + } 18 + data, err := job.NewJobRunner().TCPJob(context.Background(), monitor) 19 + if err != nil { 20 + t.Fatalf("expected no error, got %v", err) 21 + } 22 + if data.RequestStatus != "active" { 23 + t.Errorf("expected RequestStatus 'active', got '%s'", data.RequestStatus) 24 + } 25 + if data.Error != 0 { 26 + t.Errorf("expected Error 0, got %d", data.Error) 27 + } 28 + } 29 + 30 + func TestTCPJob_Failure(t *testing.T) { 31 + 32 + monitor := &v1.TCPMonitor{ 33 + Uri: "localhost:1234", 34 + Timeout: 1, 35 + Retry: 1, 36 + } 37 + 38 + data, err := job.NewJobRunner().TCPJob(context.Background(), monitor) 39 + if err != nil { 40 + t.Fatalf("expected no error, got %v", err) 41 + } 42 + if data.RequestStatus != "error" { 43 + t.Errorf("expected RequestStatus 'error', got '%s'", data.RequestStatus) 44 + } 45 + if data.Error != 1 { 46 + t.Errorf("expected Error 1, got %d", data.Error) 47 + } 48 + }
+1 -1
apps/checker/pkg/otel/otel.go
··· 6 6 "fmt" 7 7 "time" 8 8 9 - "github.com/openstatushq/openstatus/apps/checker" 9 + "github.com/openstatushq/openstatus/apps/checker/checker" 10 10 "github.com/openstatushq/openstatus/apps/checker/request" 11 11 "github.com/rs/zerolog/log" 12 12
+291
apps/checker/pkg/scheduler/scheduler.go
··· 1 + package scheduler 2 + 3 + import ( 4 + "context" 5 + "log" 6 + "sync" 7 + "time" 8 + 9 + "connectrpc.com/connect" 10 + "github.com/madflojo/tasks" 11 + "github.com/openstatushq/openstatus/apps/checker/pkg/job" 12 + v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" 13 + ) 14 + 15 + const ( 16 + Interval10s = "10s" 17 + Interval30s = "30s" 18 + Interval1m = "1m" 19 + Interval5m = "5m" 20 + Interval10m = "10m" 21 + Interval30m = "30m" 22 + Interval1h = "1h" 23 + ) 24 + 25 + type MonitorManager struct { 26 + Client v1.PrivateLocationServiceClient 27 + JobRunner job.JobRunner 28 + Scheduler *tasks.Scheduler 29 + mu sync.Mutex 30 + } 31 + 32 + func (mm *MonitorManager) UpdateMonitors(ctx context.Context) { 33 + res, err := mm.Client.Monitors(ctx, &connect.Request[v1.MonitorsRequest]{}) 34 + if err != nil { 35 + log.Printf("Failed to fetch monitors: %v", err) 36 + return 37 + } 38 + 39 + currentIDs := make(map[string]struct{}) 40 + 41 + // HTTP monitors: start jobs for new monitors 42 + for _, m := range res.Msg.HttpMonitors { 43 + currentIDs[m.Id] = struct{}{} 44 + _, err := mm.Scheduler.Lookup(m.Id) 45 + if err != nil { 46 + interval := time.Duration(intervalToSecond(m.Periodicity)) * time.Second 47 + task := tasks.Task{ 48 + Interval: interval, 49 + RunOnce: false, 50 + RunSingleInstance: true, 51 + // StartAfter: time.Duration(1) * time.Second, 52 + ErrFunc: func(e error) { 53 + log.Printf("An error occurred when executing task %s", e) 54 + }, 55 + FuncWithTaskContext: func(ctx tasks.TaskContext) error { 56 + monitor := m 57 + c := context.Background() 58 + log.Printf("Starting job for monitor %s (%s)", monitor.Id, monitor.Url) 59 + data, err := mm.JobRunner.HTTPJob(c, monitor) 60 + 61 + if err != nil { 62 + log.Printf("Monitor check failed for %s (%s): %v", monitor.Id, monitor.Url, err) 63 + return err 64 + } 65 + resp, ingestErr := mm.Client.IngestHTTP(c, &connect.Request[v1.IngestHTTPRequest]{ 66 + Msg: &v1.IngestHTTPRequest{ 67 + MonitorId: monitor.Id, 68 + Id: data.ID, 69 + Url: monitor.Url, 70 + Message: data.Message, 71 + Latency: data.Latency, 72 + Timing: data.Timing, 73 + Headers: data.Headers, 74 + Body: data.Body, 75 + RequestStatus: data.RequestStatus, 76 + StatusCode: int64(data.StatusCode), 77 + Error: int64(data.Error), 78 + CronTimestamp: data.CronTimestamp, 79 + Timestamp: data.Timestamp, 80 + }, 81 + }) 82 + if ingestErr != nil { 83 + log.Printf("Failed to ingest HTTP result for %s (%s): %v", monitor.Id, monitor.Url, ingestErr) 84 + return ingestErr 85 + } 86 + log.Printf("Monitor check succeeded for %s (%s), ingest response: %v", monitor.Id, monitor.Url, resp) 87 + return nil 88 + }, 89 + } 90 + 91 + err := mm.Scheduler.AddWithID(m.Id, &task) 92 + 93 + if err != nil { 94 + log.Printf("Failed to add HTTP monitor job for %s (%s): %v", m.Id, m.Url, err) 95 + continue 96 + } 97 + log.Printf("Started monitoring job for %s (%s)", m.Id, m.Url) 98 + continue 99 + } 100 + 101 + } 102 + 103 + // // Stop jobs for monitors that no longer exist 104 + // for id := range mm.HttpMonitors { 105 + // if _, stillExists := currentIDs[id]; !stillExists { 106 + // mm.Scheduler.Del(id) 107 + // mm.mu.Lock() 108 + // delete(mm.HttpMonitors, id) 109 + // mm.mu.Unlock() 110 + 111 + // } 112 + // } 113 + 114 + // TCP monitors: start jobs for new monitors 115 + for _, m := range res.Msg.TcpMonitors { 116 + currentIDs[m.Id] = struct{}{} 117 + _, err := mm.Scheduler.Lookup(m.Id) 118 + if err != nil { 119 + 120 + interval := time.Duration(intervalToSecond(m.Periodicity)) * time.Second 121 + task := tasks.Task{ 122 + Interval: interval, 123 + RunOnce: false, 124 + // StartAfter: time.Now().Add(5 * time.Millisecond), 125 + RunSingleInstance: true, 126 + FuncWithTaskContext: func(ctx tasks.TaskContext) error { 127 + 128 + monitor := m 129 + log.Printf("Starting TCP job for monitor %s (%s)", monitor.Id, monitor.Uri) 130 + data, err := mm.JobRunner.TCPJob(ctx.Context, monitor) 131 + if err != nil { 132 + log.Printf("TCP monitor check failed for %s (%s): %v", monitor.Id, monitor.Uri, err) 133 + } 134 + resp, ingestErr := mm.Client.IngestTCP(ctx.Context, &connect.Request[v1.IngestTCPRequest]{ 135 + Msg: &v1.IngestTCPRequest{ 136 + MonitorId: monitor.Id, 137 + Id: data.ID, 138 + Uri: monitor.Uri, 139 + Message: data.Message, 140 + Latency: data.Latency, 141 + RequestStatus: data.RequestStatus, 142 + Error: int64(data.Error), 143 + CronTimestamp: data.CronTimestamp, 144 + Timestamp: data.Timestamp, 145 + }, 146 + }) 147 + if ingestErr != nil { 148 + log.Printf("Failed to ingest TCP result for %s (%s): %v", monitor.Id, monitor.Uri, ingestErr) 149 + return ingestErr 150 + } 151 + log.Printf("TCP monitor check succeeded for %s (%s), ingest response: %v", monitor.Id, monitor.Uri, resp) 152 + 153 + return nil 154 + }, 155 + } 156 + err := mm.Scheduler.AddWithID(m.Id, &task) 157 + 158 + if err != nil { 159 + log.Printf("Failed to add TCP monitor job for %s (%s): %v", m.Id, m.Uri, err) 160 + continue 161 + } 162 + log.Printf("Started TCP monitoring job for %s (%s)", m.Id, m.Uri) 163 + } 164 + } 165 + 166 + for id := range mm.Scheduler.Tasks() { 167 + if _, stillExists := currentIDs[id]; !stillExists { 168 + mm.Scheduler.Del(id) 169 + } 170 + } 171 + // Stop jobs for TCP monitors that no longer exist 172 + // for id := range mm.TcpMonitors { 173 + // if _, stillExists := currentIDs[id]; !stillExists { 174 + // mm.Scheduler.Del(id) 175 + // mm.mu.Lock() 176 + // delete(mm.TcpMonitors, id) 177 + // mm.mu.Unlock() 178 + // } 179 + // } 180 + } 181 + 182 + func intervalToSecond(interval string) int { 183 + switch interval { 184 + case Interval30s: 185 + return 30 186 + case Interval1m: 187 + return 60 188 + case Interval5m: 189 + return 300 190 + case Interval10m: 191 + return 600 192 + case Interval30m: 193 + return 1800 194 + case Interval1h: 195 + return 3600 196 + case Interval10s: 197 + return 10 198 + default: 199 + return 0 200 + } 201 + } 202 + 203 + // func (mm *MonitorManager) ScheduleHTTPJob(ctx context.Context, monitor *v1.HTTPMonitor, done chan bool) { 204 + // interval := intervalToSecond(monitor.Periodicity) 205 + // if interval == 0 { 206 + // log.Printf("Invalid interval for monitor %s (%s): %s", monitor.Id, monitor.Url, monitor.Periodicity) 207 + // return 208 + // } 209 + 210 + // ticker := time.NewTicker(time.Duration(1) * time.Second) 211 + // defer ticker.Stop() 212 + 213 + // for { 214 + // select { 215 + // case <-ticker.C: 216 + // log.Printf("Starting job for monitor %s (%s)", monitor.Id, monitor.Url) 217 + // data, err := mm.JobRunner.HTTPJob(ctx, monitor) 218 + // if err != nil { 219 + // log.Printf("Monitor check failed for %s (%s): %v", monitor.Id, monitor.Url, err) 220 + // continue 221 + // } 222 + // resp, ingestErr := mm.Client.IngestHTTP(ctx, &connect.Request[v1.IngestHTTPRequest]{ 223 + // Msg: &v1.IngestHTTPRequest{ 224 + // Id: monitor.Id, 225 + // Url: monitor.Url, 226 + // Message: data.Message, 227 + // Latency: data.Latency, 228 + // Timing: data.Timing, 229 + // Headers: data.Headers, 230 + // Body: data.Body, 231 + // RequestStatus: data.RequestStatus, 232 + // StatusCode: int64(data.StatusCode), 233 + // Error: int64(data.Error), 234 + // CronTimestamp: data.CronTimestamp, 235 + // Timestamp: data.Timestamp, 236 + // }, 237 + // }) 238 + // if ingestErr != nil { 239 + // log.Printf("Failed to ingest HTTP result for %s (%s): %v", monitor.Id, monitor.Url, ingestErr) 240 + // } else { 241 + // log.Printf("Monitor check succeeded for %s (%s), ingest response: %v", monitor.Id, monitor.Url, resp) 242 + // } 243 + // case <-done: 244 + // log.Printf("Shutting down job for monitor %s (%s)", monitor.Id, monitor.Url) 245 + // return 246 + // } 247 + // } 248 + // } 249 + 250 + // func (mm *MonitorManager) ScheduleTCPJob(ctx context.Context, monitor *v1.TCPMonitor, done chan bool) { 251 + // interval := intervalToSecond(monitor.Periodicity) 252 + // if interval == 0 { 253 + // log.Printf("Invalid interval for TCP monitor %s (%s): %s", monitor.Id, monitor.Uri, monitor.Periodicity) 254 + // return 255 + // } 256 + 257 + // ticker := time.NewTicker(time.Duration(1) * time.Second) 258 + // defer ticker.Stop() 259 + 260 + // for { 261 + // select { 262 + // case <-ticker.C: 263 + // log.Printf("Starting TCP job for monitor %s (%s)", monitor.Id, monitor.Uri) 264 + // data, err := mm.JobRunner.TCPJob(ctx, monitor) 265 + // if err != nil { 266 + // log.Printf("TCP monitor check failed for %s (%s): %v", monitor.Id, monitor.Uri, err) 267 + // continue 268 + // } 269 + // resp, ingestErr := mm.Client.IngestTCP(ctx, &connect.Request[v1.IngestTCPRequest]{ 270 + // Msg: &v1.IngestTCPRequest{ 271 + // Id: monitor.Id, 272 + // Uri: monitor.Uri, 273 + // Message: data.Message, 274 + // Latency: data.Latency, 275 + // RequestStatus: data.RequestStatus, 276 + // Error: int64(data.Error), 277 + // CronTimestamp: data.CronTimestamp, 278 + // Timestamp: data.Timestamp, 279 + // }, 280 + // }) 281 + // if ingestErr != nil { 282 + // log.Printf("Failed to ingest TCP result for %s (%s): %v", monitor.Id, monitor.Uri, ingestErr) 283 + // } else { 284 + // log.Printf("TCP monitor check succeeded for %s (%s), ingest response: %v", monitor.Id, monitor.Uri, resp) 285 + // } 286 + // case <-done: 287 + // log.Printf("Shutting down TCP job for monitor %s (%s)", monitor.Id, monitor.Uri) 288 + // return 289 + // } 290 + // } 291 + // }
+111
apps/checker/pkg/scheduler/scheduler_test.go
··· 1 + package scheduler_test 2 + 3 + import ( 4 + "context" 5 + "sync/atomic" 6 + 7 + "sync" 8 + "testing" 9 + "time" 10 + 11 + "connectrpc.com/connect" 12 + "github.com/madflojo/tasks" 13 + "github.com/openstatushq/openstatus/apps/checker/pkg/job" 14 + "github.com/openstatushq/openstatus/apps/checker/pkg/scheduler" 15 + v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" 16 + ) 17 + 18 + // mockJobRunner implements job.JobRunner for testing 19 + type mockJobRunner struct { 20 + HTTPJobCalled atomic.Bool 21 + TCPJobCalled atomic.Bool 22 + mu sync.Mutex 23 + } 24 + 25 + func (m *mockJobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor) (*job.HttpPrivateRegionData, error) { 26 + m.HTTPJobCalled.Store(true) 27 + return &job.HttpPrivateRegionData{}, nil 28 + } 29 + func (m *mockJobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor) (*job.TCPPrivateRegionData, error) { 30 + 31 + m.TCPJobCalled.Store(true) 32 + return &job.TCPPrivateRegionData{}, nil 33 + } 34 + 35 + // mockClient implements v1.PrivateLocationServiceClient for testing 36 + type mockClient struct { 37 + MonitorsFunc func(ctx context.Context, req *connect.Request[v1.MonitorsRequest]) (*connect.Response[v1.MonitorsResponse], error) 38 + IngestHTTPFunc func(ctx context.Context, req *connect.Request[v1.IngestHTTPRequest]) (*connect.Response[v1.IngestHTTPResponse], error) 39 + IngestTCPFunc func(ctx context.Context, req *connect.Request[v1.IngestTCPRequest]) (*connect.Response[v1.IngestTCPResponse], error) 40 + } 41 + 42 + func (m *mockClient) Monitors(ctx context.Context, req *connect.Request[v1.MonitorsRequest]) (*connect.Response[v1.MonitorsResponse], error) { 43 + return m.MonitorsFunc(ctx, req) 44 + } 45 + func (m *mockClient) IngestHTTP(ctx context.Context, req *connect.Request[v1.IngestHTTPRequest]) (*connect.Response[v1.IngestHTTPResponse], error) { 46 + return m.IngestHTTPFunc(ctx, req) 47 + } 48 + func (m *mockClient) IngestTCP(ctx context.Context, req *connect.Request[v1.IngestTCPRequest]) (*connect.Response[v1.IngestTCPResponse], error) { 49 + return m.IngestTCPFunc(ctx, req) 50 + } 51 + 52 + func TestMonitorManager_StartAndStopJobs_WithJobRunner(t *testing.T) { 53 + ctx := t.Context() 54 + 55 + httpMonitor := &v1.HTTPMonitor{Id: "http1", Url: "http://openstat.us", Periodicity: "10s"} 56 + tcpMonitor := &v1.TCPMonitor{Id: "tcp1", Uri: "openstatus:80", Periodicity: "10s"} 57 + 58 + client := &mockClient{ 59 + MonitorsFunc: func(ctx context.Context, req *connect.Request[v1.MonitorsRequest]) (*connect.Response[v1.MonitorsResponse], error) { 60 + return connect.NewResponse(&v1.MonitorsResponse{ 61 + HttpMonitors: []*v1.HTTPMonitor{httpMonitor}, 62 + TcpMonitors: []*v1.TCPMonitor{tcpMonitor}, 63 + }), nil 64 + }, 65 + IngestHTTPFunc: func(ctx context.Context, req *connect.Request[v1.IngestHTTPRequest]) (*connect.Response[v1.IngestHTTPResponse], error) { 66 + return connect.NewResponse(&v1.IngestHTTPResponse{}), nil 67 + }, 68 + IngestTCPFunc: func(ctx context.Context, req *connect.Request[v1.IngestTCPRequest]) (*connect.Response[v1.IngestTCPResponse], error) { 69 + return connect.NewResponse(&v1.IngestTCPResponse{}), nil 70 + }, 71 + } 72 + jobRunner := &mockJobRunner{} 73 + 74 + s := tasks.New() 75 + defer s.Stop() 76 + 77 + mm := &scheduler.MonitorManager{ 78 + 79 + Client: client, 80 + JobRunner: jobRunner, 81 + Scheduler: s, 82 + } 83 + 84 + mm.UpdateMonitors(ctx) 85 + time.Sleep(12 * time.Second) // allow jobs to run 86 + 87 + if !jobRunner.HTTPJobCalled.Load() == true { 88 + t.Errorf("expected HTTPJob to be called") 89 + } 90 + if !jobRunner.TCPJobCalled.Load() == true { 91 + t.Errorf("expected TCPJob to be called") 92 + } 93 + 94 + // Remove monitors and ensure jobs are stopped 95 + client.MonitorsFunc = func(ctx context.Context, req *connect.Request[v1.MonitorsRequest]) (*connect.Response[v1.MonitorsResponse], error) { 96 + return connect.NewResponse(&v1.MonitorsResponse{ 97 + HttpMonitors: []*v1.HTTPMonitor{}, 98 + TcpMonitors: []*v1.TCPMonitor{}, 99 + }), nil 100 + } 101 + mm.UpdateMonitors(ctx) 102 + time.Sleep(1 * time.Second) 103 + 104 + if _, err := mm.Scheduler.Lookup("http1"); err == nil { 105 + t.Errorf("expected HTTP job to be removed") 106 + } 107 + if _, err := mm.Scheduler.Lookup("tcp1"); err == nil { 108 + t.Errorf("expected TCP job to be removed") 109 + } 110 + 111 + }
+1 -3
apps/checker/pkg/tinybird/client_test.go
··· 1 1 package tinybird_test 2 2 3 3 import ( 4 - "context" 5 4 "fmt" 6 5 "net/http" 7 6 "testing" ··· 27 26 func TestSendEvent(t *testing.T) { 28 27 t.Parallel() 29 28 30 - ctx, cancel := context.WithCancel(context.Background()) 31 - defer cancel() 29 + ctx := t.Context() 32 30 33 31 t.Run("it should return an error if it can not send the event", func(t *testing.T) { 34 32 interceptor := &interceptorHTTPClient{
+31
apps/checker/private-location.Dockerfile
··· 1 + FROM golang:1.25-alpine as builder 2 + 3 + WORKDIR /go/src/app 4 + 5 + RUN apk add --no-cache tzdata 6 + ENV TZ=UTC 7 + 8 + ENV CGO_ENABLED=0 9 + ENV GOOS=linux 10 + ENV GOARCH=amd64 11 + 12 + COPY go.* . 13 + RUN go mod download 14 + 15 + COPY . . 16 + RUN go build -trimpath -ldflags "-s -w" -o private ./cmd/private 17 + 18 + FROM scratch 19 + 20 + WORKDIR /opt/bin 21 + 22 + COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 23 + COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo 24 + 25 + COPY --from=builder /go/src/app/private /opt/bin/private 26 + 27 + ENV TZ=UTC 28 + ENV USER=1000 29 + ENV GIN_MODE=release 30 + 31 + CMD [ "/opt/bin/private" ]
+420
apps/checker/proto/private_location/v1/assertions.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.36.10 4 + // protoc (unknown) 5 + // source: private_location/v1/assertions.proto 6 + 7 + package v1 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + reflect "reflect" 13 + sync "sync" 14 + unsafe "unsafe" 15 + ) 16 + 17 + const ( 18 + // Verify that this generated code is sufficiently up-to-date. 19 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 + // Verify that runtime/protoimpl is sufficiently up-to-date. 21 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 + ) 23 + 24 + type NumberComparator int32 25 + 26 + const ( 27 + NumberComparator_NUMBER_COMPARATOR_UNSPECIFIED NumberComparator = 0 28 + NumberComparator_NUMBER_COMPARATOR_EQUAL NumberComparator = 1 29 + NumberComparator_NUMBER_COMPARATOR_NOT_EQUAL NumberComparator = 2 30 + NumberComparator_NUMBER_COMPARATOR_GREATER_THAN NumberComparator = 3 31 + NumberComparator_NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL NumberComparator = 4 32 + NumberComparator_NUMBER_COMPARATOR_LESS_THAN NumberComparator = 5 33 + NumberComparator_NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL NumberComparator = 6 34 + ) 35 + 36 + // Enum value maps for NumberComparator. 37 + var ( 38 + NumberComparator_name = map[int32]string{ 39 + 0: "NUMBER_COMPARATOR_UNSPECIFIED", 40 + 1: "NUMBER_COMPARATOR_EQUAL", 41 + 2: "NUMBER_COMPARATOR_NOT_EQUAL", 42 + 3: "NUMBER_COMPARATOR_GREATER_THAN", 43 + 4: "NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL", 44 + 5: "NUMBER_COMPARATOR_LESS_THAN", 45 + 6: "NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL", 46 + } 47 + NumberComparator_value = map[string]int32{ 48 + "NUMBER_COMPARATOR_UNSPECIFIED": 0, 49 + "NUMBER_COMPARATOR_EQUAL": 1, 50 + "NUMBER_COMPARATOR_NOT_EQUAL": 2, 51 + "NUMBER_COMPARATOR_GREATER_THAN": 3, 52 + "NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL": 4, 53 + "NUMBER_COMPARATOR_LESS_THAN": 5, 54 + "NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL": 6, 55 + } 56 + ) 57 + 58 + func (x NumberComparator) Enum() *NumberComparator { 59 + p := new(NumberComparator) 60 + *p = x 61 + return p 62 + } 63 + 64 + func (x NumberComparator) String() string { 65 + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 66 + } 67 + 68 + func (NumberComparator) Descriptor() protoreflect.EnumDescriptor { 69 + return file_private_location_v1_assertions_proto_enumTypes[0].Descriptor() 70 + } 71 + 72 + func (NumberComparator) Type() protoreflect.EnumType { 73 + return &file_private_location_v1_assertions_proto_enumTypes[0] 74 + } 75 + 76 + func (x NumberComparator) Number() protoreflect.EnumNumber { 77 + return protoreflect.EnumNumber(x) 78 + } 79 + 80 + // Deprecated: Use NumberComparator.Descriptor instead. 81 + func (NumberComparator) EnumDescriptor() ([]byte, []int) { 82 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{0} 83 + } 84 + 85 + type StringComparator int32 86 + 87 + const ( 88 + StringComparator_STRING_COMPARATOR_UNSPECIFIED StringComparator = 0 89 + StringComparator_STRING_COMPARATOR_CONTAINS StringComparator = 1 90 + StringComparator_STRING_COMPARATOR_NOT_CONTAINS StringComparator = 2 91 + StringComparator_STRING_COMPARATOR_EQUAL StringComparator = 3 92 + StringComparator_STRING_COMPARATOR_NOT_EQUAL StringComparator = 4 93 + StringComparator_STRING_COMPARATOR_EMPTY StringComparator = 5 94 + StringComparator_STRING_COMPARATOR_NOT_EMPTY StringComparator = 6 95 + StringComparator_STRING_COMPARATOR_GREATER_THAN StringComparator = 7 96 + StringComparator_STRING_COMPARATOR_GREATER_THAN_OR_EQUAL StringComparator = 8 97 + StringComparator_STRING_COMPARATOR_LESS_THAN StringComparator = 9 98 + StringComparator_STRING_COMPARATOR_LESS_THAN_OR_EQUAL StringComparator = 10 99 + ) 100 + 101 + // Enum value maps for StringComparator. 102 + var ( 103 + StringComparator_name = map[int32]string{ 104 + 0: "STRING_COMPARATOR_UNSPECIFIED", 105 + 1: "STRING_COMPARATOR_CONTAINS", 106 + 2: "STRING_COMPARATOR_NOT_CONTAINS", 107 + 3: "STRING_COMPARATOR_EQUAL", 108 + 4: "STRING_COMPARATOR_NOT_EQUAL", 109 + 5: "STRING_COMPARATOR_EMPTY", 110 + 6: "STRING_COMPARATOR_NOT_EMPTY", 111 + 7: "STRING_COMPARATOR_GREATER_THAN", 112 + 8: "STRING_COMPARATOR_GREATER_THAN_OR_EQUAL", 113 + 9: "STRING_COMPARATOR_LESS_THAN", 114 + 10: "STRING_COMPARATOR_LESS_THAN_OR_EQUAL", 115 + } 116 + StringComparator_value = map[string]int32{ 117 + "STRING_COMPARATOR_UNSPECIFIED": 0, 118 + "STRING_COMPARATOR_CONTAINS": 1, 119 + "STRING_COMPARATOR_NOT_CONTAINS": 2, 120 + "STRING_COMPARATOR_EQUAL": 3, 121 + "STRING_COMPARATOR_NOT_EQUAL": 4, 122 + "STRING_COMPARATOR_EMPTY": 5, 123 + "STRING_COMPARATOR_NOT_EMPTY": 6, 124 + "STRING_COMPARATOR_GREATER_THAN": 7, 125 + "STRING_COMPARATOR_GREATER_THAN_OR_EQUAL": 8, 126 + "STRING_COMPARATOR_LESS_THAN": 9, 127 + "STRING_COMPARATOR_LESS_THAN_OR_EQUAL": 10, 128 + } 129 + ) 130 + 131 + func (x StringComparator) Enum() *StringComparator { 132 + p := new(StringComparator) 133 + *p = x 134 + return p 135 + } 136 + 137 + func (x StringComparator) String() string { 138 + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 139 + } 140 + 141 + func (StringComparator) Descriptor() protoreflect.EnumDescriptor { 142 + return file_private_location_v1_assertions_proto_enumTypes[1].Descriptor() 143 + } 144 + 145 + func (StringComparator) Type() protoreflect.EnumType { 146 + return &file_private_location_v1_assertions_proto_enumTypes[1] 147 + } 148 + 149 + func (x StringComparator) Number() protoreflect.EnumNumber { 150 + return protoreflect.EnumNumber(x) 151 + } 152 + 153 + // Deprecated: Use StringComparator.Descriptor instead. 154 + func (StringComparator) EnumDescriptor() ([]byte, []int) { 155 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{1} 156 + } 157 + 158 + type StatusCodeAssertion struct { 159 + state protoimpl.MessageState `protogen:"open.v1"` 160 + Target int64 `protobuf:"varint,1,opt,name=target,proto3" json:"target,omitempty"` 161 + Comparator NumberComparator `protobuf:"varint,2,opt,name=comparator,proto3,enum=private_location.v1.NumberComparator" json:"comparator,omitempty"` 162 + unknownFields protoimpl.UnknownFields 163 + sizeCache protoimpl.SizeCache 164 + } 165 + 166 + func (x *StatusCodeAssertion) Reset() { 167 + *x = StatusCodeAssertion{} 168 + mi := &file_private_location_v1_assertions_proto_msgTypes[0] 169 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 170 + ms.StoreMessageInfo(mi) 171 + } 172 + 173 + func (x *StatusCodeAssertion) String() string { 174 + return protoimpl.X.MessageStringOf(x) 175 + } 176 + 177 + func (*StatusCodeAssertion) ProtoMessage() {} 178 + 179 + func (x *StatusCodeAssertion) ProtoReflect() protoreflect.Message { 180 + mi := &file_private_location_v1_assertions_proto_msgTypes[0] 181 + if x != nil { 182 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 183 + if ms.LoadMessageInfo() == nil { 184 + ms.StoreMessageInfo(mi) 185 + } 186 + return ms 187 + } 188 + return mi.MessageOf(x) 189 + } 190 + 191 + // Deprecated: Use StatusCodeAssertion.ProtoReflect.Descriptor instead. 192 + func (*StatusCodeAssertion) Descriptor() ([]byte, []int) { 193 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{0} 194 + } 195 + 196 + func (x *StatusCodeAssertion) GetTarget() int64 { 197 + if x != nil { 198 + return x.Target 199 + } 200 + return 0 201 + } 202 + 203 + func (x *StatusCodeAssertion) GetComparator() NumberComparator { 204 + if x != nil { 205 + return x.Comparator 206 + } 207 + return NumberComparator_NUMBER_COMPARATOR_UNSPECIFIED 208 + } 209 + 210 + type BodyAssertion struct { 211 + state protoimpl.MessageState `protogen:"open.v1"` 212 + Target string `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"` 213 + Comparator StringComparator `protobuf:"varint,2,opt,name=comparator,proto3,enum=private_location.v1.StringComparator" json:"comparator,omitempty"` 214 + unknownFields protoimpl.UnknownFields 215 + sizeCache protoimpl.SizeCache 216 + } 217 + 218 + func (x *BodyAssertion) Reset() { 219 + *x = BodyAssertion{} 220 + mi := &file_private_location_v1_assertions_proto_msgTypes[1] 221 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 222 + ms.StoreMessageInfo(mi) 223 + } 224 + 225 + func (x *BodyAssertion) String() string { 226 + return protoimpl.X.MessageStringOf(x) 227 + } 228 + 229 + func (*BodyAssertion) ProtoMessage() {} 230 + 231 + func (x *BodyAssertion) ProtoReflect() protoreflect.Message { 232 + mi := &file_private_location_v1_assertions_proto_msgTypes[1] 233 + if x != nil { 234 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 235 + if ms.LoadMessageInfo() == nil { 236 + ms.StoreMessageInfo(mi) 237 + } 238 + return ms 239 + } 240 + return mi.MessageOf(x) 241 + } 242 + 243 + // Deprecated: Use BodyAssertion.ProtoReflect.Descriptor instead. 244 + func (*BodyAssertion) Descriptor() ([]byte, []int) { 245 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{1} 246 + } 247 + 248 + func (x *BodyAssertion) GetTarget() string { 249 + if x != nil { 250 + return x.Target 251 + } 252 + return "" 253 + } 254 + 255 + func (x *BodyAssertion) GetComparator() StringComparator { 256 + if x != nil { 257 + return x.Comparator 258 + } 259 + return StringComparator_STRING_COMPARATOR_UNSPECIFIED 260 + } 261 + 262 + type HeaderAssertion struct { 263 + state protoimpl.MessageState `protogen:"open.v1"` 264 + Target string `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"` 265 + Comparator StringComparator `protobuf:"varint,2,opt,name=comparator,proto3,enum=private_location.v1.StringComparator" json:"comparator,omitempty"` 266 + Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` 267 + unknownFields protoimpl.UnknownFields 268 + sizeCache protoimpl.SizeCache 269 + } 270 + 271 + func (x *HeaderAssertion) Reset() { 272 + *x = HeaderAssertion{} 273 + mi := &file_private_location_v1_assertions_proto_msgTypes[2] 274 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 275 + ms.StoreMessageInfo(mi) 276 + } 277 + 278 + func (x *HeaderAssertion) String() string { 279 + return protoimpl.X.MessageStringOf(x) 280 + } 281 + 282 + func (*HeaderAssertion) ProtoMessage() {} 283 + 284 + func (x *HeaderAssertion) ProtoReflect() protoreflect.Message { 285 + mi := &file_private_location_v1_assertions_proto_msgTypes[2] 286 + if x != nil { 287 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 288 + if ms.LoadMessageInfo() == nil { 289 + ms.StoreMessageInfo(mi) 290 + } 291 + return ms 292 + } 293 + return mi.MessageOf(x) 294 + } 295 + 296 + // Deprecated: Use HeaderAssertion.ProtoReflect.Descriptor instead. 297 + func (*HeaderAssertion) Descriptor() ([]byte, []int) { 298 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{2} 299 + } 300 + 301 + func (x *HeaderAssertion) GetTarget() string { 302 + if x != nil { 303 + return x.Target 304 + } 305 + return "" 306 + } 307 + 308 + func (x *HeaderAssertion) GetComparator() StringComparator { 309 + if x != nil { 310 + return x.Comparator 311 + } 312 + return StringComparator_STRING_COMPARATOR_UNSPECIFIED 313 + } 314 + 315 + func (x *HeaderAssertion) GetKey() string { 316 + if x != nil { 317 + return x.Key 318 + } 319 + return "" 320 + } 321 + 322 + var File_private_location_v1_assertions_proto protoreflect.FileDescriptor 323 + 324 + const file_private_location_v1_assertions_proto_rawDesc = "" + 325 + "\n" + 326 + "$private_location/v1/assertions.proto\x12\x13private_location.v1\"t\n" + 327 + "\x13StatusCodeAssertion\x12\x16\n" + 328 + "\x06target\x18\x01 \x01(\x03R\x06target\x12E\n" + 329 + "\n" + 330 + "comparator\x18\x02 \x01(\x0e2%.private_location.v1.NumberComparatorR\n" + 331 + "comparator\"n\n" + 332 + "\rBodyAssertion\x12\x16\n" + 333 + "\x06target\x18\x01 \x01(\tR\x06target\x12E\n" + 334 + "\n" + 335 + "comparator\x18\x02 \x01(\x0e2%.private_location.v1.StringComparatorR\n" + 336 + "comparator\"\x82\x01\n" + 337 + "\x0fHeaderAssertion\x12\x16\n" + 338 + "\x06target\x18\x01 \x01(\tR\x06target\x12E\n" + 339 + "\n" + 340 + "comparator\x18\x02 \x01(\x0e2%.private_location.v1.StringComparatorR\n" + 341 + "comparator\x12\x10\n" + 342 + "\x03key\x18\x03 \x01(\tR\x03key*\x8f\x02\n" + 343 + "\x10NumberComparator\x12!\n" + 344 + "\x1dNUMBER_COMPARATOR_UNSPECIFIED\x10\x00\x12\x1b\n" + 345 + "\x17NUMBER_COMPARATOR_EQUAL\x10\x01\x12\x1f\n" + 346 + "\x1bNUMBER_COMPARATOR_NOT_EQUAL\x10\x02\x12\"\n" + 347 + "\x1eNUMBER_COMPARATOR_GREATER_THAN\x10\x03\x12+\n" + 348 + "'NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL\x10\x04\x12\x1f\n" + 349 + "\x1bNUMBER_COMPARATOR_LESS_THAN\x10\x05\x12(\n" + 350 + "$NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL\x10\x06*\x91\x03\n" + 351 + "\x10StringComparator\x12!\n" + 352 + "\x1dSTRING_COMPARATOR_UNSPECIFIED\x10\x00\x12\x1e\n" + 353 + "\x1aSTRING_COMPARATOR_CONTAINS\x10\x01\x12\"\n" + 354 + "\x1eSTRING_COMPARATOR_NOT_CONTAINS\x10\x02\x12\x1b\n" + 355 + "\x17STRING_COMPARATOR_EQUAL\x10\x03\x12\x1f\n" + 356 + "\x1bSTRING_COMPARATOR_NOT_EQUAL\x10\x04\x12\x1b\n" + 357 + "\x17STRING_COMPARATOR_EMPTY\x10\x05\x12\x1f\n" + 358 + "\x1bSTRING_COMPARATOR_NOT_EMPTY\x10\x06\x12\"\n" + 359 + "\x1eSTRING_COMPARATOR_GREATER_THAN\x10\a\x12+\n" + 360 + "'STRING_COMPARATOR_GREATER_THAN_OR_EQUAL\x10\b\x12\x1f\n" + 361 + "\x1bSTRING_COMPARATOR_LESS_THAN\x10\t\x12(\n" + 362 + "$STRING_COMPARATOR_LESS_THAN_OR_EQUAL\x10\n" + 363 + "BJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 364 + 365 + var ( 366 + file_private_location_v1_assertions_proto_rawDescOnce sync.Once 367 + file_private_location_v1_assertions_proto_rawDescData []byte 368 + ) 369 + 370 + func file_private_location_v1_assertions_proto_rawDescGZIP() []byte { 371 + file_private_location_v1_assertions_proto_rawDescOnce.Do(func() { 372 + file_private_location_v1_assertions_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_private_location_v1_assertions_proto_rawDesc), len(file_private_location_v1_assertions_proto_rawDesc))) 373 + }) 374 + return file_private_location_v1_assertions_proto_rawDescData 375 + } 376 + 377 + var file_private_location_v1_assertions_proto_enumTypes = make([]protoimpl.EnumInfo, 2) 378 + var file_private_location_v1_assertions_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 379 + var file_private_location_v1_assertions_proto_goTypes = []any{ 380 + (NumberComparator)(0), // 0: private_location.v1.NumberComparator 381 + (StringComparator)(0), // 1: private_location.v1.StringComparator 382 + (*StatusCodeAssertion)(nil), // 2: private_location.v1.StatusCodeAssertion 383 + (*BodyAssertion)(nil), // 3: private_location.v1.BodyAssertion 384 + (*HeaderAssertion)(nil), // 4: private_location.v1.HeaderAssertion 385 + } 386 + var file_private_location_v1_assertions_proto_depIdxs = []int32{ 387 + 0, // 0: private_location.v1.StatusCodeAssertion.comparator:type_name -> private_location.v1.NumberComparator 388 + 1, // 1: private_location.v1.BodyAssertion.comparator:type_name -> private_location.v1.StringComparator 389 + 1, // 2: private_location.v1.HeaderAssertion.comparator:type_name -> private_location.v1.StringComparator 390 + 3, // [3:3] is the sub-list for method output_type 391 + 3, // [3:3] is the sub-list for method input_type 392 + 3, // [3:3] is the sub-list for extension type_name 393 + 3, // [3:3] is the sub-list for extension extendee 394 + 0, // [0:3] is the sub-list for field type_name 395 + } 396 + 397 + func init() { file_private_location_v1_assertions_proto_init() } 398 + func file_private_location_v1_assertions_proto_init() { 399 + if File_private_location_v1_assertions_proto != nil { 400 + return 401 + } 402 + type x struct{} 403 + out := protoimpl.TypeBuilder{ 404 + File: protoimpl.DescBuilder{ 405 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 406 + RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_assertions_proto_rawDesc), len(file_private_location_v1_assertions_proto_rawDesc)), 407 + NumEnums: 2, 408 + NumMessages: 3, 409 + NumExtensions: 0, 410 + NumServices: 0, 411 + }, 412 + GoTypes: file_private_location_v1_assertions_proto_goTypes, 413 + DependencyIndexes: file_private_location_v1_assertions_proto_depIdxs, 414 + EnumInfos: file_private_location_v1_assertions_proto_enumTypes, 415 + MessageInfos: file_private_location_v1_assertions_proto_msgTypes, 416 + }.Build() 417 + File_private_location_v1_assertions_proto = out.File 418 + file_private_location_v1_assertions_proto_goTypes = nil 419 + file_private_location_v1_assertions_proto_depIdxs = nil 420 + }
+298
apps/checker/proto/private_location/v1/http_monitor.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.36.10 4 + // protoc (unknown) 5 + // source: private_location/v1/http_monitor.proto 6 + 7 + package v1 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + reflect "reflect" 13 + sync "sync" 14 + unsafe "unsafe" 15 + ) 16 + 17 + const ( 18 + // Verify that this generated code is sufficiently up-to-date. 19 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 + // Verify that runtime/protoimpl is sufficiently up-to-date. 21 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 + ) 23 + 24 + type Headers struct { 25 + state protoimpl.MessageState `protogen:"open.v1"` 26 + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` 27 + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` 28 + unknownFields protoimpl.UnknownFields 29 + sizeCache protoimpl.SizeCache 30 + } 31 + 32 + func (x *Headers) Reset() { 33 + *x = Headers{} 34 + mi := &file_private_location_v1_http_monitor_proto_msgTypes[0] 35 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 36 + ms.StoreMessageInfo(mi) 37 + } 38 + 39 + func (x *Headers) String() string { 40 + return protoimpl.X.MessageStringOf(x) 41 + } 42 + 43 + func (*Headers) ProtoMessage() {} 44 + 45 + func (x *Headers) ProtoReflect() protoreflect.Message { 46 + mi := &file_private_location_v1_http_monitor_proto_msgTypes[0] 47 + if x != nil { 48 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 49 + if ms.LoadMessageInfo() == nil { 50 + ms.StoreMessageInfo(mi) 51 + } 52 + return ms 53 + } 54 + return mi.MessageOf(x) 55 + } 56 + 57 + // Deprecated: Use Headers.ProtoReflect.Descriptor instead. 58 + func (*Headers) Descriptor() ([]byte, []int) { 59 + return file_private_location_v1_http_monitor_proto_rawDescGZIP(), []int{0} 60 + } 61 + 62 + func (x *Headers) GetKey() string { 63 + if x != nil { 64 + return x.Key 65 + } 66 + return "" 67 + } 68 + 69 + func (x *Headers) GetValue() string { 70 + if x != nil { 71 + return x.Value 72 + } 73 + return "" 74 + } 75 + 76 + type HTTPMonitor struct { 77 + state protoimpl.MessageState `protogen:"open.v1"` 78 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 79 + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` 80 + Periodicity string `protobuf:"bytes,3,opt,name=periodicity,proto3" json:"periodicity,omitempty"` 81 + Method string `protobuf:"bytes,4,opt,name=method,proto3" json:"method,omitempty"` 82 + Body string `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"` 83 + Timeout int64 `protobuf:"varint,6,opt,name=timeout,proto3" json:"timeout,omitempty"` 84 + DegradedAt *int64 `protobuf:"varint,7,opt,name=degraded_at,json=degradedAt,proto3,oneof" json:"degraded_at,omitempty"` 85 + Retry int64 `protobuf:"varint,8,opt,name=retry,proto3" json:"retry,omitempty"` 86 + FollowRedirects bool `protobuf:"varint,9,opt,name=follow_redirects,json=followRedirects,proto3" json:"follow_redirects,omitempty"` 87 + Headers []*Headers `protobuf:"bytes,10,rep,name=headers,proto3" json:"headers,omitempty"` 88 + StatusCodeAssertions []*StatusCodeAssertion `protobuf:"bytes,11,rep,name=status_code_assertions,json=statusCodeAssertions,proto3" json:"status_code_assertions,omitempty"` 89 + BodyAssertions []*BodyAssertion `protobuf:"bytes,12,rep,name=body_assertions,json=bodyAssertions,proto3" json:"body_assertions,omitempty"` 90 + HeaderAssertions []*HeaderAssertion `protobuf:"bytes,13,rep,name=header_assertions,json=headerAssertions,proto3" json:"header_assertions,omitempty"` 91 + unknownFields protoimpl.UnknownFields 92 + sizeCache protoimpl.SizeCache 93 + } 94 + 95 + func (x *HTTPMonitor) Reset() { 96 + *x = HTTPMonitor{} 97 + mi := &file_private_location_v1_http_monitor_proto_msgTypes[1] 98 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 99 + ms.StoreMessageInfo(mi) 100 + } 101 + 102 + func (x *HTTPMonitor) String() string { 103 + return protoimpl.X.MessageStringOf(x) 104 + } 105 + 106 + func (*HTTPMonitor) ProtoMessage() {} 107 + 108 + func (x *HTTPMonitor) ProtoReflect() protoreflect.Message { 109 + mi := &file_private_location_v1_http_monitor_proto_msgTypes[1] 110 + if x != nil { 111 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 112 + if ms.LoadMessageInfo() == nil { 113 + ms.StoreMessageInfo(mi) 114 + } 115 + return ms 116 + } 117 + return mi.MessageOf(x) 118 + } 119 + 120 + // Deprecated: Use HTTPMonitor.ProtoReflect.Descriptor instead. 121 + func (*HTTPMonitor) Descriptor() ([]byte, []int) { 122 + return file_private_location_v1_http_monitor_proto_rawDescGZIP(), []int{1} 123 + } 124 + 125 + func (x *HTTPMonitor) GetId() string { 126 + if x != nil { 127 + return x.Id 128 + } 129 + return "" 130 + } 131 + 132 + func (x *HTTPMonitor) GetUrl() string { 133 + if x != nil { 134 + return x.Url 135 + } 136 + return "" 137 + } 138 + 139 + func (x *HTTPMonitor) GetPeriodicity() string { 140 + if x != nil { 141 + return x.Periodicity 142 + } 143 + return "" 144 + } 145 + 146 + func (x *HTTPMonitor) GetMethod() string { 147 + if x != nil { 148 + return x.Method 149 + } 150 + return "" 151 + } 152 + 153 + func (x *HTTPMonitor) GetBody() string { 154 + if x != nil { 155 + return x.Body 156 + } 157 + return "" 158 + } 159 + 160 + func (x *HTTPMonitor) GetTimeout() int64 { 161 + if x != nil { 162 + return x.Timeout 163 + } 164 + return 0 165 + } 166 + 167 + func (x *HTTPMonitor) GetDegradedAt() int64 { 168 + if x != nil && x.DegradedAt != nil { 169 + return *x.DegradedAt 170 + } 171 + return 0 172 + } 173 + 174 + func (x *HTTPMonitor) GetRetry() int64 { 175 + if x != nil { 176 + return x.Retry 177 + } 178 + return 0 179 + } 180 + 181 + func (x *HTTPMonitor) GetFollowRedirects() bool { 182 + if x != nil { 183 + return x.FollowRedirects 184 + } 185 + return false 186 + } 187 + 188 + func (x *HTTPMonitor) GetHeaders() []*Headers { 189 + if x != nil { 190 + return x.Headers 191 + } 192 + return nil 193 + } 194 + 195 + func (x *HTTPMonitor) GetStatusCodeAssertions() []*StatusCodeAssertion { 196 + if x != nil { 197 + return x.StatusCodeAssertions 198 + } 199 + return nil 200 + } 201 + 202 + func (x *HTTPMonitor) GetBodyAssertions() []*BodyAssertion { 203 + if x != nil { 204 + return x.BodyAssertions 205 + } 206 + return nil 207 + } 208 + 209 + func (x *HTTPMonitor) GetHeaderAssertions() []*HeaderAssertion { 210 + if x != nil { 211 + return x.HeaderAssertions 212 + } 213 + return nil 214 + } 215 + 216 + var File_private_location_v1_http_monitor_proto protoreflect.FileDescriptor 217 + 218 + const file_private_location_v1_http_monitor_proto_rawDesc = "" + 219 + "\n" + 220 + "&private_location/v1/http_monitor.proto\x12\x13private_location.v1\x1a$private_location/v1/assertions.proto\"1\n" + 221 + "\aHeaders\x12\x10\n" + 222 + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + 223 + "\x05value\x18\x02 \x01(\tR\x05value\"\xc6\x04\n" + 224 + "\vHTTPMonitor\x12\x0e\n" + 225 + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + 226 + "\x03url\x18\x02 \x01(\tR\x03url\x12 \n" + 227 + "\vperiodicity\x18\x03 \x01(\tR\vperiodicity\x12\x16\n" + 228 + "\x06method\x18\x04 \x01(\tR\x06method\x12\x12\n" + 229 + "\x04body\x18\x05 \x01(\tR\x04body\x12\x18\n" + 230 + "\atimeout\x18\x06 \x01(\x03R\atimeout\x12$\n" + 231 + "\vdegraded_at\x18\a \x01(\x03H\x00R\n" + 232 + "degradedAt\x88\x01\x01\x12\x14\n" + 233 + "\x05retry\x18\b \x01(\x03R\x05retry\x12)\n" + 234 + "\x10follow_redirects\x18\t \x01(\bR\x0ffollowRedirects\x126\n" + 235 + "\aheaders\x18\n" + 236 + " \x03(\v2\x1c.private_location.v1.HeadersR\aheaders\x12^\n" + 237 + "\x16status_code_assertions\x18\v \x03(\v2(.private_location.v1.StatusCodeAssertionR\x14statusCodeAssertions\x12K\n" + 238 + "\x0fbody_assertions\x18\f \x03(\v2\".private_location.v1.BodyAssertionR\x0ebodyAssertions\x12Q\n" + 239 + "\x11header_assertions\x18\r \x03(\v2$.private_location.v1.HeaderAssertionR\x10headerAssertionsB\x0e\n" + 240 + "\f_degraded_atBJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 241 + 242 + var ( 243 + file_private_location_v1_http_monitor_proto_rawDescOnce sync.Once 244 + file_private_location_v1_http_monitor_proto_rawDescData []byte 245 + ) 246 + 247 + func file_private_location_v1_http_monitor_proto_rawDescGZIP() []byte { 248 + file_private_location_v1_http_monitor_proto_rawDescOnce.Do(func() { 249 + file_private_location_v1_http_monitor_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_private_location_v1_http_monitor_proto_rawDesc), len(file_private_location_v1_http_monitor_proto_rawDesc))) 250 + }) 251 + return file_private_location_v1_http_monitor_proto_rawDescData 252 + } 253 + 254 + var file_private_location_v1_http_monitor_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 255 + var file_private_location_v1_http_monitor_proto_goTypes = []any{ 256 + (*Headers)(nil), // 0: private_location.v1.Headers 257 + (*HTTPMonitor)(nil), // 1: private_location.v1.HTTPMonitor 258 + (*StatusCodeAssertion)(nil), // 2: private_location.v1.StatusCodeAssertion 259 + (*BodyAssertion)(nil), // 3: private_location.v1.BodyAssertion 260 + (*HeaderAssertion)(nil), // 4: private_location.v1.HeaderAssertion 261 + } 262 + var file_private_location_v1_http_monitor_proto_depIdxs = []int32{ 263 + 0, // 0: private_location.v1.HTTPMonitor.headers:type_name -> private_location.v1.Headers 264 + 2, // 1: private_location.v1.HTTPMonitor.status_code_assertions:type_name -> private_location.v1.StatusCodeAssertion 265 + 3, // 2: private_location.v1.HTTPMonitor.body_assertions:type_name -> private_location.v1.BodyAssertion 266 + 4, // 3: private_location.v1.HTTPMonitor.header_assertions:type_name -> private_location.v1.HeaderAssertion 267 + 4, // [4:4] is the sub-list for method output_type 268 + 4, // [4:4] is the sub-list for method input_type 269 + 4, // [4:4] is the sub-list for extension type_name 270 + 4, // [4:4] is the sub-list for extension extendee 271 + 0, // [0:4] is the sub-list for field type_name 272 + } 273 + 274 + func init() { file_private_location_v1_http_monitor_proto_init() } 275 + func file_private_location_v1_http_monitor_proto_init() { 276 + if File_private_location_v1_http_monitor_proto != nil { 277 + return 278 + } 279 + file_private_location_v1_assertions_proto_init() 280 + file_private_location_v1_http_monitor_proto_msgTypes[1].OneofWrappers = []any{} 281 + type x struct{} 282 + out := protoimpl.TypeBuilder{ 283 + File: protoimpl.DescBuilder{ 284 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 285 + RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_http_monitor_proto_rawDesc), len(file_private_location_v1_http_monitor_proto_rawDesc)), 286 + NumEnums: 0, 287 + NumMessages: 2, 288 + NumExtensions: 0, 289 + NumServices: 0, 290 + }, 291 + GoTypes: file_private_location_v1_http_monitor_proto_goTypes, 292 + DependencyIndexes: file_private_location_v1_http_monitor_proto_depIdxs, 293 + MessageInfos: file_private_location_v1_http_monitor_proto_msgTypes, 294 + }.Build() 295 + File_private_location_v1_http_monitor_proto = out.File 296 + file_private_location_v1_http_monitor_proto_goTypes = nil 297 + file_private_location_v1_http_monitor_proto_depIdxs = nil 298 + }
+168
apps/checker/proto/private_location/v1/private_location.connect.go
··· 1 + // Code generated by protoc-gen-connect-go. DO NOT EDIT. 2 + // 3 + // Source: private_location/v1/private_location.proto 4 + 5 + package v1 6 + 7 + import ( 8 + connect "connectrpc.com/connect" 9 + context "context" 10 + errors "errors" 11 + http "net/http" 12 + strings "strings" 13 + ) 14 + 15 + // This is a compile-time assertion to ensure that this generated file and the connect package are 16 + // compatible. If you get a compiler error that this constant is not defined, this code was 17 + // generated with a version of connect newer than the one compiled into your binary. You can fix the 18 + // problem by either regenerating this code with an older version of connect or updating the connect 19 + // version compiled into your binary. 20 + const _ = connect.IsAtLeastVersion1_13_0 21 + 22 + const ( 23 + // PrivateLocationServiceName is the fully-qualified name of the PrivateLocationService service. 24 + PrivateLocationServiceName = "private_location.v1.PrivateLocationService" 25 + ) 26 + 27 + // These constants are the fully-qualified names of the RPCs defined in this package. They're 28 + // exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. 29 + // 30 + // Note that these are different from the fully-qualified method names used by 31 + // google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to 32 + // reflection-formatted method names, remove the leading slash and convert the remaining slash to a 33 + // period. 34 + const ( 35 + // PrivateLocationServiceMonitorsProcedure is the fully-qualified name of the 36 + // PrivateLocationService's Monitors RPC. 37 + PrivateLocationServiceMonitorsProcedure = "/private_location.v1.PrivateLocationService/Monitors" 38 + // PrivateLocationServiceIngestTCPProcedure is the fully-qualified name of the 39 + // PrivateLocationService's IngestTCP RPC. 40 + PrivateLocationServiceIngestTCPProcedure = "/private_location.v1.PrivateLocationService/IngestTCP" 41 + // PrivateLocationServiceIngestHTTPProcedure is the fully-qualified name of the 42 + // PrivateLocationService's IngestHTTP RPC. 43 + PrivateLocationServiceIngestHTTPProcedure = "/private_location.v1.PrivateLocationService/IngestHTTP" 44 + ) 45 + 46 + // PrivateLocationServiceClient is a client for the private_location.v1.PrivateLocationService 47 + // service. 48 + type PrivateLocationServiceClient interface { 49 + Monitors(context.Context, *connect.Request[MonitorsRequest]) (*connect.Response[MonitorsResponse], error) 50 + IngestTCP(context.Context, *connect.Request[IngestTCPRequest]) (*connect.Response[IngestTCPResponse], error) 51 + IngestHTTP(context.Context, *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) 52 + } 53 + 54 + // NewPrivateLocationServiceClient constructs a client for the 55 + // private_location.v1.PrivateLocationService service. By default, it uses the Connect protocol with 56 + // the binary Protobuf Codec, asks for gzipped responses, and sends uncompressed requests. To use 57 + // the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or connect.WithGRPCWeb() options. 58 + // 59 + // The URL supplied here should be the base URL for the Connect or gRPC server (for example, 60 + // http://api.acme.com or https://acme.com/grpc). 61 + func NewPrivateLocationServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) PrivateLocationServiceClient { 62 + baseURL = strings.TrimRight(baseURL, "/") 63 + privateLocationServiceMethods := File_private_location_v1_private_location_proto.Services().ByName("PrivateLocationService").Methods() 64 + return &privateLocationServiceClient{ 65 + monitors: connect.NewClient[MonitorsRequest, MonitorsResponse]( 66 + httpClient, 67 + baseURL+PrivateLocationServiceMonitorsProcedure, 68 + connect.WithSchema(privateLocationServiceMethods.ByName("Monitors")), 69 + connect.WithClientOptions(opts...), 70 + ), 71 + ingestTCP: connect.NewClient[IngestTCPRequest, IngestTCPResponse]( 72 + httpClient, 73 + baseURL+PrivateLocationServiceIngestTCPProcedure, 74 + connect.WithSchema(privateLocationServiceMethods.ByName("IngestTCP")), 75 + connect.WithClientOptions(opts...), 76 + ), 77 + ingestHTTP: connect.NewClient[IngestHTTPRequest, IngestHTTPResponse]( 78 + httpClient, 79 + baseURL+PrivateLocationServiceIngestHTTPProcedure, 80 + connect.WithSchema(privateLocationServiceMethods.ByName("IngestHTTP")), 81 + connect.WithClientOptions(opts...), 82 + ), 83 + } 84 + } 85 + 86 + // privateLocationServiceClient implements PrivateLocationServiceClient. 87 + type privateLocationServiceClient struct { 88 + monitors *connect.Client[MonitorsRequest, MonitorsResponse] 89 + ingestTCP *connect.Client[IngestTCPRequest, IngestTCPResponse] 90 + ingestHTTP *connect.Client[IngestHTTPRequest, IngestHTTPResponse] 91 + } 92 + 93 + // Monitors calls private_location.v1.PrivateLocationService.Monitors. 94 + func (c *privateLocationServiceClient) Monitors(ctx context.Context, req *connect.Request[MonitorsRequest]) (*connect.Response[MonitorsResponse], error) { 95 + return c.monitors.CallUnary(ctx, req) 96 + } 97 + 98 + // IngestTCP calls private_location.v1.PrivateLocationService.IngestTCP. 99 + func (c *privateLocationServiceClient) IngestTCP(ctx context.Context, req *connect.Request[IngestTCPRequest]) (*connect.Response[IngestTCPResponse], error) { 100 + return c.ingestTCP.CallUnary(ctx, req) 101 + } 102 + 103 + // IngestHTTP calls private_location.v1.PrivateLocationService.IngestHTTP. 104 + func (c *privateLocationServiceClient) IngestHTTP(ctx context.Context, req *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) { 105 + return c.ingestHTTP.CallUnary(ctx, req) 106 + } 107 + 108 + // PrivateLocationServiceHandler is an implementation of the 109 + // private_location.v1.PrivateLocationService service. 110 + type PrivateLocationServiceHandler interface { 111 + Monitors(context.Context, *connect.Request[MonitorsRequest]) (*connect.Response[MonitorsResponse], error) 112 + IngestTCP(context.Context, *connect.Request[IngestTCPRequest]) (*connect.Response[IngestTCPResponse], error) 113 + IngestHTTP(context.Context, *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) 114 + } 115 + 116 + // NewPrivateLocationServiceHandler builds an HTTP handler from the service implementation. It 117 + // returns the path on which to mount the handler and the handler itself. 118 + // 119 + // By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf 120 + // and JSON codecs. They also support gzip compression. 121 + func NewPrivateLocationServiceHandler(svc PrivateLocationServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { 122 + privateLocationServiceMethods := File_private_location_v1_private_location_proto.Services().ByName("PrivateLocationService").Methods() 123 + privateLocationServiceMonitorsHandler := connect.NewUnaryHandler( 124 + PrivateLocationServiceMonitorsProcedure, 125 + svc.Monitors, 126 + connect.WithSchema(privateLocationServiceMethods.ByName("Monitors")), 127 + connect.WithHandlerOptions(opts...), 128 + ) 129 + privateLocationServiceIngestTCPHandler := connect.NewUnaryHandler( 130 + PrivateLocationServiceIngestTCPProcedure, 131 + svc.IngestTCP, 132 + connect.WithSchema(privateLocationServiceMethods.ByName("IngestTCP")), 133 + connect.WithHandlerOptions(opts...), 134 + ) 135 + privateLocationServiceIngestHTTPHandler := connect.NewUnaryHandler( 136 + PrivateLocationServiceIngestHTTPProcedure, 137 + svc.IngestHTTP, 138 + connect.WithSchema(privateLocationServiceMethods.ByName("IngestHTTP")), 139 + connect.WithHandlerOptions(opts...), 140 + ) 141 + return "/private_location.v1.PrivateLocationService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 142 + switch r.URL.Path { 143 + case PrivateLocationServiceMonitorsProcedure: 144 + privateLocationServiceMonitorsHandler.ServeHTTP(w, r) 145 + case PrivateLocationServiceIngestTCPProcedure: 146 + privateLocationServiceIngestTCPHandler.ServeHTTP(w, r) 147 + case PrivateLocationServiceIngestHTTPProcedure: 148 + privateLocationServiceIngestHTTPHandler.ServeHTTP(w, r) 149 + default: 150 + http.NotFound(w, r) 151 + } 152 + }) 153 + } 154 + 155 + // UnimplementedPrivateLocationServiceHandler returns CodeUnimplemented from all methods. 156 + type UnimplementedPrivateLocationServiceHandler struct{} 157 + 158 + func (UnimplementedPrivateLocationServiceHandler) Monitors(context.Context, *connect.Request[MonitorsRequest]) (*connect.Response[MonitorsResponse], error) { 159 + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("private_location.v1.PrivateLocationService.Monitors is not implemented")) 160 + } 161 + 162 + func (UnimplementedPrivateLocationServiceHandler) IngestTCP(context.Context, *connect.Request[IngestTCPRequest]) (*connect.Response[IngestTCPResponse], error) { 163 + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("private_location.v1.PrivateLocationService.IngestTCP is not implemented")) 164 + } 165 + 166 + func (UnimplementedPrivateLocationServiceHandler) IngestHTTP(context.Context, *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) { 167 + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("private_location.v1.PrivateLocationService.IngestHTTP is not implemented")) 168 + }
+549
apps/checker/proto/private_location/v1/private_location.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.36.10 4 + // protoc (unknown) 5 + // source: private_location/v1/private_location.proto 6 + 7 + package v1 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + reflect "reflect" 13 + sync "sync" 14 + unsafe "unsafe" 15 + ) 16 + 17 + const ( 18 + // Verify that this generated code is sufficiently up-to-date. 19 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 + // Verify that runtime/protoimpl is sufficiently up-to-date. 21 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 + ) 23 + 24 + type MonitorsRequest struct { 25 + state protoimpl.MessageState `protogen:"open.v1"` 26 + unknownFields protoimpl.UnknownFields 27 + sizeCache protoimpl.SizeCache 28 + } 29 + 30 + func (x *MonitorsRequest) Reset() { 31 + *x = MonitorsRequest{} 32 + mi := &file_private_location_v1_private_location_proto_msgTypes[0] 33 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 34 + ms.StoreMessageInfo(mi) 35 + } 36 + 37 + func (x *MonitorsRequest) String() string { 38 + return protoimpl.X.MessageStringOf(x) 39 + } 40 + 41 + func (*MonitorsRequest) ProtoMessage() {} 42 + 43 + func (x *MonitorsRequest) ProtoReflect() protoreflect.Message { 44 + mi := &file_private_location_v1_private_location_proto_msgTypes[0] 45 + if x != nil { 46 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 47 + if ms.LoadMessageInfo() == nil { 48 + ms.StoreMessageInfo(mi) 49 + } 50 + return ms 51 + } 52 + return mi.MessageOf(x) 53 + } 54 + 55 + // Deprecated: Use MonitorsRequest.ProtoReflect.Descriptor instead. 56 + func (*MonitorsRequest) Descriptor() ([]byte, []int) { 57 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{0} 58 + } 59 + 60 + type MonitorsResponse struct { 61 + state protoimpl.MessageState `protogen:"open.v1"` 62 + HttpMonitors []*HTTPMonitor `protobuf:"bytes,1,rep,name=http_monitors,json=httpMonitors,proto3" json:"http_monitors,omitempty"` 63 + TcpMonitors []*TCPMonitor `protobuf:"bytes,2,rep,name=tcp_monitors,json=tcpMonitors,proto3" json:"tcp_monitors,omitempty"` 64 + unknownFields protoimpl.UnknownFields 65 + sizeCache protoimpl.SizeCache 66 + } 67 + 68 + func (x *MonitorsResponse) Reset() { 69 + *x = MonitorsResponse{} 70 + mi := &file_private_location_v1_private_location_proto_msgTypes[1] 71 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 72 + ms.StoreMessageInfo(mi) 73 + } 74 + 75 + func (x *MonitorsResponse) String() string { 76 + return protoimpl.X.MessageStringOf(x) 77 + } 78 + 79 + func (*MonitorsResponse) ProtoMessage() {} 80 + 81 + func (x *MonitorsResponse) ProtoReflect() protoreflect.Message { 82 + mi := &file_private_location_v1_private_location_proto_msgTypes[1] 83 + if x != nil { 84 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 85 + if ms.LoadMessageInfo() == nil { 86 + ms.StoreMessageInfo(mi) 87 + } 88 + return ms 89 + } 90 + return mi.MessageOf(x) 91 + } 92 + 93 + // Deprecated: Use MonitorsResponse.ProtoReflect.Descriptor instead. 94 + func (*MonitorsResponse) Descriptor() ([]byte, []int) { 95 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{1} 96 + } 97 + 98 + func (x *MonitorsResponse) GetHttpMonitors() []*HTTPMonitor { 99 + if x != nil { 100 + return x.HttpMonitors 101 + } 102 + return nil 103 + } 104 + 105 + func (x *MonitorsResponse) GetTcpMonitors() []*TCPMonitor { 106 + if x != nil { 107 + return x.TcpMonitors 108 + } 109 + return nil 110 + } 111 + 112 + type IngestTCPRequest struct { 113 + state protoimpl.MessageState `protogen:"open.v1"` 114 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 115 + MonitorId string `protobuf:"bytes,2,opt,name=monitorId,proto3" json:"monitorId,omitempty"` 116 + Latency int64 `protobuf:"varint,3,opt,name=latency,proto3" json:"latency,omitempty"` 117 + Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` 118 + CronTimestamp int64 `protobuf:"varint,5,opt,name=cronTimestamp,proto3" json:"cronTimestamp,omitempty"` 119 + Uri string `protobuf:"bytes,6,opt,name=uri,proto3" json:"uri,omitempty"` 120 + Message string `protobuf:"bytes,7,opt,name=message,proto3" json:"message,omitempty"` 121 + RequestStatus string `protobuf:"bytes,8,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` 122 + Error int64 `protobuf:"varint,9,opt,name=error,proto3" json:"error,omitempty"` 123 + Timing string `protobuf:"bytes,10,opt,name=timing,proto3" json:"timing,omitempty"` 124 + unknownFields protoimpl.UnknownFields 125 + sizeCache protoimpl.SizeCache 126 + } 127 + 128 + func (x *IngestTCPRequest) Reset() { 129 + *x = IngestTCPRequest{} 130 + mi := &file_private_location_v1_private_location_proto_msgTypes[2] 131 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 132 + ms.StoreMessageInfo(mi) 133 + } 134 + 135 + func (x *IngestTCPRequest) String() string { 136 + return protoimpl.X.MessageStringOf(x) 137 + } 138 + 139 + func (*IngestTCPRequest) ProtoMessage() {} 140 + 141 + func (x *IngestTCPRequest) ProtoReflect() protoreflect.Message { 142 + mi := &file_private_location_v1_private_location_proto_msgTypes[2] 143 + if x != nil { 144 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 145 + if ms.LoadMessageInfo() == nil { 146 + ms.StoreMessageInfo(mi) 147 + } 148 + return ms 149 + } 150 + return mi.MessageOf(x) 151 + } 152 + 153 + // Deprecated: Use IngestTCPRequest.ProtoReflect.Descriptor instead. 154 + func (*IngestTCPRequest) Descriptor() ([]byte, []int) { 155 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{2} 156 + } 157 + 158 + func (x *IngestTCPRequest) GetId() string { 159 + if x != nil { 160 + return x.Id 161 + } 162 + return "" 163 + } 164 + 165 + func (x *IngestTCPRequest) GetMonitorId() string { 166 + if x != nil { 167 + return x.MonitorId 168 + } 169 + return "" 170 + } 171 + 172 + func (x *IngestTCPRequest) GetLatency() int64 { 173 + if x != nil { 174 + return x.Latency 175 + } 176 + return 0 177 + } 178 + 179 + func (x *IngestTCPRequest) GetTimestamp() int64 { 180 + if x != nil { 181 + return x.Timestamp 182 + } 183 + return 0 184 + } 185 + 186 + func (x *IngestTCPRequest) GetCronTimestamp() int64 { 187 + if x != nil { 188 + return x.CronTimestamp 189 + } 190 + return 0 191 + } 192 + 193 + func (x *IngestTCPRequest) GetUri() string { 194 + if x != nil { 195 + return x.Uri 196 + } 197 + return "" 198 + } 199 + 200 + func (x *IngestTCPRequest) GetMessage() string { 201 + if x != nil { 202 + return x.Message 203 + } 204 + return "" 205 + } 206 + 207 + func (x *IngestTCPRequest) GetRequestStatus() string { 208 + if x != nil { 209 + return x.RequestStatus 210 + } 211 + return "" 212 + } 213 + 214 + func (x *IngestTCPRequest) GetError() int64 { 215 + if x != nil { 216 + return x.Error 217 + } 218 + return 0 219 + } 220 + 221 + func (x *IngestTCPRequest) GetTiming() string { 222 + if x != nil { 223 + return x.Timing 224 + } 225 + return "" 226 + } 227 + 228 + type IngestTCPResponse struct { 229 + state protoimpl.MessageState `protogen:"open.v1"` 230 + unknownFields protoimpl.UnknownFields 231 + sizeCache protoimpl.SizeCache 232 + } 233 + 234 + func (x *IngestTCPResponse) Reset() { 235 + *x = IngestTCPResponse{} 236 + mi := &file_private_location_v1_private_location_proto_msgTypes[3] 237 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 238 + ms.StoreMessageInfo(mi) 239 + } 240 + 241 + func (x *IngestTCPResponse) String() string { 242 + return protoimpl.X.MessageStringOf(x) 243 + } 244 + 245 + func (*IngestTCPResponse) ProtoMessage() {} 246 + 247 + func (x *IngestTCPResponse) ProtoReflect() protoreflect.Message { 248 + mi := &file_private_location_v1_private_location_proto_msgTypes[3] 249 + if x != nil { 250 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 251 + if ms.LoadMessageInfo() == nil { 252 + ms.StoreMessageInfo(mi) 253 + } 254 + return ms 255 + } 256 + return mi.MessageOf(x) 257 + } 258 + 259 + // Deprecated: Use IngestTCPResponse.ProtoReflect.Descriptor instead. 260 + func (*IngestTCPResponse) Descriptor() ([]byte, []int) { 261 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{3} 262 + } 263 + 264 + type IngestHTTPRequest struct { 265 + state protoimpl.MessageState `protogen:"open.v1"` 266 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 267 + MonitorId string `protobuf:"bytes,2,opt,name=monitorId,proto3" json:"monitorId,omitempty"` 268 + Latency int64 `protobuf:"varint,3,opt,name=latency,proto3" json:"latency,omitempty"` 269 + Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` 270 + CronTimestamp int64 `protobuf:"varint,5,opt,name=cronTimestamp,proto3" json:"cronTimestamp,omitempty"` 271 + Url string `protobuf:"bytes,6,opt,name=url,proto3" json:"url,omitempty"` 272 + RequestStatus string `protobuf:"bytes,7,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` 273 + Message string `protobuf:"bytes,8,opt,name=message,proto3" json:"message,omitempty"` 274 + Body string `protobuf:"bytes,9,opt,name=body,proto3" json:"body,omitempty"` 275 + Headers string `protobuf:"bytes,10,opt,name=headers,proto3" json:"headers,omitempty"` 276 + Timing string `protobuf:"bytes,11,opt,name=timing,proto3" json:"timing,omitempty"` 277 + StatusCode int64 `protobuf:"varint,12,opt,name=statusCode,proto3" json:"statusCode,omitempty"` 278 + Error int64 `protobuf:"varint,13,opt,name=error,proto3" json:"error,omitempty"` 279 + unknownFields protoimpl.UnknownFields 280 + sizeCache protoimpl.SizeCache 281 + } 282 + 283 + func (x *IngestHTTPRequest) Reset() { 284 + *x = IngestHTTPRequest{} 285 + mi := &file_private_location_v1_private_location_proto_msgTypes[4] 286 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 287 + ms.StoreMessageInfo(mi) 288 + } 289 + 290 + func (x *IngestHTTPRequest) String() string { 291 + return protoimpl.X.MessageStringOf(x) 292 + } 293 + 294 + func (*IngestHTTPRequest) ProtoMessage() {} 295 + 296 + func (x *IngestHTTPRequest) ProtoReflect() protoreflect.Message { 297 + mi := &file_private_location_v1_private_location_proto_msgTypes[4] 298 + if x != nil { 299 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 300 + if ms.LoadMessageInfo() == nil { 301 + ms.StoreMessageInfo(mi) 302 + } 303 + return ms 304 + } 305 + return mi.MessageOf(x) 306 + } 307 + 308 + // Deprecated: Use IngestHTTPRequest.ProtoReflect.Descriptor instead. 309 + func (*IngestHTTPRequest) Descriptor() ([]byte, []int) { 310 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{4} 311 + } 312 + 313 + func (x *IngestHTTPRequest) GetId() string { 314 + if x != nil { 315 + return x.Id 316 + } 317 + return "" 318 + } 319 + 320 + func (x *IngestHTTPRequest) GetMonitorId() string { 321 + if x != nil { 322 + return x.MonitorId 323 + } 324 + return "" 325 + } 326 + 327 + func (x *IngestHTTPRequest) GetLatency() int64 { 328 + if x != nil { 329 + return x.Latency 330 + } 331 + return 0 332 + } 333 + 334 + func (x *IngestHTTPRequest) GetTimestamp() int64 { 335 + if x != nil { 336 + return x.Timestamp 337 + } 338 + return 0 339 + } 340 + 341 + func (x *IngestHTTPRequest) GetCronTimestamp() int64 { 342 + if x != nil { 343 + return x.CronTimestamp 344 + } 345 + return 0 346 + } 347 + 348 + func (x *IngestHTTPRequest) GetUrl() string { 349 + if x != nil { 350 + return x.Url 351 + } 352 + return "" 353 + } 354 + 355 + func (x *IngestHTTPRequest) GetRequestStatus() string { 356 + if x != nil { 357 + return x.RequestStatus 358 + } 359 + return "" 360 + } 361 + 362 + func (x *IngestHTTPRequest) GetMessage() string { 363 + if x != nil { 364 + return x.Message 365 + } 366 + return "" 367 + } 368 + 369 + func (x *IngestHTTPRequest) GetBody() string { 370 + if x != nil { 371 + return x.Body 372 + } 373 + return "" 374 + } 375 + 376 + func (x *IngestHTTPRequest) GetHeaders() string { 377 + if x != nil { 378 + return x.Headers 379 + } 380 + return "" 381 + } 382 + 383 + func (x *IngestHTTPRequest) GetTiming() string { 384 + if x != nil { 385 + return x.Timing 386 + } 387 + return "" 388 + } 389 + 390 + func (x *IngestHTTPRequest) GetStatusCode() int64 { 391 + if x != nil { 392 + return x.StatusCode 393 + } 394 + return 0 395 + } 396 + 397 + func (x *IngestHTTPRequest) GetError() int64 { 398 + if x != nil { 399 + return x.Error 400 + } 401 + return 0 402 + } 403 + 404 + type IngestHTTPResponse struct { 405 + state protoimpl.MessageState `protogen:"open.v1"` 406 + unknownFields protoimpl.UnknownFields 407 + sizeCache protoimpl.SizeCache 408 + } 409 + 410 + func (x *IngestHTTPResponse) Reset() { 411 + *x = IngestHTTPResponse{} 412 + mi := &file_private_location_v1_private_location_proto_msgTypes[5] 413 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 414 + ms.StoreMessageInfo(mi) 415 + } 416 + 417 + func (x *IngestHTTPResponse) String() string { 418 + return protoimpl.X.MessageStringOf(x) 419 + } 420 + 421 + func (*IngestHTTPResponse) ProtoMessage() {} 422 + 423 + func (x *IngestHTTPResponse) ProtoReflect() protoreflect.Message { 424 + mi := &file_private_location_v1_private_location_proto_msgTypes[5] 425 + if x != nil { 426 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 427 + if ms.LoadMessageInfo() == nil { 428 + ms.StoreMessageInfo(mi) 429 + } 430 + return ms 431 + } 432 + return mi.MessageOf(x) 433 + } 434 + 435 + // Deprecated: Use IngestHTTPResponse.ProtoReflect.Descriptor instead. 436 + func (*IngestHTTPResponse) Descriptor() ([]byte, []int) { 437 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{5} 438 + } 439 + 440 + var File_private_location_v1_private_location_proto protoreflect.FileDescriptor 441 + 442 + const file_private_location_v1_private_location_proto_rawDesc = "" + 443 + "\n" + 444 + "*private_location/v1/private_location.proto\x12\x13private_location.v1\x1a&private_location/v1/http_monitor.proto\x1a%private_location/v1/tcp_monitor.proto\"\x11\n" + 445 + "\x0fMonitorsRequest\"\x9d\x01\n" + 446 + "\x10MonitorsResponse\x12E\n" + 447 + "\rhttp_monitors\x18\x01 \x03(\v2 .private_location.v1.HTTPMonitorR\fhttpMonitors\x12B\n" + 448 + "\ftcp_monitors\x18\x02 \x03(\v2\x1f.private_location.v1.TCPMonitorR\vtcpMonitors\"\x9e\x02\n" + 449 + "\x10IngestTCPRequest\x12\x0e\n" + 450 + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + 451 + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + 452 + "\alatency\x18\x03 \x01(\x03R\alatency\x12\x1c\n" + 453 + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12$\n" + 454 + "\rcronTimestamp\x18\x05 \x01(\x03R\rcronTimestamp\x12\x10\n" + 455 + "\x03uri\x18\x06 \x01(\tR\x03uri\x12\x18\n" + 456 + "\amessage\x18\a \x01(\tR\amessage\x12$\n" + 457 + "\rrequestStatus\x18\b \x01(\tR\rrequestStatus\x12\x14\n" + 458 + "\x05error\x18\t \x01(\x03R\x05error\x12\x16\n" + 459 + "\x06timing\x18\n" + 460 + " \x01(\tR\x06timing\"\x13\n" + 461 + "\x11IngestTCPResponse\"\xed\x02\n" + 462 + "\x11IngestHTTPRequest\x12\x0e\n" + 463 + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + 464 + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + 465 + "\alatency\x18\x03 \x01(\x03R\alatency\x12\x1c\n" + 466 + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12$\n" + 467 + "\rcronTimestamp\x18\x05 \x01(\x03R\rcronTimestamp\x12\x10\n" + 468 + "\x03url\x18\x06 \x01(\tR\x03url\x12$\n" + 469 + "\rrequestStatus\x18\a \x01(\tR\rrequestStatus\x12\x18\n" + 470 + "\amessage\x18\b \x01(\tR\amessage\x12\x12\n" + 471 + "\x04body\x18\t \x01(\tR\x04body\x12\x18\n" + 472 + "\aheaders\x18\n" + 473 + " \x01(\tR\aheaders\x12\x16\n" + 474 + "\x06timing\x18\v \x01(\tR\x06timing\x12\x1e\n" + 475 + "\n" + 476 + "statusCode\x18\f \x01(\x03R\n" + 477 + "statusCode\x12\x14\n" + 478 + "\x05error\x18\r \x01(\x03R\x05error\"\x14\n" + 479 + "\x12IngestHTTPResponse2\xb2\x02\n" + 480 + "\x16PrivateLocationService\x12Y\n" + 481 + "\bMonitors\x12$.private_location.v1.MonitorsRequest\x1a%.private_location.v1.MonitorsResponse\"\x00\x12\\\n" + 482 + "\tIngestTCP\x12%.private_location.v1.IngestTCPRequest\x1a&.private_location.v1.IngestTCPResponse\"\x00\x12_\n" + 483 + "\n" + 484 + "IngestHTTP\x12&.private_location.v1.IngestHTTPRequest\x1a'.private_location.v1.IngestHTTPResponse\"\x00BJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 485 + 486 + var ( 487 + file_private_location_v1_private_location_proto_rawDescOnce sync.Once 488 + file_private_location_v1_private_location_proto_rawDescData []byte 489 + ) 490 + 491 + func file_private_location_v1_private_location_proto_rawDescGZIP() []byte { 492 + file_private_location_v1_private_location_proto_rawDescOnce.Do(func() { 493 + file_private_location_v1_private_location_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_private_location_v1_private_location_proto_rawDesc), len(file_private_location_v1_private_location_proto_rawDesc))) 494 + }) 495 + return file_private_location_v1_private_location_proto_rawDescData 496 + } 497 + 498 + var file_private_location_v1_private_location_proto_msgTypes = make([]protoimpl.MessageInfo, 6) 499 + var file_private_location_v1_private_location_proto_goTypes = []any{ 500 + (*MonitorsRequest)(nil), // 0: private_location.v1.MonitorsRequest 501 + (*MonitorsResponse)(nil), // 1: private_location.v1.MonitorsResponse 502 + (*IngestTCPRequest)(nil), // 2: private_location.v1.IngestTCPRequest 503 + (*IngestTCPResponse)(nil), // 3: private_location.v1.IngestTCPResponse 504 + (*IngestHTTPRequest)(nil), // 4: private_location.v1.IngestHTTPRequest 505 + (*IngestHTTPResponse)(nil), // 5: private_location.v1.IngestHTTPResponse 506 + (*HTTPMonitor)(nil), // 6: private_location.v1.HTTPMonitor 507 + (*TCPMonitor)(nil), // 7: private_location.v1.TCPMonitor 508 + } 509 + var file_private_location_v1_private_location_proto_depIdxs = []int32{ 510 + 6, // 0: private_location.v1.MonitorsResponse.http_monitors:type_name -> private_location.v1.HTTPMonitor 511 + 7, // 1: private_location.v1.MonitorsResponse.tcp_monitors:type_name -> private_location.v1.TCPMonitor 512 + 0, // 2: private_location.v1.PrivateLocationService.Monitors:input_type -> private_location.v1.MonitorsRequest 513 + 2, // 3: private_location.v1.PrivateLocationService.IngestTCP:input_type -> private_location.v1.IngestTCPRequest 514 + 4, // 4: private_location.v1.PrivateLocationService.IngestHTTP:input_type -> private_location.v1.IngestHTTPRequest 515 + 1, // 5: private_location.v1.PrivateLocationService.Monitors:output_type -> private_location.v1.MonitorsResponse 516 + 3, // 6: private_location.v1.PrivateLocationService.IngestTCP:output_type -> private_location.v1.IngestTCPResponse 517 + 5, // 7: private_location.v1.PrivateLocationService.IngestHTTP:output_type -> private_location.v1.IngestHTTPResponse 518 + 5, // [5:8] is the sub-list for method output_type 519 + 2, // [2:5] is the sub-list for method input_type 520 + 2, // [2:2] is the sub-list for extension type_name 521 + 2, // [2:2] is the sub-list for extension extendee 522 + 0, // [0:2] is the sub-list for field type_name 523 + } 524 + 525 + func init() { file_private_location_v1_private_location_proto_init() } 526 + func file_private_location_v1_private_location_proto_init() { 527 + if File_private_location_v1_private_location_proto != nil { 528 + return 529 + } 530 + file_private_location_v1_http_monitor_proto_init() 531 + file_private_location_v1_tcp_monitor_proto_init() 532 + type x struct{} 533 + out := protoimpl.TypeBuilder{ 534 + File: protoimpl.DescBuilder{ 535 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 536 + RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_private_location_proto_rawDesc), len(file_private_location_v1_private_location_proto_rawDesc)), 537 + NumEnums: 0, 538 + NumMessages: 6, 539 + NumExtensions: 0, 540 + NumServices: 1, 541 + }, 542 + GoTypes: file_private_location_v1_private_location_proto_goTypes, 543 + DependencyIndexes: file_private_location_v1_private_location_proto_depIdxs, 544 + MessageInfos: file_private_location_v1_private_location_proto_msgTypes, 545 + }.Build() 546 + File_private_location_v1_private_location_proto = out.File 547 + file_private_location_v1_private_location_proto_goTypes = nil 548 + file_private_location_v1_private_location_proto_depIdxs = nil 549 + }
+171
apps/checker/proto/private_location/v1/tcp_monitor.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.36.10 4 + // protoc (unknown) 5 + // source: private_location/v1/tcp_monitor.proto 6 + 7 + package v1 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + reflect "reflect" 13 + sync "sync" 14 + unsafe "unsafe" 15 + ) 16 + 17 + const ( 18 + // Verify that this generated code is sufficiently up-to-date. 19 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 + // Verify that runtime/protoimpl is sufficiently up-to-date. 21 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 + ) 23 + 24 + type TCPMonitor struct { 25 + state protoimpl.MessageState `protogen:"open.v1"` 26 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 27 + Uri string `protobuf:"bytes,2,opt,name=uri,proto3" json:"uri,omitempty"` 28 + Timeout int64 `protobuf:"varint,3,opt,name=timeout,proto3" json:"timeout,omitempty"` 29 + DegradedAt *int64 `protobuf:"varint,4,opt,name=degraded_at,json=degradedAt,proto3,oneof" json:"degraded_at,omitempty"` 30 + Periodicity string `protobuf:"bytes,5,opt,name=periodicity,proto3" json:"periodicity,omitempty"` 31 + Retry int64 `protobuf:"varint,6,opt,name=retry,proto3" json:"retry,omitempty"` 32 + unknownFields protoimpl.UnknownFields 33 + sizeCache protoimpl.SizeCache 34 + } 35 + 36 + func (x *TCPMonitor) Reset() { 37 + *x = TCPMonitor{} 38 + mi := &file_private_location_v1_tcp_monitor_proto_msgTypes[0] 39 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 40 + ms.StoreMessageInfo(mi) 41 + } 42 + 43 + func (x *TCPMonitor) String() string { 44 + return protoimpl.X.MessageStringOf(x) 45 + } 46 + 47 + func (*TCPMonitor) ProtoMessage() {} 48 + 49 + func (x *TCPMonitor) ProtoReflect() protoreflect.Message { 50 + mi := &file_private_location_v1_tcp_monitor_proto_msgTypes[0] 51 + if x != nil { 52 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 53 + if ms.LoadMessageInfo() == nil { 54 + ms.StoreMessageInfo(mi) 55 + } 56 + return ms 57 + } 58 + return mi.MessageOf(x) 59 + } 60 + 61 + // Deprecated: Use TCPMonitor.ProtoReflect.Descriptor instead. 62 + func (*TCPMonitor) Descriptor() ([]byte, []int) { 63 + return file_private_location_v1_tcp_monitor_proto_rawDescGZIP(), []int{0} 64 + } 65 + 66 + func (x *TCPMonitor) GetId() string { 67 + if x != nil { 68 + return x.Id 69 + } 70 + return "" 71 + } 72 + 73 + func (x *TCPMonitor) GetUri() string { 74 + if x != nil { 75 + return x.Uri 76 + } 77 + return "" 78 + } 79 + 80 + func (x *TCPMonitor) GetTimeout() int64 { 81 + if x != nil { 82 + return x.Timeout 83 + } 84 + return 0 85 + } 86 + 87 + func (x *TCPMonitor) GetDegradedAt() int64 { 88 + if x != nil && x.DegradedAt != nil { 89 + return *x.DegradedAt 90 + } 91 + return 0 92 + } 93 + 94 + func (x *TCPMonitor) GetPeriodicity() string { 95 + if x != nil { 96 + return x.Periodicity 97 + } 98 + return "" 99 + } 100 + 101 + func (x *TCPMonitor) GetRetry() int64 { 102 + if x != nil { 103 + return x.Retry 104 + } 105 + return 0 106 + } 107 + 108 + var File_private_location_v1_tcp_monitor_proto protoreflect.FileDescriptor 109 + 110 + const file_private_location_v1_tcp_monitor_proto_rawDesc = "" + 111 + "\n" + 112 + "%private_location/v1/tcp_monitor.proto\x12\x13private_location.v1\"\xb6\x01\n" + 113 + "\n" + 114 + "TCPMonitor\x12\x0e\n" + 115 + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + 116 + "\x03uri\x18\x02 \x01(\tR\x03uri\x12\x18\n" + 117 + "\atimeout\x18\x03 \x01(\x03R\atimeout\x12$\n" + 118 + "\vdegraded_at\x18\x04 \x01(\x03H\x00R\n" + 119 + "degradedAt\x88\x01\x01\x12 \n" + 120 + "\vperiodicity\x18\x05 \x01(\tR\vperiodicity\x12\x14\n" + 121 + "\x05retry\x18\x06 \x01(\x03R\x05retryB\x0e\n" + 122 + "\f_degraded_atBJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 123 + 124 + var ( 125 + file_private_location_v1_tcp_monitor_proto_rawDescOnce sync.Once 126 + file_private_location_v1_tcp_monitor_proto_rawDescData []byte 127 + ) 128 + 129 + func file_private_location_v1_tcp_monitor_proto_rawDescGZIP() []byte { 130 + file_private_location_v1_tcp_monitor_proto_rawDescOnce.Do(func() { 131 + file_private_location_v1_tcp_monitor_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_private_location_v1_tcp_monitor_proto_rawDesc), len(file_private_location_v1_tcp_monitor_proto_rawDesc))) 132 + }) 133 + return file_private_location_v1_tcp_monitor_proto_rawDescData 134 + } 135 + 136 + var file_private_location_v1_tcp_monitor_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 137 + var file_private_location_v1_tcp_monitor_proto_goTypes = []any{ 138 + (*TCPMonitor)(nil), // 0: private_location.v1.TCPMonitor 139 + } 140 + var file_private_location_v1_tcp_monitor_proto_depIdxs = []int32{ 141 + 0, // [0:0] is the sub-list for method output_type 142 + 0, // [0:0] is the sub-list for method input_type 143 + 0, // [0:0] is the sub-list for extension type_name 144 + 0, // [0:0] is the sub-list for extension extendee 145 + 0, // [0:0] is the sub-list for field type_name 146 + } 147 + 148 + func init() { file_private_location_v1_tcp_monitor_proto_init() } 149 + func file_private_location_v1_tcp_monitor_proto_init() { 150 + if File_private_location_v1_tcp_monitor_proto != nil { 151 + return 152 + } 153 + file_private_location_v1_tcp_monitor_proto_msgTypes[0].OneofWrappers = []any{} 154 + type x struct{} 155 + out := protoimpl.TypeBuilder{ 156 + File: protoimpl.DescBuilder{ 157 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 158 + RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_tcp_monitor_proto_rawDesc), len(file_private_location_v1_tcp_monitor_proto_rawDesc)), 159 + NumEnums: 0, 160 + NumMessages: 1, 161 + NumExtensions: 0, 162 + NumServices: 0, 163 + }, 164 + GoTypes: file_private_location_v1_tcp_monitor_proto_goTypes, 165 + DependencyIndexes: file_private_location_v1_tcp_monitor_proto_depIdxs, 166 + MessageInfos: file_private_location_v1_tcp_monitor_proto_msgTypes, 167 + }.Build() 168 + File_private_location_v1_tcp_monitor_proto = out.File 169 + file_private_location_v1_tcp_monitor_proto_goTypes = nil 170 + file_private_location_v1_tcp_monitor_proto_depIdxs = nil 171 + }
+2 -2
apps/checker/request/request.go
··· 66 66 OtelConfig struct { 67 67 Endpoint string `json:"endpoint"` 68 68 Headers map[string]string `json:"headers,omitempty"` 69 - } `json:"otelConfig,omitempty"` 69 + } `json:"otelConfig"` 70 70 } 71 71 72 72 type TCPCheckerRequest struct { ··· 84 84 OtelConfig struct { 85 85 Endpoint string `json:"endpoint"` 86 86 Headers map[string]string `json:"headers,omitempty"` 87 - } `json:"otelConfig,omitempty"` 87 + } `json:"otelConfig"` 88 88 } 89 89 90 90 type TCPRequest struct {
+3 -3
apps/checker/tcp.go apps/checker/checker/tcp.go
··· 31 31 Error uint8 `json:"error,omitempty"` 32 32 } 33 33 34 - func PingTcp(timeout int, url string) (TCPResponseTiming, error) { 34 + func PingTCP(timeout int, url string) (TCPResponseTiming, error) { 35 35 start := time.Now().UTC().UnixMilli() 36 36 conn, err := net.DialTimeout("tcp", url, time.Duration(timeout)*time.Second) 37 + stop := time.Now().UTC().UnixMilli() 37 38 38 39 if err != nil { 39 40 if e := err.(*net.OpError).Timeout(); e { ··· 42 43 if strings.Contains(err.Error(), "connection refused") { 43 44 return TCPResponseTiming{}, fmt.Errorf("connection refused") 44 45 } 45 - return TCPResponseTiming{}, fmt.Errorf("dial error: %v", err) 46 + return TCPResponseTiming{}, fmt.Errorf("dial error: %w", err) 46 47 } 47 - stop := time.Now().UTC().UnixMilli() 48 48 defer conn.Close() 49 49 50 50 return TCPResponseTiming{TCPStart: start, TCPDone: stop}, nil
+2 -2
apps/checker/tcp_test.go apps/checker/checker/tcp_test.go
··· 3 3 import ( 4 4 "testing" 5 5 6 - "github.com/openstatushq/openstatus/apps/checker" 6 + "github.com/openstatushq/openstatus/apps/checker/checker" 7 7 ) 8 8 9 9 func TestPingTcp(t *testing.T) { ··· 22 22 } 23 23 for _, tt := range tests { 24 24 t.Run(tt.name, func(t *testing.T) { 25 - got, err := checker.PingTcp(tt.args.timeout, tt.args.url) 25 + got, err := checker.PingTCP(tt.args.timeout, tt.args.url) 26 26 if (err != nil) != tt.wantErr { 27 27 t.Errorf("PingTcp() error = %v, wantErr %v", err, tt.wantErr) 28 28 return
apps/checker/update.go apps/checker/checker/update.go
+1
apps/dashboard/src/app/(dashboard)/monitors/[id]/edit/layout.tsx
··· 7 7 }) { 8 8 const queryClient = getQueryClient(); 9 9 await queryClient.prefetchQuery(trpc.monitorTag.list.queryOptions()); 10 + await queryClient.prefetchQuery(trpc.privateLocation.list.queryOptions()); 10 11 11 12 return <HydrateClient>{children}</HydrateClient>; 12 13 }
+1 -1
apps/dashboard/src/app/(dashboard)/monitors/[id]/layout.tsx
··· 21 21 await queryClient.prefetchQuery( 22 22 trpc.monitor.get.queryOptions({ id: Number.parseInt(id) }), 23 23 ); 24 - 25 24 await queryClient.prefetchQuery(trpc.notification.list.queryOptions()); 25 + await queryClient.prefetchQuery(trpc.privateLocation.list.queryOptions()); 26 26 27 27 return ( 28 28 <HydrateClient>
+17 -2
apps/dashboard/src/app/(dashboard)/monitors/[id]/logs/client.tsx
··· 20 20 import { DropdownStatus } from "@/components/controls-search/dropdown-status"; 21 21 import { DropdownTrigger } from "@/components/controls-search/dropdown-trigger"; 22 22 import { PopoverDate } from "@/components/controls-search/popover-date"; 23 - import { columns } from "@/components/data-table/response-logs/columns"; 23 + import { getColumns } from "@/components/data-table/response-logs/columns"; 24 24 import { Sheet } from "@/components/data-table/response-logs/data-table-sheet"; 25 25 import { DataTable } from "@/components/ui/data-table/data-table"; 26 26 import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; ··· 43 43 setSearchParams, 44 44 ] = useQueryStates(searchParamsParsers); 45 45 const { data: workspace } = useQuery(trpc.workspace.get.queryOptions()); 46 + const { data: privateLocations } = useQuery( 47 + trpc.privateLocation.list.queryOptions(), 48 + ); 46 49 const { data: monitor } = useQuery( 47 50 trpc.monitor.get.queryOptions({ id: Number.parseInt(id) }), 48 51 ); ··· 73 76 } 74 77 }, 75 78 [pageIndex, pageSize, setSearchParams], 79 + ); 80 + 81 + const columns = useMemo( 82 + () => getColumns(privateLocations ?? []), 83 + [privateLocations], 76 84 ); 77 85 78 86 if (!workspace || !monitor) return null; ··· 96 104 <PopoverDate /> 97 105 {monitor.jobType === "http" ? <DropdownStatus /> : null} 98 106 <DropdownTrigger /> 99 - <CommandRegion regions={monitor.regions} /> 107 + <CommandRegion 108 + regions={monitor.regions} 109 + privateLocations={privateLocations?.filter((location) => 110 + location.monitors.some((m) => m.id === Number(id)), 111 + )} 112 + /> 100 113 <ButtonReset /> 101 114 </div> 102 115 </Section> ··· 132 145 )} 133 146 <Sheet 134 147 data={_log?.data?.length ? _log.data[0] : null} 148 + privateLocations={privateLocations} 135 149 onClose={() => 136 150 setTimeout(() => setSearchParams({ selected: null }), 300) 137 151 } ··· 142 156 } 143 157 144 158 function BillingPlaceholder() { 159 + const columns = useMemo(() => getColumns([]), []); 145 160 return ( 146 161 <BillingOverlayContainer> 147 162 <DataTable data={exampleLogs} columns={columns} />
+1 -5
apps/dashboard/src/app/(dashboard)/monitors/[id]/logs/search-params.ts
··· 1 1 import { PERIODS, STATUS, TRIGGER } from "@/data/metrics.client"; 2 - import { monitorRegions } from "@openstatus/db/src/schema/constants"; 3 2 import { endOfDay } from "date-fns"; 4 3 import { startOfDay } from "date-fns"; 5 4 import { ··· 13 12 14 13 export const searchParamsParsers = { 15 14 period: parseAsStringLiteral(PERIODS).withDefault("1d"), 16 - regions: parseAsArrayOf(parseAsStringLiteral(monitorRegions)).withDefault( 17 - // FIXME: readonly 18 - monitorRegions as unknown as (typeof monitorRegions)[number][], 19 - ), 15 + regions: parseAsArrayOf(parseAsString), 20 16 status: parseAsStringLiteral(STATUS), 21 17 trigger: parseAsStringLiteral(TRIGGER), 22 18 selected: parseAsString,
+25 -7
apps/dashboard/src/app/(dashboard)/monitors/[id]/overview/client.tsx
··· 17 17 import { DropdownPercentile } from "@/components/controls-search/dropdown-percentile"; 18 18 import { DropdownPeriod } from "@/components/controls-search/dropdown-period"; 19 19 import { AuditLogsWrapper } from "@/components/data-table/audit-logs/wrapper"; 20 - import { columns as regionColumns } from "@/components/data-table/response-logs/regions/columns"; 20 + import { getColumns as getRegionColumns } from "@/components/data-table/response-logs/regions/columns"; 21 21 import { GlobalUptimeSection } from "@/components/metric/global-uptime/section"; 22 22 import { PopoverQuantile } from "@/components/popovers/popover-quantile"; 23 23 import { PopoverResolution } from "@/components/popovers/popover-resolution"; ··· 30 30 import { useQuery } from "@tanstack/react-query"; 31 31 import { useParams } from "next/navigation"; 32 32 import { useQueryStates } from "nuqs"; 33 - import React from "react"; 33 + import React, { useMemo } from "react"; 34 34 import { searchParamsParsers } from "./search-params"; 35 35 36 36 const TIMELINE_INTERVAL = 30; // in days ··· 38 38 export function Client() { 39 39 const trpc = useTRPC(); 40 40 const { id } = useParams<{ id: string }>(); 41 - const [{ period, regions: selectedRegions, percentile, interval }] = 41 + const [{ period, regions, percentile, interval }] = 42 42 useQueryStates(searchParamsParsers); 43 43 const { data: monitor } = useQuery( 44 44 trpc.monitor.get.queryOptions({ id: Number.parseInt(id) }), 45 45 ); 46 + const { data: privateLocations } = useQuery( 47 + trpc.privateLocation.list.queryOptions(), 48 + ); 49 + 50 + const selectedRegions = regions ?? undefined; 46 51 47 52 const regionTimelineQuery = { 48 53 ...trpc.tinybird.metricsRegions.queryOptions({ ··· 65 70 // once the data is loaded, we show all the regions that we get from TB 66 71 isLoading 67 72 ? monitor?.regions ?? [] 68 - : (monitorRegions as unknown as (typeof monitorRegions)[number][]), 73 + : [ 74 + ...monitorRegions, 75 + ...(privateLocations?.map((location) => location.id.toString()) ?? 76 + []), 77 + ], 69 78 percentile, 70 79 ); 71 - }, [regionTimeline, monitor, percentile, isLoading]); 80 + }, [regionTimeline, monitor, percentile, isLoading, privateLocations]); 81 + 82 + const regionColumns = useMemo( 83 + () => getRegionColumns(privateLocations ?? []), 84 + [privateLocations], 85 + ); 72 86 73 87 if (!monitor) return null; 74 88 ··· 90 104 <div className="flex flex-wrap gap-2"> 91 105 <div> 92 106 <DropdownPeriod /> including{" "} 93 - <CommandRegion regions={monitor.regions} /> 107 + <CommandRegion 108 + regions={monitor.regions} 109 + privateLocations={privateLocations} 110 + /> 94 111 </div> 95 112 <div> 96 113 <ButtonReset only={["period", "regions"]} /> ··· 185 202 columns={regionColumns} 186 203 defaultPagination={{ 187 204 pageIndex: 0, 188 - pageSize: selectedRegions.length, 205 + pageSize: selectedRegions?.length ?? 0, 189 206 }} 190 207 /> 191 208 </TabsContent> ··· 193 210 <ChartLineRegions 194 211 className="mt-3" 195 212 regions={selectedRegions} 213 + privateLocations={privateLocations} 196 214 data={regionMetrics.reduce( 197 215 (acc, region) => { 198 216 region.trend.forEach((t) => {
+2 -5
apps/dashboard/src/app/(dashboard)/monitors/[id]/overview/search-params.ts
··· 1 1 import { INTERVALS } from "@/data/metrics.client"; 2 - import { monitorRegions } from "@openstatus/db/src/schema/constants"; 3 2 import { 4 3 createSearchParamsCache, 5 4 parseAsArrayOf, 6 5 parseAsNumberLiteral, 6 + parseAsString, 7 7 parseAsStringLiteral, 8 8 } from "nuqs/server"; 9 9 ··· 12 12 13 13 export const searchParamsParsers = { 14 14 period: parseAsStringLiteral(PERIOD).withDefault("1d"), 15 - regions: parseAsArrayOf(parseAsStringLiteral(monitorRegions)).withDefault( 16 - // FIXME: readonly 17 - monitorRegions as unknown as (typeof monitorRegions)[number][], 18 - ), 15 + regions: parseAsArrayOf(parseAsString), 19 16 percentile: parseAsStringLiteral(PERCENTILE).withDefault("p50"), 20 17 interval: parseAsNumberLiteral(INTERVALS).withDefault(30), 21 18 };
+3
apps/dashboard/src/app/(dashboard)/notifications/client.tsx
··· 57 57 <SectionGroup> 58 58 <SectionHeader> 59 59 <SectionTitle>Notifications</SectionTitle> 60 + <SectionDescription> 61 + Define your notifications to receive alerts when downtime occurs. 62 + </SectionDescription> 60 63 </SectionHeader> 61 64 <Section> 62 65 {notifications.length === 0 ? (
+12
apps/dashboard/src/app/(dashboard)/private-locations/breadcrumb.tsx
··· 1 + "use client"; 2 + 3 + import { NavBreadcrumb } from "@/components/nav/nav-breadcrumb"; 4 + import { Globe } from "lucide-react"; 5 + 6 + export function Breadcrumb() { 7 + return ( 8 + <NavBreadcrumb 9 + items={[{ type: "page", label: "Private Locations", icon: Globe }]} 10 + /> 11 + ); 12 + }
+104
apps/dashboard/src/app/(dashboard)/private-locations/client.tsx
··· 1 + "use client"; 2 + import { Link } from "@/components/common/link"; 3 + import { 4 + BillingOverlay, 5 + BillingOverlayButton, 6 + BillingOverlayContainer, 7 + BillingOverlayDescription, 8 + } from "@/components/content/billing-overlay"; 9 + import { 10 + Section, 11 + SectionDescription, 12 + SectionGroup, 13 + SectionHeader, 14 + SectionTitle, 15 + } from "@/components/content/section"; 16 + import { columns } from "@/components/data-table/private-locations/columns"; 17 + import { UpgradeDialog } from "@/components/dialogs/upgrade"; 18 + import { DataTable } from "@/components/ui/data-table/data-table"; 19 + import { useTRPC } from "@/lib/trpc/client"; 20 + import type { RouterOutputs } from "@openstatus/api"; 21 + import { useQuery } from "@tanstack/react-query"; 22 + import { Lock } from "lucide-react"; 23 + import { useState } from "react"; 24 + 25 + const EXAMPLES = [ 26 + { 27 + id: 1, 28 + name: "Private Location 1", 29 + token: "my-secret-token", 30 + createdAt: new Date("2025-01-01"), 31 + updatedAt: new Date("2025-01-01"), 32 + workspaceId: 1, 33 + lastSeenAt: new Date("2025-10-08"), 34 + privateLocationToMonitors: [], 35 + monitors: [], 36 + }, 37 + { 38 + id: 2, 39 + name: "Private Location 2", 40 + token: "my-secret-token", 41 + createdAt: new Date("2025-01-01"), 42 + updatedAt: new Date("2025-01-01"), 43 + workspaceId: 1, 44 + lastSeenAt: new Date("2025-06-08"), 45 + privateLocationToMonitors: [], 46 + monitors: [], 47 + }, 48 + ] satisfies RouterOutputs["privateLocation"]["list"]; 49 + 50 + export function Client() { 51 + const trpc = useTRPC(); 52 + const [openDialog, setOpenDialog] = useState(false); 53 + const { data: workspace } = useQuery(trpc.workspace.get.queryOptions()); 54 + const { data: privateLocations } = useQuery( 55 + trpc.privateLocation.list.queryOptions(), 56 + ); 57 + 58 + if (!privateLocations || !workspace) return null; 59 + 60 + return ( 61 + <SectionGroup> 62 + <SectionHeader> 63 + <SectionTitle>Private Locations</SectionTitle> 64 + <SectionDescription> 65 + Create and manage your private locations. 66 + </SectionDescription> 67 + </SectionHeader> 68 + <Section> 69 + {workspace.limits["private-locations"] === false ? ( 70 + <BillingOverlayContainer> 71 + <DataTable 72 + columns={columns} 73 + data={[...EXAMPLES, ...EXAMPLES, ...EXAMPLES]} 74 + /> 75 + <BillingOverlay> 76 + <BillingOverlayButton onClick={() => setOpenDialog(true)}> 77 + <Lock /> 78 + Upgrade 79 + </BillingOverlayButton> 80 + <BillingOverlayDescription> 81 + Create private locations to monitor your internal services.{" "} 82 + <Link 83 + href="https://docs.openstatus.dev/tutorial/how-to-create-private-location/" 84 + rel="noreferrer" 85 + target="_blank" 86 + > 87 + Learn more 88 + </Link> 89 + . 90 + </BillingOverlayDescription> 91 + </BillingOverlay> 92 + <UpgradeDialog 93 + open={openDialog} 94 + onOpenChange={setOpenDialog} 95 + limit="private-locations" 96 + /> 97 + </BillingOverlayContainer> 98 + ) : ( 99 + <DataTable columns={columns} data={privateLocations} /> 100 + )} 101 + </Section> 102 + </SectionGroup> 103 + ); 104 + }
+33
apps/dashboard/src/app/(dashboard)/private-locations/layout.tsx
··· 1 + import { 2 + AppHeader, 3 + AppHeaderActions, 4 + AppHeaderContent, 5 + } from "@/components/nav/app-header"; 6 + import { AppSidebarTrigger } from "@/components/nav/app-sidebar"; 7 + import { HydrateClient, getQueryClient, trpc } from "@/lib/trpc/server"; 8 + import { Breadcrumb } from "./breadcrumb"; 9 + import { NavActions } from "./nav-actions"; 10 + 11 + export default async function Layout({ 12 + children, 13 + }: { 14 + children: React.ReactNode; 15 + }) { 16 + const queryClient = getQueryClient(); 17 + await queryClient.prefetchQuery(trpc.notification.list.queryOptions()); 18 + await queryClient.prefetchQuery(trpc.privateLocation.list.queryOptions()); 19 + return ( 20 + <HydrateClient> 21 + <AppHeader> 22 + <AppHeaderContent> 23 + <AppSidebarTrigger /> 24 + <Breadcrumb /> 25 + </AppHeaderContent> 26 + <AppHeaderActions> 27 + <NavActions /> 28 + </AppHeaderActions> 29 + </AppHeader> 30 + <main className="w-full flex-1">{children}</main> 31 + </HydrateClient> 32 + ); 33 + }
+63
apps/dashboard/src/app/(dashboard)/private-locations/nav-actions.tsx
··· 1 + "use client"; 2 + 3 + import { UpgradeDialog } from "@/components/dialogs/upgrade"; 4 + import { FormSheetPrivateLocation } from "@/components/forms/private-location/sheet"; 5 + import { NavFeedback } from "@/components/nav/nav-feedback"; 6 + import { Button } from "@/components/ui/button"; 7 + import { useTRPC } from "@/lib/trpc/client"; 8 + import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 9 + import { useState } from "react"; 10 + 11 + export function NavActions() { 12 + const trpc = useTRPC(); 13 + const queryClient = useQueryClient(); 14 + const [openDialog, setOpenDialog] = useState(false); 15 + const { data: workspace } = useQuery(trpc.workspace.get.queryOptions()); 16 + const { data: monitors } = useQuery(trpc.monitor.list.queryOptions()); 17 + const createPrivateLocationMutation = useMutation( 18 + trpc.privateLocation.new.mutationOptions({ 19 + onSuccess: () => { 20 + queryClient.invalidateQueries({ 21 + queryKey: trpc.privateLocation.list.queryKey(), 22 + }); 23 + }, 24 + }), 25 + ); 26 + 27 + if (!workspace || !monitors) return null; 28 + 29 + const limitReached = !workspace.limits["private-locations"]; 30 + 31 + return ( 32 + <div className="flex items-center gap-2 text-sm"> 33 + <NavFeedback /> 34 + {limitReached ? ( 35 + <Button 36 + size="sm" 37 + data-disabled={limitReached} 38 + className="data-[disabled=true]:opacity-50" 39 + onClick={() => setOpenDialog(true)} 40 + > 41 + Create Private Location 42 + </Button> 43 + ) : ( 44 + <FormSheetPrivateLocation 45 + monitors={monitors} 46 + onSubmit={async (values) => { 47 + await createPrivateLocationMutation.mutateAsync({ 48 + name: values.name, 49 + monitors: values.monitors, 50 + }); 51 + }} 52 + > 53 + <Button size="sm">Create Private Location</Button> 54 + </FormSheetPrivateLocation> 55 + )} 56 + <UpgradeDialog 57 + open={openDialog} 58 + onOpenChange={setOpenDialog} 59 + limit="private-locations" 60 + /> 61 + </div> 62 + ); 63 + }
+10
apps/dashboard/src/app/(dashboard)/private-locations/page.tsx
··· 1 + import type { SearchParams } from "nuqs"; 2 + import { Client } from "./client"; 3 + 4 + export default async function Page({ 5 + searchParams, 6 + }: { 7 + searchParams: Promise<SearchParams>; 8 + }) { 9 + return <Client />; 10 + }
+9 -1
apps/dashboard/src/app/(dashboard)/status-pages/[id]/subscribers/page.tsx
··· 21 21 22 22 import { Section } from "@/components/content/section"; 23 23 import { columns } from "@/components/data-table/subscribers/columns"; 24 + import { UpgradeDialog } from "@/components/dialogs/upgrade"; 24 25 import { DataTable } from "@/components/ui/data-table/data-table"; 25 26 import { useTRPC } from "@/lib/trpc/client"; 26 27 import { useQuery } from "@tanstack/react-query"; 27 28 import { Lock } from "lucide-react"; 28 29 import { useParams } from "next/navigation"; 30 + import { useState } from "react"; 29 31 30 32 const EXAMPLES = [ 31 33 { ··· 52 54 53 55 export default function Page() { 54 56 const { id } = useParams<{ id: string }>(); 57 + const [openDialog, setOpenDialog] = useState(false); 55 58 const trpc = useTRPC(); 56 59 const { data: page } = useQuery( 57 60 trpc.page.get.queryOptions({ id: Number.parseInt(id) }), ··· 79 82 data={[...EXAMPLES, ...EXAMPLES, ...EXAMPLES]} 80 83 /> 81 84 <BillingOverlay> 82 - <BillingOverlayButton> 85 + <BillingOverlayButton onClick={() => setOpenDialog(true)}> 83 86 <Lock /> 84 87 Upgrade 85 88 </BillingOverlayButton> ··· 95 98 . 96 99 </BillingOverlayDescription> 97 100 </BillingOverlay> 101 + <UpgradeDialog 102 + open={openDialog} 103 + onOpenChange={setOpenDialog} 104 + limit="status-subscribers" 105 + /> 98 106 </BillingOverlayContainer> 99 107 ) : subscribers?.length ? ( 100 108 <DataTable columns={columns} data={subscribers} />
+1 -2
apps/dashboard/src/components/chart/chart-area-latency.tsx
··· 20 20 } from "@/components/ui/chart"; 21 21 import { type PERCENTILES, mapLatency } from "@/data/metrics.client"; 22 22 import { useTRPC } from "@/lib/trpc/client"; 23 - import type { monitorRegions } from "@openstatus/db/src/schema/constants"; 24 23 import { useQuery } from "@tanstack/react-query"; 25 24 import { ChartTooltipNumber } from "./chart-tooltip-number"; 26 25 ··· 46 45 percentile: (typeof PERCENTILES)[number]; 47 46 period: "1d" | "7d" | "14d"; 48 47 type: "http" | "tcp"; 49 - regions: (typeof monitorRegions)[number][]; 48 + regions: string[] | undefined; 50 49 }) { 51 50 const trpc = useTRPC(); 52 51
+1 -2
apps/dashboard/src/components/chart/chart-area-timing-phases.tsx
··· 24 24 mapTimingPhases, 25 25 } from "@/data/metrics.client"; 26 26 import { useTRPC } from "@/lib/trpc/client"; 27 - import type { monitorRegions } from "@openstatus/db/src/schema/constants"; 28 27 import { useQuery } from "@tanstack/react-query"; 29 28 import { 30 29 ChartTooltipNumber, ··· 70 69 period: "1d" | "7d" | "14d"; 71 70 percentile: (typeof PERCENTILES)[number]; 72 71 interval: (typeof INTERVALS)[number]; 73 - regions: (typeof monitorRegions)[number][]; 72 + regions: string[] | undefined; 74 73 type: "http"; 75 74 }) { 76 75 const trpc = useTRPC();
+1 -2
apps/dashboard/src/components/chart/chart-bar-uptime.tsx
··· 13 13 import { type PERIODS, mapUptime } from "@/data/metrics.client"; 14 14 import { useIsMobile } from "@/hooks/use-mobile"; 15 15 import { useTRPC } from "@/lib/trpc/client"; 16 - import type { Region } from "@openstatus/db/src/schema/constants"; 17 16 import { useQuery } from "@tanstack/react-query"; 18 17 import { endOfDay, startOfDay, subDays } from "date-fns"; 19 18 ··· 53 52 monitorId: string; 54 53 period: (typeof PERIODS)[number]; 55 54 type: "http" | "tcp"; 56 - regions: Region[]; 55 + regions: string[] | undefined; 57 56 }) { 58 57 const isMobile = useIsMobile(); 59 58 const trpc = useTRPC();
+30 -17
apps/dashboard/src/components/chart/chart-line-regions.tsx
··· 10 10 ChartTooltip, 11 11 ChartTooltipContent, 12 12 } from "@/components/ui/chart"; 13 - import { regionColors } from "@/data/regions"; 13 + import { getRegionColor } from "@/data/regions"; 14 14 import { useIsMobile } from "@/hooks/use-mobile"; 15 15 import { cn } from "@/lib/utils"; 16 + import type { PrivateLocation } from "@openstatus/db/src/schema"; 16 17 import { monitorRegions } from "@openstatus/db/src/schema/constants"; 17 - import { regionDict } from "@openstatus/regions"; 18 + import { getRegionInfo } from "@openstatus/regions"; 18 19 import { ChartTooltipNumber } from "./chart-tooltip-number"; 19 20 20 - const chartConfig = monitorRegions.reduce((config, region) => { 21 - const regionInfo = regionDict[region as keyof typeof regionDict]; 22 - const color = regionColors[region as keyof typeof regionColors]; 21 + function getChartConfig(privateLocations?: PrivateLocation[]) { 22 + return [ 23 + ...monitorRegions, 24 + ...(privateLocations?.map((location) => location.id.toString()) ?? []), 25 + ].reduce((config, region) => { 26 + const privateLocation = privateLocations?.find( 27 + (location) => String(location.id) === String(region), 28 + ); 29 + const regionInfo = getRegionInfo(region, { 30 + location: privateLocation?.name, 31 + }); 32 + const color = getRegionColor(region); 23 33 24 - if (regionInfo && color) { 25 - config[region] = { 26 - label: `${regionInfo.location} (${regionInfo.provider})`, 27 - color, 28 - }; 29 - } 34 + if (regionInfo && color) { 35 + config[region] = { 36 + label: `${regionInfo.location} (${regionInfo.provider})`, 37 + color, 38 + }; 39 + } 30 40 31 - return config; 32 - }, {} as ChartConfig) satisfies ChartConfig; 41 + return config; 42 + }, {} as ChartConfig) satisfies ChartConfig; 43 + } 33 44 34 45 export type TrendPoint = { 35 46 timestamp: number; // unix millis ··· 40 51 className, 41 52 data, 42 53 regions, 54 + privateLocations, 43 55 }: { 44 56 className?: string; 45 57 data: TrendPoint[]; 46 - regions: string[]; 58 + regions: string[] | undefined; 59 + privateLocations?: PrivateLocation[]; 47 60 }) { 48 61 const isMobile = useIsMobile(); 49 62 const trendData = data ?? []; 50 - 63 + const chartConfig = getChartConfig(privateLocations); 51 64 const chartData = trendData.map((d) => ({ 52 65 ...d, 53 66 timestamp: new Date(d.timestamp).toLocaleString("default", { ··· 87 100 /> 88 101 } 89 102 /> 90 - {regions.map((region) => { 103 + {regions?.map((region) => { 91 104 return ( 92 105 <Line 93 106 key={region} ··· 107 120 orientation="right" 108 121 tickFormatter={(value) => `${value}ms`} 109 122 /> 110 - {regions.length <= 6 && !isMobile ? ( 123 + {regions && regions.length <= 6 && !isMobile ? ( 111 124 <ChartLegend 112 125 className="flex-wrap" 113 126 content={<ChartLegendContent className="text-nowrap" />}
+38 -9
apps/dashboard/src/components/controls-search/command-region.tsx
··· 22 22 PopoverContent, 23 23 PopoverTrigger, 24 24 } from "@/components/ui/popover"; 25 - import { REGIONS } from "@/data/metrics.client"; 25 + import type { REGIONS } from "@/data/metrics.client"; 26 26 import { useTRPC } from "@/lib/trpc/client"; 27 27 import { cn } from "@/lib/utils"; 28 28 import { formatRegionCode, groupByContinent } from "@openstatus/regions"; 29 29 import { useQuery } from "@tanstack/react-query"; 30 - import { Check, Lock } from "lucide-react"; 31 - import { parseAsArrayOf, parseAsStringLiteral, useQueryState } from "nuqs"; 32 - 33 - export const parseRegions = (regions: (typeof REGIONS)[number][]) => 34 - parseAsArrayOf( 35 - parseAsStringLiteral(REGIONS.filter((region) => regions.includes(region))), 36 - ).withDefault(regions as unknown as (typeof REGIONS)[number][]); 30 + import { Check, Globe, Lock } from "lucide-react"; 31 + import { parseAsArrayOf, parseAsString, useQueryState } from "nuqs"; 37 32 38 33 export function CommandRegion({ 39 34 regions, 35 + privateLocations, 40 36 }: { 41 37 regions: (typeof REGIONS)[number][]; 38 + privateLocations?: { id: number; name: string }[]; 42 39 }) { 43 40 const trpc = useTRPC(); 44 41 const { data: workspace } = useQuery(trpc.workspace.get.queryOptions()); 45 42 const [selectedRegions, setSelectedRegions] = useQueryState( 46 43 "regions", 47 - parseRegions(regions), 44 + parseAsArrayOf(parseAsString).withDefault([ 45 + ...regions, 46 + ...(privateLocations?.map((location) => location.id.toString()) ?? []), 47 + ]), 48 48 ); 49 49 50 50 const limited = workspace?.plan === "free"; ··· 148 148 ); 149 149 }, 150 150 )} 151 + {privateLocations && privateLocations.length > 0 ? ( 152 + <CommandGroup heading="Private Locations"> 153 + {privateLocations.map((location) => ( 154 + <CommandItem 155 + key={location.id} 156 + keywords={[location.name]} 157 + value={location.id.toString()} 158 + onSelect={() => { 159 + setSelectedRegions((prev) => 160 + prev.includes(location.id.toString()) 161 + ? prev.filter((r) => r !== location.id.toString()) 162 + : [...prev, location.id.toString()], 163 + ); 164 + }} 165 + > 166 + <Globe className="size-3" /> 167 + <span className="font-mono truncate">{location.name}</span> 168 + <Check 169 + className={cn( 170 + "ml-auto", 171 + selectedRegions.includes(location.id.toString()) 172 + ? "opacity-100" 173 + : "opacity-0", 174 + )} 175 + /> 176 + </CommandItem> 177 + ))} 178 + </CommandGroup> 179 + ) : null} 151 180 <CommandEmpty>No region found.</CommandEmpty> 152 181 </CommandList> 153 182 </Command>
+85 -75
apps/dashboard/src/components/data-table/audit-logs/columns.tsx
··· 2 2 3 3 import { HoverCardTimestamp } from "@/components/common/hover-card-timestamp"; 4 4 import { TableCellDate } from "@/components/data-table/table-cell-date"; 5 - import { config, metadata } from "@/data/audit-logs.client"; 5 + import { config, getMetadata } from "@/data/audit-logs.client"; 6 6 import { cn } from "@/lib/utils"; 7 7 import type { RouterOutputs } from "@openstatus/api"; 8 + import type { PrivateLocation } from "@openstatus/db/src/schema"; 8 9 import type { ColumnDef } from "@tanstack/react-table"; 9 10 10 11 type AuditLog = RouterOutputs["tinybird"]["auditLog"]["data"][number]; 11 12 12 - export const columns: ColumnDef<AuditLog>[] = [ 13 - { 14 - id: "icon", 15 - accessorFn: (row) => row.action, 16 - header: () => null, 17 - enableSorting: false, 18 - enableHiding: false, 19 - cell: ({ row }) => { 20 - const value = row.getValue("action"); 21 - const { icon: Icon, color } = config[value as keyof typeof config]; 22 - if (!Icon) return null; 23 - return <Icon className={cn("size-4", color)} />; 24 - }, 25 - meta: { 26 - headerClassName: "w-7", 13 + export function getColumns( 14 + privateLocations?: PrivateLocation[], 15 + ): ColumnDef<AuditLog>[] { 16 + const metadata = getMetadata(privateLocations); 17 + return [ 18 + { 19 + id: "icon", 20 + accessorFn: (row) => row.action, 21 + header: () => null, 22 + enableSorting: false, 23 + enableHiding: false, 24 + cell: ({ row }) => { 25 + const value = row.getValue("action"); 26 + const { icon: Icon, color } = config[value as keyof typeof config]; 27 + if (!Icon) return null; 28 + return <Icon className={cn("size-4", color)} />; 29 + }, 30 + meta: { 31 + headerClassName: "w-7", 32 + }, 27 33 }, 28 - }, 29 - { 30 - accessorKey: "action", 31 - header: "Action", 32 - enableSorting: false, 33 - enableHiding: false, 34 - cell: ({ row }) => { 35 - const value = row.getValue("action"); 36 - const { title } = config[value as keyof typeof config]; 37 - if (!title) return null; 38 - return <div>{title}</div>; 34 + { 35 + accessorKey: "action", 36 + header: "Action", 37 + enableSorting: false, 38 + enableHiding: false, 39 + cell: ({ row }) => { 40 + const value = row.getValue("action"); 41 + const { title } = config[value as keyof typeof config]; 42 + if (!title) return null; 43 + return <div>{title}</div>; 44 + }, 39 45 }, 40 - }, 41 - { 42 - accessorKey: "metadata", 43 - header: "Information", 44 - enableSorting: false, 45 - enableHiding: false, 46 - cell: ({ row }) => { 47 - const value = row.getValue("metadata"); 48 - if (!value) return null; 49 - return ( 50 - <div className="flex flex-wrap gap-2"> 51 - {Object.entries(value) 52 - .filter(([key, value]) => 53 - metadata[key as keyof typeof metadata]?.visible(value), 54 - ) 55 - .map(([key, value]) => ( 56 - <Pill 57 - key={key} 58 - label={metadata[key as keyof typeof metadata].key} 59 - value={metadata[key as keyof typeof metadata]?.format(value)} 60 - /> 61 - ))} 62 - </div> 63 - ); 46 + { 47 + accessorKey: "metadata", 48 + header: "Information", 49 + enableSorting: false, 50 + enableHiding: false, 51 + cell: ({ row }) => { 52 + const value = row.getValue("metadata"); 53 + if (!value) return null; 54 + return ( 55 + <div className="flex flex-wrap gap-2"> 56 + {Object.entries(value) 57 + .filter(([key, value]) => 58 + metadata[key as keyof typeof metadata]?.visible(value), 59 + ) 60 + .map(([key, value]) => { 61 + return ( 62 + <Pill 63 + key={key} 64 + label={metadata[key as keyof typeof metadata].key} 65 + value={metadata[key as keyof typeof metadata]?.format( 66 + value, 67 + )} 68 + /> 69 + ); 70 + })} 71 + </div> 72 + ); 73 + }, 64 74 }, 65 - }, 66 - { 67 - accessorKey: "timestamp", 68 - header: "Timestamp", 69 - enableSorting: false, 70 - enableHiding: false, 71 - cell: ({ row }) => { 72 - const value = row.getValue("timestamp"); 73 - if (value instanceof Date) { 75 + { 76 + accessorKey: "timestamp", 77 + header: "Timestamp", 78 + enableSorting: false, 79 + enableHiding: false, 80 + cell: ({ row }) => { 81 + const value = row.getValue("timestamp"); 82 + if (value instanceof Date) { 83 + return ( 84 + <HoverCardTimestamp date={value}> 85 + <TableCellDate value={value} className="font-mono" /> 86 + </HoverCardTimestamp> 87 + ); 88 + } 89 + const date = new Date(Number(value)); 90 + if (Number.isNaN(date.getTime())) { 91 + return <div className="font-mono">{String(value)}</div>; 92 + } 93 + 74 94 return ( 75 - <HoverCardTimestamp date={value}> 76 - <TableCellDate value={value} className="font-mono" /> 95 + <HoverCardTimestamp date={date}> 96 + <TableCellDate value={date} className="font-mono" /> 77 97 </HoverCardTimestamp> 78 98 ); 79 - } 80 - const date = new Date(Number(value)); 81 - if (Number.isNaN(date.getTime())) { 82 - return <div className="font-mono">{String(value)}</div>; 83 - } 84 - 85 - return ( 86 - <HoverCardTimestamp date={date}> 87 - <TableCellDate value={date} className="font-mono" /> 88 - </HoverCardTimestamp> 89 - ); 99 + }, 90 100 }, 91 - }, 92 - ]; 101 + ]; 102 + } 93 103 94 104 function Pill({ label, value }: { label: string; value?: string }) { 95 105 if (!value) return null;
+11 -1
apps/dashboard/src/components/data-table/audit-logs/wrapper.tsx
··· 12 12 import { useTRPC } from "@/lib/trpc/client"; 13 13 import { useQuery } from "@tanstack/react-query"; 14 14 15 - import { columns } from "./columns"; 15 + import { useMemo } from "react"; 16 + import { getColumns } from "./columns"; 16 17 17 18 export function AuditLogsWrapper({ 18 19 monitorId, ··· 25 26 26 27 const { data: auditLogs, isLoading } = useQuery( 27 28 trpc.tinybird.auditLog.queryOptions({ monitorId, interval }), 29 + ); 30 + 31 + const { data: privateLocations } = useQuery( 32 + trpc.privateLocation.list.queryOptions(), 33 + ); 34 + 35 + const columns = useMemo( 36 + () => getColumns(privateLocations), 37 + [privateLocations], 28 38 ); 29 39 30 40 if (isLoading) {
+61
apps/dashboard/src/components/data-table/private-locations/columns.tsx
··· 1 + "use client"; 2 + 3 + import { DataTableColumnHeader } from "@/components/ui/data-table/data-table-column-header"; 4 + import type { RouterOutputs } from "@openstatus/api"; 5 + import type { ColumnDef } from "@tanstack/react-table"; 6 + import { TableCellBadge } from "../table-cell-badge"; 7 + import { TableCellDate } from "../table-cell-date"; 8 + import { DataTableRowActions } from "./data-table-row-actions"; 9 + 10 + type PrivateLocation = RouterOutputs["privateLocation"]["list"][number]; 11 + 12 + export const columns: ColumnDef<PrivateLocation>[] = [ 13 + { 14 + accessorKey: "name", 15 + header: ({ column }) => ( 16 + <DataTableColumnHeader column={column} title="Name" /> 17 + ), 18 + enableHiding: false, 19 + }, 20 + { 21 + accessorKey: "lastSeenAt", 22 + header: ({ column }) => ( 23 + <DataTableColumnHeader column={column} title="Last Seen At" /> 24 + ), 25 + enableHiding: false, 26 + cell: ({ row }) => { 27 + const value = row.getValue("lastSeenAt"); 28 + return <TableCellDate value={value} />; 29 + }, 30 + }, 31 + { 32 + accessorKey: "monitors", 33 + header: "Monitors", 34 + enableSorting: false, 35 + enableHiding: false, 36 + 37 + cell: ({ row }) => { 38 + const value = row.getValue("monitors"); 39 + if (Array.isArray(value) && value.length > 0 && "name" in value[0]) { 40 + return ( 41 + <div className="flex flex-wrap gap-1"> 42 + {value.map((m) => ( 43 + <TableCellBadge key={m.id} value={m.name} /> 44 + ))} 45 + </div> 46 + ); 47 + } 48 + return <span className="text-muted-foreground">-</span>; 49 + }, 50 + meta: { 51 + cellClassName: "tabular-nums font-mono", 52 + }, 53 + }, 54 + { 55 + id: "actions", 56 + cell: ({ row }) => <DataTableRowActions row={row} />, 57 + meta: { 58 + cellClassName: "w-8", 59 + }, 60 + }, 61 + ];
+80
apps/dashboard/src/components/data-table/private-locations/data-table-row-actions.tsx
··· 1 + "use client"; 2 + 3 + import { QuickActions } from "@/components/dropdowns/quick-actions"; 4 + import { FormSheetPrivateLocation } from "@/components/forms/private-location/sheet"; 5 + import { getActions } from "@/data/notifications.client"; 6 + import { useTRPC } from "@/lib/trpc/client"; 7 + import type { RouterOutputs } from "@openstatus/api"; 8 + import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 9 + import type { Row } from "@tanstack/react-table"; 10 + import { useRef } from "react"; 11 + 12 + type PrivateLocation = RouterOutputs["privateLocation"]["list"][number]; 13 + 14 + interface DataTableRowActionsProps { 15 + row: Row<PrivateLocation>; 16 + } 17 + 18 + export function DataTableRowActions(props: DataTableRowActionsProps) { 19 + const buttonRef = useRef<HTMLButtonElement>(null); 20 + const actions = getActions({ 21 + edit: () => buttonRef.current?.click(), 22 + }); 23 + const trpc = useTRPC(); 24 + const queryClient = useQueryClient(); 25 + const { data: monitors } = useQuery(trpc.monitor.list.queryOptions()); 26 + const updatePrivateLocationMutation = useMutation( 27 + trpc.privateLocation.update.mutationOptions({ 28 + onSuccess: () => { 29 + queryClient.invalidateQueries({ 30 + queryKey: trpc.privateLocation.list.queryKey(), 31 + }); 32 + }, 33 + }), 34 + ); 35 + const deletePrivateLocationMutation = useMutation( 36 + trpc.privateLocation.delete.mutationOptions({ 37 + onSuccess: () => { 38 + queryClient.invalidateQueries({ 39 + queryKey: trpc.privateLocation.list.queryKey(), 40 + }); 41 + }, 42 + }), 43 + ); 44 + 45 + return ( 46 + <> 47 + <QuickActions 48 + actions={actions} 49 + deleteAction={{ 50 + title: "Delete", 51 + confirmationValue: "delete private location", 52 + submitAction: async () => { 53 + await deletePrivateLocationMutation.mutateAsync({ 54 + id: props.row.original.id, 55 + }); 56 + }, 57 + }} 58 + /> 59 + <FormSheetPrivateLocation 60 + defaultValues={{ 61 + name: props.row.original.name, 62 + key: props.row.original.token.toString(), 63 + monitors: props.row.original.monitors.map((m) => m.id), 64 + }} 65 + monitors={monitors ?? []} 66 + onSubmit={async (values) => { 67 + await updatePrivateLocationMutation.mutateAsync({ 68 + id: props.row.original.id, 69 + name: values.name, 70 + monitors: values.monitors, 71 + }); 72 + }} 73 + > 74 + <button ref={buttonRef} type="button" className="sr-only"> 75 + Open sheet 76 + </button> 77 + </FormSheetPrivateLocation> 78 + </> 79 + ); 80 + }
+135 -121
apps/dashboard/src/components/data-table/response-logs/columns.tsx
··· 17 17 import { getStatusCodeVariant, textColors } from "@/data/status-codes"; 18 18 import { cn } from "@/lib/utils"; 19 19 import type { RouterOutputs } from "@openstatus/api"; 20 - import { regionDict } from "@openstatus/regions"; 20 + import type { PrivateLocation } from "@openstatus/db/src/schema"; 21 + import { getRegionInfo } from "@openstatus/regions"; 21 22 import { HoverCardPortal } from "@radix-ui/react-hover-card"; 22 23 import type { ColumnDef } from "@tanstack/react-table"; 23 24 import { Clock, Workflow } from "lucide-react"; 24 25 25 26 type ResponseLog = RouterOutputs["tinybird"]["list"]["data"][number]; 26 27 27 - export const columns: ColumnDef<ResponseLog>[] = [ 28 - { 29 - accessorKey: "requestStatus", 30 - header: () => null, 31 - enableSorting: false, 32 - enableHiding: false, 33 - cell: ({ row }) => { 34 - const value = row.getValue("requestStatus"); 35 - if (value === "error") { 36 - return <div className="h-2.5 w-2.5 rounded-[2px] bg-destructive" />; 37 - } 38 - if (value === "degraded") { 39 - return <div className="h-2.5 w-2.5 rounded-[2px] bg-warning" />; 40 - } 41 - if (value === "success") { 42 - return <div className="h-2.5 w-2.5 rounded-[2px] bg-success" />; 43 - } 44 - return <div className="text-muted-foreground">-</div>; 45 - }, 46 - }, 47 - { 48 - accessorKey: "timestamp", 49 - header: "Timestamp", 50 - enableSorting: false, 51 - enableHiding: false, 52 - cell: ({ row }) => { 53 - const value = new Date(row.getValue("timestamp")); 54 - return ( 55 - <HoverCardTimestamp date={value}> 56 - <TableCellDate value={value} className="font-mono text-foreground" /> 57 - </HoverCardTimestamp> 58 - ); 28 + // export const columns: ColumnDef<ResponseLog>[] = 29 + export function getColumns( 30 + privateLocations: PrivateLocation[], 31 + ): ColumnDef<ResponseLog>[] { 32 + return [ 33 + { 34 + accessorKey: "requestStatus", 35 + header: () => null, 36 + enableSorting: false, 37 + enableHiding: false, 38 + cell: ({ row }) => { 39 + const value = row.getValue("requestStatus"); 40 + if (value === "error") { 41 + return <div className="h-2.5 w-2.5 rounded-[2px] bg-destructive" />; 42 + } 43 + if (value === "degraded") { 44 + return <div className="h-2.5 w-2.5 rounded-[2px] bg-warning" />; 45 + } 46 + if (value === "success") { 47 + return <div className="h-2.5 w-2.5 rounded-[2px] bg-success" />; 48 + } 49 + return <div className="text-muted-foreground">-</div>; 50 + }, 59 51 }, 60 - }, 61 - { 62 - accessorKey: "statusCode", 63 - header: "Status", 64 - enableSorting: false, 65 - enableHiding: false, 66 - cell: ({ row }) => { 67 - const log = row.original; 68 - if (log.type === "http") { 69 - const value = log.statusCode; 70 - const variant = getStatusCodeVariant(value); 52 + { 53 + accessorKey: "timestamp", 54 + header: "Timestamp", 55 + enableSorting: false, 56 + enableHiding: false, 57 + cell: ({ row }) => { 58 + const value = new Date(row.getValue("timestamp")); 71 59 return ( 72 - <TableCellNumber value={value} className={textColors[variant]} /> 60 + <HoverCardTimestamp date={value}> 61 + <TableCellDate 62 + value={value} 63 + className="font-mono text-foreground" 64 + /> 65 + </HoverCardTimestamp> 73 66 ); 74 - } 75 - return <div className="text-muted-foreground">-</div>; 76 - }, 77 - }, 78 - { 79 - accessorKey: "latency", 80 - header: "Latency", 81 - enableSorting: false, 82 - enableHiding: false, 83 - cell: ({ row }) => { 84 - return <TableCellNumber value={row.getValue("latency")} unit="ms" />; 67 + }, 85 68 }, 86 - }, 87 - { 88 - accessorKey: "region", 89 - header: "Region", 90 - cell: ({ row }) => { 91 - const value = row.getValue("region"); 92 - 93 - if (typeof value !== "string") { 69 + { 70 + accessorKey: "statusCode", 71 + header: "Status", 72 + enableSorting: false, 73 + enableHiding: false, 74 + cell: ({ row }) => { 75 + const log = row.original; 76 + if (log.type === "http") { 77 + const value = log.statusCode; 78 + const variant = getStatusCodeVariant(value); 79 + return ( 80 + <TableCellNumber value={value} className={textColors[variant]} /> 81 + ); 82 + } 94 83 return <div className="text-muted-foreground">-</div>; 95 - } 96 - 97 - const regionConfig = regionDict[value as keyof typeof regionDict]; 98 - return ( 99 - <div> 100 - {regionConfig.location}{" "} 101 - <span className="text-muted-foreground/70 text-xs"> 102 - ({regionConfig.provider}) 103 - </span> 104 - </div> 105 - ); 106 - }, 107 - enableSorting: false, 108 - enableHiding: false, 109 - filterFn: "arrIncludesSome", 110 - meta: { 111 - cellClassName: "text-muted-foreground font-mono", 84 + }, 112 85 }, 113 - }, 114 - { 115 - accessorKey: "timing", 116 - header: "Timing", 117 - cell: ({ row }) => { 118 - const log = row.original; 119 - if (log.type === "http" && log.timing) { 120 - return <HoverCardTiming timing={log.timing} latency={log.latency} />; 121 - } 122 - return <div className="text-muted-foreground">-</div>; 86 + { 87 + accessorKey: "latency", 88 + header: "Latency", 89 + enableSorting: false, 90 + enableHiding: false, 91 + cell: ({ row }) => { 92 + return <TableCellNumber value={row.getValue("latency")} unit="ms" />; 93 + }, 123 94 }, 124 - enableSorting: false, 125 - enableHiding: false, 126 - }, 127 - { 128 - accessorKey: "trigger", 129 - header: "Trigger", 130 - cell: ({ row }) => { 131 - const value = row.getValue("trigger"); 132 - if (value === "cron" || value === "api") { 133 - const Icon = value === "cron" ? Clock : Workflow; 134 - const label = value === "cron" ? "Scheduled" : "API"; 95 + { 96 + accessorKey: "region", 97 + header: "Region", 98 + cell: ({ row }) => { 99 + const value = row.getValue("region"); 100 + 101 + if (typeof value !== "string") { 102 + return <div className="text-muted-foreground">-</div>; 103 + } 104 + 105 + const regionConfig = getRegionInfo(value, { 106 + location: privateLocations.find( 107 + (location) => String(location.id) === String(value), 108 + )?.name, 109 + }); 110 + 135 111 return ( 136 - <TooltipProvider> 137 - <Tooltip> 138 - <TooltipTrigger> 139 - <Icon className="size-3 text-muted-foreground" /> 140 - </TooltipTrigger> 141 - <TooltipContent side="right"> 142 - <p>{label}</p> 143 - </TooltipContent> 144 - </Tooltip> 145 - </TooltipProvider> 112 + <div> 113 + {regionConfig.location}{" "} 114 + <span className="text-muted-foreground/70 text-xs"> 115 + ({regionConfig.provider}) 116 + </span> 117 + </div> 146 118 ); 147 - } 148 - return <div className="text-muted-foreground">-</div>; 119 + }, 120 + enableSorting: false, 121 + enableHiding: false, 122 + filterFn: "arrIncludesSome", 123 + meta: { 124 + cellClassName: "text-muted-foreground font-mono", 125 + }, 149 126 }, 150 - enableSorting: false, 151 - enableHiding: false, 152 - meta: { 153 - cellClassName: "font-mono", 154 - headerClassName: "sr-only", 127 + { 128 + accessorKey: "timing", 129 + header: "Timing", 130 + cell: ({ row }) => { 131 + const log = row.original; 132 + if (log.type === "http" && log.timing) { 133 + return <HoverCardTiming timing={log.timing} latency={log.latency} />; 134 + } 135 + return <div className="text-muted-foreground">-</div>; 136 + }, 137 + enableSorting: false, 138 + enableHiding: false, 155 139 }, 156 - }, 157 - ]; 140 + { 141 + accessorKey: "trigger", 142 + header: "Trigger", 143 + cell: ({ row }) => { 144 + const value = row.getValue("trigger"); 145 + if (value === "cron" || value === "api") { 146 + const Icon = value === "cron" ? Clock : Workflow; 147 + const label = value === "cron" ? "Scheduled" : "API"; 148 + return ( 149 + <TooltipProvider> 150 + <Tooltip> 151 + <TooltipTrigger> 152 + <Icon className="size-3 text-muted-foreground" /> 153 + </TooltipTrigger> 154 + <TooltipContent side="right"> 155 + <p>{label}</p> 156 + </TooltipContent> 157 + </Tooltip> 158 + </TooltipProvider> 159 + ); 160 + } 161 + return <div className="text-muted-foreground">-</div>; 162 + }, 163 + enableSorting: false, 164 + enableHiding: false, 165 + meta: { 166 + cellClassName: "font-mono", 167 + headerClassName: "sr-only", 168 + }, 169 + }, 170 + ]; 171 + } 158 172 159 173 function HoverCardTiming({ 160 174 timing,
+31 -6
apps/dashboard/src/components/data-table/response-logs/data-table-basics.tsx
··· 16 16 import { formatMilliseconds, formatPercentage } from "@/lib/formatter"; 17 17 import { cn } from "@/lib/utils"; 18 18 import type { RouterOutputs } from "@openstatus/api"; 19 - import { regionDict } from "@openstatus/regions"; 19 + import type { PrivateLocation } from "@openstatus/db/src/schema"; 20 + import { getRegionInfo } from "@openstatus/regions"; 20 21 import { Braces, TableProperties } from "lucide-react"; 21 22 22 23 type ResponseLog = RouterOutputs["tinybird"]["get"]["data"][number]; 23 24 24 - export function DataTableBasics({ data }: { data: ResponseLog }) { 25 + export function DataTableBasics({ 26 + data, 27 + privateLocations, 28 + }: { 29 + data: ResponseLog; 30 + privateLocations?: PrivateLocation[]; 31 + }) { 25 32 if (data.type === "http") { 26 - return <DataTableBasicsHTTP data={data} />; 33 + return ( 34 + <DataTableBasicsHTTP data={data} privateLocations={privateLocations} /> 35 + ); 27 36 } 28 37 if (data.type === "tcp") { 29 - return <DataTableBasicsTCP data={data} />; 38 + return ( 39 + <DataTableBasicsTCP data={data} privateLocations={privateLocations} /> 40 + ); 30 41 } 31 42 return null; 32 43 } 33 44 34 45 export function DataTableBasicsHTTP({ 35 46 data, 47 + privateLocations, 36 48 }: { 37 49 data: Extract<ResponseLog, { type: "http" }> & { 38 50 trigger?: "cron" | "api" | "test" | null; 39 51 }; 52 + privateLocations?: PrivateLocation[]; 40 53 }) { 41 - const regionConfig = regionDict[data.region]; 54 + const privateLocataion = privateLocations?.find( 55 + (location) => String(location.id) === String(data.region), 56 + ); 57 + const regionConfig = getRegionInfo(data.region, { 58 + location: privateLocataion?.name, 59 + }); 42 60 return ( 43 61 <Table className="table-fixed"> 44 62 <colgroup> ··· 304 322 305 323 export function DataTableBasicsTCP({ 306 324 data, 325 + privateLocations, 307 326 }: { 308 327 data: Extract<ResponseLog, { type: "tcp" }> & { 309 328 trigger?: "cron" | "api" | "test" | null; 310 329 }; 330 + privateLocations?: PrivateLocation[]; 311 331 }) { 312 - const regionConfig = regionDict[data.region]; 332 + const privateLocataion = privateLocations?.find( 333 + (location) => String(location.id) === String(data.region), 334 + ); 335 + const regionConfig = getRegionInfo(data.region, { 336 + location: privateLocataion?.name, 337 + }); 313 338 return ( 314 339 <Table className="table-fixed"> 315 340 <colgroup>
+4 -1
apps/dashboard/src/components/data-table/response-logs/data-table-sheet.tsx
··· 11 11 import { Separator } from "@/components/ui/separator"; 12 12 import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"; 13 13 import type { RouterOutputs } from "@openstatus/api"; 14 + import type { PrivateLocation } from "@openstatus/db/src/schema"; 14 15 import { Check, Copy } from "lucide-react"; 15 16 import { DataTableBasics } from "./data-table-basics"; 16 17 ··· 18 19 19 20 export function Sheet({ 20 21 data, 22 + privateLocations, 21 23 onClose, 22 24 }: { 23 25 data: ResponseLog | null; 26 + privateLocations?: PrivateLocation[]; 24 27 onClose: () => void; 25 28 }) { 26 29 const { copy, isCopied } = useCopyToClipboard(); ··· 32 35 <DataTableSheetHeader className="px-2"> 33 36 <DataTableSheetTitle>Response Logs</DataTableSheetTitle> 34 37 </DataTableSheetHeader> 35 - <DataTableBasics data={data} /> 38 + <DataTableBasics data={data} privateLocations={privateLocations} /> 36 39 <Separator /> 37 40 <DataTableSheetFooter> 38 41 <Button
+117 -105
apps/dashboard/src/components/data-table/response-logs/regions/columns.tsx
··· 12 12 } from "@/components/ui/tooltip"; 13 13 import type { RegionMetric } from "@/data/region-metrics"; 14 14 import { getActions } from "@/data/region-metrics.client"; 15 - import { formatRegionCode, regionDict } from "@openstatus/regions"; 15 + import type { PrivateLocation } from "@openstatus/db/src/schema"; 16 + import { formatRegionCode, getRegionInfo } from "@openstatus/regions"; 16 17 import type { ColumnDef } from "@tanstack/react-table"; 17 18 // import { toast } from "sonner"; 18 19 import { useRouter } from "next/navigation"; ··· 21 22 return <ChartLineRegion className="h-[50px]" data={trend} />; 22 23 } 23 24 24 - export const columns: ColumnDef<RegionMetric>[] = [ 25 - { 26 - accessorKey: "region", 27 - header: "Region", 28 - cell: ({ row }) => { 29 - const value = row.getValue("region"); 30 - if (typeof value === "string") { 31 - const region = regionDict[value as keyof typeof regionDict]; 32 - return ( 33 - <TooltipProvider> 34 - <Tooltip> 35 - <TooltipTrigger className="flex h-[50px] items-center gap-1"> 36 - {region.flag} {formatRegionCode(region.code)} 37 - </TooltipTrigger> 38 - <TooltipContent side="left"> 39 - {region.location} ({region.provider}) 40 - </TooltipContent> 41 - </Tooltip> 42 - </TooltipProvider> 43 - ); 44 - } 45 - return null; 25 + export function getColumns( 26 + privateLocations: PrivateLocation[], 27 + ): ColumnDef<RegionMetric>[] { 28 + return [ 29 + { 30 + accessorKey: "region", 31 + header: "Region", 32 + cell: ({ row }) => { 33 + const value = row.getValue("region"); 34 + if (typeof value === "string") { 35 + const region = getRegionInfo(value, { 36 + location: privateLocations.find( 37 + (location) => String(location.id) === String(value), 38 + )?.name, 39 + }); 40 + return ( 41 + <TooltipProvider> 42 + <Tooltip> 43 + <TooltipTrigger className="flex h-[50px] items-center gap-1"> 44 + {region.flag}{" "} 45 + <span className="truncate max-w-[90px]"> 46 + {formatRegionCode(region.code)} 47 + </span> 48 + </TooltipTrigger> 49 + <TooltipContent side="left"> 50 + {region.location} ({region.provider}) 51 + </TooltipContent> 52 + </Tooltip> 53 + </TooltipProvider> 54 + ); 55 + } 56 + return null; 57 + }, 58 + enableSorting: false, 59 + enableHiding: false, 60 + meta: { 61 + cellClassName: "w-24 font-mono", 62 + }, 46 63 }, 47 - enableSorting: false, 48 - enableHiding: false, 49 - meta: { 50 - cellClassName: "w-24 font-mono", 51 - }, 52 - }, 53 - { 54 - accessorKey: "trend", 55 - header: "Trend", 56 - cell: ({ row }) => { 57 - return <TrendCell trend={row.original.trend} />; 58 - }, 59 - enableSorting: false, 60 - enableHiding: false, 61 - meta: { 62 - cellClassName: "w-full min-w-[200px] max-w-full", 63 - }, 64 - }, 65 - { 66 - accessorKey: "p50", 67 - header: ({ column }) => ( 68 - <DataTableColumnHeader column={column} title="P50" /> 69 - ), 70 - cell: ({ row }) => { 71 - return <TableCellNumber value={row.getValue("p50")} unit="ms" />; 72 - }, 73 - enableHiding: false, 74 - meta: { 75 - cellClassName: "w-12", 76 - }, 77 - }, 78 - { 79 - accessorKey: "p90", 80 - header: ({ column }) => ( 81 - <DataTableColumnHeader column={column} title="P90" /> 82 - ), 83 - cell: ({ row }) => { 84 - return <TableCellNumber value={row.getValue("p90")} unit="ms" />; 85 - }, 86 - enableHiding: false, 87 - meta: { 88 - cellClassName: "w-12", 64 + { 65 + accessorKey: "trend", 66 + header: "Trend", 67 + cell: ({ row }) => { 68 + return <TrendCell trend={row.original.trend} />; 69 + }, 70 + enableSorting: false, 71 + enableHiding: false, 72 + meta: { 73 + cellClassName: "w-full min-w-[200px] max-w-full", 74 + }, 89 75 }, 90 - }, 91 - { 92 - accessorKey: "p99", 93 - header: ({ column }) => ( 94 - <DataTableColumnHeader column={column} title="P99" /> 95 - ), 96 - cell: ({ row }) => { 97 - return <TableCellNumber value={row.getValue("p99")} unit="ms" />; 76 + { 77 + accessorKey: "p50", 78 + header: ({ column }) => ( 79 + <DataTableColumnHeader column={column} title="P50" /> 80 + ), 81 + cell: ({ row }) => { 82 + return <TableCellNumber value={row.getValue("p50")} unit="ms" />; 83 + }, 84 + enableHiding: false, 85 + meta: { 86 + cellClassName: "w-12", 87 + }, 98 88 }, 99 - enableHiding: false, 100 - meta: { 101 - cellClassName: "w-12", 89 + { 90 + accessorKey: "p90", 91 + header: ({ column }) => ( 92 + <DataTableColumnHeader column={column} title="P90" /> 93 + ), 94 + cell: ({ row }) => { 95 + return <TableCellNumber value={row.getValue("p90")} unit="ms" />; 96 + }, 97 + enableHiding: false, 98 + meta: { 99 + cellClassName: "w-12", 100 + }, 102 101 }, 103 - }, 104 - { 105 - id: "actions", 106 - cell: ({ row }) => { 107 - // NOTE: works, but is not very react-esque 108 - // eslint-disable-next-line react-hooks/rules-of-hooks 109 - const router = useRouter(); 110 - const actions = getActions({ 111 - filter: async () => { 112 - router.push(`?regions=${row.original.region}`); 113 - }, 114 - // TODO: add triggerById in TRPC client 115 - // trigger: async () => { 116 - // console.log(row.original); 117 - // const promise = new Promise((resolve) => setTimeout(resolve, 1000)); 118 - // toast.promise(promise, { 119 - // loading: "Checking...", 120 - // success: "Success", 121 - // error: "Failed", 122 - // }); 123 - // await promise; 124 - // }, 125 - }); 126 - return <QuickActions actions={actions} />; 102 + { 103 + accessorKey: "p99", 104 + header: ({ column }) => ( 105 + <DataTableColumnHeader column={column} title="P99" /> 106 + ), 107 + cell: ({ row }) => { 108 + return <TableCellNumber value={row.getValue("p99")} unit="ms" />; 109 + }, 110 + enableHiding: false, 111 + meta: { 112 + cellClassName: "w-12", 113 + }, 127 114 }, 128 - meta: { 129 - headerClassName: "w-12", 130 - cellClassName: "text-right", 115 + { 116 + id: "actions", 117 + cell: ({ row }) => { 118 + // NOTE: works, but is not very react-esque 119 + // eslint-disable-next-line react-hooks/rules-of-hooks 120 + const router = useRouter(); 121 + const actions = getActions({ 122 + filter: async () => { 123 + router.push(`?regions=${row.original.region}`); 124 + }, 125 + // TODO: add triggerById in TRPC client 126 + // trigger: async () => { 127 + // console.log(row.original); 128 + // const promise = new Promise((resolve) => setTimeout(resolve, 1000)); 129 + // toast.promise(promise, { 130 + // loading: "Checking...", 131 + // success: "Success", 132 + // error: "Failed", 133 + // }); 134 + // await promise; 135 + // }, 136 + }); 137 + return <QuickActions actions={actions} />; 138 + }, 139 + meta: { 140 + headerClassName: "w-12", 141 + cellClassName: "text-right", 142 + }, 131 143 }, 132 - }, 133 - ]; 144 + ]; 145 + }
+16 -4
apps/dashboard/src/components/dialogs/upgrade.tsx
··· 9 9 } from "@/components/ui/dialog"; 10 10 import { useTRPC } from "@/lib/trpc/client"; 11 11 import type { WorkspacePlan } from "@openstatus/db/src/schema"; 12 + import type { Limits } from "@openstatus/db/src/schema/plan/schema"; 13 + import { getPlansForLimit } from "@openstatus/db/src/schema/plan/utils"; 12 14 import type { DialogProps } from "@radix-ui/react-dialog"; 13 15 import { useQuery } from "@tanstack/react-query"; 14 16 import { CalendarClock } from "lucide-react"; ··· 19 21 team: [], 20 22 } satisfies Record<WorkspacePlan, WorkspacePlan[]>; 21 23 22 - export function UpgradeDialog(props: DialogProps) { 24 + export function UpgradeDialog( 25 + props: DialogProps & { 26 + limit?: keyof Limits; 27 + restrictTo?: WorkspacePlan[]; 28 + }, 29 + ) { 23 30 const trpc = useTRPC(); 24 31 const { data: workspace } = useQuery(trpc.workspace.get.queryOptions()); 25 32 26 33 if (!workspace) return null; 27 34 28 - const restrictTo = PLANS[workspace.plan]; 35 + const getRestrictTo = () => { 36 + if (props.restrictTo) return props.restrictTo; 37 + if (props.limit) return getPlansForLimit(workspace.plan, props.limit); 38 + return PLANS[workspace.plan]; 39 + }; 40 + 41 + const restrictTo = getRestrictTo(); 29 42 30 43 return ( 31 44 <Dialog {...props}> ··· 40 53 {restrictTo.length === 0 ? ( 41 54 <Note> 42 55 <CalendarClock /> 43 - You&apos;re already on our highest plan. Let&apos;s chat about your 44 - needs. 56 + Please contact us to upgrade your plan. 45 57 <NoteButton variant="outline" asChild> 46 58 <a 47 59 href="https://openstatus.dev/cal"
+104 -1
apps/dashboard/src/components/forms/monitor/form-scheduling-regions.tsx
··· 50 50 } from "@openstatus/regions"; 51 51 import { useQuery } from "@tanstack/react-query"; 52 52 import { isTRPCClientError } from "@trpc/client"; 53 - import { CircleX, Info } from "lucide-react"; 53 + import { CircleX, Globe, Info } from "lucide-react"; 54 54 55 55 const DEFAULT_PERIODICITY = "10m"; 56 56 const DEFAULT_REGIONS = ["ams", "fra", "iad", "syd", "jnb", "gru"]; 57 57 const PERIODICITY = monitorPeriodicity.filter((p) => p !== "other"); 58 + const DEFAULT_PRIVATE_LOCATIONS = [] satisfies { id: number; name: string }[]; 58 59 59 60 const schema = z.object({ 60 61 regions: z.array(z.string()), 61 62 periodicity: z.enum(monitorPeriodicity), 63 + privateLocations: z.array(z.number()), 62 64 }); 63 65 64 66 type FormValues = z.infer<typeof schema>; ··· 66 68 export function FormSchedulingRegions({ 67 69 defaultValues, 68 70 onSubmit, 71 + privateLocations, 69 72 ...props 70 73 }: Omit<React.ComponentProps<"form">, "onSubmit"> & { 71 74 defaultValues?: FormValues; 72 75 onSubmit: (values: FormValues) => Promise<void>; 76 + privateLocations: { id: number; name: string }[]; 73 77 }) { 74 78 const trpc = useTRPC(); 75 79 const [openDialog, setOpenDialog] = useState(false); ··· 79 83 defaultValues: defaultValues ?? { 80 84 regions: DEFAULT_REGIONS, 81 85 periodicity: DEFAULT_PERIODICITY, 86 + privateLocations: DEFAULT_PRIVATE_LOCATIONS, 82 87 }, 83 88 }); 84 89 const [isPending, startTransition] = useTransition(); 85 90 const watchPeriodicity = form.watch("periodicity"); 86 91 const watchRegions = form.watch("regions"); 92 + const watchPrivateLocations = form.watch("privateLocations"); 87 93 88 94 function submitAction(values: FormValues) { 89 95 if (isPending) return; ··· 395 401 . 396 402 </div> 397 403 </Note> 404 + </FormCardContent> 405 + <FormCardSeparator /> 406 + <FormCardContent className="grid gap-4"> 407 + {privateLocations.length === 0 ? ( 408 + <Note> 409 + <Globe /> 410 + Monitor your endpoints from private locations. 411 + <NoteButton variant="outline" asChild> 412 + <Link href="/private-locations">Learn more</Link> 413 + </NoteButton> 414 + </Note> 415 + ) : ( 416 + <FormField 417 + control={form.control} 418 + name="privateLocations" 419 + render={() => ( 420 + <FormItem> 421 + <div className="flex items-center justify-between"> 422 + <FormLabel> 423 + Private Locations{" "} 424 + <span className="align-baseline font-mono font-normal text-muted-foreground/70 text-xs tabular-nums"> 425 + ({watchPrivateLocations.length}/ 426 + {privateLocations.length}) 427 + </span> 428 + </FormLabel> 429 + <Button 430 + variant="ghost" 431 + size="sm" 432 + type="button" 433 + className={cn( 434 + watchPrivateLocations.length === 435 + privateLocations.length && "text-muted-foreground", 436 + )} 437 + onClick={() => { 438 + const allSelected = privateLocations.every((item) => 439 + watchPrivateLocations.includes(item.id), 440 + ); 441 + 442 + if (!allSelected) { 443 + form.setValue( 444 + "privateLocations", 445 + privateLocations.map((item) => item.id), 446 + ); 447 + } else { 448 + form.setValue("privateLocations", []); 449 + } 450 + }} 451 + > 452 + Select all 453 + </Button> 454 + </div> 455 + <div className="grid grid-cols-2 gap-2"> 456 + {privateLocations.map((item) => ( 457 + <FormField 458 + key={item.id} 459 + control={form.control} 460 + name="privateLocations" 461 + render={({ field }) => { 462 + return ( 463 + <FormItem 464 + key={item.id} 465 + className="flex items-center" 466 + > 467 + <FormControl> 468 + <Checkbox 469 + checked={ 470 + field.value?.includes(item.id) || false 471 + } 472 + onCheckedChange={(checked) => { 473 + return checked 474 + ? field.onChange([ 475 + ...field.value, 476 + item.id, 477 + ]) 478 + : field.onChange( 479 + field.value?.filter( 480 + (value) => value !== item.id, 481 + ), 482 + ); 483 + }} 484 + /> 485 + </FormControl> 486 + <FormLabel className="w-full truncate font-mono font-normal text-sm"> 487 + {item.name} 488 + <Globe className="size-3" /> 489 + </FormLabel> 490 + </FormItem> 491 + ); 492 + }} 493 + /> 494 + ))} 495 + </div> 496 + <FormMessage /> 497 + </FormItem> 498 + )} 499 + /> 500 + )} 398 501 </FormCardContent> 399 502 <FormCardFooter> 400 503 <FormCardFooterInfo>
+14 -1
apps/dashboard/src/components/forms/monitor/update.tsx
··· 26 26 trpc.monitor.get.queryOptions({ id: Number.parseInt(id) }), 27 27 ); 28 28 const { data: statusPages } = useQuery(trpc.page.list.queryOptions()); 29 + const { data: privateLocations } = useQuery( 30 + trpc.privateLocation.list.queryOptions(), 31 + ); 29 32 const { data: notifications } = useQuery( 30 33 trpc.notification.list.queryOptions(), 31 34 ); ··· 105 108 }), 106 109 ); 107 110 108 - if (!monitor || !statusPages || !notifications || !workspace) return null; 111 + if ( 112 + !monitor || 113 + !statusPages || 114 + !notifications || 115 + !workspace || 116 + !privateLocations 117 + ) 118 + return null; 109 119 110 120 return ( 111 121 <FormCardGroup> ··· 166 176 }} 167 177 /> 168 178 <FormSchedulingRegions 179 + privateLocations={privateLocations} 169 180 defaultValues={{ 170 181 regions: monitor.regions, 171 182 periodicity: monitor.periodicity, 183 + privateLocations: monitor.privateLocations.map(({ id }) => id), 172 184 }} 173 185 onSubmit={async (values) => { 174 186 await updateSchedulingRegionsMutation.mutateAsync({ 175 187 id: Number.parseInt(id), 176 188 regions: values.regions, 177 189 periodicity: values.periodicity, 190 + privateLocations: values.privateLocations, 178 191 }); 179 192 }} 180 193 />
+211
apps/dashboard/src/components/forms/private-location/form.tsx
··· 1 + "use client"; 2 + 3 + import { 4 + EmptyStateContainer, 5 + EmptyStateTitle, 6 + } from "@/components/content/empty-state"; 7 + import { 8 + FormCardContent, 9 + FormCardSeparator, 10 + } from "@/components/forms/form-card"; 11 + import { Checkbox } from "@/components/ui/checkbox"; 12 + import { 13 + Form, 14 + FormControl, 15 + FormDescription, 16 + FormField, 17 + FormItem, 18 + FormLabel, 19 + FormMessage, 20 + } from "@/components/ui/form"; 21 + import { Input } from "@/components/ui/input"; 22 + import { 23 + InputGroup, 24 + InputGroupAddon, 25 + InputGroupButton, 26 + InputGroupInput, 27 + } from "@/components/ui/input-group"; 28 + import { Label } from "@/components/ui/label"; 29 + import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"; 30 + import { cn } from "@/lib/utils"; 31 + import { zodResolver } from "@hookform/resolvers/zod"; 32 + import { isTRPCClientError } from "@trpc/client"; 33 + import { Check, Copy } from "lucide-react"; 34 + import type React from "react"; 35 + import { useTransition } from "react"; 36 + import { useForm } from "react-hook-form"; 37 + import { toast } from "sonner"; 38 + import { z } from "zod"; 39 + 40 + const schema = z.object({ 41 + name: z.string().min(1, "Name is required"), 42 + key: z.string(), 43 + monitors: z.array(z.number()), 44 + }); 45 + 46 + export type FormValues = z.infer<typeof schema>; 47 + 48 + export function FormPrivateLocation({ 49 + defaultValues, 50 + onSubmit, 51 + className, 52 + monitors, 53 + ...props 54 + }: Omit<React.ComponentProps<"form">, "onSubmit"> & { 55 + defaultValues?: FormValues; 56 + monitors: { id: number; name: string; url: string }[]; 57 + onSubmit: (values: FormValues) => Promise<void>; 58 + }) { 59 + const form = useForm<FormValues>({ 60 + resolver: zodResolver(schema), 61 + defaultValues: defaultValues ?? { 62 + name: "", 63 + key: crypto.randomUUID(), 64 + monitors: [], 65 + }, 66 + }); 67 + const [isPending, startTransition] = useTransition(); 68 + const { copy, isCopied } = useCopyToClipboard(); 69 + 70 + function submitAction(values: FormValues) { 71 + if (isPending) return; 72 + 73 + startTransition(async () => { 74 + try { 75 + const promise = onSubmit(values); 76 + toast.promise(promise, { 77 + loading: "Saving...", 78 + success: () => "Saved", 79 + error: (error) => { 80 + if (isTRPCClientError(error)) { 81 + return error.message; 82 + } 83 + return "Failed to save"; 84 + }, 85 + }); 86 + await promise; 87 + } catch (error) { 88 + console.error(error); 89 + } 90 + }); 91 + } 92 + 93 + return ( 94 + <Form {...form}> 95 + <form 96 + className={cn("grid gap-4", className)} 97 + onSubmit={form.handleSubmit(submitAction)} 98 + {...props} 99 + > 100 + <FormCardContent> 101 + <FormField 102 + control={form.control} 103 + name="name" 104 + render={({ field }) => ( 105 + <FormItem> 106 + <FormLabel>Name</FormLabel> 107 + <FormControl> 108 + <Input placeholder="My Raspberry Pi" {...field} /> 109 + </FormControl> 110 + <FormMessage /> 111 + </FormItem> 112 + )} 113 + /> 114 + </FormCardContent> 115 + <FormCardSeparator /> 116 + <FormCardContent> 117 + <FormField 118 + control={form.control} 119 + name="key" 120 + disabled 121 + render={({ field }) => ( 122 + <FormItem> 123 + <FormLabel>Key</FormLabel> 124 + <FormControl> 125 + <InputGroup> 126 + <InputGroupInput 127 + placeholder="Private Location Key" 128 + readOnly 129 + value={field.value} 130 + /> 131 + <InputGroupAddon align="inline-end"> 132 + <InputGroupButton 133 + aria-label="Copy" 134 + title="Copy" 135 + size="icon-xs" 136 + onClick={() => { 137 + copy(field.value, { 138 + successMessage: "Key copied to clipboard", 139 + }); 140 + }} 141 + > 142 + {isCopied ? <Check /> : <Copy />} 143 + </InputGroupButton> 144 + </InputGroupAddon> 145 + </InputGroup> 146 + </FormControl> 147 + <FormMessage /> 148 + </FormItem> 149 + )} 150 + /> 151 + </FormCardContent> 152 + <FormCardSeparator /> 153 + <FormCardContent> 154 + <FormField 155 + control={form.control} 156 + name="monitors" 157 + render={({ field }) => ( 158 + <FormItem> 159 + <FormLabel>Monitors</FormLabel> 160 + <FormDescription> 161 + Connected monitors will be automatically activated for the 162 + private location. 163 + </FormDescription> 164 + {monitors.length ? ( 165 + <div className="grid gap-3"> 166 + <div className="flex items-center gap-2"> 167 + <FormControl> 168 + <Checkbox 169 + id="all" 170 + checked={field.value?.length === monitors.length} 171 + onCheckedChange={(checked) => { 172 + field.onChange( 173 + checked ? monitors.map((m) => m.id) : [], 174 + ); 175 + }} 176 + /> 177 + </FormControl> 178 + <Label htmlFor="all">Select all</Label> 179 + </div> 180 + {monitors.map((item) => ( 181 + <div key={item.id} className="flex items-center gap-2"> 182 + <FormControl> 183 + <Checkbox 184 + id={String(item.id)} 185 + checked={field.value?.includes(item.id)} 186 + onCheckedChange={(checked) => { 187 + const newValue = checked 188 + ? [...(field.value || []), item.id] 189 + : field.value?.filter((id) => id !== item.id); 190 + field.onChange(newValue); 191 + }} 192 + /> 193 + </FormControl> 194 + <Label htmlFor={String(item.id)}>{item.name}</Label> 195 + </div> 196 + ))} 197 + </div> 198 + ) : ( 199 + <EmptyStateContainer> 200 + <EmptyStateTitle>No monitors found</EmptyStateTitle> 201 + </EmptyStateContainer> 202 + )} 203 + <FormMessage /> 204 + </FormItem> 205 + )} 206 + /> 207 + </FormCardContent> 208 + </form> 209 + </Form> 210 + ); 211 + }
+67
apps/dashboard/src/components/forms/private-location/sheet.tsx
··· 1 + "use client"; 2 + 3 + import { FormCard, FormCardGroup } from "@/components/forms/form-card"; 4 + import { 5 + FormSheet, 6 + FormSheetContent, 7 + FormSheetDescription, 8 + FormSheetFooter, 9 + FormSheetHeader, 10 + FormSheetTitle, 11 + FormSheetTrigger, 12 + } from "@/components/forms/form-sheet"; 13 + import { 14 + FormPrivateLocation, 15 + type FormValues, 16 + } from "@/components/forms/private-location/form"; 17 + import { Button } from "@/components/ui/button"; 18 + import { useState } from "react"; 19 + 20 + export function FormSheetPrivateLocation({ 21 + children, 22 + defaultValues, 23 + onSubmit, 24 + monitors, 25 + ...props 26 + }: Omit<React.ComponentProps<typeof FormSheetTrigger>, "onSubmit"> & { 27 + defaultValues?: FormValues; 28 + monitors: { id: number; name: string; url: string }[]; 29 + onSubmit: (values: FormValues) => Promise<void>; 30 + }) { 31 + const [open, setOpen] = useState(false); 32 + 33 + return ( 34 + <FormSheet open={open} onOpenChange={setOpen}> 35 + <FormSheetTrigger {...props} asChild> 36 + {children} 37 + </FormSheetTrigger> 38 + <FormSheetContent> 39 + <FormSheetHeader> 40 + <FormSheetTitle>Private Location</FormSheetTitle> 41 + <FormSheetDescription> 42 + Configure and update the private location. 43 + </FormSheetDescription> 44 + </FormSheetHeader> 45 + <FormCardGroup className="overflow-y-auto"> 46 + <FormCard className="overflow-auto rounded-none border-none"> 47 + <FormPrivateLocation 48 + monitors={monitors} 49 + onSubmit={async (values) => { 50 + await onSubmit(values); 51 + setOpen(false); 52 + }} 53 + defaultValues={defaultValues} 54 + id="private-location-form" 55 + className="my-4" 56 + /> 57 + </FormCard> 58 + </FormCardGroup> 59 + <FormSheetFooter> 60 + <Button type="submit" form="private-location-form"> 61 + Submit 62 + </Button> 63 + </FormSheetFooter> 64 + </FormSheetContent> 65 + </FormSheet> 66 + ); 67 + }
+1 -2
apps/dashboard/src/components/metric/global-uptime/section.tsx
··· 18 18 formatNumber, 19 19 formatPercentage, 20 20 } from "@/lib/formatter"; 21 - import type { monitorRegions } from "@openstatus/db/src/schema/constants"; 22 21 import { formatDistanceToNow } from "date-fns"; 23 22 24 23 type Metric = { ··· 40 39 monitorId: string; 41 40 jobType: "http" | "tcp"; 42 41 period: "1d" | "7d" | "14d"; 43 - regions: (typeof monitorRegions)[number][]; 42 + regions: string[] | undefined; 44 43 }) { 45 44 const trpc = useTRPC(); 46 45
+6
apps/dashboard/src/components/nav/app-sidebar.tsx
··· 4 4 Activity, 5 5 Bell, 6 6 Cog, 7 + Globe, 7 8 LayoutGrid, 8 9 PanelTop, 9 10 Terminal, ··· 68 69 name: "Settings", 69 70 url: "/settings/general", 70 71 icon: Cog, 72 + }, 73 + { 74 + name: "Private Locations", 75 + url: "/private-locations", 76 + icon: Globe, 71 77 }, 72 78 { 73 79 name: "CLI",
+170
apps/dashboard/src/components/ui/input-group.tsx
··· 1 + "use client"; 2 + 3 + import { type VariantProps, cva } from "class-variance-authority"; 4 + import type * as React from "react"; 5 + 6 + import { Button } from "@/components/ui/button"; 7 + import { Input } from "@/components/ui/input"; 8 + import { Textarea } from "@/components/ui/textarea"; 9 + import { cn } from "@/lib/utils"; 10 + 11 + function InputGroup({ className, ...props }: React.ComponentProps<"div">) { 12 + return ( 13 + <div 14 + data-slot="input-group" 15 + role="group" 16 + className={cn( 17 + "group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs outline-none transition-[color,box-shadow] dark:bg-input/30", 18 + "h-9 min-w-0 has-[>textarea]:h-auto", 19 + 20 + // Variants based on alignment. 21 + "has-[>[data-align=inline-start]]:[&>input]:pl-2", 22 + "has-[>[data-align=inline-end]]:[&>input]:pr-2", 23 + "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3", 24 + "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3", 25 + 26 + // Focus state. 27 + "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50", 28 + 29 + // Error state. 30 + "has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40", 31 + 32 + className, 33 + )} 34 + {...props} 35 + /> 36 + ); 37 + } 38 + 39 + const inputGroupAddonVariants = cva( 40 + "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50", 41 + { 42 + variants: { 43 + align: { 44 + "inline-start": 45 + "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]", 46 + "inline-end": 47 + "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]", 48 + "block-start": 49 + "order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5", 50 + "block-end": 51 + "order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5", 52 + }, 53 + }, 54 + defaultVariants: { 55 + align: "inline-start", 56 + }, 57 + }, 58 + ); 59 + 60 + function InputGroupAddon({ 61 + className, 62 + align = "inline-start", 63 + ...props 64 + }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) { 65 + return ( 66 + <div 67 + role="group" 68 + data-slot="input-group-addon" 69 + data-align={align} 70 + className={cn(inputGroupAddonVariants({ align }), className)} 71 + onClick={(e) => { 72 + if ((e.target as HTMLElement).closest("button")) { 73 + return; 74 + } 75 + e.currentTarget.parentElement?.querySelector("input")?.focus(); 76 + }} 77 + {...props} 78 + /> 79 + ); 80 + } 81 + 82 + const inputGroupButtonVariants = cva( 83 + "text-sm shadow-none flex gap-2 items-center", 84 + { 85 + variants: { 86 + size: { 87 + xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2", 88 + sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5", 89 + "icon-xs": 90 + "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0", 91 + "icon-sm": "size-8 p-0 has-[>svg]:p-0", 92 + }, 93 + }, 94 + defaultVariants: { 95 + size: "xs", 96 + }, 97 + }, 98 + ); 99 + 100 + function InputGroupButton({ 101 + className, 102 + type = "button", 103 + variant = "ghost", 104 + size = "xs", 105 + ...props 106 + }: Omit<React.ComponentProps<typeof Button>, "size"> & 107 + VariantProps<typeof inputGroupButtonVariants>) { 108 + return ( 109 + <Button 110 + type={type} 111 + data-size={size} 112 + variant={variant} 113 + className={cn(inputGroupButtonVariants({ size }), className)} 114 + {...props} 115 + /> 116 + ); 117 + } 118 + 119 + function InputGroupText({ className, ...props }: React.ComponentProps<"span">) { 120 + return ( 121 + <span 122 + className={cn( 123 + "flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none", 124 + className, 125 + )} 126 + {...props} 127 + /> 128 + ); 129 + } 130 + 131 + function InputGroupInput({ 132 + className, 133 + ...props 134 + }: React.ComponentProps<"input">) { 135 + return ( 136 + <Input 137 + data-slot="input-group-control" 138 + className={cn( 139 + "flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent", 140 + className, 141 + )} 142 + {...props} 143 + /> 144 + ); 145 + } 146 + 147 + function InputGroupTextarea({ 148 + className, 149 + ...props 150 + }: React.ComponentProps<"textarea">) { 151 + return ( 152 + <Textarea 153 + data-slot="input-group-control" 154 + className={cn( 155 + "flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent", 156 + className, 157 + )} 158 + {...props} 159 + /> 160 + ); 161 + } 162 + 163 + export { 164 + InputGroup, 165 + InputGroupAddon, 166 + InputGroupButton, 167 + InputGroupText, 168 + InputGroupInput, 169 + InputGroupTextarea, 170 + };
+57 -46
apps/dashboard/src/data/audit-logs.client.ts
··· 1 1 import { formatMilliseconds } from "@/lib/formatter"; 2 + import type { PrivateLocation } from "@openstatus/db/src/schema"; 3 + import { getRegionInfo } from "@openstatus/regions"; 2 4 import { 3 5 CircleAlert, 4 6 CircleCheck, ··· 40 42 }, 41 43 } as const; 42 44 43 - export const metadata = { 44 - region: { 45 - label: "Region", 46 - key: "region", 47 - unit: undefined, 48 - visible: () => true, 49 - format: (value) => String(value), 50 - }, 51 - cronTimestamp: { 52 - label: "Timestamp", 53 - key: "timestamp", 54 - unit: undefined, 55 - visible: () => false, 56 - format: (value) => String(value), 57 - }, 58 - statusCode: { 59 - label: "Status Code", 60 - key: "status", 61 - unit: undefined, 62 - visible: (_value) => typeof _value === "number" && _value !== -1, 63 - format: (value) => String(value), 64 - }, 65 - latency: { 66 - label: "Latency", 67 - key: "latency", 68 - unit: "ms", 69 - visible: () => true, 70 - format: (value) => formatMilliseconds(Number(value)), 71 - }, 72 - provider: { 73 - label: "Provider", 74 - key: "provider", 75 - unit: undefined, 76 - visible: () => true, 77 - format: (value) => String(value), 78 - }, 79 - } as const satisfies Record< 80 - string, 81 - { 82 - label: string; 83 - key: string; 84 - unit?: string | undefined; 85 - visible: (value: string | number) => boolean; 86 - format: (value: string | number) => string; 87 - } 88 - >; 45 + export const getMetadata = (privateLocations?: PrivateLocation[]) => { 46 + return { 47 + region: { 48 + label: "Region", 49 + key: "region", 50 + unit: undefined, 51 + visible: () => true, 52 + format: (value) => { 53 + const regionInfo = getRegionInfo(`${value}`, { 54 + location: privateLocations?.find( 55 + (location) => String(location.id) === String(value), 56 + )?.name, 57 + }); 58 + return `${regionInfo.location} (${regionInfo.provider})`; 59 + }, 60 + }, 61 + cronTimestamp: { 62 + label: "Timestamp", 63 + key: "timestamp", 64 + unit: undefined, 65 + visible: () => false, 66 + format: (value) => String(value), 67 + }, 68 + statusCode: { 69 + label: "Status Code", 70 + key: "status", 71 + unit: undefined, 72 + visible: (_value) => typeof _value === "number" && _value !== -1, 73 + format: (value) => String(value), 74 + }, 75 + latency: { 76 + label: "Latency", 77 + key: "latency", 78 + unit: "ms", 79 + visible: () => true, 80 + format: (value) => formatMilliseconds(Number(value)), 81 + }, 82 + provider: { 83 + label: "Provider", 84 + key: "provider", 85 + unit: undefined, 86 + visible: () => true, 87 + format: (value) => String(value), 88 + }, 89 + } as const satisfies Record< 90 + string, 91 + { 92 + label: string; 93 + key: string; 94 + unit?: string | undefined; 95 + visible: (value: string | number) => boolean; 96 + format: (value: string | number) => string; 97 + } 98 + >; 99 + };
+6 -7
apps/dashboard/src/data/metrics.client.ts
··· 5 5 import type { RouterOutputs } from "@openstatus/api"; 6 6 import { monitorRegions } from "@openstatus/db/src/schema/constants"; 7 7 import type { RegionMetric } from "./region-metrics"; 8 - import type { Region } from "./regions"; 9 8 10 9 export const STATUS = ["success", "error", "degraded"] as const; 11 10 export const PERIODS = ["1d", "7d", "14d"] as const; ··· 109 108 */ 110 109 export function mapRegionMetrics( 111 110 timeline: RouterOutputs["tinybird"]["metricsRegions"] | undefined, 112 - regions: Region[], 111 + regions: string[], 113 112 percentile: (typeof PERCENTILES)[number], 114 113 ): RegionMetric[] { 115 114 if (!timeline) ··· 125 124 timestamp: number; 126 125 [key: string]: number; 127 126 }[], 128 - })) ?? []) as RegionMetric[]; 127 + })) ?? []) satisfies RegionMetric[]; 129 128 130 129 type TimelineRow = (typeof timeline.data)[number]; 131 130 132 131 const map = new Map< 133 - Region, 132 + string, 134 133 { 135 - region: Region; 134 + region: string; 136 135 p50: number; 137 136 p90: number; 138 137 p99: number; ··· 145 144 >(); 146 145 147 146 (timeline.data as TimelineRow[]) 148 - .filter((row) => regions.includes(row.region as Region)) 147 + .filter((row) => regions.includes(row.region)) 149 148 .sort((a, b) => a.region.localeCompare(b.region)) 150 149 .forEach((row) => { 151 - const region = row.region as Region; 150 + const region = row.region; 152 151 const entry = map.get(region) ?? { 153 152 region, 154 153 p50: 0,
+3 -5
apps/dashboard/src/data/region-metrics.ts
··· 1 - import type { Region } from "./regions"; 2 - 3 1 export const regionMetrics = [ 4 2 { 5 - region: "ams" as const satisfies Region, 3 + region: "ams", 6 4 p50: 100, 7 5 p90: 150, 8 6 p99: 200, ··· 13 11 }[], 14 12 }, 15 13 { 16 - region: "fra" as const satisfies Region, 14 + region: "fra", 17 15 p50: 110, 18 16 p90: 155, 19 17 p99: 220, ··· 24 22 }[], 25 23 }, 26 24 { 27 - region: "gru" as const satisfies Region, 25 + region: "gru", 28 26 p50: 120, 29 27 p90: 160, 30 28 p99: 230,
+7
apps/dashboard/src/data/regions.ts
··· 376 376 "railway_us-east4-eqdc4a": "hsl(0 0% 45.1%)", 377 377 "railway_us-west2": "hsl(0 0% 45.1%)", 378 378 } satisfies Record<Region, string>; 379 + 380 + export function getRegionColor(region: string) { 381 + if (region in regionColors) { 382 + return regionColors[region as keyof typeof regionColors]; 383 + } 384 + return "hsl(0 0% 45.1%)"; 385 + }
+4
apps/docs/astro.config.mjs
··· 97 97 slug: "tutorial/how-to-configure-status-page", 98 98 }, 99 99 { 100 + label: "How to create a private location (beta)", 101 + slug: "tutorial/how-to-create-private-location", 102 + }, 103 + { 100 104 label: "Get Started with OpenStatus CLI", 101 105 slug: "tutorial/get-started-with-openstatus-cli", 102 106 },
+24
apps/docs/src/content/docs/tutorial/how-to-create-private-location.mdx
··· 1 + --- 2 + title: How to create a private location (beta) 3 + --- 4 + 5 + ### What are private locations? 6 + 7 + **Private locations** allow you to monitor internal applications and network resources from within your own infrastructure, rather than solely relying on public cloud-based servers. 8 + 9 + By deploying our **monitoring probes** (as containers) on your own machines inside your firewall, you can run tests against private endpoints like internal APIs or servers and get detailed *timing phases and latency information* from your specific deployment location. This enables you to monitor not just *datacenter to datacenter* (when your web services are deployed on platforms like Cloudflare, AWS, or GCP), but also from **private locations** like on-prem servers or even Raspberry Pi devices. 10 + 11 + Private locations are crucial for verifying *performance and availability* of internal systems, integrating with your development pipelines, and ensuring **security** without exposing sensitive services to the open internet. 12 + 13 + 14 + ### How to use private locations? 15 + 16 + 1. create a private location and access a generated `token` 17 + 2. install the docker image on your server 18 + 3. in the private location settings, choose which monitors to track (or vice versa, configure it in your monitor settings) 19 + 4. done (within a couple of minutes, we'll check the everything) 20 + 21 + ### What is missing? 22 + 23 + - Incidents will not be created (yet) if you run your monitors via privat regions. That means you should still use the openstatus regions to get alerts if a monitor is down. 24 + - Public monitors on status pages do not support private regions (yet).
+47
apps/private-location/.air.toml
··· 1 + root = "." 2 + testdata_dir = "testdata" 3 + tmp_dir = "tmp" 4 + 5 + [build] 6 + args_bin = [] 7 + bin = "./tmp/main" 8 + cmd = "go build -o ./tmp/main ./cmd/server/main.go" 9 + delay = 1000 10 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] 11 + exclude_file = [] 12 + exclude_regex = ["_test.go"] 13 + exclude_unchanged = false 14 + follow_symlink = false 15 + full_bin = "" 16 + include_dir = [] 17 + include_ext = ["go", "tpl", "tmpl", "html"] 18 + include_file = [] 19 + kill_delay = "0s" 20 + log = "build-errors.log" 21 + poll = false 22 + poll_interval = 0 23 + post_cmd = [] 24 + pre_cmd = [] 25 + rerun = false 26 + rerun_delay = 500 27 + send_interrupt = false 28 + stop_on_error = false 29 + 30 + 31 + [color] 32 + app = "" 33 + build = "yellow" 34 + main = "magenta" 35 + runner = "green" 36 + watcher = "cyan" 37 + 38 + [log] 39 + main_only = false 40 + time = false 41 + 42 + [misc] 43 + clean_on_exit = false 44 + 45 + [screen] 46 + clear_on_rebuild = false 47 + keep_scroll = true
+1
apps/private-location/.gitignore
··· 1 + /tmp
+4
apps/private-location/.golangci.yml
··· 1 + version: "2" 2 + 3 + linters: 4 + default: fast
+31
apps/private-location/Dockerfile
··· 1 + FROM golang:1.25-alpine as builder 2 + 3 + WORKDIR /go/src/app 4 + 5 + RUN apk add --no-cache tzdata 6 + ENV TZ=UTC 7 + 8 + ENV CGO_ENABLED=0 9 + ENV GOOS=linux 10 + ENV GOARCH=amd64 11 + 12 + COPY go.* . 13 + RUN go mod download 14 + 15 + COPY . . 16 + RUN go build -trimpath -ldflags "-s -w" -o private-location ./cmd/server 17 + 18 + FROM scratch 19 + 20 + WORKDIR /opt/bin 21 + 22 + COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 23 + COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo 24 + 25 + COPY --from=builder /go/src/app/private-location /opt/bin/private-location 26 + 27 + ENV TZ=UTC 28 + ENV USER=1000 29 + ENV GIN_MODE=release 30 + 31 + CMD [ "/opt/bin/private-location" ]
+4
apps/private-location/README.md
··· 1 + # Private Location Orchestrator 2 + 3 + 4 + A server that allows private regions to register and ingest data from them.
+58
apps/private-location/cmd/server/main.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "log" 7 + "net/http" 8 + "os/signal" 9 + "syscall" 10 + "time" 11 + 12 + "github.com/openstatushq/openstatus/apps/private-location/internal/server" 13 + ) 14 + 15 + func gracefulShutdown(apiServer *http.Server, done chan bool) { 16 + // Create context that listens for the interrupt signal from the OS. 17 + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) 18 + defer stop() 19 + 20 + // Listen for the interrupt signal. 21 + <-ctx.Done() 22 + 23 + log.Println("shutting down gracefully, press Ctrl+C again to force") 24 + stop() // Allow Ctrl+C to force shutdown 25 + 26 + // The context is used to inform the server it has 5 seconds to finish 27 + // the request it is currently handling 28 + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 29 + defer cancel() 30 + if err := apiServer.Shutdown(ctx); err != nil { 31 + log.Printf("Server forced to shutdown with error: %v", err) 32 + } 33 + 34 + log.Println("Server exiting") 35 + 36 + // Notify the main goroutine that the shutdown is complete 37 + done <- true 38 + } 39 + 40 + func main() { 41 + 42 + server := server.NewServer() 43 + 44 + // Create a done channel to signal when the shutdown is complete 45 + done := make(chan bool, 1) 46 + 47 + // Run graceful shutdown in a separate goroutine 48 + go gracefulShutdown(server, done) 49 + 50 + err := server.ListenAndServe() 51 + if err != nil && err != http.ErrServerClosed { 52 + panic(fmt.Sprintf("http server error: %s", err)) 53 + } 54 + 55 + // Wait for the graceful shutdown to complete 56 + <-done 57 + log.Println("Graceful shutdown complete.") 58 + }
+42
apps/private-location/fly.toml
··· 1 + # fly.toml app configuration file generated for openstatus-checker on 2023-11-30T20:23:20+01:00 2 + # 3 + # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 + # 5 + 6 + app = "openstatus-private-location" 7 + primary_region = "ams" 8 + 9 + [build] 10 + dockerfile = "./Dockerfile" 11 + 12 + [deploy] 13 + strategy = "canary" 14 + 15 + 16 + [env] 17 + PORT = "8080" 18 + 19 + [http_service] 20 + internal_port = 8080 21 + force_https = true 22 + auto_stop_machines = "off" 23 + auto_start_machines = false 24 + processes = ["app"] 25 + 26 + [[vm]] 27 + cpu_kind = "shared" 28 + cpus = 1 29 + memory_mb = 256 30 + 31 + 32 + [[http_service.checks]] 33 + grace_period = "10s" 34 + interval = "15s" 35 + method = "GET" 36 + timeout = "5s" 37 + path = "/health" 38 + 39 + [http_service.concurrency] 40 + type = "requests" 41 + hard_limit = 1000 42 + soft_limit = 500
+30
apps/private-location/go.mod
··· 1 + module github.com/openstatushq/openstatus/apps/private-location 2 + 3 + go 1.25.2 4 + 5 + require ( 6 + connectrpc.com/connect v1.19.1 7 + github.com/go-chi/chi/v5 v5.2.3 8 + github.com/go-chi/render v1.0.3 9 + github.com/jmoiron/sqlx v1.4.0 10 + github.com/joho/godotenv v1.5.1 11 + github.com/mattn/go-sqlite3 v1.14.22 12 + github.com/openstatushq/openstatus/apps/checker v0.0.0-20251012205355-e366f661c23e 13 + github.com/rs/zerolog v1.34.0 14 + github.com/stretchr/testify v1.11.1 15 + github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d 16 + google.golang.org/protobuf v1.36.10 17 + ) 18 + 19 + require ( 20 + github.com/ajg/form v1.5.1 // indirect 21 + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 22 + github.com/coder/websocket v1.8.12 // indirect 23 + github.com/davecgh/go-spew v1.1.1 // indirect 24 + github.com/mattn/go-colorable v0.1.13 // indirect 25 + github.com/mattn/go-isatty v0.0.20 // indirect 26 + github.com/pmezard/go-difflib v1.0.0 // indirect 27 + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect 28 + golang.org/x/sys v0.29.0 // indirect 29 + gopkg.in/yaml.v3 v3.0.1 // indirect 30 + )
+61
apps/private-location/go.sum
··· 1 + connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= 2 + connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= 3 + filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 4 + filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 5 + github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= 6 + github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= 7 + github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= 8 + github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= 9 + github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= 10 + github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= 11 + github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 12 + github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 + github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 + github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= 15 + github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 16 + github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= 17 + github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= 18 + github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= 19 + github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= 20 + github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 21 + github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 22 + github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 23 + github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= 24 + github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= 25 + github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 26 + github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 27 + github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 28 + github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 29 + github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 30 + github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 31 + github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 32 + github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 33 + github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 34 + github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 35 + github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 36 + github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 37 + github.com/openstatushq/openstatus/apps/checker v0.0.0-20251012205355-e366f661c23e h1:54C0zQNHzGszQseO2QcNzM8fL7vyAYk03pRtrJIyoV0= 38 + github.com/openstatushq/openstatus/apps/checker v0.0.0-20251012205355-e366f661c23e/go.mod h1:R84xAJYFys7XOZTDk/AyjJi4Ga9ovtLhJsfTLgTsYKg= 39 + github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 40 + github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 41 + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 42 + github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= 43 + github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= 44 + github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= 45 + github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 46 + github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 47 + github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU= 48 + github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s= 49 + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= 50 + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= 51 + golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 52 + golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 53 + golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 54 + golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 55 + golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 56 + google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= 57 + google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 58 + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 59 + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 60 + gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 61 + gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+41
apps/private-location/internal/database/database.go
··· 1 + package database 2 + 3 + import ( 4 + // "database/sql" 5 + "database/sql" 6 + "fmt" 7 + 8 + // "log" 9 + "os" 10 + 11 + "github.com/jmoiron/sqlx" 12 + _ "github.com/joho/godotenv/autoload" 13 + // "github.com/tursodatabase/go-libsql" 14 + _ "github.com/tursodatabase/libsql-client-go/libsql" 15 + ) 16 + 17 + var DB *sqlx.DB 18 + 19 + var ( 20 + dbUrl = os.Getenv("DB_URL") 21 + authToken = os.Getenv("DB_AUTH_TOKEN") 22 + dbInstance *sqlx.DB 23 + ) 24 + 25 + func New() *sqlx.DB { 26 + // Reuse Connection 27 + if dbInstance != nil { 28 + return dbInstance 29 + } 30 + 31 + url := fmt.Sprintf("%s?auth_token=%s", dbUrl, authToken) 32 + c, err := sql.Open("libsql", url) 33 + if err != nil { 34 + fmt.Fprintf(os.Stderr, "failed to open db %s: %s", url, err) 35 + os.Exit(1) 36 + } 37 + 38 + db := sqlx.NewDb(c, "sqlite3") 39 + 40 + return db 41 + }
+44
apps/private-location/internal/database/models.go
··· 1 + package database 2 + 3 + import "database/sql" 4 + 5 + // JobType represents the type of job for a monitor. 6 + type JobType string 7 + 8 + const ( 9 + JobTypeTCP JobType = "tcp" 10 + JobTypeUDP JobType = "udp" 11 + JobTypeHTTP JobType = "http" 12 + JobTypeDNS JobType = "dns" 13 + ) 14 + 15 + type Monitor struct { 16 + ID int `db:"id"` 17 + Active bool `db:"active"` 18 + WorkspaceID int `db:"workspace_id"` 19 + JobType JobType `db:"job_type"` 20 + Periodicity string `db:"periodicity"` 21 + URL string `db:"url"` 22 + Headers string `db:"headers"` 23 + Body string `db:"body"` 24 + Method string `db:"method"` 25 + Timeout int64 `db:"timeout"` 26 + DegradedAfter sql.NullInt64 `db:"degraded_after"` 27 + Assertions sql.NullString `db:"assertions"` 28 + Retry int `db:"retry"` 29 + FollowRedirects bool `db:"follow_redirects"` 30 + OtelEndpoint sql.NullString `db:"otel_endpoint" json:"-"` 31 + OtelHeaders sql.NullString `db:"otel_headers" json:"-"` 32 + Name string `db:"name" json:"-"` 33 + Description string `db:"description" json:"-"` 34 + CreatedAt int `db:"created_at" json:"-"` 35 + UpdatedAt int `db:"updated_at" json:"-"` 36 + DeletedAt sql.NullInt64 `db:"deleted_at" json:"-"` 37 + Regions string `db:"regions" json:"-"` 38 + Status string `db:"status" json:"-"` 39 + Public bool `db:"public" json:"-"` 40 + } 41 + 42 + type PrivateLocation struct { 43 + ID int `db:"id"` 44 + }
+76
apps/private-location/internal/models/assertions.go
··· 1 + package models 2 + 3 + import "encoding/json" 4 + 5 + type AssertionType string 6 + 7 + const ( 8 + AssertionHeader AssertionType = "header" 9 + AssertionTextBody AssertionType = "textBody" 10 + AssertionStatus AssertionType = "status" 11 + AssertionJsonBody AssertionType = "jsonBody" 12 + ) 13 + 14 + type StringComparator string 15 + 16 + func (c StringComparator) String() string { 17 + return string(c) 18 + } 19 + 20 + func (c NumberComparator) String() string { 21 + return string(c) 22 + } 23 + 24 + const ( 25 + StringContains StringComparator = "contains" 26 + StringNotContains StringComparator = "not_contains" 27 + StringEquals StringComparator = "eq" 28 + StringNotEquals StringComparator = "not_eq" 29 + StringEmpty StringComparator = "empty" 30 + StringNotEmpty StringComparator = "not_empty" 31 + StringGreaterThan StringComparator = "gt" 32 + StringGreaterThanEqual StringComparator = "gte" 33 + StringLowerThan StringComparator = "lt" 34 + StringLowerThanEqual StringComparator = "lte" 35 + ) 36 + 37 + type NumberComparator string 38 + 39 + const ( 40 + NumberEquals NumberComparator = "eq" 41 + NumberNotEquals NumberComparator = "not_eq" 42 + NumberGreaterThan NumberComparator = "gt" 43 + NumberGreaterThanEqual NumberComparator = "gte" 44 + NumberLowerThan NumberComparator = "lt" 45 + NumberLowerThanEqual NumberComparator = "lte" 46 + ) 47 + 48 + type Assertion struct { 49 + AssertionType AssertionType `json:"type"` 50 + Comparator json.RawMessage `json:"compare"` 51 + RawTarget json.RawMessage `json:"target"` 52 + } 53 + 54 + type StatusTarget struct { 55 + AssertionType AssertionType `json:"type"` 56 + Comparator NumberComparator `json:"compare"` 57 + Target int64 `json:"target"` 58 + } 59 + 60 + type HeaderTarget struct { 61 + AssertionType AssertionType `json:"type"` 62 + Comparator StringComparator `json:"compare"` 63 + Target string `json:"target"` 64 + Key string `json:"key"` 65 + } 66 + 67 + type StringTargetType struct { 68 + Comparator StringComparator `json:"compare"` 69 + Target string `json:"target"` 70 + } 71 + 72 + type BodyString struct { 73 + AssertionType AssertionType `json:"type"` 74 + Comparator StringComparator `json:"compare"` 75 + Target string `json:"target"` 76 + }
+465
apps/private-location/internal/server/db_testdata
··· 1 + DROP TABLE IF EXISTS "__drizzle_migrations"; 2 + CREATE TABLE "__drizzle_migrations" ( 3 + id SERIAL PRIMARY KEY, 4 + hash text NOT NULL, 5 + created_at numeric 6 + ); 7 + 8 + DROP TABLE IF EXISTS "page"; 9 + CREATE TABLE `page` ( 10 + `id` integer PRIMARY KEY NOT NULL, 11 + `workspace_id` integer NOT NULL, 12 + `title` text NOT NULL, 13 + `description` text NOT NULL, 14 + `icon` text(256), 15 + `slug` text(256) NOT NULL, 16 + `custom_domain` text(256) NOT NULL, 17 + `published` integer DEFAULT false, 18 + "created_at" integer DEFAULT (strftime('%s', 'now')), `updated_at` integer, `password` text(256), `password_protected` integer DEFAULT false, `show_monitor_values` integer DEFAULT true, `force_theme` text DEFAULT 'system' NOT NULL, `legacy_page` integer DEFAULT true NOT NULL, `configuration` text, `homepage_url` text(256), `contact_url` text(256), 19 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE cascade 20 + ); 21 + 22 + DROP TABLE IF EXISTS "monitors_to_pages"; 23 + CREATE TABLE `monitors_to_pages` ( 24 + `monitor_id` integer NOT NULL, 25 + `page_id` integer NOT NULL, `created_at` integer DEFAULT (strftime('%s', 'now')), `order` integer DEFAULT 0, 26 + PRIMARY KEY(`monitor_id`, `page_id`), 27 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE cascade, 28 + FOREIGN KEY (`page_id`) REFERENCES `page`(`id`) ON UPDATE no action ON DELETE cascade 29 + ); 30 + 31 + DROP TABLE IF EXISTS "user"; 32 + CREATE TABLE `user` ( 33 + `id` integer PRIMARY KEY NOT NULL, 34 + `tenant_id` text(256), 35 + "created_at" integer DEFAULT (strftime('%s', 'now')) 36 + , `first_name` text DEFAULT '', `last_name` text DEFAULT '', `email` text DEFAULT '', `photo_url` text DEFAULT '', `updated_at` integer, `name` text, `emailVerified` integer); 37 + 38 + DROP TABLE IF EXISTS "users_to_workspaces"; 39 + CREATE TABLE `users_to_workspaces` ( 40 + `user_id` integer NOT NULL, 41 + `workspace_id` integer NOT NULL, `role` text DEFAULT 'owner' NOT NULL, `created_at` integer DEFAULT (strftime('%s', 'now')), 42 + PRIMARY KEY(`user_id`, `workspace_id`), 43 + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, 44 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action 45 + ); 46 + 47 + DROP TABLE IF EXISTS "workspace"; 48 + CREATE TABLE `workspace` ( 49 + `id` integer PRIMARY KEY NOT NULL, 50 + `slug` text NOT NULL, 51 + `stripe_id` text(256), 52 + `name` text, 53 + "created_at" integer DEFAULT (strftime('%s', 'now')) 54 + , `updated_at` integer, `subscription_id` text, `plan` text(3), `ends_at` integer, `paid_until` integer, `dsn` text, `limits` text DEFAULT '{}' NOT NULL); 55 + 56 + DROP TABLE IF EXISTS "status_report_update"; 57 + CREATE TABLE "status_report_update" ( 58 + `id` integer PRIMARY KEY NOT NULL, 59 + "status" text NOT NULL, 60 + `date` integer NOT NULL, 61 + `message` text NOT NULL, 62 + `created_at` integer DEFAULT (strftime('%s', 'now')), 63 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 64 + "status_report_id" integer NOT NULL, 65 + FOREIGN KEY ("status_report_id") REFERENCES "status_report"(`id`) ON UPDATE no action ON DELETE cascade 66 + ); 67 + 68 + DROP TABLE IF EXISTS "status_report_to_monitors"; 69 + CREATE TABLE "status_report_to_monitors" ( 70 + `monitor_id` integer NOT NULL, 71 + "status_report_id" integer NOT NULL, `created_at` integer DEFAULT (strftime('%s', 'now')), 72 + PRIMARY KEY("status_report_id", `monitor_id`), 73 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE cascade, 74 + FOREIGN KEY ("status_report_id") REFERENCES "status_report"(`id`) ON UPDATE no action ON DELETE cascade 75 + ); 76 + 77 + DROP TABLE IF EXISTS "monitor"; 78 + CREATE TABLE "monitor" ( 79 + `id` integer PRIMARY KEY NOT NULL, 80 + `job_type` text(3) DEFAULT 'other' NOT NULL, 81 + `periodicity` text(6) DEFAULT 'other' NOT NULL, 82 + `active` integer DEFAULT false, 83 + `url` text(512) NOT NULL, 84 + `name` text(256) DEFAULT '' NOT NULL, 85 + `description` text DEFAULT '' NOT NULL, 86 + `workspace_id` integer, 87 + `headers` text DEFAULT '', 88 + `body` text DEFAULT '', 89 + `method` text(5) DEFAULT 'GET', 90 + `created_at` integer DEFAULT (strftime('%s', 'now')), `regions` text DEFAULT '' NOT NULL, `updated_at` integer, `status` text(2) DEFAULT 'active' NOT NULL, `assertions` text, `deleted_at` integer, `public` integer DEFAULT false, `timeout` integer DEFAULT 45000 NOT NULL, `degraded_after` integer, `otel_endpoint` text, `otel_headers` text, `retry` integer DEFAULT 3, `follow_redirects` integer DEFAULT true, 91 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action 92 + ); 93 + 94 + DROP TABLE IF EXISTS "integration"; 95 + CREATE TABLE `integration` ( 96 + `id` integer PRIMARY KEY NOT NULL, 97 + `name` text(256) NOT NULL, 98 + `workspace_id` integer, 99 + `credential` text, 100 + `external_id` text NOT NULL, 101 + `created_at` integer DEFAULT (strftime('%s', 'now')), 102 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 103 + `data` text NOT NULL, 104 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action 105 + ); 106 + 107 + DROP TABLE IF EXISTS "notification"; 108 + CREATE TABLE `notification` ( 109 + `id` integer PRIMARY KEY NOT NULL, 110 + `name` text NOT NULL, 111 + `provider` text NOT NULL, 112 + `data` text DEFAULT '{}', 113 + `workspace_id` integer, 114 + `created_at` integer DEFAULT (strftime('%s', 'now')), 115 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 116 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action 117 + ); 118 + 119 + DROP TABLE IF EXISTS "notifications_to_monitors"; 120 + CREATE TABLE `notifications_to_monitors` ( 121 + `monitor_id` integer NOT NULL, 122 + `notification_id` integer NOT NULL, `created_at` integer DEFAULT (strftime('%s', 'now')), 123 + PRIMARY KEY(`monitor_id`, `notification_id`), 124 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE cascade, 125 + FOREIGN KEY (`notification_id`) REFERENCES `notification`(`id`) ON UPDATE no action ON DELETE cascade 126 + ); 127 + 128 + DROP TABLE IF EXISTS "status_reports_to_pages"; 129 + CREATE TABLE "status_reports_to_pages" ( 130 + `page_id` integer NOT NULL, 131 + "status_report_id" integer NOT NULL, `created_at` integer DEFAULT (strftime('%s', 'now')), 132 + PRIMARY KEY("status_report_id", `page_id`), 133 + FOREIGN KEY (`page_id`) REFERENCES `page`(`id`) ON UPDATE no action ON DELETE cascade, 134 + FOREIGN KEY ("status_report_id") REFERENCES "status_report"(`id`) ON UPDATE no action ON DELETE cascade 135 + ); 136 + 137 + DROP TABLE IF EXISTS "monitor_status"; 138 + CREATE TABLE `monitor_status` ( 139 + `monitor_id` integer NOT NULL, 140 + `region` text DEFAULT '' NOT NULL, 141 + `status` text DEFAULT 'active' NOT NULL, 142 + `created_at` integer DEFAULT (strftime('%s', 'now')), 143 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 144 + PRIMARY KEY(`monitor_id`, `region`), 145 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE cascade 146 + ); 147 + 148 + DROP TABLE IF EXISTS "invitation"; 149 + CREATE TABLE `invitation` ( 150 + `id` integer PRIMARY KEY NOT NULL, 151 + `email` text NOT NULL, 152 + `role` text DEFAULT 'member' NOT NULL, 153 + `workspace_id` integer NOT NULL, 154 + `token` text NOT NULL, 155 + `expires_at` integer NOT NULL, 156 + `created_at` integer DEFAULT (strftime('%s', 'now')), 157 + `accepted_at` integer 158 + ); 159 + 160 + DROP TABLE IF EXISTS "incident"; 161 + CREATE TABLE "incident" ( 162 + `id` integer PRIMARY KEY NOT NULL, 163 + `title` text DEFAULT '' NOT NULL, 164 + `summary` text DEFAULT '' NOT NULL, 165 + `status` text DEFAULT 'triage' NOT NULL, 166 + `monitor_id` integer, 167 + `workspace_id` integer, 168 + `started_at` integer DEFAULT (strftime('%s', 'now')) NOT NULL, 169 + `acknowledged_at` integer, 170 + `acknowledged_by` integer, 171 + `resolved_at` integer, 172 + `resolved_by` integer, 173 + `created_at` integer DEFAULT (strftime('%s', 'now')), 174 + `updated_at` integer DEFAULT (strftime('%s', 'now')), `auto_resolved` integer DEFAULT false, `incident_screenshot_url` text, `recovery_screenshot_url` text, 175 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE set default, 176 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action, 177 + FOREIGN KEY (`acknowledged_by`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, 178 + FOREIGN KEY (`resolved_by`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action 179 + ); 180 + 181 + DROP TABLE IF EXISTS "monitor_tag"; 182 + CREATE TABLE `monitor_tag` ( 183 + `id` integer PRIMARY KEY NOT NULL, 184 + `workspace_id` integer NOT NULL, 185 + `name` text NOT NULL, 186 + `color` text NOT NULL, 187 + `created_at` integer DEFAULT (strftime('%s', 'now')), 188 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 189 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE cascade 190 + ); 191 + 192 + DROP TABLE IF EXISTS "monitor_tag_to_monitor"; 193 + CREATE TABLE `monitor_tag_to_monitor` ( 194 + `monitor_id` integer NOT NULL, 195 + `monitor_tag_id` integer NOT NULL, 196 + `created_at` integer DEFAULT (strftime('%s', 'now')), 197 + PRIMARY KEY(`monitor_id`, `monitor_tag_id`), 198 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE cascade, 199 + FOREIGN KEY (`monitor_tag_id`) REFERENCES `monitor_tag`(`id`) ON UPDATE no action ON DELETE cascade 200 + ); 201 + 202 + DROP TABLE IF EXISTS "account"; 203 + CREATE TABLE `account` ( 204 + `user_id` integer NOT NULL, 205 + `type` text NOT NULL, 206 + `provider` text NOT NULL, 207 + `provider_account_id` text NOT NULL, 208 + `refresh_token` text, 209 + `access_token` text, 210 + `expires_at` integer, 211 + `token_type` text, 212 + `scope` text, 213 + `id_token` text, 214 + `session_state` text, 215 + PRIMARY KEY(`provider`, `provider_account_id`), 216 + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade 217 + ); 218 + 219 + DROP TABLE IF EXISTS "session"; 220 + CREATE TABLE `session` ( 221 + `session_token` text PRIMARY KEY NOT NULL, 222 + `user_id` integer NOT NULL, 223 + `expires` integer NOT NULL, 224 + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade 225 + ); 226 + 227 + DROP TABLE IF EXISTS "verification_token"; 228 + CREATE TABLE `verification_token` ( 229 + `identifier` text NOT NULL, 230 + `token` text NOT NULL, 231 + `expires` integer NOT NULL, 232 + PRIMARY KEY(`identifier`, `token`) 233 + ); 234 + 235 + DROP TABLE IF EXISTS "application"; 236 + CREATE TABLE `application` ( 237 + `id` integer PRIMARY KEY NOT NULL, 238 + `name` text, 239 + `dsn` text, 240 + `workspace_id` integer, 241 + `created_at` integer DEFAULT (strftime('%s', 'now')), 242 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 243 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action 244 + ); 245 + 246 + DROP TABLE IF EXISTS "maintenance_to_monitor"; 247 + CREATE TABLE `maintenance_to_monitor` ( 248 + `monitor_id` integer NOT NULL, 249 + `maintenance_id` integer NOT NULL, 250 + `created_at` integer DEFAULT (strftime('%s', 'now')), 251 + PRIMARY KEY(`maintenance_id`, `monitor_id`), 252 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE cascade, 253 + FOREIGN KEY (`maintenance_id`) REFERENCES `maintenance`(`id`) ON UPDATE no action ON DELETE cascade 254 + ); 255 + 256 + DROP TABLE IF EXISTS "check"; 257 + CREATE TABLE `check` ( 258 + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, 259 + `regions` text DEFAULT '' NOT NULL, 260 + `url` text(4096) NOT NULL, 261 + `headers` text DEFAULT '', 262 + `body` text DEFAULT '', 263 + `method` text DEFAULT 'GET', 264 + `count_requests` integer DEFAULT 1, 265 + `workspace_id` integer, 266 + `created_at` integer DEFAULT (strftime('%s', 'now')), 267 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action 268 + ); 269 + 270 + 271 + 272 + DROP TABLE IF EXISTS "monitor_run"; 273 + CREATE TABLE `monitor_run` ( 274 + `id` integer PRIMARY KEY NOT NULL, 275 + `workspace_id` integer, 276 + `monitor_id` integer, 277 + `runned_at` integer, 278 + `created_at` integer DEFAULT (strftime('%s', 'now')), 279 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action, 280 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE no action 281 + ); 282 + 283 + DROP TABLE IF EXISTS "page_subscriber"; 284 + CREATE TABLE "page_subscriber" ( 285 + `id` integer PRIMARY KEY NOT NULL, 286 + `email` text NOT NULL, 287 + `page_id` integer NOT NULL, 288 + `token` text, 289 + `accepted_at` integer, 290 + `expires_at` integer, 291 + `created_at` integer DEFAULT (strftime('%s', 'now')), 292 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 293 + FOREIGN KEY (`page_id`) REFERENCES `page`(`id`) ON UPDATE no action ON DELETE cascade 294 + ); 295 + 296 + DROP TABLE IF EXISTS "status_report"; 297 + CREATE TABLE "status_report" ( 298 + `id` integer PRIMARY KEY NOT NULL, 299 + `status` text NOT NULL, 300 + `title` text(256) NOT NULL, 301 + `workspace_id` integer, 302 + `page_id` integer, 303 + `created_at` integer DEFAULT (strftime('%s', 'now')), 304 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 305 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action, 306 + FOREIGN KEY (`page_id`) REFERENCES `page`(`id`) ON UPDATE no action ON DELETE cascade 307 + ); 308 + 309 + DROP TABLE IF EXISTS "notification_trigger"; 310 + CREATE TABLE "notification_trigger" ( 311 + `id` integer PRIMARY KEY NOT NULL, 312 + `monitor_id` integer, 313 + `notification_id` integer, 314 + `cron_timestamp` integer NOT NULL, 315 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE cascade, 316 + FOREIGN KEY (`notification_id`) REFERENCES `notification`(`id`) ON UPDATE no action ON DELETE cascade 317 + ); 318 + 319 + DROP TABLE IF EXISTS "maintenance"; 320 + CREATE TABLE "maintenance" ( 321 + `id` integer PRIMARY KEY NOT NULL, 322 + `title` text(256) NOT NULL, 323 + `message` text NOT NULL, 324 + `from` integer NOT NULL, 325 + `to` integer NOT NULL, 326 + `workspace_id` integer, 327 + `page_id` integer, 328 + `created_at` integer DEFAULT (strftime('%s', 'now')), 329 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 330 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action, 331 + FOREIGN KEY (`page_id`) REFERENCES `page`(`id`) ON UPDATE no action ON DELETE cascade 332 + ); 333 + 334 + DROP TABLE IF EXISTS "private_location"; 335 + CREATE TABLE `private_location` ( 336 + `id` integer PRIMARY KEY NOT NULL, 337 + `name` text NOT NULL, 338 + `token` text NOT NULL, 339 + `last_seen_at` integer, 340 + `workspace_id` integer, 341 + `created_at` integer DEFAULT (strftime('%s', 'now')), 342 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 343 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action 344 + ); 345 + 346 + DROP TABLE IF EXISTS "private_location_to_monitor"; 347 + CREATE TABLE `private_location_to_monitor` ( 348 + `private_location_id` integer, 349 + `monitor_id` integer, 350 + `created_at` integer DEFAULT (strftime('%s', 'now')), 351 + `deleted_at` integer, 352 + FOREIGN KEY (`private_location_id`) REFERENCES `private_location`(`id`) ON UPDATE no action ON DELETE no action, 353 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE no action 354 + ); 355 + 356 + 357 + INSERT INTO "__drizzle_migrations" ("id", "hash", "created_at") VALUES 358 + (NULL, 'ea497587bb639bbeae27f3f644634b7429f37df241c999e22f3acbf3cce74ec9', '1690309905039'), 359 + (NULL, '680e79fc5537135bcfa4da88cc0c06a7a9eaf810ab87f8ae06ef67f7b4802fad', '1690892003254'), 360 + (NULL, 'b8ae7d5887e4cd8416a443f193753dabc6bc1ee76a6cbcfd4b338fbc6d3d10e7', '1691573899721'), 361 + (NULL, '790cf15e409b0c02f0e164824289aaacacb7aee21f5e84ab4f5a48df4b46e528', '1691614487733'), 362 + (NULL, 'ac5db9b31935382412791565e1c11ce1c99e69bd6f826da9ff08fb4ea64acc67', '1691850907670'), 363 + (NULL, '4cc5cc57b2e087276e6283cbd639e2ccd27c800eec6def5bb3ff7ea3ee3d9ba9', '1691930414569'), 364 + (NULL, 'cc99f49622240771a187a79a7c5646ed7f1e6e86b6eefcc04e9a6be78e57a5b0', '1692646649111'), 365 + (NULL, '49e497500b2fdb01624090863808ab15db1e50d4bc393c1e6e820960cb070d60', '1694362217174'), 366 + (NULL, 'a76eb5c9a12c6828bdef95e6ca65483055e7dabdf94f0b95fdae437c811b6b55', '1695756345957'), 367 + (NULL, '223292dcce0a81148dfbf7338ed4fe5878503d6bf785e1a13042b7c7b7b5bf24', '1697285841283'), 368 + (NULL, '5cc395568f9f61ebf4b57f69fd6397da2661062db813a85c5433d56841cecc9c', '1700586221141'), 369 + (NULL, '4ceefb80e855b8fde6c430758d9b557cef09c42afca4575c8fe4236628112d33', '1701100570578'), 370 + (NULL, '4c8d34ebf56874f41a1b22cb93db50e022233ff53f763eb73a73d2e01223fa39', '1701713135829'), 371 + (NULL, '4f47a6efc84f60e41437134c6eec5b2a3be68aae2a0525f2109b61f7473bb5ea', '1702144660818'), 372 + (NULL, '6f1b9eac8a3c7cf72703171402a9f42ea7523c091e393b3972fd231ab6eac6f9', '1702227904130'), 373 + (NULL, '4ae3b4c679065c4ca450126e14a73dbb3d21d71b7b1de2be182c32500b5c16ea', '1705856545397'), 374 + (NULL, '04e2ca02dbf77bce1755dc28e624d1620d662bec94373cea08983f4801fe778d', '1706111184826'), 375 + (NULL, 'd3c0f8670dd8e3666b3e0bd3ef1d75aec6b593c11d35e5a75bf58abe6208c642', '1707411900987'), 376 + (NULL, 'a028a05328ef4480fb5d79caed96da17b68793f7c9af037dbe8bb77ce42f5e36', '1707770189561'), 377 + (NULL, '826d9207a73609ff0f15acb2109fed80a253beb826836a93e122157584f9ca35', '1707899175705'), 378 + (NULL, '0055fd4b62b8b1c7029ed978cdd2f946084acae39966cb8cf85c26923a87d1cc', '1707905605592'), 379 + (NULL, 'af0035a9de14837b62dddba9e40bb85bf249a0c75683867be2a2d6f70f28da6d', '1710677383007'), 380 + (NULL, '498de5adf9c0ea5ed4c3d57b6a0905b709a58550e79c157909f13e1269896c5a', '1711307113089'), 381 + (NULL, 'ce825562d32c32c28da76e2d06b5e76f4c1603d178ce82ff97e1436819f3273c', '1712311348272'), 382 + (NULL, '45fc9d668e1a0d69701267444ca79da8e392180635761f24e41d9a7ac30a6d8c', '1712354121499'), 383 + (NULL, 'e9a11163fbaa7bdb2e2350265cc3e260bdb24ae1eb42d975fbf03cba7b3c8ce7', '1713095971713'), 384 + (NULL, '62f1cd998d6258df5c2a89222f5b71082d575b474f6d86b75fb4bf3aefe1d063', '1713384976187'), 385 + (NULL, '4c0d7cd418b66257e0a49454c54e8e65c388c5a246b3f5a51df0618da1faa67d', '1714586658374'), 386 + (NULL, 'd785a89fd7aa7bad6dbae4db4a3daaaa26c5f14374ed045cfd22420453cc4cea', '1715173356076'), 387 + (NULL, '9216b9d717de25f7b615f625659b7acfbbb09adacd3b121fa4f05ec0796fbd3c', '1716215342026'), 388 + (NULL, 'a9ced3ac1a3f9e8de9d8240ccdeed9d9e9c80b1bb6caefea59a7dbf7cf411fa7', '1716364430118'), 389 + (NULL, '9d0ae1d1e4bc742555e5ae5d990b62d23bb3dcefd7f62079df0bfa908f74133b', '1717837961923'), 390 + (NULL, '698cdffe2cad7d5b65190d09cb8f2b2ba906b1218fedb1a727fb72049090dfb6', '1718027484219'), 391 + (NULL, '6255470457dc29e4e1f7b8aa84f349a68b04ba3d51b9380006ddac53d5ce90a2', '1719740057514'), 392 + (NULL, 'bbf2f3122e76c5cbd3b76bc36e97d8f8efdddfab25c98c16f8024154e9bb2609', '1720727898360'), 393 + (NULL, '812b9def5df2ffe21b91680764590eda65e5ac4674500966d921d9e8627ca2f0', '1721159796428'), 394 + (NULL, '56181582f13b5eb66f2598a28fc7800ec6a0b9dd31d74c99cea1299ea7321917', '1723459608109'), 395 + (NULL, '82e57027c88612343af361f30edb148f2fca91919d6fa44048e992462d71163d', '1729533101998'), 396 + (NULL, '215e2ab2354eaa50923566a10a99e79f8d8514d7e3d4ca5c5720a9f5d16714ee', '1729579461221'), 397 + (NULL, 'fca63d63ad18ce6864418f1597b7bb0013ab2a3e0b4c681326ee4b257de2eb66', '1739193014150'), 398 + (NULL, '42d3cbf03d76541be2bdd9036e2f97864aa2807fe625f3b4c2b4f6440db60bd4', '1740684132626'), 399 + (NULL, '1a8b01776fbc88eaad155e2d0d2c69fc41d970411193ddd97806d70e1c66248b', '1741936835660'), 400 + (NULL, '427ca6a7f11dce1f4c0a20344d32af5f3f146a128adce1e87c7f76c80ab080fd', '1747410497521'), 401 + (NULL, '150ecc57f8a96341713848b1eec1293e713289b8b87854067d543fce3ff03849', '1747908803707'), 402 + (NULL, '8980cf55bf7b9b7f9a2cb00e5f21be8454260dd2826644b7683aaad65ff40df6', '1753730490635'), 403 + (NULL, 'bd5e4767fcfcd79179bb9ac9576b58a0c51c0a3af65fc0140e53f57c40e56a9c', '1756185045968'), 404 + (NULL, 'c7180363acc6879b749b2e9c34d3b9ac012f13aed0c62bf7ed2104e936f72085', '1757580216081'), 405 + (NULL, '0b90478b428afe479c6da99669ba33dd78ca2b8b5039ca2305bd9771213f4089', '1757840904190'), 406 + (NULL, 'b2c7b149f424a30adbf9dbe4c1db363e85d5dfe9aca3845969b14238c1117178', '1759865914553'); 407 + 408 + INSERT INTO "page" ("id", "workspace_id", "title", "description", "icon", "slug", "custom_domain", "published", "created_at", "updated_at", "password", "password_protected", "show_monitor_values", "force_theme", "legacy_page", "configuration", "homepage_url", "contact_url") VALUES 409 + ('1', '1', 'Test Page', 'hello', 'https://www.openstatus.dev/favicon.ico', 'status', '', '1', '1760358329', '1760358329', NULL, '0', '1', 'system', '1', NULL, NULL, NULL); 410 + 411 + INSERT INTO "monitors_to_pages" ("monitor_id", "page_id", "created_at", "order") VALUES 412 + ('1', '1', '1760358329', '0'); 413 + 414 + INSERT INTO "user" ("id", "tenant_id", "created_at", "first_name", "last_name", "email", "photo_url", "updated_at", "name", "emailVerified") VALUES 415 + ('1', '1', '1760358329', 'Speed', 'Matters', 'ping@openstatus.dev', '', '1760358329', NULL, NULL); 416 + 417 + INSERT INTO "users_to_workspaces" ("user_id", "workspace_id", "role", "created_at") VALUES 418 + ('1', '1', 'member', '1760358329'); 419 + 420 + INSERT INTO "workspace" ("id", "slug", "stripe_id", "name", "created_at", "updated_at", "subscription_id", "plan", "ends_at", "paid_until", "dsn", "limits") VALUES 421 + ('1', 'love-openstatus', 'stripeId1', 'test', '1760358329', '1760358329', 'subscriptionId', 'team', NULL, NULL, NULL, '{"monitors":50,"synthetic-checks":150000,"periodicity":["30s","1m","5m","10m","30m","1h"],"multi-region":true,"max-regions":35,"data-retention":"24 months","status-pages":20,"maintenance":true,"status-subscribers":true,"custom-domain":true,"password-protection":true,"white-label":true,"notifications":true,"sms":true,"pagerduty":true,"notification-channels":50,"members":"Unlimited","audit-log":true,"regions":["ams","arn","atl","bog","bom","bos","cdg","den","dfw","ewr","eze","fra","gdl","gig","gru","hkg","iad","jnb","lax","lhr","mad","mia","nrt","ord","otp","phx","qro","scl","sea","sin","sjc","syd","waw","yul","yyz"]}'), 422 + ('2', 'test2', 'stripeId2', 'test2', '1760358329', '1760358329', 'subscriptionId2', 'free', NULL, NULL, NULL, '{}'), 423 + ('3', 'test3', 'stripeId3', 'test3', '1760358329', '1760358329', 'subscriptionId3', 'team', NULL, NULL, NULL, '{}'); 424 + 425 + INSERT INTO "status_report_update" ("id", "status", "date", "message", "created_at", "updated_at", "status_report_id") VALUES 426 + ('1', 'investigating', '1760358329', 'Message', '1760358329', '1760358329', '1'), 427 + ('2', 'investigating', '1760358329', 'Message', '1760358329', '1760358329', '2'), 428 + ('3', 'monitoring', '1760358329', 'test', '1760358329', '1760358329', '1'); 429 + 430 + INSERT INTO "status_report_to_monitors" ("monitor_id", "status_report_id", "created_at") VALUES 431 + ('1', '2', '1760358329'), 432 + ('2', '2', '1760358329'); 433 + 434 + INSERT INTO "monitor" ("id", "job_type", "periodicity", "active", "url", "name", "description", "workspace_id", "headers", "body", "method", "created_at", "regions", "updated_at", "status", "assertions", "deleted_at", "public", "timeout", "degraded_after", "otel_endpoint", "otel_headers", "retry", "follow_redirects") VALUES 435 + ('1', 'http', '1m', '1', 'https://www.openstatus.dev', 'OpenStatus', 'OpenStatus website', '1', '[{"key":"key", "value":"value"}]', '{"hello":"world"}', 'POST', '1760358329', 'ams', '1760358329', 'active', NULL, NULL, '0', '45000', NULL, NULL, NULL, '3', '1'), 436 + ('2', 'http', '10m', '0', 'https://www.google.com', '', '', '1', '', '', 'GET', '1760358329', 'gru', '1760358329', 'active', NULL, NULL, '1', '45000', NULL, NULL, NULL, '3', '1'), 437 + ('3', 'http', '1m', '1', 'https://www.openstatus.dev', 'OpenStatus', 'OpenStatus website', '1', '[{"key":"key", "value":"value"}]', '{"hello":"world"}', 'GET', '1760358329', 'ams', '1760358329', 'active', NULL, NULL, '0', '45000', NULL, NULL, NULL, '3', '1'), 438 + ('4', 'http', '10m', '1', 'https://www.google.com', '', '', '1', '', '', 'GET', '1760358329', 'gru', '1760358329', 'active', NULL, NULL, '1', '45000', NULL, 'https://otel.com:4337', '[{"key":"Authorization","value":"Basic"}]', '3', '1'), 439 + ('5', 'http', '10m', '1', 'https://openstat.us', '', '', '3', '', '', 'GET', '1760358329', 'ams', '1760358329', 'active', NULL, NULL, '1', '45000', NULL, NULL, NULL, '3', '1'); 440 + 441 + INSERT INTO "notification" ("id", "name", "provider", "data", "workspace_id", "created_at", "updated_at") VALUES 442 + ('1', 'sample test notification', 'email', '{"email":"ping@openstatus.dev"}', '1', '1760358329', '1760358329'); 443 + 444 + INSERT INTO "notifications_to_monitors" ("monitor_id", "notification_id", "created_at") VALUES 445 + ('1', '1', '1760358329'); 446 + 447 + INSERT INTO "incident" ("id", "title", "summary", "status", "monitor_id", "workspace_id", "started_at", "acknowledged_at", "acknowledged_by", "resolved_at", "resolved_by", "created_at", "updated_at", "auto_resolved", "incident_screenshot_url", "recovery_screenshot_url") VALUES 448 + ('1', '', '', 'triage', '1', '1', '1760358329', NULL, NULL, NULL, NULL, '1760358329', '1760358329', '0', NULL, NULL), 449 + ('2', '', '', 'triage', '1', '1', '1760358330', NULL, NULL, NULL, NULL, '1760358329', '1760358329', '0', NULL, NULL); 450 + 451 + INSERT INTO "maintenance_to_monitor" ("monitor_id", "maintenance_id", "created_at") VALUES 452 + ('1', '1', '1760358329'); 453 + 454 + INSERT INTO "status_report" ("id", "status", "title", "workspace_id", "page_id", "created_at", "updated_at") VALUES 455 + ('1', 'monitoring', 'Test Status Report', '1', '1', '1760358329', '1760358329'), 456 + ('2', 'investigating', 'Test Status Report', '1', '1', '1760358329', '1760358329'); 457 + 458 + INSERT INTO "maintenance" ("id", "title", "message", "from", "to", "workspace_id", "page_id", "created_at", "updated_at") VALUES 459 + ('1', 'Test Maintenance', 'Test message', '1760358329', '1760358330', '1', '1', '1760358329', '1760358329'); 460 + 461 + INSERT INTO "private_location" ("id", "name", "token", "last_seen_at", "workspace_id", "created_at", "updated_at") VALUES 462 + ('1', 'My Home', 'my-secret-key', NULL, '3', '1760358329', '1760358329'); 463 + 464 + INSERT INTO "private_location_to_monitor" ("private_location_id", "monitor_id", "created_at", "deleted_at") VALUES 465 + ('1', '5', '1760358329', NULL);
+92
apps/private-location/internal/server/ingest_http.go
··· 1 + package server 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "strconv" 7 + "time" 8 + 9 + "connectrpc.com/connect" 10 + "github.com/openstatushq/openstatus/apps/private-location/internal/database" 11 + private_locationv1 "github.com/openstatushq/openstatus/apps/private-location/proto/private_location/v1" 12 + "github.com/rs/zerolog/log" 13 + ) 14 + 15 + type PingData struct { 16 + ID string `json:"id"` 17 + WorkspaceID string `json:"workspaceId"` 18 + MonitorID string `json:"monitorId"` 19 + URL string `json:"url"` 20 + Method string `json:"method"` 21 + Region string `json:"region"` 22 + Message string `json:"message,omitempty"` 23 + Timing string `json:"timing,omitempty"` 24 + Headers string `json:"headers,omitempty"` 25 + Assertions string `json:"assertions"` 26 + Body string `json:"body,omitempty"` 27 + Trigger string `json:"trigger,omitempty"` 28 + RequestStatus string `json:"requestStatus,omitempty"` 29 + Latency int64 `json:"latency"` 30 + CronTimestamp int64 `json:"cronTimestamp"` 31 + Timestamp int64 `json:"timestamp"` 32 + StatusCode int `json:"statusCode,omitempty"` 33 + Error uint8 `json:"error"` 34 + } 35 + 36 + func (h *privateLocationHandler) IngestHTTP(ctx context.Context, req *connect.Request[private_locationv1.IngestHTTPRequest]) (*connect.Response[private_locationv1.IngestHTTPResponse], error) { 37 + token := req.Header().Get("openstatus-token") 38 + if token == "" { 39 + return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("missing token")) 40 + } 41 + 42 + dataSourceName := "ping_response__v8" 43 + 44 + var monitors database.Monitor 45 + err := h.db.Get(&monitors, "SELECT monitor.* FROM monitor JOIN private_location_to_monitor a ON monitor.id = a.monitor_id JOIN private_location b ON a.private_location_id = b.id WHERE b.token = ? AND monitor.deleted_at IS NULL and monitor.id = ?", token, req.Msg.MonitorId) 46 + 47 + if err != nil { 48 + log.Ctx(ctx).Error().Err(err).Msg("Failed to get monitors") 49 + 50 + return nil, connect.NewError(connect.CodeInternal, err) 51 + } 52 + 53 + var region database.PrivateLocation 54 + err = h.db.Get(&region, "SELECT private_location.id FROM private_location join private_location_to_monitor a ON private_location.id = a.private_location_id WHERE a.monitor_id = ? AND private_location.token = ?", monitors.ID, token) 55 + 56 + if err != nil { 57 + 58 + log.Ctx(ctx).Error().Err(err).Msg("Failed to get private location") 59 + return nil, connect.NewError(connect.CodeInternal, err) 60 + } 61 + 62 + data := PingData{ 63 + ID: req.Msg.Id, 64 + Latency: req.Msg.Latency, 65 + StatusCode: int(req.Msg.StatusCode), 66 + MonitorID: req.Msg.MonitorId, 67 + Region: strconv.Itoa(region.ID), 68 + WorkspaceID: strconv.Itoa(monitors.WorkspaceID), 69 + Timestamp: req.Msg.Timestamp, 70 + CronTimestamp: req.Msg.CronTimestamp, 71 + URL: monitors.URL, 72 + Method: monitors.Method, 73 + Timing: req.Msg.Timing, 74 + Headers: req.Msg.Headers, 75 + Body: req.Msg.Body, 76 + Trigger: "cron", 77 + RequestStatus: req.Msg.RequestStatus, 78 + Assertions: monitors.Assertions.String, 79 + } 80 + if err := h.TbClient.SendEvent(ctx, data, dataSourceName); err != nil { 81 + log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 82 + } 83 + _, err = h.db.NamedExec("UPDATE private_location SET last_seen_at = :last_seen_at WHERE id = :id", map[string]any{ 84 + "last_seen_at": time.Now().Unix(), 85 + "id": region.ID, 86 + }) 87 + if err != nil { 88 + log.Ctx(ctx).Error().Err(err).Msg("failed to update private location") 89 + } 90 + 91 + return connect.NewResponse(&private_locationv1.IngestHTTPResponse{}), nil 92 + }
+129
apps/private-location/internal/server/ingest_http_test.go
··· 1 + package server_test 2 + 3 + import ( 4 + "context" 5 + "log" 6 + "net/http" 7 + "os" 8 + "testing" 9 + 10 + "connectrpc.com/connect" 11 + "github.com/jmoiron/sqlx" 12 + _ "github.com/mattn/go-sqlite3" 13 + "github.com/openstatushq/openstatus/apps/private-location/internal/server" 14 + "github.com/openstatushq/openstatus/apps/private-location/internal/tinybird" 15 + private_locationv1 "github.com/openstatushq/openstatus/apps/private-location/proto/private_location/v1" 16 + ) 17 + 18 + func testDB() *sqlx.DB { 19 + 20 + f, err := os.CreateTemp("", "db") 21 + if err != nil { 22 + log.Fatalln(err) 23 + } 24 + db, err := sqlx.Connect("sqlite3", f.Name()) 25 + if err != nil { 26 + log.Fatalln(err) 27 + } 28 + dat, err := os.ReadFile("./db_testdata") 29 + db.MustExec(string(dat)) 30 + 31 + return db 32 + } 33 + 34 + type interceptorHTTPClient struct { 35 + f func(req *http.Request) (*http.Response, error) 36 + } 37 + 38 + func (i *interceptorHTTPClient) RoundTrip(req *http.Request) (*http.Response, error) { 39 + return i.f(req) 40 + } 41 + 42 + func (i *interceptorHTTPClient) GetHTTPClient() *http.Client { 43 + return &http.Client{ 44 + Transport: i, 45 + } 46 + } 47 + 48 + func getTBClient(ctx context.Context) tinybird.Client { 49 + interceptor := &interceptorHTTPClient{ 50 + f: func(req *http.Request) (*http.Response, error) { 51 + return &http.Response{ 52 + StatusCode: http.StatusAccepted, 53 + }, nil 54 + }, 55 + } 56 + 57 + client := tinybird.NewClient(interceptor.GetHTTPClient(), "apiKey") 58 + return client 59 + } 60 + 61 + func TestIngestHTTP_Unauthenticated(t *testing.T) { 62 + h := server.NewPrivateLocationServer(testDB(), getTBClient(context.Background())) 63 + 64 + req := connect.NewRequest(&private_locationv1.IngestHTTPRequest{}) 65 + // No token header 66 + resp, err := h.IngestHTTP(context.Background(), req) 67 + if err == nil { 68 + t.Fatalf("expected error for missing token, got nil") 69 + } 70 + if connect.CodeOf(err) != connect.CodeUnauthenticated { 71 + t.Errorf("expected unauthenticated code, got %v", connect.CodeOf(err)) 72 + } 73 + if resp != nil { 74 + t.Errorf("expected nil response, got %v", resp) 75 + } 76 + } 77 + 78 + func TestIngestHTTP_DBError(t *testing.T) { 79 + h := server.NewPrivateLocationServer(testDB(), getTBClient(context.Background())) 80 + 81 + req := connect.NewRequest(&private_locationv1.IngestHTTPRequest{}) 82 + req.Header().Set("openstatus-token", "token123") 83 + req.Msg.Id = "monitor1" 84 + resp, err := h.IngestHTTP(context.Background(), req) 85 + if err == nil { 86 + t.Fatalf("expected error for db failure, got nil") 87 + } 88 + if connect.CodeOf(err) != connect.CodeInternal { 89 + t.Errorf("expected internal code, got %v", connect.CodeOf(err)) 90 + } 91 + if resp != nil { 92 + t.Errorf("expected nil response, got %v", resp) 93 + } 94 + } 95 + 96 + func TestIngestHTTP_MonitorNotExist(t *testing.T) { 97 + h := server.NewPrivateLocationServer(testDB(), getTBClient(context.Background())) 98 + 99 + req := connect.NewRequest(&private_locationv1.IngestHTTPRequest{}) 100 + req.Header().Set("openstatus-token", "my-secret-key") 101 + req.Msg.Id = "monitor1" 102 + resp, err := h.IngestHTTP(context.Background(), req) 103 + if err == nil { 104 + t.Fatalf("expected error for db failure, got nil") 105 + } 106 + if connect.CodeOf(err) != connect.CodeInternal { 107 + t.Errorf("expected internal code, got %v", connect.CodeOf(err)) 108 + } 109 + if resp != nil { 110 + t.Errorf("expected nil response, got %v", resp) 111 + } 112 + } 113 + 114 + func TestIngestHTTP_MonitorExist(t *testing.T) { 115 + h := server.NewPrivateLocationServer(testDB(), getTBClient(context.Background())) 116 + 117 + req := connect.NewRequest(&private_locationv1.IngestHTTPRequest{}) 118 + req.Header().Set("openstatus-token", "my-secret-key") 119 + req.Msg.Id = "monitor1" 120 + req.Msg.MonitorId = "5" 121 + resp, err := h.IngestHTTP(context.Background(), req) 122 + if err != nil { 123 + t.Fatalf("expected nil error, got %v", err) 124 + } 125 + 126 + if resp == nil { 127 + t.Errorf("expected not response, got %v", resp) 128 + } 129 + }
+82
apps/private-location/internal/server/ingest_tcp.go
··· 1 + package server 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "strconv" 7 + "time" 8 + 9 + "connectrpc.com/connect" 10 + "github.com/openstatushq/openstatus/apps/private-location/internal/database" 11 + private_locationv1 "github.com/openstatushq/openstatus/apps/private-location/proto/private_location/v1" 12 + "github.com/rs/zerolog/log" 13 + ) 14 + 15 + type TCPData struct { 16 + ID string `json:"id"` 17 + Timing string `json:"timing"` 18 + ErrorMessage string `json:"errorMessage"` 19 + Region string `json:"region"` 20 + Trigger string `json:"trigger"` 21 + URI string `json:"uri"` 22 + RequestStatus string `json:"requestStatus,omitempty"` 23 + 24 + RequestId int64 `json:"requestId,omitempty"` 25 + WorkspaceID int64 `json:"workspaceId"` 26 + MonitorID int64 `json:"monitorId"` 27 + Timestamp int64 `json:"timestamp"` 28 + Latency int64 `json:"latency"` 29 + CronTimestamp int64 `json:"cronTimestamp"` 30 + 31 + Error uint8 `json:"error"` 32 + } 33 + 34 + func (h *privateLocationHandler) IngestTCP(ctx context.Context, req *connect.Request[private_locationv1.IngestTCPRequest]) (*connect.Response[private_locationv1.IngestTCPResponse], error) { 35 + token := req.Header().Get("openstatus-token") 36 + if token == "" { 37 + return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("missing token")) 38 + } 39 + 40 + dataSourceName := "tcp_response__v0" 41 + 42 + var monitors database.Monitor 43 + err := h.db.Get(&monitors, "SELECT monitor.* FROM monitor JOIN private_location_to_monitor a ON monitor.id = a.monitor_id JOIN private_location b ON a.private_location_id = b.id WHERE b.token = ? AND monitor.deleted_at IS NULL and monitor.id = ?", token, req.Msg.Id) 44 + 45 + if err != nil { 46 + return nil, connect.NewError(connect.CodeInternal, err) 47 + } 48 + 49 + var region database.PrivateLocation 50 + err = h.db.Get(&region, "SELECT private_location.id FROM private_location join private_location_to_monitor a ON private_location.id = a.private_location_id WHERE a.monitor_id = ? and private_location.token = ?", monitors.ID, token) 51 + 52 + if err != nil { 53 + return nil, connect.NewError(connect.CodeInternal, err) 54 + } 55 + 56 + data := TCPData{ 57 + ID: req.Msg.Id, 58 + WorkspaceID: int64(monitors.WorkspaceID), 59 + Timestamp: req.Msg.Timestamp, 60 + Error: uint8(req.Msg.Error), 61 + // ErrorMessage: req.Msg.ErrorMessage, 62 + Region: strconv.Itoa(region.ID), 63 + MonitorID: int64(monitors.ID), 64 + Timing: req.Msg.Timing, 65 + Latency: req.Msg.Latency, 66 + CronTimestamp: req.Msg.CronTimestamp, 67 + Trigger: "cron", 68 + URI: req.Msg.Uri, 69 + RequestStatus: req.Msg.RequestStatus, 70 + } 71 + if err := h.TbClient.SendEvent(ctx, data, dataSourceName); err != nil { 72 + log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 73 + } 74 + _, err = h.db.NamedExec("UPDATE private_location SET last_seen_at = :last_seen_at WHERE id = :id", map[string]any{ 75 + "last_seen_at": time.Now().Unix(), 76 + "id": region.ID, 77 + }) 78 + if err != nil { 79 + log.Ctx(ctx).Error().Err(err).Msg("failed to update private location") 80 + } 81 + return connect.NewResponse(&private_locationv1.IngestTCPResponse{}), nil 82 + }
+48
apps/private-location/internal/server/ingest_tcp_test.go
··· 1 + package server_test 2 + 3 + import ( 4 + "context" 5 + "net/http" 6 + "testing" 7 + 8 + "connectrpc.com/connect" 9 + 10 + "github.com/openstatushq/openstatus/apps/private-location/internal/server" 11 + "github.com/openstatushq/openstatus/apps/private-location/internal/tinybird" 12 + private_locationv1 "github.com/openstatushq/openstatus/apps/private-location/proto/private_location/v1" 13 + ) 14 + 15 + func TestIngestTCP_Unauthenticated(t *testing.T) { 16 + h := server.NewPrivateLocationServer(testDB(), tinybird.NewClient(http.DefaultClient, "")) 17 + 18 + req := connect.NewRequest(&private_locationv1.IngestTCPRequest{}) 19 + // No token header 20 + resp, err := h.IngestTCP(context.Background(), req) 21 + if err == nil { 22 + t.Fatalf("expected error for missing token, got nil") 23 + } 24 + if connect.CodeOf(err) != connect.CodeUnauthenticated { 25 + t.Errorf("expected unauthenticated code, got %v", connect.CodeOf(err)) 26 + } 27 + if resp != nil { 28 + t.Errorf("expected nil response, got %v", resp) 29 + } 30 + } 31 + 32 + func TestIngestTCP_DBError(t *testing.T) { 33 + h := server.NewPrivateLocationServer(testDB(), tinybird.NewClient(http.DefaultClient, "")) 34 + 35 + req := connect.NewRequest(&private_locationv1.IngestTCPRequest{}) 36 + req.Header().Set("openstatus-token", "token123") 37 + req.Msg.Id = "monitor1" 38 + resp, err := h.IngestTCP(context.Background(), req) 39 + if err == nil { 40 + t.Fatalf("expected error for db failure, got nil") 41 + } 42 + if connect.CodeOf(err) != connect.CodeInternal { 43 + t.Errorf("expected internal code, got %v", connect.CodeOf(err)) 44 + } 45 + if resp != nil { 46 + t.Errorf("expected nil response, got %v", resp) 47 + } 48 + }
+159
apps/private-location/internal/server/monitors.go
··· 1 + package server 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "encoding/json" 7 + "errors" 8 + "strconv" 9 + 10 + "connectrpc.com/connect" 11 + "github.com/openstatushq/openstatus/apps/private-location/internal/database" 12 + "github.com/openstatushq/openstatus/apps/private-location/internal/models" 13 + private_locationv1 "github.com/openstatushq/openstatus/apps/private-location/proto/private_location/v1" 14 + "github.com/rs/zerolog/log" 15 + ) 16 + 17 + // Converts models.NumberComparator to proto NumberComparator 18 + func convertNumberComparator(m models.NumberComparator) private_locationv1.NumberComparator { 19 + switch m { 20 + case models.NumberNotEquals: 21 + return private_locationv1.NumberComparator_NUMBER_COMPARATOR_NOT_EQUAL 22 + case models.NumberEquals: 23 + return private_locationv1.NumberComparator_NUMBER_COMPARATOR_EQUAL 24 + case models.NumberGreaterThan: 25 + return private_locationv1.NumberComparator_NUMBER_COMPARATOR_GREATER_THAN 26 + case models.NumberGreaterThanEqual: 27 + return private_locationv1.NumberComparator_NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL 28 + case models.NumberLowerThan: 29 + return private_locationv1.NumberComparator_NUMBER_COMPARATOR_LESS_THAN 30 + case models.NumberLowerThanEqual: 31 + return private_locationv1.NumberComparator_NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL 32 + default: 33 + return private_locationv1.NumberComparator_NUMBER_COMPARATOR_UNSPECIFIED 34 + } 35 + } 36 + 37 + // Converts models.StringComparator to proto StringComparator 38 + func convertStringComparator(m models.StringComparator) private_locationv1.StringComparator { 39 + switch m { 40 + case models.StringNotEquals: 41 + return private_locationv1.StringComparator_STRING_COMPARATOR_NOT_EQUAL 42 + case models.StringEquals: 43 + return private_locationv1.StringComparator_STRING_COMPARATOR_EQUAL 44 + case models.StringContains: 45 + return private_locationv1.StringComparator_STRING_COMPARATOR_CONTAINS 46 + case models.StringNotContains: 47 + return private_locationv1.StringComparator_STRING_COMPARATOR_NOT_CONTAINS 48 + case models.StringEmpty: 49 + return private_locationv1.StringComparator_STRING_COMPARATOR_EMPTY 50 + case models.StringNotEmpty: 51 + return private_locationv1.StringComparator_STRING_COMPARATOR_NOT_EMPTY 52 + default: 53 + return private_locationv1.StringComparator_STRING_COMPARATOR_UNSPECIFIED 54 + } 55 + } 56 + 57 + // Helper to parse assertions 58 + func ParseAssertions(assertions sql.NullString) ( 59 + statusAssertions []*private_locationv1.StatusCodeAssertion, 60 + headerAssertions []*private_locationv1.HeaderAssertion, 61 + bodyAssertions []*private_locationv1.BodyAssertion, 62 + ) { 63 + if !assertions.Valid { 64 + return 65 + } 66 + var rawAssertions []json.RawMessage 67 + if err := json.Unmarshal([]byte(assertions.String), &rawAssertions); err != nil { 68 + log.Printf("Failed to unmarshal assertions: %v", err) 69 + return 70 + } 71 + for _, a := range rawAssertions { 72 + var assert models.Assertion 73 + if err := json.Unmarshal(a, &assert); err != nil { 74 + log.Printf("Failed to unmarshal assertion: %v", err) 75 + continue 76 + } 77 + switch assert.AssertionType { 78 + case models.AssertionStatus: 79 + var target models.StatusTarget 80 + if err := json.Unmarshal(a, &target); err != nil { 81 + log.Printf("Failed to unmarshal status target: %v", err) 82 + continue 83 + } 84 + statusAssertions = append(statusAssertions, &private_locationv1.StatusCodeAssertion{ 85 + Target: target.Target, 86 + Comparator: convertNumberComparator(target.Comparator), 87 + }) 88 + case models.AssertionHeader: 89 + var target models.HeaderTarget 90 + if err := json.Unmarshal(a, &target); err != nil { 91 + log.Error().Err(err).Msg("unable to encode payload") 92 + continue 93 + } 94 + headerAssertions = append(headerAssertions, &private_locationv1.HeaderAssertion{ 95 + Key: target.Key, 96 + Target: target.Target, 97 + Comparator: convertStringComparator(target.Comparator), 98 + }) 99 + case models.AssertionTextBody: 100 + var target models.BodyString 101 + if err := json.Unmarshal(a, &target); err != nil { 102 + log.Printf("Failed to unmarshal body target: %v", err) 103 + continue 104 + } 105 + bodyAssertions = append(bodyAssertions, &private_locationv1.BodyAssertion{ 106 + Target: target.Target, 107 + Comparator: convertStringComparator(target.Comparator), 108 + }) 109 + } 110 + } 111 + return 112 + } 113 + 114 + func (h *privateLocationHandler) Monitors(ctx context.Context, req *connect.Request[private_locationv1.MonitorsRequest]) (*connect.Response[private_locationv1.MonitorsResponse], error) { 115 + token := req.Header().Get("openstatus-token") 116 + if token == "" { 117 + return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("missing token")) 118 + } 119 + 120 + var monitors []database.Monitor 121 + err := h.db.Select(&monitors, "SELECT monitor.* FROM monitor JOIN private_location_to_monitor a ON monitor.id = a.monitor_id JOIN private_location b ON a.private_location_id = b.id WHERE b.token = ? AND monitor.deleted_at IS NULL", token) 122 + if err != nil { 123 + return nil, connect.NewError(connect.CodeInternal, err) 124 + } 125 + 126 + var httpMonitors []*private_locationv1.HTTPMonitor 127 + for _, monitor := range monitors { 128 + if monitor.JobType != "http" { 129 + continue 130 + } 131 + 132 + var headers []*private_locationv1.Headers 133 + if err := json.Unmarshal([]byte(monitor.Headers), &headers); err != nil { 134 + log.Ctx(ctx).Error().Err(err).Msg("unable to unmarshal headers") 135 + headers = nil 136 + } 137 + 138 + statusAssertions, headerAssertions, bodyAssertions := ParseAssertions(monitor.Assertions) 139 + 140 + httpMonitors = append(httpMonitors, &private_locationv1.HTTPMonitor{ 141 + Url: monitor.URL, 142 + Periodicity: monitor.Periodicity, 143 + Id: strconv.Itoa(monitor.ID), 144 + Method: monitor.Method, 145 + Body: monitor.Body, 146 + Timeout: monitor.Timeout, 147 + DegradedAt: &monitor.DegradedAfter.Int64, 148 + FollowRedirects: monitor.FollowRedirects, 149 + Headers: headers, 150 + StatusCodeAssertions: statusAssertions, 151 + HeaderAssertions: headerAssertions, 152 + BodyAssertions: bodyAssertions, 153 + }) 154 + } 155 + 156 + return connect.NewResponse(&private_locationv1.MonitorsResponse{ 157 + HttpMonitors: httpMonitors, 158 + }), nil 159 + }
+57
apps/private-location/internal/server/monitors_test.go
··· 1 + package server_test 2 + 3 + import ( 4 + "database/sql" 5 + "testing" 6 + 7 + "github.com/openstatushq/openstatus/apps/private-location/internal/server" 8 + private_locationv1 "github.com/openstatushq/openstatus/apps/private-location/proto/private_location/v1" 9 + ) 10 + 11 + func TestParseAssertions_TextBodyContains(t *testing.T) { 12 + // Input JSON for the test 13 + input := `[{"version":"v1","type":"textBody","compare":"contains","target":"mydata"}]` 14 + assertions := sql.NullString{ 15 + String: input, 16 + Valid: true, 17 + } 18 + 19 + _, _, bodyAssertions := server.ParseAssertions(assertions) 20 + 21 + if len(bodyAssertions) != 1 { 22 + t.Fatalf("expected 1 body assertion, got %d", len(bodyAssertions)) 23 + } 24 + 25 + got := bodyAssertions[0] 26 + if got.Target != "mydata" { 27 + t.Errorf("expected Target to be 'mydata', got '%s'", got.Target) 28 + } 29 + 30 + if got.Comparator != private_locationv1.StringComparator_STRING_COMPARATOR_CONTAINS { 31 + t.Errorf("expected Comparator to be STRING_COMPARATOR_CONTAINS, got %v", got.Comparator) 32 + } 33 + } 34 + 35 + func TestParseAssertions_HttpStatusEquals(t *testing.T) { 36 + // Input JSON for the test 37 + input := `[{"version":"v1","type":"status","compare":"eq","target":200}]` 38 + assertions := sql.NullString{ 39 + String: input, 40 + Valid: true, 41 + } 42 + 43 + statusAssertion, _, _ := server.ParseAssertions(assertions) 44 + 45 + if len(statusAssertion) != 1 { 46 + t.Fatalf("expected 1 body assertion, got %d", len(statusAssertion)) 47 + } 48 + 49 + got := statusAssertion[0] 50 + if got.Target != 200 { 51 + t.Errorf("expected Target to be 'mydata', got '%d'", got.Target) 52 + } 53 + 54 + if got.Comparator != private_locationv1.NumberComparator_NUMBER_COMPARATOR_EQUAL { 55 + t.Errorf("expected Comparator to be STRING_COMPARATOR_CONTAINS, got %v", got.Comparator) 56 + } 57 + }
+60
apps/private-location/internal/server/routes.go
··· 1 + package server 2 + 3 + import ( 4 + "net/http" 5 + "os" 6 + "time" 7 + 8 + _ "github.com/joho/godotenv/autoload" 9 + 10 + "github.com/go-chi/chi/v5" 11 + "github.com/go-chi/render" 12 + "github.com/jmoiron/sqlx" 13 + "github.com/openstatushq/openstatus/apps/private-location/internal/tinybird" 14 + v1 "github.com/openstatushq/openstatus/apps/private-location/proto/private_location/v1" 15 + ) 16 + 17 + // Monitor represents a monitoring job. 18 + 19 + type privateLocationHandler struct { 20 + db *sqlx.DB 21 + TbClient tinybird.Client 22 + } 23 + 24 + func NewPrivateLocationServer(db *sqlx.DB, tbClient tinybird.Client) *privateLocationHandler { 25 + return &privateLocationHandler{ 26 + db: db, 27 + TbClient: tbClient, 28 + } 29 + } 30 + 31 + // RegisterRoutes sets up the HTTP routes for the server. 32 + func (s *Server) RegisterRoutes() http.Handler { 33 + r := chi.NewRouter() 34 + r.Get("/health", s.healthHandler) 35 + 36 + tinyBirdToken := os.Getenv("TINYBIRD_TOKEN") 37 + 38 + httpClient := &http.Client{ 39 + Timeout: 45 * time.Second, 40 + } 41 + 42 + tinybirdClient := tinybird.NewClient(httpClient, tinyBirdToken) 43 + 44 + privateLocationServer := NewPrivateLocationServer(s.db, tinybirdClient) 45 + path, handler := v1.NewPrivateLocationServiceHandler(privateLocationServer) 46 + 47 + r.Group(func(r chi.Router) { 48 + r.Mount(path, handler) 49 + }) 50 + return r 51 + } 52 + 53 + // healthHandler responds with the health status of the server. 54 + func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { 55 + 56 + render.JSON(w, r, map[string]any{ 57 + "status": "ok", 58 + }) 59 + render.Status(r, http.StatusOK) 60 + }
+41
apps/private-location/internal/server/server.go
··· 1 + package server 2 + 3 + import ( 4 + // "database/sql" 5 + "fmt" 6 + "net/http" 7 + "os" 8 + "strconv" 9 + "time" 10 + 11 + "github.com/jmoiron/sqlx" 12 + _ "github.com/joho/godotenv/autoload" 13 + 14 + "github.com/openstatushq/openstatus/apps/private-location/internal/database" 15 + ) 16 + 17 + type Server struct { 18 + port int 19 + 20 + db *sqlx.DB 21 + } 22 + 23 + func NewServer() *http.Server { 24 + port, _ := strconv.Atoi(os.Getenv("PORT")) 25 + NewServer := &Server{ 26 + port: port, 27 + 28 + db: database.New(), 29 + } 30 + 31 + // Declare Server config 32 + server := &http.Server{ 33 + Addr: fmt.Sprintf(":%d", NewServer.port), 34 + Handler: NewServer.RegisterRoutes(), 35 + IdleTimeout: time.Minute, 36 + ReadTimeout: 10 * time.Second, 37 + WriteTimeout: 30 * time.Second, 38 + } 39 + 40 + return server 41 + }
+69
apps/private-location/internal/tinybird/client.go
··· 1 + package tinybird 2 + 3 + import ( 4 + "bytes" 5 + "context" 6 + "encoding/json" 7 + "fmt" 8 + "net/http" 9 + "net/url" 10 + 11 + "github.com/rs/zerolog/log" 12 + ) 13 + 14 + const baseURL = "https://api.tinybird.co/v0/events" 15 + 16 + type Client interface { 17 + SendEvent(ctx context.Context, event any, dataSourceName string) error 18 + } 19 + 20 + type client struct { 21 + httpClient *http.Client 22 + apiKey string 23 + } 24 + 25 + func NewClient(httpClient *http.Client, apiKey string) Client { 26 + return client{ 27 + httpClient: httpClient, 28 + apiKey: apiKey, 29 + } 30 + } 31 + 32 + func (c client) SendEvent(ctx context.Context, event any, dataSourceName string) error { 33 + requestURL, err := url.Parse(baseURL) 34 + if err != nil { 35 + log.Ctx(ctx).Error().Err(err).Msg("unable to parse url") 36 + return fmt.Errorf("unable to parse url: %w", err) 37 + } 38 + 39 + q := requestURL.Query() 40 + q.Add("name", dataSourceName) 41 + requestURL.RawQuery = q.Encode() 42 + 43 + var payload bytes.Buffer 44 + if err := json.NewEncoder(&payload).Encode(event); err != nil { 45 + log.Ctx(ctx).Error().Err(err).Msg("unable to encode payload") 46 + return fmt.Errorf("unable to encode payload: %w", err) 47 + } 48 + 49 + req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL.String(), bytes.NewReader(payload.Bytes())) 50 + if err != nil { 51 + log.Ctx(ctx).Error().Err(err).Msg("unable to create request") 52 + return fmt.Errorf("unable to create request: %w", err) 53 + } 54 + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey)) 55 + 56 + resp, err := c.httpClient.Do(req) 57 + if err != nil { 58 + log.Ctx(ctx).Error().Err(err).Msg("unable to send request") 59 + return fmt.Errorf("unable to send request: %w", err) 60 + } 61 + defer resp.Body.Close() 62 + 63 + if resp.StatusCode != http.StatusAccepted { 64 + log.Ctx(ctx).Error().Str("status", resp.Status).Msg("unexpected status code") 65 + return fmt.Errorf("unexpected status code: %d", resp.StatusCode) 66 + } 67 + 68 + return nil 69 + }
+76
apps/private-location/internal/tinybird/client_test.go
··· 1 + package tinybird_test 2 + 3 + import ( 4 + "fmt" 5 + "net/http" 6 + "testing" 7 + 8 + "github.com/openstatushq/openstatus/apps/checker/pkg/tinybird" 9 + "github.com/stretchr/testify/require" 10 + ) 11 + 12 + type interceptorHTTPClient struct { 13 + f func(req *http.Request) (*http.Response, error) 14 + } 15 + 16 + func (i *interceptorHTTPClient) RoundTrip(req *http.Request) (*http.Response, error) { 17 + return i.f(req) 18 + } 19 + 20 + func (i *interceptorHTTPClient) GetHTTPClient() *http.Client { 21 + return &http.Client{ 22 + Transport: i, 23 + } 24 + } 25 + 26 + func TestSendEvent(t *testing.T) { 27 + t.Parallel() 28 + 29 + ctx := t.Context() 30 + 31 + t.Run("it should return an error if it can not send the event", func(t *testing.T) { 32 + interceptor := &interceptorHTTPClient{ 33 + f: func(req *http.Request) (*http.Response, error) { 34 + return nil, fmt.Errorf("unable to send request") 35 + }, 36 + } 37 + 38 + client := tinybird.NewClient(interceptor.GetHTTPClient(), "apiKey") 39 + 40 + err := client.SendEvent(ctx, "event", "test") 41 + require.Error(t, err) 42 + }) 43 + 44 + t.Run("it should return an error if the response status code is not 200", func(t *testing.T) { 45 + interceptor := &interceptorHTTPClient{ 46 + f: func(req *http.Request) (*http.Response, error) { 47 + return &http.Response{ 48 + StatusCode: http.StatusInternalServerError, 49 + }, nil 50 + }, 51 + } 52 + 53 + client := tinybird.NewClient(interceptor.GetHTTPClient(), "apiKey") 54 + 55 + err := client.SendEvent(ctx, "event", "test") 56 + require.Error(t, err) 57 + }) 58 + 59 + t.Run("it should succeed and return nothing", func(t *testing.T) { 60 + var url string 61 + interceptor := &interceptorHTTPClient{ 62 + f: func(req *http.Request) (*http.Response, error) { 63 + url = req.URL.String() 64 + return &http.Response{ 65 + StatusCode: http.StatusAccepted, 66 + }, nil 67 + }, 68 + } 69 + 70 + client := tinybird.NewClient(interceptor.GetHTTPClient(), "apiKey") 71 + 72 + err := client.SendEvent(ctx, "event", "test") 73 + require.NoError(t, err) 74 + require.Equal(t, "https://api.tinybird.co/v0/events?name=test", url) 75 + }) 76 + }
+25
apps/private-location/justfile
··· 1 + # Simple Makefile for a Go project 2 + 3 + # Build the application 4 + all: build test 5 + 6 + build: 7 + echo "Building..." 8 + 9 + 10 + go build -o main cmd/server/main.go 11 + dev: 12 + air 13 + # Run the application 14 + run: 15 + go run cmd/server/main.go 16 + 17 + # Test the application 18 + test: 19 + echo "Testing..." 20 + go test ./... -v 21 + 22 + # Clean the binary 23 + clean: 24 + echo "Cleaning..." 25 + rm -f main
+420
apps/private-location/proto/private_location/v1/assertions.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.36.10 4 + // protoc (unknown) 5 + // source: private_location/v1/assertions.proto 6 + 7 + package v1 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + reflect "reflect" 13 + sync "sync" 14 + unsafe "unsafe" 15 + ) 16 + 17 + const ( 18 + // Verify that this generated code is sufficiently up-to-date. 19 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 + // Verify that runtime/protoimpl is sufficiently up-to-date. 21 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 + ) 23 + 24 + type NumberComparator int32 25 + 26 + const ( 27 + NumberComparator_NUMBER_COMPARATOR_UNSPECIFIED NumberComparator = 0 28 + NumberComparator_NUMBER_COMPARATOR_EQUAL NumberComparator = 1 29 + NumberComparator_NUMBER_COMPARATOR_NOT_EQUAL NumberComparator = 2 30 + NumberComparator_NUMBER_COMPARATOR_GREATER_THAN NumberComparator = 3 31 + NumberComparator_NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL NumberComparator = 4 32 + NumberComparator_NUMBER_COMPARATOR_LESS_THAN NumberComparator = 5 33 + NumberComparator_NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL NumberComparator = 6 34 + ) 35 + 36 + // Enum value maps for NumberComparator. 37 + var ( 38 + NumberComparator_name = map[int32]string{ 39 + 0: "NUMBER_COMPARATOR_UNSPECIFIED", 40 + 1: "NUMBER_COMPARATOR_EQUAL", 41 + 2: "NUMBER_COMPARATOR_NOT_EQUAL", 42 + 3: "NUMBER_COMPARATOR_GREATER_THAN", 43 + 4: "NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL", 44 + 5: "NUMBER_COMPARATOR_LESS_THAN", 45 + 6: "NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL", 46 + } 47 + NumberComparator_value = map[string]int32{ 48 + "NUMBER_COMPARATOR_UNSPECIFIED": 0, 49 + "NUMBER_COMPARATOR_EQUAL": 1, 50 + "NUMBER_COMPARATOR_NOT_EQUAL": 2, 51 + "NUMBER_COMPARATOR_GREATER_THAN": 3, 52 + "NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL": 4, 53 + "NUMBER_COMPARATOR_LESS_THAN": 5, 54 + "NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL": 6, 55 + } 56 + ) 57 + 58 + func (x NumberComparator) Enum() *NumberComparator { 59 + p := new(NumberComparator) 60 + *p = x 61 + return p 62 + } 63 + 64 + func (x NumberComparator) String() string { 65 + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 66 + } 67 + 68 + func (NumberComparator) Descriptor() protoreflect.EnumDescriptor { 69 + return file_private_location_v1_assertions_proto_enumTypes[0].Descriptor() 70 + } 71 + 72 + func (NumberComparator) Type() protoreflect.EnumType { 73 + return &file_private_location_v1_assertions_proto_enumTypes[0] 74 + } 75 + 76 + func (x NumberComparator) Number() protoreflect.EnumNumber { 77 + return protoreflect.EnumNumber(x) 78 + } 79 + 80 + // Deprecated: Use NumberComparator.Descriptor instead. 81 + func (NumberComparator) EnumDescriptor() ([]byte, []int) { 82 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{0} 83 + } 84 + 85 + type StringComparator int32 86 + 87 + const ( 88 + StringComparator_STRING_COMPARATOR_UNSPECIFIED StringComparator = 0 89 + StringComparator_STRING_COMPARATOR_CONTAINS StringComparator = 1 90 + StringComparator_STRING_COMPARATOR_NOT_CONTAINS StringComparator = 2 91 + StringComparator_STRING_COMPARATOR_EQUAL StringComparator = 3 92 + StringComparator_STRING_COMPARATOR_NOT_EQUAL StringComparator = 4 93 + StringComparator_STRING_COMPARATOR_EMPTY StringComparator = 5 94 + StringComparator_STRING_COMPARATOR_NOT_EMPTY StringComparator = 6 95 + StringComparator_STRING_COMPARATOR_GREATER_THAN StringComparator = 7 96 + StringComparator_STRING_COMPARATOR_GREATER_THAN_OR_EQUAL StringComparator = 8 97 + StringComparator_STRING_COMPARATOR_LESS_THAN StringComparator = 9 98 + StringComparator_STRING_COMPARATOR_LESS_THAN_OR_EQUAL StringComparator = 10 99 + ) 100 + 101 + // Enum value maps for StringComparator. 102 + var ( 103 + StringComparator_name = map[int32]string{ 104 + 0: "STRING_COMPARATOR_UNSPECIFIED", 105 + 1: "STRING_COMPARATOR_CONTAINS", 106 + 2: "STRING_COMPARATOR_NOT_CONTAINS", 107 + 3: "STRING_COMPARATOR_EQUAL", 108 + 4: "STRING_COMPARATOR_NOT_EQUAL", 109 + 5: "STRING_COMPARATOR_EMPTY", 110 + 6: "STRING_COMPARATOR_NOT_EMPTY", 111 + 7: "STRING_COMPARATOR_GREATER_THAN", 112 + 8: "STRING_COMPARATOR_GREATER_THAN_OR_EQUAL", 113 + 9: "STRING_COMPARATOR_LESS_THAN", 114 + 10: "STRING_COMPARATOR_LESS_THAN_OR_EQUAL", 115 + } 116 + StringComparator_value = map[string]int32{ 117 + "STRING_COMPARATOR_UNSPECIFIED": 0, 118 + "STRING_COMPARATOR_CONTAINS": 1, 119 + "STRING_COMPARATOR_NOT_CONTAINS": 2, 120 + "STRING_COMPARATOR_EQUAL": 3, 121 + "STRING_COMPARATOR_NOT_EQUAL": 4, 122 + "STRING_COMPARATOR_EMPTY": 5, 123 + "STRING_COMPARATOR_NOT_EMPTY": 6, 124 + "STRING_COMPARATOR_GREATER_THAN": 7, 125 + "STRING_COMPARATOR_GREATER_THAN_OR_EQUAL": 8, 126 + "STRING_COMPARATOR_LESS_THAN": 9, 127 + "STRING_COMPARATOR_LESS_THAN_OR_EQUAL": 10, 128 + } 129 + ) 130 + 131 + func (x StringComparator) Enum() *StringComparator { 132 + p := new(StringComparator) 133 + *p = x 134 + return p 135 + } 136 + 137 + func (x StringComparator) String() string { 138 + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 139 + } 140 + 141 + func (StringComparator) Descriptor() protoreflect.EnumDescriptor { 142 + return file_private_location_v1_assertions_proto_enumTypes[1].Descriptor() 143 + } 144 + 145 + func (StringComparator) Type() protoreflect.EnumType { 146 + return &file_private_location_v1_assertions_proto_enumTypes[1] 147 + } 148 + 149 + func (x StringComparator) Number() protoreflect.EnumNumber { 150 + return protoreflect.EnumNumber(x) 151 + } 152 + 153 + // Deprecated: Use StringComparator.Descriptor instead. 154 + func (StringComparator) EnumDescriptor() ([]byte, []int) { 155 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{1} 156 + } 157 + 158 + type StatusCodeAssertion struct { 159 + state protoimpl.MessageState `protogen:"open.v1"` 160 + Target int64 `protobuf:"varint,1,opt,name=target,proto3" json:"target,omitempty"` 161 + Comparator NumberComparator `protobuf:"varint,2,opt,name=comparator,proto3,enum=private_location.v1.NumberComparator" json:"comparator,omitempty"` 162 + unknownFields protoimpl.UnknownFields 163 + sizeCache protoimpl.SizeCache 164 + } 165 + 166 + func (x *StatusCodeAssertion) Reset() { 167 + *x = StatusCodeAssertion{} 168 + mi := &file_private_location_v1_assertions_proto_msgTypes[0] 169 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 170 + ms.StoreMessageInfo(mi) 171 + } 172 + 173 + func (x *StatusCodeAssertion) String() string { 174 + return protoimpl.X.MessageStringOf(x) 175 + } 176 + 177 + func (*StatusCodeAssertion) ProtoMessage() {} 178 + 179 + func (x *StatusCodeAssertion) ProtoReflect() protoreflect.Message { 180 + mi := &file_private_location_v1_assertions_proto_msgTypes[0] 181 + if x != nil { 182 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 183 + if ms.LoadMessageInfo() == nil { 184 + ms.StoreMessageInfo(mi) 185 + } 186 + return ms 187 + } 188 + return mi.MessageOf(x) 189 + } 190 + 191 + // Deprecated: Use StatusCodeAssertion.ProtoReflect.Descriptor instead. 192 + func (*StatusCodeAssertion) Descriptor() ([]byte, []int) { 193 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{0} 194 + } 195 + 196 + func (x *StatusCodeAssertion) GetTarget() int64 { 197 + if x != nil { 198 + return x.Target 199 + } 200 + return 0 201 + } 202 + 203 + func (x *StatusCodeAssertion) GetComparator() NumberComparator { 204 + if x != nil { 205 + return x.Comparator 206 + } 207 + return NumberComparator_NUMBER_COMPARATOR_UNSPECIFIED 208 + } 209 + 210 + type BodyAssertion struct { 211 + state protoimpl.MessageState `protogen:"open.v1"` 212 + Target string `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"` 213 + Comparator StringComparator `protobuf:"varint,2,opt,name=comparator,proto3,enum=private_location.v1.StringComparator" json:"comparator,omitempty"` 214 + unknownFields protoimpl.UnknownFields 215 + sizeCache protoimpl.SizeCache 216 + } 217 + 218 + func (x *BodyAssertion) Reset() { 219 + *x = BodyAssertion{} 220 + mi := &file_private_location_v1_assertions_proto_msgTypes[1] 221 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 222 + ms.StoreMessageInfo(mi) 223 + } 224 + 225 + func (x *BodyAssertion) String() string { 226 + return protoimpl.X.MessageStringOf(x) 227 + } 228 + 229 + func (*BodyAssertion) ProtoMessage() {} 230 + 231 + func (x *BodyAssertion) ProtoReflect() protoreflect.Message { 232 + mi := &file_private_location_v1_assertions_proto_msgTypes[1] 233 + if x != nil { 234 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 235 + if ms.LoadMessageInfo() == nil { 236 + ms.StoreMessageInfo(mi) 237 + } 238 + return ms 239 + } 240 + return mi.MessageOf(x) 241 + } 242 + 243 + // Deprecated: Use BodyAssertion.ProtoReflect.Descriptor instead. 244 + func (*BodyAssertion) Descriptor() ([]byte, []int) { 245 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{1} 246 + } 247 + 248 + func (x *BodyAssertion) GetTarget() string { 249 + if x != nil { 250 + return x.Target 251 + } 252 + return "" 253 + } 254 + 255 + func (x *BodyAssertion) GetComparator() StringComparator { 256 + if x != nil { 257 + return x.Comparator 258 + } 259 + return StringComparator_STRING_COMPARATOR_UNSPECIFIED 260 + } 261 + 262 + type HeaderAssertion struct { 263 + state protoimpl.MessageState `protogen:"open.v1"` 264 + Target string `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"` 265 + Comparator StringComparator `protobuf:"varint,2,opt,name=comparator,proto3,enum=private_location.v1.StringComparator" json:"comparator,omitempty"` 266 + Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` 267 + unknownFields protoimpl.UnknownFields 268 + sizeCache protoimpl.SizeCache 269 + } 270 + 271 + func (x *HeaderAssertion) Reset() { 272 + *x = HeaderAssertion{} 273 + mi := &file_private_location_v1_assertions_proto_msgTypes[2] 274 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 275 + ms.StoreMessageInfo(mi) 276 + } 277 + 278 + func (x *HeaderAssertion) String() string { 279 + return protoimpl.X.MessageStringOf(x) 280 + } 281 + 282 + func (*HeaderAssertion) ProtoMessage() {} 283 + 284 + func (x *HeaderAssertion) ProtoReflect() protoreflect.Message { 285 + mi := &file_private_location_v1_assertions_proto_msgTypes[2] 286 + if x != nil { 287 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 288 + if ms.LoadMessageInfo() == nil { 289 + ms.StoreMessageInfo(mi) 290 + } 291 + return ms 292 + } 293 + return mi.MessageOf(x) 294 + } 295 + 296 + // Deprecated: Use HeaderAssertion.ProtoReflect.Descriptor instead. 297 + func (*HeaderAssertion) Descriptor() ([]byte, []int) { 298 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{2} 299 + } 300 + 301 + func (x *HeaderAssertion) GetTarget() string { 302 + if x != nil { 303 + return x.Target 304 + } 305 + return "" 306 + } 307 + 308 + func (x *HeaderAssertion) GetComparator() StringComparator { 309 + if x != nil { 310 + return x.Comparator 311 + } 312 + return StringComparator_STRING_COMPARATOR_UNSPECIFIED 313 + } 314 + 315 + func (x *HeaderAssertion) GetKey() string { 316 + if x != nil { 317 + return x.Key 318 + } 319 + return "" 320 + } 321 + 322 + var File_private_location_v1_assertions_proto protoreflect.FileDescriptor 323 + 324 + const file_private_location_v1_assertions_proto_rawDesc = "" + 325 + "\n" + 326 + "$private_location/v1/assertions.proto\x12\x13private_location.v1\"t\n" + 327 + "\x13StatusCodeAssertion\x12\x16\n" + 328 + "\x06target\x18\x01 \x01(\x03R\x06target\x12E\n" + 329 + "\n" + 330 + "comparator\x18\x02 \x01(\x0e2%.private_location.v1.NumberComparatorR\n" + 331 + "comparator\"n\n" + 332 + "\rBodyAssertion\x12\x16\n" + 333 + "\x06target\x18\x01 \x01(\tR\x06target\x12E\n" + 334 + "\n" + 335 + "comparator\x18\x02 \x01(\x0e2%.private_location.v1.StringComparatorR\n" + 336 + "comparator\"\x82\x01\n" + 337 + "\x0fHeaderAssertion\x12\x16\n" + 338 + "\x06target\x18\x01 \x01(\tR\x06target\x12E\n" + 339 + "\n" + 340 + "comparator\x18\x02 \x01(\x0e2%.private_location.v1.StringComparatorR\n" + 341 + "comparator\x12\x10\n" + 342 + "\x03key\x18\x03 \x01(\tR\x03key*\x8f\x02\n" + 343 + "\x10NumberComparator\x12!\n" + 344 + "\x1dNUMBER_COMPARATOR_UNSPECIFIED\x10\x00\x12\x1b\n" + 345 + "\x17NUMBER_COMPARATOR_EQUAL\x10\x01\x12\x1f\n" + 346 + "\x1bNUMBER_COMPARATOR_NOT_EQUAL\x10\x02\x12\"\n" + 347 + "\x1eNUMBER_COMPARATOR_GREATER_THAN\x10\x03\x12+\n" + 348 + "'NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL\x10\x04\x12\x1f\n" + 349 + "\x1bNUMBER_COMPARATOR_LESS_THAN\x10\x05\x12(\n" + 350 + "$NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL\x10\x06*\x91\x03\n" + 351 + "\x10StringComparator\x12!\n" + 352 + "\x1dSTRING_COMPARATOR_UNSPECIFIED\x10\x00\x12\x1e\n" + 353 + "\x1aSTRING_COMPARATOR_CONTAINS\x10\x01\x12\"\n" + 354 + "\x1eSTRING_COMPARATOR_NOT_CONTAINS\x10\x02\x12\x1b\n" + 355 + "\x17STRING_COMPARATOR_EQUAL\x10\x03\x12\x1f\n" + 356 + "\x1bSTRING_COMPARATOR_NOT_EQUAL\x10\x04\x12\x1b\n" + 357 + "\x17STRING_COMPARATOR_EMPTY\x10\x05\x12\x1f\n" + 358 + "\x1bSTRING_COMPARATOR_NOT_EMPTY\x10\x06\x12\"\n" + 359 + "\x1eSTRING_COMPARATOR_GREATER_THAN\x10\a\x12+\n" + 360 + "'STRING_COMPARATOR_GREATER_THAN_OR_EQUAL\x10\b\x12\x1f\n" + 361 + "\x1bSTRING_COMPARATOR_LESS_THAN\x10\t\x12(\n" + 362 + "$STRING_COMPARATOR_LESS_THAN_OR_EQUAL\x10\n" + 363 + "BJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 364 + 365 + var ( 366 + file_private_location_v1_assertions_proto_rawDescOnce sync.Once 367 + file_private_location_v1_assertions_proto_rawDescData []byte 368 + ) 369 + 370 + func file_private_location_v1_assertions_proto_rawDescGZIP() []byte { 371 + file_private_location_v1_assertions_proto_rawDescOnce.Do(func() { 372 + file_private_location_v1_assertions_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_private_location_v1_assertions_proto_rawDesc), len(file_private_location_v1_assertions_proto_rawDesc))) 373 + }) 374 + return file_private_location_v1_assertions_proto_rawDescData 375 + } 376 + 377 + var file_private_location_v1_assertions_proto_enumTypes = make([]protoimpl.EnumInfo, 2) 378 + var file_private_location_v1_assertions_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 379 + var file_private_location_v1_assertions_proto_goTypes = []any{ 380 + (NumberComparator)(0), // 0: private_location.v1.NumberComparator 381 + (StringComparator)(0), // 1: private_location.v1.StringComparator 382 + (*StatusCodeAssertion)(nil), // 2: private_location.v1.StatusCodeAssertion 383 + (*BodyAssertion)(nil), // 3: private_location.v1.BodyAssertion 384 + (*HeaderAssertion)(nil), // 4: private_location.v1.HeaderAssertion 385 + } 386 + var file_private_location_v1_assertions_proto_depIdxs = []int32{ 387 + 0, // 0: private_location.v1.StatusCodeAssertion.comparator:type_name -> private_location.v1.NumberComparator 388 + 1, // 1: private_location.v1.BodyAssertion.comparator:type_name -> private_location.v1.StringComparator 389 + 1, // 2: private_location.v1.HeaderAssertion.comparator:type_name -> private_location.v1.StringComparator 390 + 3, // [3:3] is the sub-list for method output_type 391 + 3, // [3:3] is the sub-list for method input_type 392 + 3, // [3:3] is the sub-list for extension type_name 393 + 3, // [3:3] is the sub-list for extension extendee 394 + 0, // [0:3] is the sub-list for field type_name 395 + } 396 + 397 + func init() { file_private_location_v1_assertions_proto_init() } 398 + func file_private_location_v1_assertions_proto_init() { 399 + if File_private_location_v1_assertions_proto != nil { 400 + return 401 + } 402 + type x struct{} 403 + out := protoimpl.TypeBuilder{ 404 + File: protoimpl.DescBuilder{ 405 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 406 + RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_assertions_proto_rawDesc), len(file_private_location_v1_assertions_proto_rawDesc)), 407 + NumEnums: 2, 408 + NumMessages: 3, 409 + NumExtensions: 0, 410 + NumServices: 0, 411 + }, 412 + GoTypes: file_private_location_v1_assertions_proto_goTypes, 413 + DependencyIndexes: file_private_location_v1_assertions_proto_depIdxs, 414 + EnumInfos: file_private_location_v1_assertions_proto_enumTypes, 415 + MessageInfos: file_private_location_v1_assertions_proto_msgTypes, 416 + }.Build() 417 + File_private_location_v1_assertions_proto = out.File 418 + file_private_location_v1_assertions_proto_goTypes = nil 419 + file_private_location_v1_assertions_proto_depIdxs = nil 420 + }
+298
apps/private-location/proto/private_location/v1/http_monitor.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.36.10 4 + // protoc (unknown) 5 + // source: private_location/v1/http_monitor.proto 6 + 7 + package v1 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + reflect "reflect" 13 + sync "sync" 14 + unsafe "unsafe" 15 + ) 16 + 17 + const ( 18 + // Verify that this generated code is sufficiently up-to-date. 19 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 + // Verify that runtime/protoimpl is sufficiently up-to-date. 21 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 + ) 23 + 24 + type Headers struct { 25 + state protoimpl.MessageState `protogen:"open.v1"` 26 + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` 27 + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` 28 + unknownFields protoimpl.UnknownFields 29 + sizeCache protoimpl.SizeCache 30 + } 31 + 32 + func (x *Headers) Reset() { 33 + *x = Headers{} 34 + mi := &file_private_location_v1_http_monitor_proto_msgTypes[0] 35 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 36 + ms.StoreMessageInfo(mi) 37 + } 38 + 39 + func (x *Headers) String() string { 40 + return protoimpl.X.MessageStringOf(x) 41 + } 42 + 43 + func (*Headers) ProtoMessage() {} 44 + 45 + func (x *Headers) ProtoReflect() protoreflect.Message { 46 + mi := &file_private_location_v1_http_monitor_proto_msgTypes[0] 47 + if x != nil { 48 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 49 + if ms.LoadMessageInfo() == nil { 50 + ms.StoreMessageInfo(mi) 51 + } 52 + return ms 53 + } 54 + return mi.MessageOf(x) 55 + } 56 + 57 + // Deprecated: Use Headers.ProtoReflect.Descriptor instead. 58 + func (*Headers) Descriptor() ([]byte, []int) { 59 + return file_private_location_v1_http_monitor_proto_rawDescGZIP(), []int{0} 60 + } 61 + 62 + func (x *Headers) GetKey() string { 63 + if x != nil { 64 + return x.Key 65 + } 66 + return "" 67 + } 68 + 69 + func (x *Headers) GetValue() string { 70 + if x != nil { 71 + return x.Value 72 + } 73 + return "" 74 + } 75 + 76 + type HTTPMonitor struct { 77 + state protoimpl.MessageState `protogen:"open.v1"` 78 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 79 + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` 80 + Periodicity string `protobuf:"bytes,3,opt,name=periodicity,proto3" json:"periodicity,omitempty"` 81 + Method string `protobuf:"bytes,4,opt,name=method,proto3" json:"method,omitempty"` 82 + Body string `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"` 83 + Timeout int64 `protobuf:"varint,6,opt,name=timeout,proto3" json:"timeout,omitempty"` 84 + DegradedAt *int64 `protobuf:"varint,7,opt,name=degraded_at,json=degradedAt,proto3,oneof" json:"degraded_at,omitempty"` 85 + Retry int64 `protobuf:"varint,8,opt,name=retry,proto3" json:"retry,omitempty"` 86 + FollowRedirects bool `protobuf:"varint,9,opt,name=follow_redirects,json=followRedirects,proto3" json:"follow_redirects,omitempty"` 87 + Headers []*Headers `protobuf:"bytes,10,rep,name=headers,proto3" json:"headers,omitempty"` 88 + StatusCodeAssertions []*StatusCodeAssertion `protobuf:"bytes,11,rep,name=status_code_assertions,json=statusCodeAssertions,proto3" json:"status_code_assertions,omitempty"` 89 + BodyAssertions []*BodyAssertion `protobuf:"bytes,12,rep,name=body_assertions,json=bodyAssertions,proto3" json:"body_assertions,omitempty"` 90 + HeaderAssertions []*HeaderAssertion `protobuf:"bytes,13,rep,name=header_assertions,json=headerAssertions,proto3" json:"header_assertions,omitempty"` 91 + unknownFields protoimpl.UnknownFields 92 + sizeCache protoimpl.SizeCache 93 + } 94 + 95 + func (x *HTTPMonitor) Reset() { 96 + *x = HTTPMonitor{} 97 + mi := &file_private_location_v1_http_monitor_proto_msgTypes[1] 98 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 99 + ms.StoreMessageInfo(mi) 100 + } 101 + 102 + func (x *HTTPMonitor) String() string { 103 + return protoimpl.X.MessageStringOf(x) 104 + } 105 + 106 + func (*HTTPMonitor) ProtoMessage() {} 107 + 108 + func (x *HTTPMonitor) ProtoReflect() protoreflect.Message { 109 + mi := &file_private_location_v1_http_monitor_proto_msgTypes[1] 110 + if x != nil { 111 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 112 + if ms.LoadMessageInfo() == nil { 113 + ms.StoreMessageInfo(mi) 114 + } 115 + return ms 116 + } 117 + return mi.MessageOf(x) 118 + } 119 + 120 + // Deprecated: Use HTTPMonitor.ProtoReflect.Descriptor instead. 121 + func (*HTTPMonitor) Descriptor() ([]byte, []int) { 122 + return file_private_location_v1_http_monitor_proto_rawDescGZIP(), []int{1} 123 + } 124 + 125 + func (x *HTTPMonitor) GetId() string { 126 + if x != nil { 127 + return x.Id 128 + } 129 + return "" 130 + } 131 + 132 + func (x *HTTPMonitor) GetUrl() string { 133 + if x != nil { 134 + return x.Url 135 + } 136 + return "" 137 + } 138 + 139 + func (x *HTTPMonitor) GetPeriodicity() string { 140 + if x != nil { 141 + return x.Periodicity 142 + } 143 + return "" 144 + } 145 + 146 + func (x *HTTPMonitor) GetMethod() string { 147 + if x != nil { 148 + return x.Method 149 + } 150 + return "" 151 + } 152 + 153 + func (x *HTTPMonitor) GetBody() string { 154 + if x != nil { 155 + return x.Body 156 + } 157 + return "" 158 + } 159 + 160 + func (x *HTTPMonitor) GetTimeout() int64 { 161 + if x != nil { 162 + return x.Timeout 163 + } 164 + return 0 165 + } 166 + 167 + func (x *HTTPMonitor) GetDegradedAt() int64 { 168 + if x != nil && x.DegradedAt != nil { 169 + return *x.DegradedAt 170 + } 171 + return 0 172 + } 173 + 174 + func (x *HTTPMonitor) GetRetry() int64 { 175 + if x != nil { 176 + return x.Retry 177 + } 178 + return 0 179 + } 180 + 181 + func (x *HTTPMonitor) GetFollowRedirects() bool { 182 + if x != nil { 183 + return x.FollowRedirects 184 + } 185 + return false 186 + } 187 + 188 + func (x *HTTPMonitor) GetHeaders() []*Headers { 189 + if x != nil { 190 + return x.Headers 191 + } 192 + return nil 193 + } 194 + 195 + func (x *HTTPMonitor) GetStatusCodeAssertions() []*StatusCodeAssertion { 196 + if x != nil { 197 + return x.StatusCodeAssertions 198 + } 199 + return nil 200 + } 201 + 202 + func (x *HTTPMonitor) GetBodyAssertions() []*BodyAssertion { 203 + if x != nil { 204 + return x.BodyAssertions 205 + } 206 + return nil 207 + } 208 + 209 + func (x *HTTPMonitor) GetHeaderAssertions() []*HeaderAssertion { 210 + if x != nil { 211 + return x.HeaderAssertions 212 + } 213 + return nil 214 + } 215 + 216 + var File_private_location_v1_http_monitor_proto protoreflect.FileDescriptor 217 + 218 + const file_private_location_v1_http_monitor_proto_rawDesc = "" + 219 + "\n" + 220 + "&private_location/v1/http_monitor.proto\x12\x13private_location.v1\x1a$private_location/v1/assertions.proto\"1\n" + 221 + "\aHeaders\x12\x10\n" + 222 + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + 223 + "\x05value\x18\x02 \x01(\tR\x05value\"\xc6\x04\n" + 224 + "\vHTTPMonitor\x12\x0e\n" + 225 + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + 226 + "\x03url\x18\x02 \x01(\tR\x03url\x12 \n" + 227 + "\vperiodicity\x18\x03 \x01(\tR\vperiodicity\x12\x16\n" + 228 + "\x06method\x18\x04 \x01(\tR\x06method\x12\x12\n" + 229 + "\x04body\x18\x05 \x01(\tR\x04body\x12\x18\n" + 230 + "\atimeout\x18\x06 \x01(\x03R\atimeout\x12$\n" + 231 + "\vdegraded_at\x18\a \x01(\x03H\x00R\n" + 232 + "degradedAt\x88\x01\x01\x12\x14\n" + 233 + "\x05retry\x18\b \x01(\x03R\x05retry\x12)\n" + 234 + "\x10follow_redirects\x18\t \x01(\bR\x0ffollowRedirects\x126\n" + 235 + "\aheaders\x18\n" + 236 + " \x03(\v2\x1c.private_location.v1.HeadersR\aheaders\x12^\n" + 237 + "\x16status_code_assertions\x18\v \x03(\v2(.private_location.v1.StatusCodeAssertionR\x14statusCodeAssertions\x12K\n" + 238 + "\x0fbody_assertions\x18\f \x03(\v2\".private_location.v1.BodyAssertionR\x0ebodyAssertions\x12Q\n" + 239 + "\x11header_assertions\x18\r \x03(\v2$.private_location.v1.HeaderAssertionR\x10headerAssertionsB\x0e\n" + 240 + "\f_degraded_atBJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 241 + 242 + var ( 243 + file_private_location_v1_http_monitor_proto_rawDescOnce sync.Once 244 + file_private_location_v1_http_monitor_proto_rawDescData []byte 245 + ) 246 + 247 + func file_private_location_v1_http_monitor_proto_rawDescGZIP() []byte { 248 + file_private_location_v1_http_monitor_proto_rawDescOnce.Do(func() { 249 + file_private_location_v1_http_monitor_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_private_location_v1_http_monitor_proto_rawDesc), len(file_private_location_v1_http_monitor_proto_rawDesc))) 250 + }) 251 + return file_private_location_v1_http_monitor_proto_rawDescData 252 + } 253 + 254 + var file_private_location_v1_http_monitor_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 255 + var file_private_location_v1_http_monitor_proto_goTypes = []any{ 256 + (*Headers)(nil), // 0: private_location.v1.Headers 257 + (*HTTPMonitor)(nil), // 1: private_location.v1.HTTPMonitor 258 + (*StatusCodeAssertion)(nil), // 2: private_location.v1.StatusCodeAssertion 259 + (*BodyAssertion)(nil), // 3: private_location.v1.BodyAssertion 260 + (*HeaderAssertion)(nil), // 4: private_location.v1.HeaderAssertion 261 + } 262 + var file_private_location_v1_http_monitor_proto_depIdxs = []int32{ 263 + 0, // 0: private_location.v1.HTTPMonitor.headers:type_name -> private_location.v1.Headers 264 + 2, // 1: private_location.v1.HTTPMonitor.status_code_assertions:type_name -> private_location.v1.StatusCodeAssertion 265 + 3, // 2: private_location.v1.HTTPMonitor.body_assertions:type_name -> private_location.v1.BodyAssertion 266 + 4, // 3: private_location.v1.HTTPMonitor.header_assertions:type_name -> private_location.v1.HeaderAssertion 267 + 4, // [4:4] is the sub-list for method output_type 268 + 4, // [4:4] is the sub-list for method input_type 269 + 4, // [4:4] is the sub-list for extension type_name 270 + 4, // [4:4] is the sub-list for extension extendee 271 + 0, // [0:4] is the sub-list for field type_name 272 + } 273 + 274 + func init() { file_private_location_v1_http_monitor_proto_init() } 275 + func file_private_location_v1_http_monitor_proto_init() { 276 + if File_private_location_v1_http_monitor_proto != nil { 277 + return 278 + } 279 + file_private_location_v1_assertions_proto_init() 280 + file_private_location_v1_http_monitor_proto_msgTypes[1].OneofWrappers = []any{} 281 + type x struct{} 282 + out := protoimpl.TypeBuilder{ 283 + File: protoimpl.DescBuilder{ 284 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 285 + RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_http_monitor_proto_rawDesc), len(file_private_location_v1_http_monitor_proto_rawDesc)), 286 + NumEnums: 0, 287 + NumMessages: 2, 288 + NumExtensions: 0, 289 + NumServices: 0, 290 + }, 291 + GoTypes: file_private_location_v1_http_monitor_proto_goTypes, 292 + DependencyIndexes: file_private_location_v1_http_monitor_proto_depIdxs, 293 + MessageInfos: file_private_location_v1_http_monitor_proto_msgTypes, 294 + }.Build() 295 + File_private_location_v1_http_monitor_proto = out.File 296 + file_private_location_v1_http_monitor_proto_goTypes = nil 297 + file_private_location_v1_http_monitor_proto_depIdxs = nil 298 + }
+168
apps/private-location/proto/private_location/v1/private_location.connect.go
··· 1 + // Code generated by protoc-gen-connect-go. DO NOT EDIT. 2 + // 3 + // Source: private_location/v1/private_location.proto 4 + 5 + package v1 6 + 7 + import ( 8 + connect "connectrpc.com/connect" 9 + context "context" 10 + errors "errors" 11 + http "net/http" 12 + strings "strings" 13 + ) 14 + 15 + // This is a compile-time assertion to ensure that this generated file and the connect package are 16 + // compatible. If you get a compiler error that this constant is not defined, this code was 17 + // generated with a version of connect newer than the one compiled into your binary. You can fix the 18 + // problem by either regenerating this code with an older version of connect or updating the connect 19 + // version compiled into your binary. 20 + const _ = connect.IsAtLeastVersion1_13_0 21 + 22 + const ( 23 + // PrivateLocationServiceName is the fully-qualified name of the PrivateLocationService service. 24 + PrivateLocationServiceName = "private_location.v1.PrivateLocationService" 25 + ) 26 + 27 + // These constants are the fully-qualified names of the RPCs defined in this package. They're 28 + // exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. 29 + // 30 + // Note that these are different from the fully-qualified method names used by 31 + // google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to 32 + // reflection-formatted method names, remove the leading slash and convert the remaining slash to a 33 + // period. 34 + const ( 35 + // PrivateLocationServiceMonitorsProcedure is the fully-qualified name of the 36 + // PrivateLocationService's Monitors RPC. 37 + PrivateLocationServiceMonitorsProcedure = "/private_location.v1.PrivateLocationService/Monitors" 38 + // PrivateLocationServiceIngestTCPProcedure is the fully-qualified name of the 39 + // PrivateLocationService's IngestTCP RPC. 40 + PrivateLocationServiceIngestTCPProcedure = "/private_location.v1.PrivateLocationService/IngestTCP" 41 + // PrivateLocationServiceIngestHTTPProcedure is the fully-qualified name of the 42 + // PrivateLocationService's IngestHTTP RPC. 43 + PrivateLocationServiceIngestHTTPProcedure = "/private_location.v1.PrivateLocationService/IngestHTTP" 44 + ) 45 + 46 + // PrivateLocationServiceClient is a client for the private_location.v1.PrivateLocationService 47 + // service. 48 + type PrivateLocationServiceClient interface { 49 + Monitors(context.Context, *connect.Request[MonitorsRequest]) (*connect.Response[MonitorsResponse], error) 50 + IngestTCP(context.Context, *connect.Request[IngestTCPRequest]) (*connect.Response[IngestTCPResponse], error) 51 + IngestHTTP(context.Context, *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) 52 + } 53 + 54 + // NewPrivateLocationServiceClient constructs a client for the 55 + // private_location.v1.PrivateLocationService service. By default, it uses the Connect protocol with 56 + // the binary Protobuf Codec, asks for gzipped responses, and sends uncompressed requests. To use 57 + // the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or connect.WithGRPCWeb() options. 58 + // 59 + // The URL supplied here should be the base URL for the Connect or gRPC server (for example, 60 + // http://api.acme.com or https://acme.com/grpc). 61 + func NewPrivateLocationServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) PrivateLocationServiceClient { 62 + baseURL = strings.TrimRight(baseURL, "/") 63 + privateLocationServiceMethods := File_private_location_v1_private_location_proto.Services().ByName("PrivateLocationService").Methods() 64 + return &privateLocationServiceClient{ 65 + monitors: connect.NewClient[MonitorsRequest, MonitorsResponse]( 66 + httpClient, 67 + baseURL+PrivateLocationServiceMonitorsProcedure, 68 + connect.WithSchema(privateLocationServiceMethods.ByName("Monitors")), 69 + connect.WithClientOptions(opts...), 70 + ), 71 + ingestTCP: connect.NewClient[IngestTCPRequest, IngestTCPResponse]( 72 + httpClient, 73 + baseURL+PrivateLocationServiceIngestTCPProcedure, 74 + connect.WithSchema(privateLocationServiceMethods.ByName("IngestTCP")), 75 + connect.WithClientOptions(opts...), 76 + ), 77 + ingestHTTP: connect.NewClient[IngestHTTPRequest, IngestHTTPResponse]( 78 + httpClient, 79 + baseURL+PrivateLocationServiceIngestHTTPProcedure, 80 + connect.WithSchema(privateLocationServiceMethods.ByName("IngestHTTP")), 81 + connect.WithClientOptions(opts...), 82 + ), 83 + } 84 + } 85 + 86 + // privateLocationServiceClient implements PrivateLocationServiceClient. 87 + type privateLocationServiceClient struct { 88 + monitors *connect.Client[MonitorsRequest, MonitorsResponse] 89 + ingestTCP *connect.Client[IngestTCPRequest, IngestTCPResponse] 90 + ingestHTTP *connect.Client[IngestHTTPRequest, IngestHTTPResponse] 91 + } 92 + 93 + // Monitors calls private_location.v1.PrivateLocationService.Monitors. 94 + func (c *privateLocationServiceClient) Monitors(ctx context.Context, req *connect.Request[MonitorsRequest]) (*connect.Response[MonitorsResponse], error) { 95 + return c.monitors.CallUnary(ctx, req) 96 + } 97 + 98 + // IngestTCP calls private_location.v1.PrivateLocationService.IngestTCP. 99 + func (c *privateLocationServiceClient) IngestTCP(ctx context.Context, req *connect.Request[IngestTCPRequest]) (*connect.Response[IngestTCPResponse], error) { 100 + return c.ingestTCP.CallUnary(ctx, req) 101 + } 102 + 103 + // IngestHTTP calls private_location.v1.PrivateLocationService.IngestHTTP. 104 + func (c *privateLocationServiceClient) IngestHTTP(ctx context.Context, req *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) { 105 + return c.ingestHTTP.CallUnary(ctx, req) 106 + } 107 + 108 + // PrivateLocationServiceHandler is an implementation of the 109 + // private_location.v1.PrivateLocationService service. 110 + type PrivateLocationServiceHandler interface { 111 + Monitors(context.Context, *connect.Request[MonitorsRequest]) (*connect.Response[MonitorsResponse], error) 112 + IngestTCP(context.Context, *connect.Request[IngestTCPRequest]) (*connect.Response[IngestTCPResponse], error) 113 + IngestHTTP(context.Context, *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) 114 + } 115 + 116 + // NewPrivateLocationServiceHandler builds an HTTP handler from the service implementation. It 117 + // returns the path on which to mount the handler and the handler itself. 118 + // 119 + // By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf 120 + // and JSON codecs. They also support gzip compression. 121 + func NewPrivateLocationServiceHandler(svc PrivateLocationServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { 122 + privateLocationServiceMethods := File_private_location_v1_private_location_proto.Services().ByName("PrivateLocationService").Methods() 123 + privateLocationServiceMonitorsHandler := connect.NewUnaryHandler( 124 + PrivateLocationServiceMonitorsProcedure, 125 + svc.Monitors, 126 + connect.WithSchema(privateLocationServiceMethods.ByName("Monitors")), 127 + connect.WithHandlerOptions(opts...), 128 + ) 129 + privateLocationServiceIngestTCPHandler := connect.NewUnaryHandler( 130 + PrivateLocationServiceIngestTCPProcedure, 131 + svc.IngestTCP, 132 + connect.WithSchema(privateLocationServiceMethods.ByName("IngestTCP")), 133 + connect.WithHandlerOptions(opts...), 134 + ) 135 + privateLocationServiceIngestHTTPHandler := connect.NewUnaryHandler( 136 + PrivateLocationServiceIngestHTTPProcedure, 137 + svc.IngestHTTP, 138 + connect.WithSchema(privateLocationServiceMethods.ByName("IngestHTTP")), 139 + connect.WithHandlerOptions(opts...), 140 + ) 141 + return "/private_location.v1.PrivateLocationService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 142 + switch r.URL.Path { 143 + case PrivateLocationServiceMonitorsProcedure: 144 + privateLocationServiceMonitorsHandler.ServeHTTP(w, r) 145 + case PrivateLocationServiceIngestTCPProcedure: 146 + privateLocationServiceIngestTCPHandler.ServeHTTP(w, r) 147 + case PrivateLocationServiceIngestHTTPProcedure: 148 + privateLocationServiceIngestHTTPHandler.ServeHTTP(w, r) 149 + default: 150 + http.NotFound(w, r) 151 + } 152 + }) 153 + } 154 + 155 + // UnimplementedPrivateLocationServiceHandler returns CodeUnimplemented from all methods. 156 + type UnimplementedPrivateLocationServiceHandler struct{} 157 + 158 + func (UnimplementedPrivateLocationServiceHandler) Monitors(context.Context, *connect.Request[MonitorsRequest]) (*connect.Response[MonitorsResponse], error) { 159 + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("private_location.v1.PrivateLocationService.Monitors is not implemented")) 160 + } 161 + 162 + func (UnimplementedPrivateLocationServiceHandler) IngestTCP(context.Context, *connect.Request[IngestTCPRequest]) (*connect.Response[IngestTCPResponse], error) { 163 + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("private_location.v1.PrivateLocationService.IngestTCP is not implemented")) 164 + } 165 + 166 + func (UnimplementedPrivateLocationServiceHandler) IngestHTTP(context.Context, *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) { 167 + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("private_location.v1.PrivateLocationService.IngestHTTP is not implemented")) 168 + }
+549
apps/private-location/proto/private_location/v1/private_location.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.36.10 4 + // protoc (unknown) 5 + // source: private_location/v1/private_location.proto 6 + 7 + package v1 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + reflect "reflect" 13 + sync "sync" 14 + unsafe "unsafe" 15 + ) 16 + 17 + const ( 18 + // Verify that this generated code is sufficiently up-to-date. 19 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 + // Verify that runtime/protoimpl is sufficiently up-to-date. 21 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 + ) 23 + 24 + type MonitorsRequest struct { 25 + state protoimpl.MessageState `protogen:"open.v1"` 26 + unknownFields protoimpl.UnknownFields 27 + sizeCache protoimpl.SizeCache 28 + } 29 + 30 + func (x *MonitorsRequest) Reset() { 31 + *x = MonitorsRequest{} 32 + mi := &file_private_location_v1_private_location_proto_msgTypes[0] 33 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 34 + ms.StoreMessageInfo(mi) 35 + } 36 + 37 + func (x *MonitorsRequest) String() string { 38 + return protoimpl.X.MessageStringOf(x) 39 + } 40 + 41 + func (*MonitorsRequest) ProtoMessage() {} 42 + 43 + func (x *MonitorsRequest) ProtoReflect() protoreflect.Message { 44 + mi := &file_private_location_v1_private_location_proto_msgTypes[0] 45 + if x != nil { 46 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 47 + if ms.LoadMessageInfo() == nil { 48 + ms.StoreMessageInfo(mi) 49 + } 50 + return ms 51 + } 52 + return mi.MessageOf(x) 53 + } 54 + 55 + // Deprecated: Use MonitorsRequest.ProtoReflect.Descriptor instead. 56 + func (*MonitorsRequest) Descriptor() ([]byte, []int) { 57 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{0} 58 + } 59 + 60 + type MonitorsResponse struct { 61 + state protoimpl.MessageState `protogen:"open.v1"` 62 + HttpMonitors []*HTTPMonitor `protobuf:"bytes,1,rep,name=http_monitors,json=httpMonitors,proto3" json:"http_monitors,omitempty"` 63 + TcpMonitors []*TCPMonitor `protobuf:"bytes,2,rep,name=tcp_monitors,json=tcpMonitors,proto3" json:"tcp_monitors,omitempty"` 64 + unknownFields protoimpl.UnknownFields 65 + sizeCache protoimpl.SizeCache 66 + } 67 + 68 + func (x *MonitorsResponse) Reset() { 69 + *x = MonitorsResponse{} 70 + mi := &file_private_location_v1_private_location_proto_msgTypes[1] 71 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 72 + ms.StoreMessageInfo(mi) 73 + } 74 + 75 + func (x *MonitorsResponse) String() string { 76 + return protoimpl.X.MessageStringOf(x) 77 + } 78 + 79 + func (*MonitorsResponse) ProtoMessage() {} 80 + 81 + func (x *MonitorsResponse) ProtoReflect() protoreflect.Message { 82 + mi := &file_private_location_v1_private_location_proto_msgTypes[1] 83 + if x != nil { 84 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 85 + if ms.LoadMessageInfo() == nil { 86 + ms.StoreMessageInfo(mi) 87 + } 88 + return ms 89 + } 90 + return mi.MessageOf(x) 91 + } 92 + 93 + // Deprecated: Use MonitorsResponse.ProtoReflect.Descriptor instead. 94 + func (*MonitorsResponse) Descriptor() ([]byte, []int) { 95 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{1} 96 + } 97 + 98 + func (x *MonitorsResponse) GetHttpMonitors() []*HTTPMonitor { 99 + if x != nil { 100 + return x.HttpMonitors 101 + } 102 + return nil 103 + } 104 + 105 + func (x *MonitorsResponse) GetTcpMonitors() []*TCPMonitor { 106 + if x != nil { 107 + return x.TcpMonitors 108 + } 109 + return nil 110 + } 111 + 112 + type IngestTCPRequest struct { 113 + state protoimpl.MessageState `protogen:"open.v1"` 114 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 115 + MonitorId string `protobuf:"bytes,2,opt,name=monitorId,proto3" json:"monitorId,omitempty"` 116 + Latency int64 `protobuf:"varint,3,opt,name=latency,proto3" json:"latency,omitempty"` 117 + Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` 118 + CronTimestamp int64 `protobuf:"varint,5,opt,name=cronTimestamp,proto3" json:"cronTimestamp,omitempty"` 119 + Uri string `protobuf:"bytes,6,opt,name=uri,proto3" json:"uri,omitempty"` 120 + Message string `protobuf:"bytes,7,opt,name=message,proto3" json:"message,omitempty"` 121 + RequestStatus string `protobuf:"bytes,8,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` 122 + Error int64 `protobuf:"varint,9,opt,name=error,proto3" json:"error,omitempty"` 123 + Timing string `protobuf:"bytes,10,opt,name=timing,proto3" json:"timing,omitempty"` 124 + unknownFields protoimpl.UnknownFields 125 + sizeCache protoimpl.SizeCache 126 + } 127 + 128 + func (x *IngestTCPRequest) Reset() { 129 + *x = IngestTCPRequest{} 130 + mi := &file_private_location_v1_private_location_proto_msgTypes[2] 131 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 132 + ms.StoreMessageInfo(mi) 133 + } 134 + 135 + func (x *IngestTCPRequest) String() string { 136 + return protoimpl.X.MessageStringOf(x) 137 + } 138 + 139 + func (*IngestTCPRequest) ProtoMessage() {} 140 + 141 + func (x *IngestTCPRequest) ProtoReflect() protoreflect.Message { 142 + mi := &file_private_location_v1_private_location_proto_msgTypes[2] 143 + if x != nil { 144 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 145 + if ms.LoadMessageInfo() == nil { 146 + ms.StoreMessageInfo(mi) 147 + } 148 + return ms 149 + } 150 + return mi.MessageOf(x) 151 + } 152 + 153 + // Deprecated: Use IngestTCPRequest.ProtoReflect.Descriptor instead. 154 + func (*IngestTCPRequest) Descriptor() ([]byte, []int) { 155 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{2} 156 + } 157 + 158 + func (x *IngestTCPRequest) GetId() string { 159 + if x != nil { 160 + return x.Id 161 + } 162 + return "" 163 + } 164 + 165 + func (x *IngestTCPRequest) GetMonitorId() string { 166 + if x != nil { 167 + return x.MonitorId 168 + } 169 + return "" 170 + } 171 + 172 + func (x *IngestTCPRequest) GetLatency() int64 { 173 + if x != nil { 174 + return x.Latency 175 + } 176 + return 0 177 + } 178 + 179 + func (x *IngestTCPRequest) GetTimestamp() int64 { 180 + if x != nil { 181 + return x.Timestamp 182 + } 183 + return 0 184 + } 185 + 186 + func (x *IngestTCPRequest) GetCronTimestamp() int64 { 187 + if x != nil { 188 + return x.CronTimestamp 189 + } 190 + return 0 191 + } 192 + 193 + func (x *IngestTCPRequest) GetUri() string { 194 + if x != nil { 195 + return x.Uri 196 + } 197 + return "" 198 + } 199 + 200 + func (x *IngestTCPRequest) GetMessage() string { 201 + if x != nil { 202 + return x.Message 203 + } 204 + return "" 205 + } 206 + 207 + func (x *IngestTCPRequest) GetRequestStatus() string { 208 + if x != nil { 209 + return x.RequestStatus 210 + } 211 + return "" 212 + } 213 + 214 + func (x *IngestTCPRequest) GetError() int64 { 215 + if x != nil { 216 + return x.Error 217 + } 218 + return 0 219 + } 220 + 221 + func (x *IngestTCPRequest) GetTiming() string { 222 + if x != nil { 223 + return x.Timing 224 + } 225 + return "" 226 + } 227 + 228 + type IngestTCPResponse struct { 229 + state protoimpl.MessageState `protogen:"open.v1"` 230 + unknownFields protoimpl.UnknownFields 231 + sizeCache protoimpl.SizeCache 232 + } 233 + 234 + func (x *IngestTCPResponse) Reset() { 235 + *x = IngestTCPResponse{} 236 + mi := &file_private_location_v1_private_location_proto_msgTypes[3] 237 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 238 + ms.StoreMessageInfo(mi) 239 + } 240 + 241 + func (x *IngestTCPResponse) String() string { 242 + return protoimpl.X.MessageStringOf(x) 243 + } 244 + 245 + func (*IngestTCPResponse) ProtoMessage() {} 246 + 247 + func (x *IngestTCPResponse) ProtoReflect() protoreflect.Message { 248 + mi := &file_private_location_v1_private_location_proto_msgTypes[3] 249 + if x != nil { 250 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 251 + if ms.LoadMessageInfo() == nil { 252 + ms.StoreMessageInfo(mi) 253 + } 254 + return ms 255 + } 256 + return mi.MessageOf(x) 257 + } 258 + 259 + // Deprecated: Use IngestTCPResponse.ProtoReflect.Descriptor instead. 260 + func (*IngestTCPResponse) Descriptor() ([]byte, []int) { 261 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{3} 262 + } 263 + 264 + type IngestHTTPRequest struct { 265 + state protoimpl.MessageState `protogen:"open.v1"` 266 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 267 + MonitorId string `protobuf:"bytes,2,opt,name=monitorId,proto3" json:"monitorId,omitempty"` 268 + Latency int64 `protobuf:"varint,3,opt,name=latency,proto3" json:"latency,omitempty"` 269 + Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` 270 + CronTimestamp int64 `protobuf:"varint,5,opt,name=cronTimestamp,proto3" json:"cronTimestamp,omitempty"` 271 + Url string `protobuf:"bytes,6,opt,name=url,proto3" json:"url,omitempty"` 272 + RequestStatus string `protobuf:"bytes,7,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` 273 + Message string `protobuf:"bytes,8,opt,name=message,proto3" json:"message,omitempty"` 274 + Body string `protobuf:"bytes,9,opt,name=body,proto3" json:"body,omitempty"` 275 + Headers string `protobuf:"bytes,10,opt,name=headers,proto3" json:"headers,omitempty"` 276 + Timing string `protobuf:"bytes,11,opt,name=timing,proto3" json:"timing,omitempty"` 277 + StatusCode int64 `protobuf:"varint,12,opt,name=statusCode,proto3" json:"statusCode,omitempty"` 278 + Error int64 `protobuf:"varint,13,opt,name=error,proto3" json:"error,omitempty"` 279 + unknownFields protoimpl.UnknownFields 280 + sizeCache protoimpl.SizeCache 281 + } 282 + 283 + func (x *IngestHTTPRequest) Reset() { 284 + *x = IngestHTTPRequest{} 285 + mi := &file_private_location_v1_private_location_proto_msgTypes[4] 286 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 287 + ms.StoreMessageInfo(mi) 288 + } 289 + 290 + func (x *IngestHTTPRequest) String() string { 291 + return protoimpl.X.MessageStringOf(x) 292 + } 293 + 294 + func (*IngestHTTPRequest) ProtoMessage() {} 295 + 296 + func (x *IngestHTTPRequest) ProtoReflect() protoreflect.Message { 297 + mi := &file_private_location_v1_private_location_proto_msgTypes[4] 298 + if x != nil { 299 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 300 + if ms.LoadMessageInfo() == nil { 301 + ms.StoreMessageInfo(mi) 302 + } 303 + return ms 304 + } 305 + return mi.MessageOf(x) 306 + } 307 + 308 + // Deprecated: Use IngestHTTPRequest.ProtoReflect.Descriptor instead. 309 + func (*IngestHTTPRequest) Descriptor() ([]byte, []int) { 310 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{4} 311 + } 312 + 313 + func (x *IngestHTTPRequest) GetId() string { 314 + if x != nil { 315 + return x.Id 316 + } 317 + return "" 318 + } 319 + 320 + func (x *IngestHTTPRequest) GetMonitorId() string { 321 + if x != nil { 322 + return x.MonitorId 323 + } 324 + return "" 325 + } 326 + 327 + func (x *IngestHTTPRequest) GetLatency() int64 { 328 + if x != nil { 329 + return x.Latency 330 + } 331 + return 0 332 + } 333 + 334 + func (x *IngestHTTPRequest) GetTimestamp() int64 { 335 + if x != nil { 336 + return x.Timestamp 337 + } 338 + return 0 339 + } 340 + 341 + func (x *IngestHTTPRequest) GetCronTimestamp() int64 { 342 + if x != nil { 343 + return x.CronTimestamp 344 + } 345 + return 0 346 + } 347 + 348 + func (x *IngestHTTPRequest) GetUrl() string { 349 + if x != nil { 350 + return x.Url 351 + } 352 + return "" 353 + } 354 + 355 + func (x *IngestHTTPRequest) GetRequestStatus() string { 356 + if x != nil { 357 + return x.RequestStatus 358 + } 359 + return "" 360 + } 361 + 362 + func (x *IngestHTTPRequest) GetMessage() string { 363 + if x != nil { 364 + return x.Message 365 + } 366 + return "" 367 + } 368 + 369 + func (x *IngestHTTPRequest) GetBody() string { 370 + if x != nil { 371 + return x.Body 372 + } 373 + return "" 374 + } 375 + 376 + func (x *IngestHTTPRequest) GetHeaders() string { 377 + if x != nil { 378 + return x.Headers 379 + } 380 + return "" 381 + } 382 + 383 + func (x *IngestHTTPRequest) GetTiming() string { 384 + if x != nil { 385 + return x.Timing 386 + } 387 + return "" 388 + } 389 + 390 + func (x *IngestHTTPRequest) GetStatusCode() int64 { 391 + if x != nil { 392 + return x.StatusCode 393 + } 394 + return 0 395 + } 396 + 397 + func (x *IngestHTTPRequest) GetError() int64 { 398 + if x != nil { 399 + return x.Error 400 + } 401 + return 0 402 + } 403 + 404 + type IngestHTTPResponse struct { 405 + state protoimpl.MessageState `protogen:"open.v1"` 406 + unknownFields protoimpl.UnknownFields 407 + sizeCache protoimpl.SizeCache 408 + } 409 + 410 + func (x *IngestHTTPResponse) Reset() { 411 + *x = IngestHTTPResponse{} 412 + mi := &file_private_location_v1_private_location_proto_msgTypes[5] 413 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 414 + ms.StoreMessageInfo(mi) 415 + } 416 + 417 + func (x *IngestHTTPResponse) String() string { 418 + return protoimpl.X.MessageStringOf(x) 419 + } 420 + 421 + func (*IngestHTTPResponse) ProtoMessage() {} 422 + 423 + func (x *IngestHTTPResponse) ProtoReflect() protoreflect.Message { 424 + mi := &file_private_location_v1_private_location_proto_msgTypes[5] 425 + if x != nil { 426 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 427 + if ms.LoadMessageInfo() == nil { 428 + ms.StoreMessageInfo(mi) 429 + } 430 + return ms 431 + } 432 + return mi.MessageOf(x) 433 + } 434 + 435 + // Deprecated: Use IngestHTTPResponse.ProtoReflect.Descriptor instead. 436 + func (*IngestHTTPResponse) Descriptor() ([]byte, []int) { 437 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{5} 438 + } 439 + 440 + var File_private_location_v1_private_location_proto protoreflect.FileDescriptor 441 + 442 + const file_private_location_v1_private_location_proto_rawDesc = "" + 443 + "\n" + 444 + "*private_location/v1/private_location.proto\x12\x13private_location.v1\x1a&private_location/v1/http_monitor.proto\x1a%private_location/v1/tcp_monitor.proto\"\x11\n" + 445 + "\x0fMonitorsRequest\"\x9d\x01\n" + 446 + "\x10MonitorsResponse\x12E\n" + 447 + "\rhttp_monitors\x18\x01 \x03(\v2 .private_location.v1.HTTPMonitorR\fhttpMonitors\x12B\n" + 448 + "\ftcp_monitors\x18\x02 \x03(\v2\x1f.private_location.v1.TCPMonitorR\vtcpMonitors\"\x9e\x02\n" + 449 + "\x10IngestTCPRequest\x12\x0e\n" + 450 + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + 451 + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + 452 + "\alatency\x18\x03 \x01(\x03R\alatency\x12\x1c\n" + 453 + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12$\n" + 454 + "\rcronTimestamp\x18\x05 \x01(\x03R\rcronTimestamp\x12\x10\n" + 455 + "\x03uri\x18\x06 \x01(\tR\x03uri\x12\x18\n" + 456 + "\amessage\x18\a \x01(\tR\amessage\x12$\n" + 457 + "\rrequestStatus\x18\b \x01(\tR\rrequestStatus\x12\x14\n" + 458 + "\x05error\x18\t \x01(\x03R\x05error\x12\x16\n" + 459 + "\x06timing\x18\n" + 460 + " \x01(\tR\x06timing\"\x13\n" + 461 + "\x11IngestTCPResponse\"\xed\x02\n" + 462 + "\x11IngestHTTPRequest\x12\x0e\n" + 463 + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + 464 + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + 465 + "\alatency\x18\x03 \x01(\x03R\alatency\x12\x1c\n" + 466 + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12$\n" + 467 + "\rcronTimestamp\x18\x05 \x01(\x03R\rcronTimestamp\x12\x10\n" + 468 + "\x03url\x18\x06 \x01(\tR\x03url\x12$\n" + 469 + "\rrequestStatus\x18\a \x01(\tR\rrequestStatus\x12\x18\n" + 470 + "\amessage\x18\b \x01(\tR\amessage\x12\x12\n" + 471 + "\x04body\x18\t \x01(\tR\x04body\x12\x18\n" + 472 + "\aheaders\x18\n" + 473 + " \x01(\tR\aheaders\x12\x16\n" + 474 + "\x06timing\x18\v \x01(\tR\x06timing\x12\x1e\n" + 475 + "\n" + 476 + "statusCode\x18\f \x01(\x03R\n" + 477 + "statusCode\x12\x14\n" + 478 + "\x05error\x18\r \x01(\x03R\x05error\"\x14\n" + 479 + "\x12IngestHTTPResponse2\xb2\x02\n" + 480 + "\x16PrivateLocationService\x12Y\n" + 481 + "\bMonitors\x12$.private_location.v1.MonitorsRequest\x1a%.private_location.v1.MonitorsResponse\"\x00\x12\\\n" + 482 + "\tIngestTCP\x12%.private_location.v1.IngestTCPRequest\x1a&.private_location.v1.IngestTCPResponse\"\x00\x12_\n" + 483 + "\n" + 484 + "IngestHTTP\x12&.private_location.v1.IngestHTTPRequest\x1a'.private_location.v1.IngestHTTPResponse\"\x00BJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 485 + 486 + var ( 487 + file_private_location_v1_private_location_proto_rawDescOnce sync.Once 488 + file_private_location_v1_private_location_proto_rawDescData []byte 489 + ) 490 + 491 + func file_private_location_v1_private_location_proto_rawDescGZIP() []byte { 492 + file_private_location_v1_private_location_proto_rawDescOnce.Do(func() { 493 + file_private_location_v1_private_location_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_private_location_v1_private_location_proto_rawDesc), len(file_private_location_v1_private_location_proto_rawDesc))) 494 + }) 495 + return file_private_location_v1_private_location_proto_rawDescData 496 + } 497 + 498 + var file_private_location_v1_private_location_proto_msgTypes = make([]protoimpl.MessageInfo, 6) 499 + var file_private_location_v1_private_location_proto_goTypes = []any{ 500 + (*MonitorsRequest)(nil), // 0: private_location.v1.MonitorsRequest 501 + (*MonitorsResponse)(nil), // 1: private_location.v1.MonitorsResponse 502 + (*IngestTCPRequest)(nil), // 2: private_location.v1.IngestTCPRequest 503 + (*IngestTCPResponse)(nil), // 3: private_location.v1.IngestTCPResponse 504 + (*IngestHTTPRequest)(nil), // 4: private_location.v1.IngestHTTPRequest 505 + (*IngestHTTPResponse)(nil), // 5: private_location.v1.IngestHTTPResponse 506 + (*HTTPMonitor)(nil), // 6: private_location.v1.HTTPMonitor 507 + (*TCPMonitor)(nil), // 7: private_location.v1.TCPMonitor 508 + } 509 + var file_private_location_v1_private_location_proto_depIdxs = []int32{ 510 + 6, // 0: private_location.v1.MonitorsResponse.http_monitors:type_name -> private_location.v1.HTTPMonitor 511 + 7, // 1: private_location.v1.MonitorsResponse.tcp_monitors:type_name -> private_location.v1.TCPMonitor 512 + 0, // 2: private_location.v1.PrivateLocationService.Monitors:input_type -> private_location.v1.MonitorsRequest 513 + 2, // 3: private_location.v1.PrivateLocationService.IngestTCP:input_type -> private_location.v1.IngestTCPRequest 514 + 4, // 4: private_location.v1.PrivateLocationService.IngestHTTP:input_type -> private_location.v1.IngestHTTPRequest 515 + 1, // 5: private_location.v1.PrivateLocationService.Monitors:output_type -> private_location.v1.MonitorsResponse 516 + 3, // 6: private_location.v1.PrivateLocationService.IngestTCP:output_type -> private_location.v1.IngestTCPResponse 517 + 5, // 7: private_location.v1.PrivateLocationService.IngestHTTP:output_type -> private_location.v1.IngestHTTPResponse 518 + 5, // [5:8] is the sub-list for method output_type 519 + 2, // [2:5] is the sub-list for method input_type 520 + 2, // [2:2] is the sub-list for extension type_name 521 + 2, // [2:2] is the sub-list for extension extendee 522 + 0, // [0:2] is the sub-list for field type_name 523 + } 524 + 525 + func init() { file_private_location_v1_private_location_proto_init() } 526 + func file_private_location_v1_private_location_proto_init() { 527 + if File_private_location_v1_private_location_proto != nil { 528 + return 529 + } 530 + file_private_location_v1_http_monitor_proto_init() 531 + file_private_location_v1_tcp_monitor_proto_init() 532 + type x struct{} 533 + out := protoimpl.TypeBuilder{ 534 + File: protoimpl.DescBuilder{ 535 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 536 + RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_private_location_proto_rawDesc), len(file_private_location_v1_private_location_proto_rawDesc)), 537 + NumEnums: 0, 538 + NumMessages: 6, 539 + NumExtensions: 0, 540 + NumServices: 1, 541 + }, 542 + GoTypes: file_private_location_v1_private_location_proto_goTypes, 543 + DependencyIndexes: file_private_location_v1_private_location_proto_depIdxs, 544 + MessageInfos: file_private_location_v1_private_location_proto_msgTypes, 545 + }.Build() 546 + File_private_location_v1_private_location_proto = out.File 547 + file_private_location_v1_private_location_proto_goTypes = nil 548 + file_private_location_v1_private_location_proto_depIdxs = nil 549 + }
+171
apps/private-location/proto/private_location/v1/tcp_monitor.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.36.10 4 + // protoc (unknown) 5 + // source: private_location/v1/tcp_monitor.proto 6 + 7 + package v1 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + reflect "reflect" 13 + sync "sync" 14 + unsafe "unsafe" 15 + ) 16 + 17 + const ( 18 + // Verify that this generated code is sufficiently up-to-date. 19 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 + // Verify that runtime/protoimpl is sufficiently up-to-date. 21 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 + ) 23 + 24 + type TCPMonitor struct { 25 + state protoimpl.MessageState `protogen:"open.v1"` 26 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 27 + Uri string `protobuf:"bytes,2,opt,name=uri,proto3" json:"uri,omitempty"` 28 + Timeout int64 `protobuf:"varint,3,opt,name=timeout,proto3" json:"timeout,omitempty"` 29 + DegradedAt *int64 `protobuf:"varint,4,opt,name=degraded_at,json=degradedAt,proto3,oneof" json:"degraded_at,omitempty"` 30 + Periodicity string `protobuf:"bytes,5,opt,name=periodicity,proto3" json:"periodicity,omitempty"` 31 + Retry int64 `protobuf:"varint,6,opt,name=retry,proto3" json:"retry,omitempty"` 32 + unknownFields protoimpl.UnknownFields 33 + sizeCache protoimpl.SizeCache 34 + } 35 + 36 + func (x *TCPMonitor) Reset() { 37 + *x = TCPMonitor{} 38 + mi := &file_private_location_v1_tcp_monitor_proto_msgTypes[0] 39 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 40 + ms.StoreMessageInfo(mi) 41 + } 42 + 43 + func (x *TCPMonitor) String() string { 44 + return protoimpl.X.MessageStringOf(x) 45 + } 46 + 47 + func (*TCPMonitor) ProtoMessage() {} 48 + 49 + func (x *TCPMonitor) ProtoReflect() protoreflect.Message { 50 + mi := &file_private_location_v1_tcp_monitor_proto_msgTypes[0] 51 + if x != nil { 52 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 53 + if ms.LoadMessageInfo() == nil { 54 + ms.StoreMessageInfo(mi) 55 + } 56 + return ms 57 + } 58 + return mi.MessageOf(x) 59 + } 60 + 61 + // Deprecated: Use TCPMonitor.ProtoReflect.Descriptor instead. 62 + func (*TCPMonitor) Descriptor() ([]byte, []int) { 63 + return file_private_location_v1_tcp_monitor_proto_rawDescGZIP(), []int{0} 64 + } 65 + 66 + func (x *TCPMonitor) GetId() string { 67 + if x != nil { 68 + return x.Id 69 + } 70 + return "" 71 + } 72 + 73 + func (x *TCPMonitor) GetUri() string { 74 + if x != nil { 75 + return x.Uri 76 + } 77 + return "" 78 + } 79 + 80 + func (x *TCPMonitor) GetTimeout() int64 { 81 + if x != nil { 82 + return x.Timeout 83 + } 84 + return 0 85 + } 86 + 87 + func (x *TCPMonitor) GetDegradedAt() int64 { 88 + if x != nil && x.DegradedAt != nil { 89 + return *x.DegradedAt 90 + } 91 + return 0 92 + } 93 + 94 + func (x *TCPMonitor) GetPeriodicity() string { 95 + if x != nil { 96 + return x.Periodicity 97 + } 98 + return "" 99 + } 100 + 101 + func (x *TCPMonitor) GetRetry() int64 { 102 + if x != nil { 103 + return x.Retry 104 + } 105 + return 0 106 + } 107 + 108 + var File_private_location_v1_tcp_monitor_proto protoreflect.FileDescriptor 109 + 110 + const file_private_location_v1_tcp_monitor_proto_rawDesc = "" + 111 + "\n" + 112 + "%private_location/v1/tcp_monitor.proto\x12\x13private_location.v1\"\xb6\x01\n" + 113 + "\n" + 114 + "TCPMonitor\x12\x0e\n" + 115 + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + 116 + "\x03uri\x18\x02 \x01(\tR\x03uri\x12\x18\n" + 117 + "\atimeout\x18\x03 \x01(\x03R\atimeout\x12$\n" + 118 + "\vdegraded_at\x18\x04 \x01(\x03H\x00R\n" + 119 + "degradedAt\x88\x01\x01\x12 \n" + 120 + "\vperiodicity\x18\x05 \x01(\tR\vperiodicity\x12\x14\n" + 121 + "\x05retry\x18\x06 \x01(\x03R\x05retryB\x0e\n" + 122 + "\f_degraded_atBJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 123 + 124 + var ( 125 + file_private_location_v1_tcp_monitor_proto_rawDescOnce sync.Once 126 + file_private_location_v1_tcp_monitor_proto_rawDescData []byte 127 + ) 128 + 129 + func file_private_location_v1_tcp_monitor_proto_rawDescGZIP() []byte { 130 + file_private_location_v1_tcp_monitor_proto_rawDescOnce.Do(func() { 131 + file_private_location_v1_tcp_monitor_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_private_location_v1_tcp_monitor_proto_rawDesc), len(file_private_location_v1_tcp_monitor_proto_rawDesc))) 132 + }) 133 + return file_private_location_v1_tcp_monitor_proto_rawDescData 134 + } 135 + 136 + var file_private_location_v1_tcp_monitor_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 137 + var file_private_location_v1_tcp_monitor_proto_goTypes = []any{ 138 + (*TCPMonitor)(nil), // 0: private_location.v1.TCPMonitor 139 + } 140 + var file_private_location_v1_tcp_monitor_proto_depIdxs = []int32{ 141 + 0, // [0:0] is the sub-list for method output_type 142 + 0, // [0:0] is the sub-list for method input_type 143 + 0, // [0:0] is the sub-list for extension type_name 144 + 0, // [0:0] is the sub-list for extension extendee 145 + 0, // [0:0] is the sub-list for field type_name 146 + } 147 + 148 + func init() { file_private_location_v1_tcp_monitor_proto_init() } 149 + func file_private_location_v1_tcp_monitor_proto_init() { 150 + if File_private_location_v1_tcp_monitor_proto != nil { 151 + return 152 + } 153 + file_private_location_v1_tcp_monitor_proto_msgTypes[0].OneofWrappers = []any{} 154 + type x struct{} 155 + out := protoimpl.TypeBuilder{ 156 + File: protoimpl.DescBuilder{ 157 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 158 + RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_tcp_monitor_proto_rawDesc), len(file_private_location_v1_tcp_monitor_proto_rawDesc)), 159 + NumEnums: 0, 160 + NumMessages: 1, 161 + NumExtensions: 0, 162 + NumServices: 0, 163 + }, 164 + GoTypes: file_private_location_v1_tcp_monitor_proto_goTypes, 165 + DependencyIndexes: file_private_location_v1_tcp_monitor_proto_depIdxs, 166 + MessageInfos: file_private_location_v1_tcp_monitor_proto_msgTypes, 167 + }.Build() 168 + File_private_location_v1_tcp_monitor_proto = out.File 169 + file_private_location_v1_tcp_monitor_proto_goTypes = nil 170 + file_private_location_v1_tcp_monitor_proto_depIdxs = nil 171 + }
+1 -1
apps/web/src/app/(pages)/(content)/play/checker/_components/informations.tsx
··· 5 5 const TOTAL_REGIONS = Object.keys(regionDict).length; 6 6 const TOTAL_PROVIDERS = Object.keys(regionDict).reduce((acc, region) => { 7 7 return acc.add(regionDict[region as keyof typeof regionDict].provider); 8 - }, new Set<"fly" | "koyeb" | "railway">()); 8 + }, new Set<"fly" | "koyeb" | "railway" | "private">()); 9 9 10 10 export function Informations() { 11 11 return (
+2 -2
apps/web/src/components/monitor-charts/utils.tsx
··· 1 - import { regionDict } from "@openstatus/regions"; 1 + import { getRegionInfo, regionDict } from "@openstatus/regions"; 2 2 3 3 import type { Period, Quantile } from "@/lib/monitor/utils"; 4 4 import type { ResponseGraph } from "@/lib/tb"; ··· 24 24 (acc, curr) => { 25 25 const { timestamp, region } = curr; 26 26 const latency = curr[`${quantile}Latency`]; 27 - const { flag, code, location } = regionDict[region]; 27 + const { flag, code, location } = getRegionInfo(region); 28 28 const fullNameRegion = `${code}`; 29 29 regions[fullNameRegion] = { flag, code, location }; // to get the region keys 30 30 if (timestamp === currentTimestamp) {
+1 -2
apps/web/src/components/monitor-dashboard/response-details.tsx
··· 10 10 import { ResponseHeaderTable } from "@/components/ping-response-analysis/response-header-table"; 11 11 import { ResponseTimingTable } from "@/components/ping-response-analysis/response-timing-table"; 12 12 import { prepareGetByPeriod } from "@/lib/tb"; 13 - import type { Region } from "@openstatus/db/src/schema/constants"; 14 13 15 14 interface ResponseDetailsProps { 16 15 monitorId: string; 17 16 url?: string | undefined; 18 - region?: Region; 17 + region?: string; 19 18 cronTimestamp?: number | undefined; 20 19 type: "http" | "tcp"; 21 20 }
+1 -2
apps/web/src/components/ping-response-analysis/region-info.tsx
··· 1 1 import { StatusCodeBadge } from "@/components/monitor/status-code-badge"; 2 - import type { Region } from "@openstatus/db/src/schema/constants"; 3 2 import { latencyFormatter, regionFormatter, timestampFormatter } from "./utils"; 4 3 5 4 export function RegionInfo({ ··· 7 6 error, 8 7 }: { 9 8 check: { 10 - region: Region; 9 + region: string; 11 10 timestamp: number; 12 11 latency: number; 13 12 status?: number;
+3 -3
apps/web/src/components/ping-response-analysis/utils.ts
··· 6 6 monitorRegionSchema, 7 7 } from "@openstatus/db/src/schema/constants"; 8 8 import type { Region } from "@openstatus/db/src/schema/constants"; 9 - import { continentDict, regionDict } from "@openstatus/regions"; 9 + import { continentDict, getRegionInfo, regionDict } from "@openstatus/regions"; 10 10 11 11 export function latencyFormatter(value: number) { 12 12 return `${new Intl.NumberFormat("us").format(value).toString()}ms`; ··· 22 22 } 23 23 24 24 export function regionFormatter( 25 - region: Region, 25 + region: string, 26 26 type: "short" | "long" = "short", 27 27 ) { 28 - const { code, flag, location } = regionDict[region]; 28 + const { code, flag, location } = getRegionInfo(region); 29 29 if (type === "short") return `${code} ${flag}`; 30 30 return `${location} ${flag}`; 31 31 }
+2
packages/api/src/edge.ts
··· 13 13 import { notificationRouter } from "./router/notification"; 14 14 import { pageRouter } from "./router/page"; 15 15 import { pageSubscriberRouter } from "./router/pageSubscriber"; 16 + import { privateLocationRouter } from "./router/privateLocation"; 16 17 import { statusPageRouter } from "./router/statusPage"; 17 18 import { statusReportRouter } from "./router/statusReport"; 18 19 import { tinybirdRouter } from "./router/tinybird"; ··· 42 43 blob: blobRouter, 43 44 feedback: feedbackRouter, 44 45 statusPage: statusPageRouter, 46 + privateLocation: privateLocationRouter, 45 47 });
+34 -11
packages/api/src/router/monitor.ts
··· 26 26 notification, 27 27 notificationsToMonitors, 28 28 page, 29 + privateLocationToMonitors, 29 30 selectIncidentSchema, 30 31 selectMaintenanceSchema, 31 32 selectMonitorSchema, 32 33 selectMonitorTagSchema, 33 34 selectNotificationSchema, 34 35 selectPageSchema, 36 + selectPrivateLocationSchema, 35 37 selectPublicMonitorSchema, 36 38 } from "@openstatus/db/src/schema"; 37 39 ··· 887 889 with: { maintenance: true }, 888 890 }, 889 891 incidents: true, 892 + privateLocationToMonitors: { 893 + with: { privateLocation: true }, 894 + }, 890 895 }, 891 896 }); 892 897 ··· 899 904 tags: z.array(selectMonitorTagSchema).default([]), 900 905 maintenances: z.array(selectMaintenanceSchema).default([]), 901 906 incidents: z.array(selectIncidentSchema).default([]), 907 + privateLocations: z.array(selectPrivateLocationSchema).default([]), 902 908 }) 903 909 .parse({ 904 910 ...data, ··· 909 915 tags: data.monitorTagsToMonitors.map((t) => t.monitorTag), 910 916 maintenances: data.maintenancesToMonitors.map((m) => m.maintenance), 911 917 incidents: data.incidents, 918 + privateLocations: data.privateLocationToMonitors.map( 919 + (p) => p.privateLocation, 920 + ), 912 921 }); 913 922 }), 914 923 ··· 1060 1069 id: z.number(), 1061 1070 regions: z.array(z.string()), 1062 1071 periodicity: z.enum(monitorPeriodicity), 1072 + privateLocations: z.array(z.number()), 1063 1073 }), 1064 1074 ) 1065 1075 .mutation(async ({ ctx, input }) => { ··· 1085 1095 }); 1086 1096 } 1087 1097 1088 - console.log(input.regions, limits.regions); 1089 - 1090 1098 if ( 1091 1099 input.regions.length > 0 && 1092 1100 !input.regions.every((r) => ··· 1099 1107 }); 1100 1108 } 1101 1109 1102 - await ctx.db 1103 - .update(monitor) 1104 - .set({ 1105 - regions: input.regions.join(","), 1106 - periodicity: input.periodicity, 1107 - updatedAt: new Date(), 1108 - }) 1109 - .where(and(...whereConditions)) 1110 - .run(); 1110 + await ctx.db.transaction(async (tx) => { 1111 + await tx 1112 + .update(monitor) 1113 + .set({ 1114 + regions: input.regions.join(","), 1115 + periodicity: input.periodicity, 1116 + updatedAt: new Date(), 1117 + }) 1118 + .where(and(...whereConditions)) 1119 + .run(); 1120 + 1121 + await tx 1122 + .delete(privateLocationToMonitors) 1123 + .where(eq(privateLocationToMonitors.monitorId, input.id)); 1124 + 1125 + if (input.privateLocations && input.privateLocations.length > 0) { 1126 + await tx.insert(privateLocationToMonitors).values( 1127 + input.privateLocations.map((privateLocationId) => ({ 1128 + monitorId: input.id, 1129 + privateLocationId, 1130 + })), 1131 + ); 1132 + } 1133 + }); 1111 1134 }), 1112 1135 1113 1136 updateResponseTime: protectedProcedure
+120
packages/api/src/router/privateLocation.ts
··· 1 + import { and, eq } from "@openstatus/db"; 2 + import { 3 + privateLocation, 4 + privateLocationToMonitors, 5 + } from "@openstatus/db/src/schema"; 6 + import { z } from "zod"; 7 + 8 + import { createTRPCRouter, protectedProcedure } from "../trpc"; 9 + 10 + export const privateLocationRouter = createTRPCRouter({ 11 + list: protectedProcedure.query(async (opts) => { 12 + const privateLocations = await opts.ctx.db.transaction(async (tx) => { 13 + return await tx.query.privateLocation.findMany({ 14 + where: eq(privateLocation.workspaceId, opts.ctx.workspace.id), 15 + with: { 16 + privateLocationToMonitors: { 17 + with: { monitor: true }, 18 + }, 19 + }, 20 + }); 21 + }); 22 + const result = privateLocations.map((privateLocation) => ({ 23 + ...privateLocation, 24 + monitors: privateLocation.privateLocationToMonitors 25 + .map((m) => m.monitor) 26 + .filter((m) => m !== null), 27 + })); 28 + return result; 29 + }), 30 + new: protectedProcedure 31 + .input( 32 + z.object({ 33 + name: z.string(), 34 + monitors: z.array(z.number()), 35 + }), 36 + ) 37 + .mutation(async (opts) => { 38 + const token = crypto.randomUUID(); 39 + 40 + return await opts.ctx.db.transaction(async (tx) => { 41 + const _privateLocation = await tx 42 + .insert(privateLocation) 43 + .values({ 44 + name: opts.input.name, 45 + token, 46 + workspaceId: opts.ctx.workspace.id, 47 + }) 48 + .returning() 49 + .get(); 50 + 51 + if (opts.input.monitors.length) { 52 + await tx.insert(privateLocationToMonitors).values( 53 + opts.input.monitors.map((monitorId) => ({ 54 + privateLocationId: _privateLocation.id, 55 + monitorId, 56 + })), 57 + ); 58 + } 59 + return _privateLocation; 60 + }); 61 + }), 62 + update: protectedProcedure 63 + .input( 64 + z.object({ 65 + id: z.number(), 66 + name: z.string(), 67 + monitors: z.array(z.number()), 68 + }), 69 + ) 70 + .mutation(async (opts) => { 71 + return await opts.ctx.db.transaction(async (tx) => { 72 + const _privateLocation = await tx 73 + .update(privateLocation) 74 + .set({ name: opts.input.name, updatedAt: new Date() }) 75 + .where( 76 + and( 77 + eq(privateLocation.id, opts.input.id), 78 + eq(privateLocation.workspaceId, opts.ctx.workspace.id), 79 + ), 80 + ) 81 + .returning() 82 + .get(); 83 + 84 + await tx 85 + .delete(privateLocationToMonitors) 86 + .where( 87 + eq( 88 + privateLocationToMonitors.privateLocationId, 89 + _privateLocation.id, 90 + ), 91 + ); 92 + 93 + if (opts.input.monitors.length) { 94 + await tx.insert(privateLocationToMonitors).values( 95 + opts.input.monitors.map((monitorId) => ({ 96 + privateLocationId: _privateLocation.id, 97 + monitorId, 98 + })), 99 + ); 100 + } 101 + 102 + return _privateLocation; 103 + }); 104 + }), 105 + delete: protectedProcedure 106 + .input(z.object({ id: z.number() })) 107 + .mutation(async (opts) => { 108 + console.log("delete private location", opts.input.id); 109 + return await opts.ctx.db.transaction(async (tx) => { 110 + await tx 111 + .delete(privateLocation) 112 + .where( 113 + and( 114 + eq(privateLocation.id, opts.input.id), 115 + eq(privateLocation.workspaceId, opts.ctx.workspace.id), 116 + ), 117 + ); 118 + }); 119 + }), 120 + });
+10 -10
packages/api/src/router/tinybird/index.ts
··· 168 168 .input( 169 169 z.object({ 170 170 monitorId: z.string(), 171 - region: z.enum(monitorRegions).optional(), 171 + region: z.enum(monitorRegions).or(z.string()).optional(), 172 172 cronTimestamp: z.number().int().optional(), 173 173 }), 174 174 ) ··· 181 181 .input( 182 182 z.object({ 183 183 monitorId: z.string(), 184 - region: z.enum(monitorRegions).optional(), 184 + region: z.enum(monitorRegions).or(z.string()).optional(), 185 185 cronTimestamp: z.number().int().optional(), 186 186 from: z.coerce.date().optional(), 187 187 to: z.coerce.date().optional(), ··· 224 224 fromDate: z.string().optional(), 225 225 toDate: z.string().optional(), 226 226 interval: z.number().int().optional(), // in minutes, default 30 227 - regions: z.enum(monitorRegions).array().optional(), 227 + regions: z.enum(monitorRegions).or(z.string()).array().optional(), 228 228 type: z.enum(types).default("http"), 229 229 period: z.enum(["7d", "30d"]).default("30d"), 230 230 }), ··· 286 286 monitorId: z.string(), 287 287 period: z.enum(periods), 288 288 type: z.enum(types).default("http"), 289 - regions: z.array(z.enum(monitorRegions)).optional(), 289 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 290 290 cronTimestamp: z.number().int().optional(), 291 291 }), 292 292 ) ··· 321 321 monitorId: z.string(), 322 322 period: z.enum(periods), 323 323 type: z.enum(types).default("http"), 324 - region: z.enum(monitorRegions).optional(), 324 + region: z.enum(monitorRegions).or(z.string()).optional(), 325 325 cronTimestamp: z.number().int().optional(), 326 326 }), 327 327 ) ··· 355 355 monitorId: z.string(), 356 356 period: z.enum(periods), 357 357 type: z.enum(types).default("http"), 358 - region: z.enum(monitorRegions).optional(), 358 + region: z.enum(monitorRegions).or(z.string()).optional(), 359 359 cronTimestamp: z.number().int().optional(), 360 360 }), 361 361 ) ··· 391 391 type: z.enum(types).default("http"), 392 392 // Additional filters 393 393 interval: z.number().int().optional(), 394 - regions: z.array(z.enum(monitorRegions)).optional(), 394 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 395 395 cronTimestamp: z.number().int().optional(), 396 396 }), 397 397 ) ··· 429 429 monitorIds: z.string().array(), 430 430 period: z.enum(["45d"]), 431 431 type: z.enum(types).default("http"), 432 - region: z.enum(monitorRegions).optional(), 432 + region: z.enum(monitorRegions).or(z.string()).optional(), 433 433 cronTimestamp: z.number().int().optional(), 434 434 }), 435 435 ) ··· 519 519 z.object({ 520 520 monitorId: z.string(), 521 521 period: z.enum(periods), 522 - regions: z.array(z.enum(monitorRegions)).optional(), 522 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 523 523 type: z.enum(types).default("http"), 524 524 }), 525 525 ) ··· 541 541 monitorId: z.string(), 542 542 period: z.enum(periods), 543 543 interval: z.number().int().optional(), 544 - regions: z.array(z.enum(monitorRegions)).optional(), 544 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 545 545 type: z.literal("http"), 546 546 }), 547 547 )
+19
packages/db/drizzle/0048_neat_tempest.sql
··· 1 + CREATE TABLE `private_location` ( 2 + `id` integer PRIMARY KEY NOT NULL, 3 + `name` text NOT NULL, 4 + `token` text NOT NULL, 5 + `last_seen_at` integer, 6 + `workspace_id` integer, 7 + `created_at` integer DEFAULT (strftime('%s', 'now')), 8 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 9 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action 10 + ); 11 + --> statement-breakpoint 12 + CREATE TABLE `private_location_to_monitor` ( 13 + `private_location_id` integer, 14 + `monitor_id` integer, 15 + `created_at` integer DEFAULT (strftime('%s', 'now')), 16 + `deleted_at` integer, 17 + FOREIGN KEY (`private_location_id`) REFERENCES `private_location`(`id`) ON UPDATE no action ON DELETE cascade, 18 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE cascade 19 + );
+2587
packages/db/drizzle/meta/0048_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "091d5fb3-ae4f-4ce2-a91f-695d054c0f79", 5 + "prevId": "686060ea-046b-414a-909f-1b087e086065", 6 + "tables": { 7 + "workspace": { 8 + "name": "workspace", 9 + "columns": { 10 + "id": { 11 + "name": "id", 12 + "type": "integer", 13 + "primaryKey": true, 14 + "notNull": true, 15 + "autoincrement": false 16 + }, 17 + "slug": { 18 + "name": "slug", 19 + "type": "text", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + }, 24 + "name": { 25 + "name": "name", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": false, 29 + "autoincrement": false 30 + }, 31 + "stripe_id": { 32 + "name": "stripe_id", 33 + "type": "text(256)", 34 + "primaryKey": false, 35 + "notNull": false, 36 + "autoincrement": false 37 + }, 38 + "subscription_id": { 39 + "name": "subscription_id", 40 + "type": "text", 41 + "primaryKey": false, 42 + "notNull": false, 43 + "autoincrement": false 44 + }, 45 + "plan": { 46 + "name": "plan", 47 + "type": "text", 48 + "primaryKey": false, 49 + "notNull": false, 50 + "autoincrement": false 51 + }, 52 + "ends_at": { 53 + "name": "ends_at", 54 + "type": "integer", 55 + "primaryKey": false, 56 + "notNull": false, 57 + "autoincrement": false 58 + }, 59 + "paid_until": { 60 + "name": "paid_until", 61 + "type": "integer", 62 + "primaryKey": false, 63 + "notNull": false, 64 + "autoincrement": false 65 + }, 66 + "limits": { 67 + "name": "limits", 68 + "type": "text", 69 + "primaryKey": false, 70 + "notNull": true, 71 + "autoincrement": false, 72 + "default": "'{}'" 73 + }, 74 + "created_at": { 75 + "name": "created_at", 76 + "type": "integer", 77 + "primaryKey": false, 78 + "notNull": false, 79 + "autoincrement": false, 80 + "default": "(strftime('%s', 'now'))" 81 + }, 82 + "updated_at": { 83 + "name": "updated_at", 84 + "type": "integer", 85 + "primaryKey": false, 86 + "notNull": false, 87 + "autoincrement": false, 88 + "default": "(strftime('%s', 'now'))" 89 + }, 90 + "dsn": { 91 + "name": "dsn", 92 + "type": "text", 93 + "primaryKey": false, 94 + "notNull": false, 95 + "autoincrement": false 96 + } 97 + }, 98 + "indexes": { 99 + "workspace_slug_unique": { 100 + "name": "workspace_slug_unique", 101 + "columns": [ 102 + "slug" 103 + ], 104 + "isUnique": true 105 + }, 106 + "workspace_stripe_id_unique": { 107 + "name": "workspace_stripe_id_unique", 108 + "columns": [ 109 + "stripe_id" 110 + ], 111 + "isUnique": true 112 + }, 113 + "workspace_id_dsn_unique": { 114 + "name": "workspace_id_dsn_unique", 115 + "columns": [ 116 + "id", 117 + "dsn" 118 + ], 119 + "isUnique": true 120 + } 121 + }, 122 + "foreignKeys": {}, 123 + "compositePrimaryKeys": {}, 124 + "uniqueConstraints": {}, 125 + "checkConstraints": {} 126 + }, 127 + "account": { 128 + "name": "account", 129 + "columns": { 130 + "user_id": { 131 + "name": "user_id", 132 + "type": "integer", 133 + "primaryKey": false, 134 + "notNull": true, 135 + "autoincrement": false 136 + }, 137 + "type": { 138 + "name": "type", 139 + "type": "text", 140 + "primaryKey": false, 141 + "notNull": true, 142 + "autoincrement": false 143 + }, 144 + "provider": { 145 + "name": "provider", 146 + "type": "text", 147 + "primaryKey": false, 148 + "notNull": true, 149 + "autoincrement": false 150 + }, 151 + "provider_account_id": { 152 + "name": "provider_account_id", 153 + "type": "text", 154 + "primaryKey": false, 155 + "notNull": true, 156 + "autoincrement": false 157 + }, 158 + "refresh_token": { 159 + "name": "refresh_token", 160 + "type": "text", 161 + "primaryKey": false, 162 + "notNull": false, 163 + "autoincrement": false 164 + }, 165 + "access_token": { 166 + "name": "access_token", 167 + "type": "text", 168 + "primaryKey": false, 169 + "notNull": false, 170 + "autoincrement": false 171 + }, 172 + "expires_at": { 173 + "name": "expires_at", 174 + "type": "integer", 175 + "primaryKey": false, 176 + "notNull": false, 177 + "autoincrement": false 178 + }, 179 + "token_type": { 180 + "name": "token_type", 181 + "type": "text", 182 + "primaryKey": false, 183 + "notNull": false, 184 + "autoincrement": false 185 + }, 186 + "scope": { 187 + "name": "scope", 188 + "type": "text", 189 + "primaryKey": false, 190 + "notNull": false, 191 + "autoincrement": false 192 + }, 193 + "id_token": { 194 + "name": "id_token", 195 + "type": "text", 196 + "primaryKey": false, 197 + "notNull": false, 198 + "autoincrement": false 199 + }, 200 + "session_state": { 201 + "name": "session_state", 202 + "type": "text", 203 + "primaryKey": false, 204 + "notNull": false, 205 + "autoincrement": false 206 + } 207 + }, 208 + "indexes": {}, 209 + "foreignKeys": { 210 + "account_user_id_user_id_fk": { 211 + "name": "account_user_id_user_id_fk", 212 + "tableFrom": "account", 213 + "tableTo": "user", 214 + "columnsFrom": [ 215 + "user_id" 216 + ], 217 + "columnsTo": [ 218 + "id" 219 + ], 220 + "onDelete": "cascade", 221 + "onUpdate": "no action" 222 + } 223 + }, 224 + "compositePrimaryKeys": { 225 + "account_provider_provider_account_id_pk": { 226 + "columns": [ 227 + "provider", 228 + "provider_account_id" 229 + ], 230 + "name": "account_provider_provider_account_id_pk" 231 + } 232 + }, 233 + "uniqueConstraints": {}, 234 + "checkConstraints": {} 235 + }, 236 + "session": { 237 + "name": "session", 238 + "columns": { 239 + "session_token": { 240 + "name": "session_token", 241 + "type": "text", 242 + "primaryKey": true, 243 + "notNull": true, 244 + "autoincrement": false 245 + }, 246 + "user_id": { 247 + "name": "user_id", 248 + "type": "integer", 249 + "primaryKey": false, 250 + "notNull": true, 251 + "autoincrement": false 252 + }, 253 + "expires": { 254 + "name": "expires", 255 + "type": "integer", 256 + "primaryKey": false, 257 + "notNull": true, 258 + "autoincrement": false 259 + } 260 + }, 261 + "indexes": {}, 262 + "foreignKeys": { 263 + "session_user_id_user_id_fk": { 264 + "name": "session_user_id_user_id_fk", 265 + "tableFrom": "session", 266 + "tableTo": "user", 267 + "columnsFrom": [ 268 + "user_id" 269 + ], 270 + "columnsTo": [ 271 + "id" 272 + ], 273 + "onDelete": "cascade", 274 + "onUpdate": "no action" 275 + } 276 + }, 277 + "compositePrimaryKeys": {}, 278 + "uniqueConstraints": {}, 279 + "checkConstraints": {} 280 + }, 281 + "user": { 282 + "name": "user", 283 + "columns": { 284 + "id": { 285 + "name": "id", 286 + "type": "integer", 287 + "primaryKey": true, 288 + "notNull": true, 289 + "autoincrement": false 290 + }, 291 + "tenant_id": { 292 + "name": "tenant_id", 293 + "type": "text(256)", 294 + "primaryKey": false, 295 + "notNull": false, 296 + "autoincrement": false 297 + }, 298 + "first_name": { 299 + "name": "first_name", 300 + "type": "text", 301 + "primaryKey": false, 302 + "notNull": false, 303 + "autoincrement": false, 304 + "default": "''" 305 + }, 306 + "last_name": { 307 + "name": "last_name", 308 + "type": "text", 309 + "primaryKey": false, 310 + "notNull": false, 311 + "autoincrement": false, 312 + "default": "''" 313 + }, 314 + "photo_url": { 315 + "name": "photo_url", 316 + "type": "text", 317 + "primaryKey": false, 318 + "notNull": false, 319 + "autoincrement": false, 320 + "default": "''" 321 + }, 322 + "name": { 323 + "name": "name", 324 + "type": "text", 325 + "primaryKey": false, 326 + "notNull": false, 327 + "autoincrement": false 328 + }, 329 + "email": { 330 + "name": "email", 331 + "type": "text", 332 + "primaryKey": false, 333 + "notNull": false, 334 + "autoincrement": false, 335 + "default": "''" 336 + }, 337 + "emailVerified": { 338 + "name": "emailVerified", 339 + "type": "integer", 340 + "primaryKey": false, 341 + "notNull": false, 342 + "autoincrement": false 343 + }, 344 + "created_at": { 345 + "name": "created_at", 346 + "type": "integer", 347 + "primaryKey": false, 348 + "notNull": false, 349 + "autoincrement": false, 350 + "default": "(strftime('%s', 'now'))" 351 + }, 352 + "updated_at": { 353 + "name": "updated_at", 354 + "type": "integer", 355 + "primaryKey": false, 356 + "notNull": false, 357 + "autoincrement": false, 358 + "default": "(strftime('%s', 'now'))" 359 + } 360 + }, 361 + "indexes": { 362 + "user_tenant_id_unique": { 363 + "name": "user_tenant_id_unique", 364 + "columns": [ 365 + "tenant_id" 366 + ], 367 + "isUnique": true 368 + } 369 + }, 370 + "foreignKeys": {}, 371 + "compositePrimaryKeys": {}, 372 + "uniqueConstraints": {}, 373 + "checkConstraints": {} 374 + }, 375 + "users_to_workspaces": { 376 + "name": "users_to_workspaces", 377 + "columns": { 378 + "user_id": { 379 + "name": "user_id", 380 + "type": "integer", 381 + "primaryKey": false, 382 + "notNull": true, 383 + "autoincrement": false 384 + }, 385 + "workspace_id": { 386 + "name": "workspace_id", 387 + "type": "integer", 388 + "primaryKey": false, 389 + "notNull": true, 390 + "autoincrement": false 391 + }, 392 + "role": { 393 + "name": "role", 394 + "type": "text", 395 + "primaryKey": false, 396 + "notNull": true, 397 + "autoincrement": false, 398 + "default": "'member'" 399 + }, 400 + "created_at": { 401 + "name": "created_at", 402 + "type": "integer", 403 + "primaryKey": false, 404 + "notNull": false, 405 + "autoincrement": false, 406 + "default": "(strftime('%s', 'now'))" 407 + } 408 + }, 409 + "indexes": {}, 410 + "foreignKeys": { 411 + "users_to_workspaces_user_id_user_id_fk": { 412 + "name": "users_to_workspaces_user_id_user_id_fk", 413 + "tableFrom": "users_to_workspaces", 414 + "tableTo": "user", 415 + "columnsFrom": [ 416 + "user_id" 417 + ], 418 + "columnsTo": [ 419 + "id" 420 + ], 421 + "onDelete": "no action", 422 + "onUpdate": "no action" 423 + }, 424 + "users_to_workspaces_workspace_id_workspace_id_fk": { 425 + "name": "users_to_workspaces_workspace_id_workspace_id_fk", 426 + "tableFrom": "users_to_workspaces", 427 + "tableTo": "workspace", 428 + "columnsFrom": [ 429 + "workspace_id" 430 + ], 431 + "columnsTo": [ 432 + "id" 433 + ], 434 + "onDelete": "no action", 435 + "onUpdate": "no action" 436 + } 437 + }, 438 + "compositePrimaryKeys": { 439 + "users_to_workspaces_user_id_workspace_id_pk": { 440 + "columns": [ 441 + "user_id", 442 + "workspace_id" 443 + ], 444 + "name": "users_to_workspaces_user_id_workspace_id_pk" 445 + } 446 + }, 447 + "uniqueConstraints": {}, 448 + "checkConstraints": {} 449 + }, 450 + "verification_token": { 451 + "name": "verification_token", 452 + "columns": { 453 + "identifier": { 454 + "name": "identifier", 455 + "type": "text", 456 + "primaryKey": false, 457 + "notNull": true, 458 + "autoincrement": false 459 + }, 460 + "token": { 461 + "name": "token", 462 + "type": "text", 463 + "primaryKey": false, 464 + "notNull": true, 465 + "autoincrement": false 466 + }, 467 + "expires": { 468 + "name": "expires", 469 + "type": "integer", 470 + "primaryKey": false, 471 + "notNull": true, 472 + "autoincrement": false 473 + } 474 + }, 475 + "indexes": {}, 476 + "foreignKeys": {}, 477 + "compositePrimaryKeys": { 478 + "verification_token_identifier_token_pk": { 479 + "columns": [ 480 + "identifier", 481 + "token" 482 + ], 483 + "name": "verification_token_identifier_token_pk" 484 + } 485 + }, 486 + "uniqueConstraints": {}, 487 + "checkConstraints": {} 488 + }, 489 + "status_report_to_monitors": { 490 + "name": "status_report_to_monitors", 491 + "columns": { 492 + "monitor_id": { 493 + "name": "monitor_id", 494 + "type": "integer", 495 + "primaryKey": false, 496 + "notNull": true, 497 + "autoincrement": false 498 + }, 499 + "status_report_id": { 500 + "name": "status_report_id", 501 + "type": "integer", 502 + "primaryKey": false, 503 + "notNull": true, 504 + "autoincrement": false 505 + }, 506 + "created_at": { 507 + "name": "created_at", 508 + "type": "integer", 509 + "primaryKey": false, 510 + "notNull": false, 511 + "autoincrement": false, 512 + "default": "(strftime('%s', 'now'))" 513 + } 514 + }, 515 + "indexes": {}, 516 + "foreignKeys": { 517 + "status_report_to_monitors_monitor_id_monitor_id_fk": { 518 + "name": "status_report_to_monitors_monitor_id_monitor_id_fk", 519 + "tableFrom": "status_report_to_monitors", 520 + "tableTo": "monitor", 521 + "columnsFrom": [ 522 + "monitor_id" 523 + ], 524 + "columnsTo": [ 525 + "id" 526 + ], 527 + "onDelete": "cascade", 528 + "onUpdate": "no action" 529 + }, 530 + "status_report_to_monitors_status_report_id_status_report_id_fk": { 531 + "name": "status_report_to_monitors_status_report_id_status_report_id_fk", 532 + "tableFrom": "status_report_to_monitors", 533 + "tableTo": "status_report", 534 + "columnsFrom": [ 535 + "status_report_id" 536 + ], 537 + "columnsTo": [ 538 + "id" 539 + ], 540 + "onDelete": "cascade", 541 + "onUpdate": "no action" 542 + } 543 + }, 544 + "compositePrimaryKeys": { 545 + "status_report_to_monitors_monitor_id_status_report_id_pk": { 546 + "columns": [ 547 + "monitor_id", 548 + "status_report_id" 549 + ], 550 + "name": "status_report_to_monitors_monitor_id_status_report_id_pk" 551 + } 552 + }, 553 + "uniqueConstraints": {}, 554 + "checkConstraints": {} 555 + }, 556 + "status_report": { 557 + "name": "status_report", 558 + "columns": { 559 + "id": { 560 + "name": "id", 561 + "type": "integer", 562 + "primaryKey": true, 563 + "notNull": true, 564 + "autoincrement": false 565 + }, 566 + "status": { 567 + "name": "status", 568 + "type": "text", 569 + "primaryKey": false, 570 + "notNull": true, 571 + "autoincrement": false 572 + }, 573 + "title": { 574 + "name": "title", 575 + "type": "text(256)", 576 + "primaryKey": false, 577 + "notNull": true, 578 + "autoincrement": false 579 + }, 580 + "workspace_id": { 581 + "name": "workspace_id", 582 + "type": "integer", 583 + "primaryKey": false, 584 + "notNull": false, 585 + "autoincrement": false 586 + }, 587 + "page_id": { 588 + "name": "page_id", 589 + "type": "integer", 590 + "primaryKey": false, 591 + "notNull": false, 592 + "autoincrement": false 593 + }, 594 + "created_at": { 595 + "name": "created_at", 596 + "type": "integer", 597 + "primaryKey": false, 598 + "notNull": false, 599 + "autoincrement": false, 600 + "default": "(strftime('%s', 'now'))" 601 + }, 602 + "updated_at": { 603 + "name": "updated_at", 604 + "type": "integer", 605 + "primaryKey": false, 606 + "notNull": false, 607 + "autoincrement": false, 608 + "default": "(strftime('%s', 'now'))" 609 + } 610 + }, 611 + "indexes": {}, 612 + "foreignKeys": { 613 + "status_report_workspace_id_workspace_id_fk": { 614 + "name": "status_report_workspace_id_workspace_id_fk", 615 + "tableFrom": "status_report", 616 + "tableTo": "workspace", 617 + "columnsFrom": [ 618 + "workspace_id" 619 + ], 620 + "columnsTo": [ 621 + "id" 622 + ], 623 + "onDelete": "no action", 624 + "onUpdate": "no action" 625 + }, 626 + "status_report_page_id_page_id_fk": { 627 + "name": "status_report_page_id_page_id_fk", 628 + "tableFrom": "status_report", 629 + "tableTo": "page", 630 + "columnsFrom": [ 631 + "page_id" 632 + ], 633 + "columnsTo": [ 634 + "id" 635 + ], 636 + "onDelete": "cascade", 637 + "onUpdate": "no action" 638 + } 639 + }, 640 + "compositePrimaryKeys": {}, 641 + "uniqueConstraints": {}, 642 + "checkConstraints": {} 643 + }, 644 + "status_report_update": { 645 + "name": "status_report_update", 646 + "columns": { 647 + "id": { 648 + "name": "id", 649 + "type": "integer", 650 + "primaryKey": true, 651 + "notNull": true, 652 + "autoincrement": false 653 + }, 654 + "status": { 655 + "name": "status", 656 + "type": "text", 657 + "primaryKey": false, 658 + "notNull": true, 659 + "autoincrement": false 660 + }, 661 + "date": { 662 + "name": "date", 663 + "type": "integer", 664 + "primaryKey": false, 665 + "notNull": true, 666 + "autoincrement": false 667 + }, 668 + "message": { 669 + "name": "message", 670 + "type": "text", 671 + "primaryKey": false, 672 + "notNull": true, 673 + "autoincrement": false 674 + }, 675 + "status_report_id": { 676 + "name": "status_report_id", 677 + "type": "integer", 678 + "primaryKey": false, 679 + "notNull": true, 680 + "autoincrement": false 681 + }, 682 + "created_at": { 683 + "name": "created_at", 684 + "type": "integer", 685 + "primaryKey": false, 686 + "notNull": false, 687 + "autoincrement": false, 688 + "default": "(strftime('%s', 'now'))" 689 + }, 690 + "updated_at": { 691 + "name": "updated_at", 692 + "type": "integer", 693 + "primaryKey": false, 694 + "notNull": false, 695 + "autoincrement": false, 696 + "default": "(strftime('%s', 'now'))" 697 + } 698 + }, 699 + "indexes": {}, 700 + "foreignKeys": { 701 + "status_report_update_status_report_id_status_report_id_fk": { 702 + "name": "status_report_update_status_report_id_status_report_id_fk", 703 + "tableFrom": "status_report_update", 704 + "tableTo": "status_report", 705 + "columnsFrom": [ 706 + "status_report_id" 707 + ], 708 + "columnsTo": [ 709 + "id" 710 + ], 711 + "onDelete": "cascade", 712 + "onUpdate": "no action" 713 + } 714 + }, 715 + "compositePrimaryKeys": {}, 716 + "uniqueConstraints": {}, 717 + "checkConstraints": {} 718 + }, 719 + "integration": { 720 + "name": "integration", 721 + "columns": { 722 + "id": { 723 + "name": "id", 724 + "type": "integer", 725 + "primaryKey": true, 726 + "notNull": true, 727 + "autoincrement": false 728 + }, 729 + "name": { 730 + "name": "name", 731 + "type": "text(256)", 732 + "primaryKey": false, 733 + "notNull": true, 734 + "autoincrement": false 735 + }, 736 + "workspace_id": { 737 + "name": "workspace_id", 738 + "type": "integer", 739 + "primaryKey": false, 740 + "notNull": false, 741 + "autoincrement": false 742 + }, 743 + "credential": { 744 + "name": "credential", 745 + "type": "text", 746 + "primaryKey": false, 747 + "notNull": false, 748 + "autoincrement": false 749 + }, 750 + "external_id": { 751 + "name": "external_id", 752 + "type": "text", 753 + "primaryKey": false, 754 + "notNull": true, 755 + "autoincrement": false 756 + }, 757 + "created_at": { 758 + "name": "created_at", 759 + "type": "integer", 760 + "primaryKey": false, 761 + "notNull": false, 762 + "autoincrement": false, 763 + "default": "(strftime('%s', 'now'))" 764 + }, 765 + "updated_at": { 766 + "name": "updated_at", 767 + "type": "integer", 768 + "primaryKey": false, 769 + "notNull": false, 770 + "autoincrement": false, 771 + "default": "(strftime('%s', 'now'))" 772 + }, 773 + "data": { 774 + "name": "data", 775 + "type": "text", 776 + "primaryKey": false, 777 + "notNull": true, 778 + "autoincrement": false 779 + } 780 + }, 781 + "indexes": {}, 782 + "foreignKeys": { 783 + "integration_workspace_id_workspace_id_fk": { 784 + "name": "integration_workspace_id_workspace_id_fk", 785 + "tableFrom": "integration", 786 + "tableTo": "workspace", 787 + "columnsFrom": [ 788 + "workspace_id" 789 + ], 790 + "columnsTo": [ 791 + "id" 792 + ], 793 + "onDelete": "no action", 794 + "onUpdate": "no action" 795 + } 796 + }, 797 + "compositePrimaryKeys": {}, 798 + "uniqueConstraints": {}, 799 + "checkConstraints": {} 800 + }, 801 + "page": { 802 + "name": "page", 803 + "columns": { 804 + "id": { 805 + "name": "id", 806 + "type": "integer", 807 + "primaryKey": true, 808 + "notNull": true, 809 + "autoincrement": false 810 + }, 811 + "workspace_id": { 812 + "name": "workspace_id", 813 + "type": "integer", 814 + "primaryKey": false, 815 + "notNull": true, 816 + "autoincrement": false 817 + }, 818 + "title": { 819 + "name": "title", 820 + "type": "text", 821 + "primaryKey": false, 822 + "notNull": true, 823 + "autoincrement": false 824 + }, 825 + "description": { 826 + "name": "description", 827 + "type": "text", 828 + "primaryKey": false, 829 + "notNull": true, 830 + "autoincrement": false 831 + }, 832 + "icon": { 833 + "name": "icon", 834 + "type": "text(256)", 835 + "primaryKey": false, 836 + "notNull": false, 837 + "autoincrement": false, 838 + "default": "''" 839 + }, 840 + "slug": { 841 + "name": "slug", 842 + "type": "text(256)", 843 + "primaryKey": false, 844 + "notNull": true, 845 + "autoincrement": false 846 + }, 847 + "custom_domain": { 848 + "name": "custom_domain", 849 + "type": "text(256)", 850 + "primaryKey": false, 851 + "notNull": true, 852 + "autoincrement": false 853 + }, 854 + "published": { 855 + "name": "published", 856 + "type": "integer", 857 + "primaryKey": false, 858 + "notNull": false, 859 + "autoincrement": false, 860 + "default": false 861 + }, 862 + "force_theme": { 863 + "name": "force_theme", 864 + "type": "text", 865 + "primaryKey": false, 866 + "notNull": true, 867 + "autoincrement": false, 868 + "default": "'system'" 869 + }, 870 + "password": { 871 + "name": "password", 872 + "type": "text(256)", 873 + "primaryKey": false, 874 + "notNull": false, 875 + "autoincrement": false 876 + }, 877 + "password_protected": { 878 + "name": "password_protected", 879 + "type": "integer", 880 + "primaryKey": false, 881 + "notNull": false, 882 + "autoincrement": false, 883 + "default": false 884 + }, 885 + "homepage_url": { 886 + "name": "homepage_url", 887 + "type": "text(256)", 888 + "primaryKey": false, 889 + "notNull": false, 890 + "autoincrement": false 891 + }, 892 + "contact_url": { 893 + "name": "contact_url", 894 + "type": "text(256)", 895 + "primaryKey": false, 896 + "notNull": false, 897 + "autoincrement": false 898 + }, 899 + "legacy_page": { 900 + "name": "legacy_page", 901 + "type": "integer", 902 + "primaryKey": false, 903 + "notNull": true, 904 + "autoincrement": false, 905 + "default": true 906 + }, 907 + "configuration": { 908 + "name": "configuration", 909 + "type": "text", 910 + "primaryKey": false, 911 + "notNull": false, 912 + "autoincrement": false 913 + }, 914 + "show_monitor_values": { 915 + "name": "show_monitor_values", 916 + "type": "integer", 917 + "primaryKey": false, 918 + "notNull": false, 919 + "autoincrement": false, 920 + "default": true 921 + }, 922 + "created_at": { 923 + "name": "created_at", 924 + "type": "integer", 925 + "primaryKey": false, 926 + "notNull": false, 927 + "autoincrement": false, 928 + "default": "(strftime('%s', 'now'))" 929 + }, 930 + "updated_at": { 931 + "name": "updated_at", 932 + "type": "integer", 933 + "primaryKey": false, 934 + "notNull": false, 935 + "autoincrement": false, 936 + "default": "(strftime('%s', 'now'))" 937 + } 938 + }, 939 + "indexes": { 940 + "page_slug_unique": { 941 + "name": "page_slug_unique", 942 + "columns": [ 943 + "slug" 944 + ], 945 + "isUnique": true 946 + } 947 + }, 948 + "foreignKeys": { 949 + "page_workspace_id_workspace_id_fk": { 950 + "name": "page_workspace_id_workspace_id_fk", 951 + "tableFrom": "page", 952 + "tableTo": "workspace", 953 + "columnsFrom": [ 954 + "workspace_id" 955 + ], 956 + "columnsTo": [ 957 + "id" 958 + ], 959 + "onDelete": "cascade", 960 + "onUpdate": "no action" 961 + } 962 + }, 963 + "compositePrimaryKeys": {}, 964 + "uniqueConstraints": {}, 965 + "checkConstraints": {} 966 + }, 967 + "monitor": { 968 + "name": "monitor", 969 + "columns": { 970 + "id": { 971 + "name": "id", 972 + "type": "integer", 973 + "primaryKey": true, 974 + "notNull": true, 975 + "autoincrement": false 976 + }, 977 + "job_type": { 978 + "name": "job_type", 979 + "type": "text", 980 + "primaryKey": false, 981 + "notNull": true, 982 + "autoincrement": false, 983 + "default": "'http'" 984 + }, 985 + "periodicity": { 986 + "name": "periodicity", 987 + "type": "text", 988 + "primaryKey": false, 989 + "notNull": true, 990 + "autoincrement": false, 991 + "default": "'other'" 992 + }, 993 + "status": { 994 + "name": "status", 995 + "type": "text", 996 + "primaryKey": false, 997 + "notNull": true, 998 + "autoincrement": false, 999 + "default": "'active'" 1000 + }, 1001 + "active": { 1002 + "name": "active", 1003 + "type": "integer", 1004 + "primaryKey": false, 1005 + "notNull": false, 1006 + "autoincrement": false, 1007 + "default": false 1008 + }, 1009 + "regions": { 1010 + "name": "regions", 1011 + "type": "text", 1012 + "primaryKey": false, 1013 + "notNull": true, 1014 + "autoincrement": false, 1015 + "default": "''" 1016 + }, 1017 + "url": { 1018 + "name": "url", 1019 + "type": "text(2048)", 1020 + "primaryKey": false, 1021 + "notNull": true, 1022 + "autoincrement": false 1023 + }, 1024 + "name": { 1025 + "name": "name", 1026 + "type": "text(256)", 1027 + "primaryKey": false, 1028 + "notNull": true, 1029 + "autoincrement": false, 1030 + "default": "''" 1031 + }, 1032 + "description": { 1033 + "name": "description", 1034 + "type": "text", 1035 + "primaryKey": false, 1036 + "notNull": true, 1037 + "autoincrement": false, 1038 + "default": "''" 1039 + }, 1040 + "headers": { 1041 + "name": "headers", 1042 + "type": "text", 1043 + "primaryKey": false, 1044 + "notNull": false, 1045 + "autoincrement": false, 1046 + "default": "''" 1047 + }, 1048 + "body": { 1049 + "name": "body", 1050 + "type": "text", 1051 + "primaryKey": false, 1052 + "notNull": false, 1053 + "autoincrement": false, 1054 + "default": "''" 1055 + }, 1056 + "method": { 1057 + "name": "method", 1058 + "type": "text", 1059 + "primaryKey": false, 1060 + "notNull": false, 1061 + "autoincrement": false, 1062 + "default": "'GET'" 1063 + }, 1064 + "workspace_id": { 1065 + "name": "workspace_id", 1066 + "type": "integer", 1067 + "primaryKey": false, 1068 + "notNull": false, 1069 + "autoincrement": false 1070 + }, 1071 + "timeout": { 1072 + "name": "timeout", 1073 + "type": "integer", 1074 + "primaryKey": false, 1075 + "notNull": true, 1076 + "autoincrement": false, 1077 + "default": 45000 1078 + }, 1079 + "degraded_after": { 1080 + "name": "degraded_after", 1081 + "type": "integer", 1082 + "primaryKey": false, 1083 + "notNull": false, 1084 + "autoincrement": false 1085 + }, 1086 + "assertions": { 1087 + "name": "assertions", 1088 + "type": "text", 1089 + "primaryKey": false, 1090 + "notNull": false, 1091 + "autoincrement": false 1092 + }, 1093 + "otel_endpoint": { 1094 + "name": "otel_endpoint", 1095 + "type": "text", 1096 + "primaryKey": false, 1097 + "notNull": false, 1098 + "autoincrement": false 1099 + }, 1100 + "otel_headers": { 1101 + "name": "otel_headers", 1102 + "type": "text", 1103 + "primaryKey": false, 1104 + "notNull": false, 1105 + "autoincrement": false 1106 + }, 1107 + "public": { 1108 + "name": "public", 1109 + "type": "integer", 1110 + "primaryKey": false, 1111 + "notNull": false, 1112 + "autoincrement": false, 1113 + "default": false 1114 + }, 1115 + "retry": { 1116 + "name": "retry", 1117 + "type": "integer", 1118 + "primaryKey": false, 1119 + "notNull": false, 1120 + "autoincrement": false, 1121 + "default": 3 1122 + }, 1123 + "follow_redirects": { 1124 + "name": "follow_redirects", 1125 + "type": "integer", 1126 + "primaryKey": false, 1127 + "notNull": false, 1128 + "autoincrement": false, 1129 + "default": true 1130 + }, 1131 + "created_at": { 1132 + "name": "created_at", 1133 + "type": "integer", 1134 + "primaryKey": false, 1135 + "notNull": false, 1136 + "autoincrement": false, 1137 + "default": "(strftime('%s', 'now'))" 1138 + }, 1139 + "updated_at": { 1140 + "name": "updated_at", 1141 + "type": "integer", 1142 + "primaryKey": false, 1143 + "notNull": false, 1144 + "autoincrement": false, 1145 + "default": "(strftime('%s', 'now'))" 1146 + }, 1147 + "deleted_at": { 1148 + "name": "deleted_at", 1149 + "type": "integer", 1150 + "primaryKey": false, 1151 + "notNull": false, 1152 + "autoincrement": false 1153 + } 1154 + }, 1155 + "indexes": {}, 1156 + "foreignKeys": { 1157 + "monitor_workspace_id_workspace_id_fk": { 1158 + "name": "monitor_workspace_id_workspace_id_fk", 1159 + "tableFrom": "monitor", 1160 + "tableTo": "workspace", 1161 + "columnsFrom": [ 1162 + "workspace_id" 1163 + ], 1164 + "columnsTo": [ 1165 + "id" 1166 + ], 1167 + "onDelete": "no action", 1168 + "onUpdate": "no action" 1169 + } 1170 + }, 1171 + "compositePrimaryKeys": {}, 1172 + "uniqueConstraints": {}, 1173 + "checkConstraints": {} 1174 + }, 1175 + "monitors_to_pages": { 1176 + "name": "monitors_to_pages", 1177 + "columns": { 1178 + "monitor_id": { 1179 + "name": "monitor_id", 1180 + "type": "integer", 1181 + "primaryKey": false, 1182 + "notNull": true, 1183 + "autoincrement": false 1184 + }, 1185 + "page_id": { 1186 + "name": "page_id", 1187 + "type": "integer", 1188 + "primaryKey": false, 1189 + "notNull": true, 1190 + "autoincrement": false 1191 + }, 1192 + "created_at": { 1193 + "name": "created_at", 1194 + "type": "integer", 1195 + "primaryKey": false, 1196 + "notNull": false, 1197 + "autoincrement": false, 1198 + "default": "(strftime('%s', 'now'))" 1199 + }, 1200 + "order": { 1201 + "name": "order", 1202 + "type": "integer", 1203 + "primaryKey": false, 1204 + "notNull": false, 1205 + "autoincrement": false, 1206 + "default": 0 1207 + } 1208 + }, 1209 + "indexes": {}, 1210 + "foreignKeys": { 1211 + "monitors_to_pages_monitor_id_monitor_id_fk": { 1212 + "name": "monitors_to_pages_monitor_id_monitor_id_fk", 1213 + "tableFrom": "monitors_to_pages", 1214 + "tableTo": "monitor", 1215 + "columnsFrom": [ 1216 + "monitor_id" 1217 + ], 1218 + "columnsTo": [ 1219 + "id" 1220 + ], 1221 + "onDelete": "cascade", 1222 + "onUpdate": "no action" 1223 + }, 1224 + "monitors_to_pages_page_id_page_id_fk": { 1225 + "name": "monitors_to_pages_page_id_page_id_fk", 1226 + "tableFrom": "monitors_to_pages", 1227 + "tableTo": "page", 1228 + "columnsFrom": [ 1229 + "page_id" 1230 + ], 1231 + "columnsTo": [ 1232 + "id" 1233 + ], 1234 + "onDelete": "cascade", 1235 + "onUpdate": "no action" 1236 + } 1237 + }, 1238 + "compositePrimaryKeys": { 1239 + "monitors_to_pages_monitor_id_page_id_pk": { 1240 + "columns": [ 1241 + "monitor_id", 1242 + "page_id" 1243 + ], 1244 + "name": "monitors_to_pages_monitor_id_page_id_pk" 1245 + } 1246 + }, 1247 + "uniqueConstraints": {}, 1248 + "checkConstraints": {} 1249 + }, 1250 + "page_subscriber": { 1251 + "name": "page_subscriber", 1252 + "columns": { 1253 + "id": { 1254 + "name": "id", 1255 + "type": "integer", 1256 + "primaryKey": true, 1257 + "notNull": true, 1258 + "autoincrement": false 1259 + }, 1260 + "email": { 1261 + "name": "email", 1262 + "type": "text", 1263 + "primaryKey": false, 1264 + "notNull": true, 1265 + "autoincrement": false 1266 + }, 1267 + "page_id": { 1268 + "name": "page_id", 1269 + "type": "integer", 1270 + "primaryKey": false, 1271 + "notNull": true, 1272 + "autoincrement": false 1273 + }, 1274 + "token": { 1275 + "name": "token", 1276 + "type": "text", 1277 + "primaryKey": false, 1278 + "notNull": false, 1279 + "autoincrement": false 1280 + }, 1281 + "accepted_at": { 1282 + "name": "accepted_at", 1283 + "type": "integer", 1284 + "primaryKey": false, 1285 + "notNull": false, 1286 + "autoincrement": false 1287 + }, 1288 + "expires_at": { 1289 + "name": "expires_at", 1290 + "type": "integer", 1291 + "primaryKey": false, 1292 + "notNull": false, 1293 + "autoincrement": false 1294 + }, 1295 + "created_at": { 1296 + "name": "created_at", 1297 + "type": "integer", 1298 + "primaryKey": false, 1299 + "notNull": false, 1300 + "autoincrement": false, 1301 + "default": "(strftime('%s', 'now'))" 1302 + }, 1303 + "updated_at": { 1304 + "name": "updated_at", 1305 + "type": "integer", 1306 + "primaryKey": false, 1307 + "notNull": false, 1308 + "autoincrement": false, 1309 + "default": "(strftime('%s', 'now'))" 1310 + } 1311 + }, 1312 + "indexes": {}, 1313 + "foreignKeys": { 1314 + "page_subscriber_page_id_page_id_fk": { 1315 + "name": "page_subscriber_page_id_page_id_fk", 1316 + "tableFrom": "page_subscriber", 1317 + "tableTo": "page", 1318 + "columnsFrom": [ 1319 + "page_id" 1320 + ], 1321 + "columnsTo": [ 1322 + "id" 1323 + ], 1324 + "onDelete": "cascade", 1325 + "onUpdate": "no action" 1326 + } 1327 + }, 1328 + "compositePrimaryKeys": {}, 1329 + "uniqueConstraints": {}, 1330 + "checkConstraints": {} 1331 + }, 1332 + "notification": { 1333 + "name": "notification", 1334 + "columns": { 1335 + "id": { 1336 + "name": "id", 1337 + "type": "integer", 1338 + "primaryKey": true, 1339 + "notNull": true, 1340 + "autoincrement": false 1341 + }, 1342 + "name": { 1343 + "name": "name", 1344 + "type": "text", 1345 + "primaryKey": false, 1346 + "notNull": true, 1347 + "autoincrement": false 1348 + }, 1349 + "provider": { 1350 + "name": "provider", 1351 + "type": "text", 1352 + "primaryKey": false, 1353 + "notNull": true, 1354 + "autoincrement": false 1355 + }, 1356 + "data": { 1357 + "name": "data", 1358 + "type": "text", 1359 + "primaryKey": false, 1360 + "notNull": false, 1361 + "autoincrement": false, 1362 + "default": "'{}'" 1363 + }, 1364 + "workspace_id": { 1365 + "name": "workspace_id", 1366 + "type": "integer", 1367 + "primaryKey": false, 1368 + "notNull": false, 1369 + "autoincrement": false 1370 + }, 1371 + "created_at": { 1372 + "name": "created_at", 1373 + "type": "integer", 1374 + "primaryKey": false, 1375 + "notNull": false, 1376 + "autoincrement": false, 1377 + "default": "(strftime('%s', 'now'))" 1378 + }, 1379 + "updated_at": { 1380 + "name": "updated_at", 1381 + "type": "integer", 1382 + "primaryKey": false, 1383 + "notNull": false, 1384 + "autoincrement": false, 1385 + "default": "(strftime('%s', 'now'))" 1386 + } 1387 + }, 1388 + "indexes": {}, 1389 + "foreignKeys": { 1390 + "notification_workspace_id_workspace_id_fk": { 1391 + "name": "notification_workspace_id_workspace_id_fk", 1392 + "tableFrom": "notification", 1393 + "tableTo": "workspace", 1394 + "columnsFrom": [ 1395 + "workspace_id" 1396 + ], 1397 + "columnsTo": [ 1398 + "id" 1399 + ], 1400 + "onDelete": "no action", 1401 + "onUpdate": "no action" 1402 + } 1403 + }, 1404 + "compositePrimaryKeys": {}, 1405 + "uniqueConstraints": {}, 1406 + "checkConstraints": {} 1407 + }, 1408 + "notification_trigger": { 1409 + "name": "notification_trigger", 1410 + "columns": { 1411 + "id": { 1412 + "name": "id", 1413 + "type": "integer", 1414 + "primaryKey": true, 1415 + "notNull": true, 1416 + "autoincrement": false 1417 + }, 1418 + "monitor_id": { 1419 + "name": "monitor_id", 1420 + "type": "integer", 1421 + "primaryKey": false, 1422 + "notNull": false, 1423 + "autoincrement": false 1424 + }, 1425 + "notification_id": { 1426 + "name": "notification_id", 1427 + "type": "integer", 1428 + "primaryKey": false, 1429 + "notNull": false, 1430 + "autoincrement": false 1431 + }, 1432 + "cron_timestamp": { 1433 + "name": "cron_timestamp", 1434 + "type": "integer", 1435 + "primaryKey": false, 1436 + "notNull": true, 1437 + "autoincrement": false 1438 + } 1439 + }, 1440 + "indexes": { 1441 + "notification_id_monitor_id_crontimestampe": { 1442 + "name": "notification_id_monitor_id_crontimestampe", 1443 + "columns": [ 1444 + "notification_id", 1445 + "monitor_id", 1446 + "cron_timestamp" 1447 + ], 1448 + "isUnique": true 1449 + } 1450 + }, 1451 + "foreignKeys": { 1452 + "notification_trigger_monitor_id_monitor_id_fk": { 1453 + "name": "notification_trigger_monitor_id_monitor_id_fk", 1454 + "tableFrom": "notification_trigger", 1455 + "tableTo": "monitor", 1456 + "columnsFrom": [ 1457 + "monitor_id" 1458 + ], 1459 + "columnsTo": [ 1460 + "id" 1461 + ], 1462 + "onDelete": "cascade", 1463 + "onUpdate": "no action" 1464 + }, 1465 + "notification_trigger_notification_id_notification_id_fk": { 1466 + "name": "notification_trigger_notification_id_notification_id_fk", 1467 + "tableFrom": "notification_trigger", 1468 + "tableTo": "notification", 1469 + "columnsFrom": [ 1470 + "notification_id" 1471 + ], 1472 + "columnsTo": [ 1473 + "id" 1474 + ], 1475 + "onDelete": "cascade", 1476 + "onUpdate": "no action" 1477 + } 1478 + }, 1479 + "compositePrimaryKeys": {}, 1480 + "uniqueConstraints": {}, 1481 + "checkConstraints": {} 1482 + }, 1483 + "notifications_to_monitors": { 1484 + "name": "notifications_to_monitors", 1485 + "columns": { 1486 + "monitor_id": { 1487 + "name": "monitor_id", 1488 + "type": "integer", 1489 + "primaryKey": false, 1490 + "notNull": true, 1491 + "autoincrement": false 1492 + }, 1493 + "notification_id": { 1494 + "name": "notification_id", 1495 + "type": "integer", 1496 + "primaryKey": false, 1497 + "notNull": true, 1498 + "autoincrement": false 1499 + }, 1500 + "created_at": { 1501 + "name": "created_at", 1502 + "type": "integer", 1503 + "primaryKey": false, 1504 + "notNull": false, 1505 + "autoincrement": false, 1506 + "default": "(strftime('%s', 'now'))" 1507 + } 1508 + }, 1509 + "indexes": {}, 1510 + "foreignKeys": { 1511 + "notifications_to_monitors_monitor_id_monitor_id_fk": { 1512 + "name": "notifications_to_monitors_monitor_id_monitor_id_fk", 1513 + "tableFrom": "notifications_to_monitors", 1514 + "tableTo": "monitor", 1515 + "columnsFrom": [ 1516 + "monitor_id" 1517 + ], 1518 + "columnsTo": [ 1519 + "id" 1520 + ], 1521 + "onDelete": "cascade", 1522 + "onUpdate": "no action" 1523 + }, 1524 + "notifications_to_monitors_notification_id_notification_id_fk": { 1525 + "name": "notifications_to_monitors_notification_id_notification_id_fk", 1526 + "tableFrom": "notifications_to_monitors", 1527 + "tableTo": "notification", 1528 + "columnsFrom": [ 1529 + "notification_id" 1530 + ], 1531 + "columnsTo": [ 1532 + "id" 1533 + ], 1534 + "onDelete": "cascade", 1535 + "onUpdate": "no action" 1536 + } 1537 + }, 1538 + "compositePrimaryKeys": { 1539 + "notifications_to_monitors_monitor_id_notification_id_pk": { 1540 + "columns": [ 1541 + "monitor_id", 1542 + "notification_id" 1543 + ], 1544 + "name": "notifications_to_monitors_monitor_id_notification_id_pk" 1545 + } 1546 + }, 1547 + "uniqueConstraints": {}, 1548 + "checkConstraints": {} 1549 + }, 1550 + "monitor_status": { 1551 + "name": "monitor_status", 1552 + "columns": { 1553 + "monitor_id": { 1554 + "name": "monitor_id", 1555 + "type": "integer", 1556 + "primaryKey": false, 1557 + "notNull": true, 1558 + "autoincrement": false 1559 + }, 1560 + "region": { 1561 + "name": "region", 1562 + "type": "text", 1563 + "primaryKey": false, 1564 + "notNull": true, 1565 + "autoincrement": false, 1566 + "default": "''" 1567 + }, 1568 + "status": { 1569 + "name": "status", 1570 + "type": "text", 1571 + "primaryKey": false, 1572 + "notNull": true, 1573 + "autoincrement": false, 1574 + "default": "'active'" 1575 + }, 1576 + "created_at": { 1577 + "name": "created_at", 1578 + "type": "integer", 1579 + "primaryKey": false, 1580 + "notNull": false, 1581 + "autoincrement": false, 1582 + "default": "(strftime('%s', 'now'))" 1583 + }, 1584 + "updated_at": { 1585 + "name": "updated_at", 1586 + "type": "integer", 1587 + "primaryKey": false, 1588 + "notNull": false, 1589 + "autoincrement": false, 1590 + "default": "(strftime('%s', 'now'))" 1591 + } 1592 + }, 1593 + "indexes": { 1594 + "monitor_status_idx": { 1595 + "name": "monitor_status_idx", 1596 + "columns": [ 1597 + "monitor_id", 1598 + "region" 1599 + ], 1600 + "isUnique": false 1601 + } 1602 + }, 1603 + "foreignKeys": { 1604 + "monitor_status_monitor_id_monitor_id_fk": { 1605 + "name": "monitor_status_monitor_id_monitor_id_fk", 1606 + "tableFrom": "monitor_status", 1607 + "tableTo": "monitor", 1608 + "columnsFrom": [ 1609 + "monitor_id" 1610 + ], 1611 + "columnsTo": [ 1612 + "id" 1613 + ], 1614 + "onDelete": "cascade", 1615 + "onUpdate": "no action" 1616 + } 1617 + }, 1618 + "compositePrimaryKeys": { 1619 + "monitor_status_monitor_id_region_pk": { 1620 + "columns": [ 1621 + "monitor_id", 1622 + "region" 1623 + ], 1624 + "name": "monitor_status_monitor_id_region_pk" 1625 + } 1626 + }, 1627 + "uniqueConstraints": {}, 1628 + "checkConstraints": {} 1629 + }, 1630 + "invitation": { 1631 + "name": "invitation", 1632 + "columns": { 1633 + "id": { 1634 + "name": "id", 1635 + "type": "integer", 1636 + "primaryKey": true, 1637 + "notNull": true, 1638 + "autoincrement": false 1639 + }, 1640 + "email": { 1641 + "name": "email", 1642 + "type": "text", 1643 + "primaryKey": false, 1644 + "notNull": true, 1645 + "autoincrement": false 1646 + }, 1647 + "role": { 1648 + "name": "role", 1649 + "type": "text", 1650 + "primaryKey": false, 1651 + "notNull": true, 1652 + "autoincrement": false, 1653 + "default": "'member'" 1654 + }, 1655 + "workspace_id": { 1656 + "name": "workspace_id", 1657 + "type": "integer", 1658 + "primaryKey": false, 1659 + "notNull": true, 1660 + "autoincrement": false 1661 + }, 1662 + "token": { 1663 + "name": "token", 1664 + "type": "text", 1665 + "primaryKey": false, 1666 + "notNull": true, 1667 + "autoincrement": false 1668 + }, 1669 + "expires_at": { 1670 + "name": "expires_at", 1671 + "type": "integer", 1672 + "primaryKey": false, 1673 + "notNull": true, 1674 + "autoincrement": false 1675 + }, 1676 + "created_at": { 1677 + "name": "created_at", 1678 + "type": "integer", 1679 + "primaryKey": false, 1680 + "notNull": false, 1681 + "autoincrement": false, 1682 + "default": "(strftime('%s', 'now'))" 1683 + }, 1684 + "accepted_at": { 1685 + "name": "accepted_at", 1686 + "type": "integer", 1687 + "primaryKey": false, 1688 + "notNull": false, 1689 + "autoincrement": false 1690 + } 1691 + }, 1692 + "indexes": {}, 1693 + "foreignKeys": {}, 1694 + "compositePrimaryKeys": {}, 1695 + "uniqueConstraints": {}, 1696 + "checkConstraints": {} 1697 + }, 1698 + "incident": { 1699 + "name": "incident", 1700 + "columns": { 1701 + "id": { 1702 + "name": "id", 1703 + "type": "integer", 1704 + "primaryKey": true, 1705 + "notNull": true, 1706 + "autoincrement": false 1707 + }, 1708 + "title": { 1709 + "name": "title", 1710 + "type": "text", 1711 + "primaryKey": false, 1712 + "notNull": true, 1713 + "autoincrement": false, 1714 + "default": "''" 1715 + }, 1716 + "summary": { 1717 + "name": "summary", 1718 + "type": "text", 1719 + "primaryKey": false, 1720 + "notNull": true, 1721 + "autoincrement": false, 1722 + "default": "''" 1723 + }, 1724 + "status": { 1725 + "name": "status", 1726 + "type": "text", 1727 + "primaryKey": false, 1728 + "notNull": true, 1729 + "autoincrement": false, 1730 + "default": "'triage'" 1731 + }, 1732 + "monitor_id": { 1733 + "name": "monitor_id", 1734 + "type": "integer", 1735 + "primaryKey": false, 1736 + "notNull": false, 1737 + "autoincrement": false 1738 + }, 1739 + "workspace_id": { 1740 + "name": "workspace_id", 1741 + "type": "integer", 1742 + "primaryKey": false, 1743 + "notNull": false, 1744 + "autoincrement": false 1745 + }, 1746 + "started_at": { 1747 + "name": "started_at", 1748 + "type": "integer", 1749 + "primaryKey": false, 1750 + "notNull": true, 1751 + "autoincrement": false, 1752 + "default": "(strftime('%s', 'now'))" 1753 + }, 1754 + "acknowledged_at": { 1755 + "name": "acknowledged_at", 1756 + "type": "integer", 1757 + "primaryKey": false, 1758 + "notNull": false, 1759 + "autoincrement": false 1760 + }, 1761 + "acknowledged_by": { 1762 + "name": "acknowledged_by", 1763 + "type": "integer", 1764 + "primaryKey": false, 1765 + "notNull": false, 1766 + "autoincrement": false 1767 + }, 1768 + "resolved_at": { 1769 + "name": "resolved_at", 1770 + "type": "integer", 1771 + "primaryKey": false, 1772 + "notNull": false, 1773 + "autoincrement": false 1774 + }, 1775 + "resolved_by": { 1776 + "name": "resolved_by", 1777 + "type": "integer", 1778 + "primaryKey": false, 1779 + "notNull": false, 1780 + "autoincrement": false 1781 + }, 1782 + "incident_screenshot_url": { 1783 + "name": "incident_screenshot_url", 1784 + "type": "text", 1785 + "primaryKey": false, 1786 + "notNull": false, 1787 + "autoincrement": false 1788 + }, 1789 + "recovery_screenshot_url": { 1790 + "name": "recovery_screenshot_url", 1791 + "type": "text", 1792 + "primaryKey": false, 1793 + "notNull": false, 1794 + "autoincrement": false 1795 + }, 1796 + "auto_resolved": { 1797 + "name": "auto_resolved", 1798 + "type": "integer", 1799 + "primaryKey": false, 1800 + "notNull": false, 1801 + "autoincrement": false, 1802 + "default": false 1803 + }, 1804 + "created_at": { 1805 + "name": "created_at", 1806 + "type": "integer", 1807 + "primaryKey": false, 1808 + "notNull": false, 1809 + "autoincrement": false, 1810 + "default": "(strftime('%s', 'now'))" 1811 + }, 1812 + "updated_at": { 1813 + "name": "updated_at", 1814 + "type": "integer", 1815 + "primaryKey": false, 1816 + "notNull": false, 1817 + "autoincrement": false, 1818 + "default": "(strftime('%s', 'now'))" 1819 + } 1820 + }, 1821 + "indexes": { 1822 + "incident_monitor_id_started_at_unique": { 1823 + "name": "incident_monitor_id_started_at_unique", 1824 + "columns": [ 1825 + "monitor_id", 1826 + "started_at" 1827 + ], 1828 + "isUnique": true 1829 + } 1830 + }, 1831 + "foreignKeys": { 1832 + "incident_monitor_id_monitor_id_fk": { 1833 + "name": "incident_monitor_id_monitor_id_fk", 1834 + "tableFrom": "incident", 1835 + "tableTo": "monitor", 1836 + "columnsFrom": [ 1837 + "monitor_id" 1838 + ], 1839 + "columnsTo": [ 1840 + "id" 1841 + ], 1842 + "onDelete": "set default", 1843 + "onUpdate": "no action" 1844 + }, 1845 + "incident_workspace_id_workspace_id_fk": { 1846 + "name": "incident_workspace_id_workspace_id_fk", 1847 + "tableFrom": "incident", 1848 + "tableTo": "workspace", 1849 + "columnsFrom": [ 1850 + "workspace_id" 1851 + ], 1852 + "columnsTo": [ 1853 + "id" 1854 + ], 1855 + "onDelete": "no action", 1856 + "onUpdate": "no action" 1857 + }, 1858 + "incident_acknowledged_by_user_id_fk": { 1859 + "name": "incident_acknowledged_by_user_id_fk", 1860 + "tableFrom": "incident", 1861 + "tableTo": "user", 1862 + "columnsFrom": [ 1863 + "acknowledged_by" 1864 + ], 1865 + "columnsTo": [ 1866 + "id" 1867 + ], 1868 + "onDelete": "no action", 1869 + "onUpdate": "no action" 1870 + }, 1871 + "incident_resolved_by_user_id_fk": { 1872 + "name": "incident_resolved_by_user_id_fk", 1873 + "tableFrom": "incident", 1874 + "tableTo": "user", 1875 + "columnsFrom": [ 1876 + "resolved_by" 1877 + ], 1878 + "columnsTo": [ 1879 + "id" 1880 + ], 1881 + "onDelete": "no action", 1882 + "onUpdate": "no action" 1883 + } 1884 + }, 1885 + "compositePrimaryKeys": {}, 1886 + "uniqueConstraints": {}, 1887 + "checkConstraints": {} 1888 + }, 1889 + "monitor_tag": { 1890 + "name": "monitor_tag", 1891 + "columns": { 1892 + "id": { 1893 + "name": "id", 1894 + "type": "integer", 1895 + "primaryKey": true, 1896 + "notNull": true, 1897 + "autoincrement": false 1898 + }, 1899 + "workspace_id": { 1900 + "name": "workspace_id", 1901 + "type": "integer", 1902 + "primaryKey": false, 1903 + "notNull": true, 1904 + "autoincrement": false 1905 + }, 1906 + "name": { 1907 + "name": "name", 1908 + "type": "text", 1909 + "primaryKey": false, 1910 + "notNull": true, 1911 + "autoincrement": false 1912 + }, 1913 + "color": { 1914 + "name": "color", 1915 + "type": "text", 1916 + "primaryKey": false, 1917 + "notNull": true, 1918 + "autoincrement": false 1919 + }, 1920 + "created_at": { 1921 + "name": "created_at", 1922 + "type": "integer", 1923 + "primaryKey": false, 1924 + "notNull": false, 1925 + "autoincrement": false, 1926 + "default": "(strftime('%s', 'now'))" 1927 + }, 1928 + "updated_at": { 1929 + "name": "updated_at", 1930 + "type": "integer", 1931 + "primaryKey": false, 1932 + "notNull": false, 1933 + "autoincrement": false, 1934 + "default": "(strftime('%s', 'now'))" 1935 + } 1936 + }, 1937 + "indexes": {}, 1938 + "foreignKeys": { 1939 + "monitor_tag_workspace_id_workspace_id_fk": { 1940 + "name": "monitor_tag_workspace_id_workspace_id_fk", 1941 + "tableFrom": "monitor_tag", 1942 + "tableTo": "workspace", 1943 + "columnsFrom": [ 1944 + "workspace_id" 1945 + ], 1946 + "columnsTo": [ 1947 + "id" 1948 + ], 1949 + "onDelete": "cascade", 1950 + "onUpdate": "no action" 1951 + } 1952 + }, 1953 + "compositePrimaryKeys": {}, 1954 + "uniqueConstraints": {}, 1955 + "checkConstraints": {} 1956 + }, 1957 + "monitor_tag_to_monitor": { 1958 + "name": "monitor_tag_to_monitor", 1959 + "columns": { 1960 + "monitor_id": { 1961 + "name": "monitor_id", 1962 + "type": "integer", 1963 + "primaryKey": false, 1964 + "notNull": true, 1965 + "autoincrement": false 1966 + }, 1967 + "monitor_tag_id": { 1968 + "name": "monitor_tag_id", 1969 + "type": "integer", 1970 + "primaryKey": false, 1971 + "notNull": true, 1972 + "autoincrement": false 1973 + }, 1974 + "created_at": { 1975 + "name": "created_at", 1976 + "type": "integer", 1977 + "primaryKey": false, 1978 + "notNull": false, 1979 + "autoincrement": false, 1980 + "default": "(strftime('%s', 'now'))" 1981 + } 1982 + }, 1983 + "indexes": {}, 1984 + "foreignKeys": { 1985 + "monitor_tag_to_monitor_monitor_id_monitor_id_fk": { 1986 + "name": "monitor_tag_to_monitor_monitor_id_monitor_id_fk", 1987 + "tableFrom": "monitor_tag_to_monitor", 1988 + "tableTo": "monitor", 1989 + "columnsFrom": [ 1990 + "monitor_id" 1991 + ], 1992 + "columnsTo": [ 1993 + "id" 1994 + ], 1995 + "onDelete": "cascade", 1996 + "onUpdate": "no action" 1997 + }, 1998 + "monitor_tag_to_monitor_monitor_tag_id_monitor_tag_id_fk": { 1999 + "name": "monitor_tag_to_monitor_monitor_tag_id_monitor_tag_id_fk", 2000 + "tableFrom": "monitor_tag_to_monitor", 2001 + "tableTo": "monitor_tag", 2002 + "columnsFrom": [ 2003 + "monitor_tag_id" 2004 + ], 2005 + "columnsTo": [ 2006 + "id" 2007 + ], 2008 + "onDelete": "cascade", 2009 + "onUpdate": "no action" 2010 + } 2011 + }, 2012 + "compositePrimaryKeys": { 2013 + "monitor_tag_to_monitor_monitor_id_monitor_tag_id_pk": { 2014 + "columns": [ 2015 + "monitor_id", 2016 + "monitor_tag_id" 2017 + ], 2018 + "name": "monitor_tag_to_monitor_monitor_id_monitor_tag_id_pk" 2019 + } 2020 + }, 2021 + "uniqueConstraints": {}, 2022 + "checkConstraints": {} 2023 + }, 2024 + "application": { 2025 + "name": "application", 2026 + "columns": { 2027 + "id": { 2028 + "name": "id", 2029 + "type": "integer", 2030 + "primaryKey": true, 2031 + "notNull": true, 2032 + "autoincrement": false 2033 + }, 2034 + "name": { 2035 + "name": "name", 2036 + "type": "text", 2037 + "primaryKey": false, 2038 + "notNull": false, 2039 + "autoincrement": false 2040 + }, 2041 + "dsn": { 2042 + "name": "dsn", 2043 + "type": "text", 2044 + "primaryKey": false, 2045 + "notNull": false, 2046 + "autoincrement": false 2047 + }, 2048 + "workspace_id": { 2049 + "name": "workspace_id", 2050 + "type": "integer", 2051 + "primaryKey": false, 2052 + "notNull": false, 2053 + "autoincrement": false 2054 + }, 2055 + "created_at": { 2056 + "name": "created_at", 2057 + "type": "integer", 2058 + "primaryKey": false, 2059 + "notNull": false, 2060 + "autoincrement": false, 2061 + "default": "(strftime('%s', 'now'))" 2062 + }, 2063 + "updated_at": { 2064 + "name": "updated_at", 2065 + "type": "integer", 2066 + "primaryKey": false, 2067 + "notNull": false, 2068 + "autoincrement": false, 2069 + "default": "(strftime('%s', 'now'))" 2070 + } 2071 + }, 2072 + "indexes": { 2073 + "application_dsn_unique": { 2074 + "name": "application_dsn_unique", 2075 + "columns": [ 2076 + "dsn" 2077 + ], 2078 + "isUnique": true 2079 + } 2080 + }, 2081 + "foreignKeys": { 2082 + "application_workspace_id_workspace_id_fk": { 2083 + "name": "application_workspace_id_workspace_id_fk", 2084 + "tableFrom": "application", 2085 + "tableTo": "workspace", 2086 + "columnsFrom": [ 2087 + "workspace_id" 2088 + ], 2089 + "columnsTo": [ 2090 + "id" 2091 + ], 2092 + "onDelete": "no action", 2093 + "onUpdate": "no action" 2094 + } 2095 + }, 2096 + "compositePrimaryKeys": {}, 2097 + "uniqueConstraints": {}, 2098 + "checkConstraints": {} 2099 + }, 2100 + "maintenance": { 2101 + "name": "maintenance", 2102 + "columns": { 2103 + "id": { 2104 + "name": "id", 2105 + "type": "integer", 2106 + "primaryKey": true, 2107 + "notNull": true, 2108 + "autoincrement": false 2109 + }, 2110 + "title": { 2111 + "name": "title", 2112 + "type": "text(256)", 2113 + "primaryKey": false, 2114 + "notNull": true, 2115 + "autoincrement": false 2116 + }, 2117 + "message": { 2118 + "name": "message", 2119 + "type": "text", 2120 + "primaryKey": false, 2121 + "notNull": true, 2122 + "autoincrement": false 2123 + }, 2124 + "from": { 2125 + "name": "from", 2126 + "type": "integer", 2127 + "primaryKey": false, 2128 + "notNull": true, 2129 + "autoincrement": false 2130 + }, 2131 + "to": { 2132 + "name": "to", 2133 + "type": "integer", 2134 + "primaryKey": false, 2135 + "notNull": true, 2136 + "autoincrement": false 2137 + }, 2138 + "workspace_id": { 2139 + "name": "workspace_id", 2140 + "type": "integer", 2141 + "primaryKey": false, 2142 + "notNull": false, 2143 + "autoincrement": false 2144 + }, 2145 + "page_id": { 2146 + "name": "page_id", 2147 + "type": "integer", 2148 + "primaryKey": false, 2149 + "notNull": false, 2150 + "autoincrement": false 2151 + }, 2152 + "created_at": { 2153 + "name": "created_at", 2154 + "type": "integer", 2155 + "primaryKey": false, 2156 + "notNull": false, 2157 + "autoincrement": false, 2158 + "default": "(strftime('%s', 'now'))" 2159 + }, 2160 + "updated_at": { 2161 + "name": "updated_at", 2162 + "type": "integer", 2163 + "primaryKey": false, 2164 + "notNull": false, 2165 + "autoincrement": false, 2166 + "default": "(strftime('%s', 'now'))" 2167 + } 2168 + }, 2169 + "indexes": {}, 2170 + "foreignKeys": { 2171 + "maintenance_workspace_id_workspace_id_fk": { 2172 + "name": "maintenance_workspace_id_workspace_id_fk", 2173 + "tableFrom": "maintenance", 2174 + "tableTo": "workspace", 2175 + "columnsFrom": [ 2176 + "workspace_id" 2177 + ], 2178 + "columnsTo": [ 2179 + "id" 2180 + ], 2181 + "onDelete": "no action", 2182 + "onUpdate": "no action" 2183 + }, 2184 + "maintenance_page_id_page_id_fk": { 2185 + "name": "maintenance_page_id_page_id_fk", 2186 + "tableFrom": "maintenance", 2187 + "tableTo": "page", 2188 + "columnsFrom": [ 2189 + "page_id" 2190 + ], 2191 + "columnsTo": [ 2192 + "id" 2193 + ], 2194 + "onDelete": "cascade", 2195 + "onUpdate": "no action" 2196 + } 2197 + }, 2198 + "compositePrimaryKeys": {}, 2199 + "uniqueConstraints": {}, 2200 + "checkConstraints": {} 2201 + }, 2202 + "maintenance_to_monitor": { 2203 + "name": "maintenance_to_monitor", 2204 + "columns": { 2205 + "maintenance_id": { 2206 + "name": "maintenance_id", 2207 + "type": "integer", 2208 + "primaryKey": false, 2209 + "notNull": true, 2210 + "autoincrement": false 2211 + }, 2212 + "monitor_id": { 2213 + "name": "monitor_id", 2214 + "type": "integer", 2215 + "primaryKey": false, 2216 + "notNull": true, 2217 + "autoincrement": false 2218 + }, 2219 + "created_at": { 2220 + "name": "created_at", 2221 + "type": "integer", 2222 + "primaryKey": false, 2223 + "notNull": false, 2224 + "autoincrement": false, 2225 + "default": "(strftime('%s', 'now'))" 2226 + } 2227 + }, 2228 + "indexes": {}, 2229 + "foreignKeys": { 2230 + "maintenance_to_monitor_maintenance_id_maintenance_id_fk": { 2231 + "name": "maintenance_to_monitor_maintenance_id_maintenance_id_fk", 2232 + "tableFrom": "maintenance_to_monitor", 2233 + "tableTo": "maintenance", 2234 + "columnsFrom": [ 2235 + "maintenance_id" 2236 + ], 2237 + "columnsTo": [ 2238 + "id" 2239 + ], 2240 + "onDelete": "cascade", 2241 + "onUpdate": "no action" 2242 + }, 2243 + "maintenance_to_monitor_monitor_id_monitor_id_fk": { 2244 + "name": "maintenance_to_monitor_monitor_id_monitor_id_fk", 2245 + "tableFrom": "maintenance_to_monitor", 2246 + "tableTo": "monitor", 2247 + "columnsFrom": [ 2248 + "monitor_id" 2249 + ], 2250 + "columnsTo": [ 2251 + "id" 2252 + ], 2253 + "onDelete": "cascade", 2254 + "onUpdate": "no action" 2255 + } 2256 + }, 2257 + "compositePrimaryKeys": { 2258 + "maintenance_to_monitor_maintenance_id_monitor_id_pk": { 2259 + "columns": [ 2260 + "maintenance_id", 2261 + "monitor_id" 2262 + ], 2263 + "name": "maintenance_to_monitor_maintenance_id_monitor_id_pk" 2264 + } 2265 + }, 2266 + "uniqueConstraints": {}, 2267 + "checkConstraints": {} 2268 + }, 2269 + "check": { 2270 + "name": "check", 2271 + "columns": { 2272 + "id": { 2273 + "name": "id", 2274 + "type": "integer", 2275 + "primaryKey": true, 2276 + "notNull": true, 2277 + "autoincrement": true 2278 + }, 2279 + "regions": { 2280 + "name": "regions", 2281 + "type": "text", 2282 + "primaryKey": false, 2283 + "notNull": true, 2284 + "autoincrement": false, 2285 + "default": "''" 2286 + }, 2287 + "url": { 2288 + "name": "url", 2289 + "type": "text(4096)", 2290 + "primaryKey": false, 2291 + "notNull": true, 2292 + "autoincrement": false 2293 + }, 2294 + "headers": { 2295 + "name": "headers", 2296 + "type": "text", 2297 + "primaryKey": false, 2298 + "notNull": false, 2299 + "autoincrement": false, 2300 + "default": "''" 2301 + }, 2302 + "body": { 2303 + "name": "body", 2304 + "type": "text", 2305 + "primaryKey": false, 2306 + "notNull": false, 2307 + "autoincrement": false, 2308 + "default": "''" 2309 + }, 2310 + "method": { 2311 + "name": "method", 2312 + "type": "text", 2313 + "primaryKey": false, 2314 + "notNull": false, 2315 + "autoincrement": false, 2316 + "default": "'GET'" 2317 + }, 2318 + "count_requests": { 2319 + "name": "count_requests", 2320 + "type": "integer", 2321 + "primaryKey": false, 2322 + "notNull": false, 2323 + "autoincrement": false, 2324 + "default": 1 2325 + }, 2326 + "workspace_id": { 2327 + "name": "workspace_id", 2328 + "type": "integer", 2329 + "primaryKey": false, 2330 + "notNull": false, 2331 + "autoincrement": false 2332 + }, 2333 + "created_at": { 2334 + "name": "created_at", 2335 + "type": "integer", 2336 + "primaryKey": false, 2337 + "notNull": false, 2338 + "autoincrement": false, 2339 + "default": "(strftime('%s', 'now'))" 2340 + } 2341 + }, 2342 + "indexes": {}, 2343 + "foreignKeys": { 2344 + "check_workspace_id_workspace_id_fk": { 2345 + "name": "check_workspace_id_workspace_id_fk", 2346 + "tableFrom": "check", 2347 + "tableTo": "workspace", 2348 + "columnsFrom": [ 2349 + "workspace_id" 2350 + ], 2351 + "columnsTo": [ 2352 + "id" 2353 + ], 2354 + "onDelete": "no action", 2355 + "onUpdate": "no action" 2356 + } 2357 + }, 2358 + "compositePrimaryKeys": {}, 2359 + "uniqueConstraints": {}, 2360 + "checkConstraints": {} 2361 + }, 2362 + "monitor_run": { 2363 + "name": "monitor_run", 2364 + "columns": { 2365 + "id": { 2366 + "name": "id", 2367 + "type": "integer", 2368 + "primaryKey": true, 2369 + "notNull": true, 2370 + "autoincrement": false 2371 + }, 2372 + "workspace_id": { 2373 + "name": "workspace_id", 2374 + "type": "integer", 2375 + "primaryKey": false, 2376 + "notNull": false, 2377 + "autoincrement": false 2378 + }, 2379 + "monitor_id": { 2380 + "name": "monitor_id", 2381 + "type": "integer", 2382 + "primaryKey": false, 2383 + "notNull": false, 2384 + "autoincrement": false 2385 + }, 2386 + "runned_at": { 2387 + "name": "runned_at", 2388 + "type": "integer", 2389 + "primaryKey": false, 2390 + "notNull": false, 2391 + "autoincrement": false 2392 + }, 2393 + "created_at": { 2394 + "name": "created_at", 2395 + "type": "integer", 2396 + "primaryKey": false, 2397 + "notNull": false, 2398 + "autoincrement": false, 2399 + "default": "(strftime('%s', 'now'))" 2400 + } 2401 + }, 2402 + "indexes": {}, 2403 + "foreignKeys": { 2404 + "monitor_run_workspace_id_workspace_id_fk": { 2405 + "name": "monitor_run_workspace_id_workspace_id_fk", 2406 + "tableFrom": "monitor_run", 2407 + "tableTo": "workspace", 2408 + "columnsFrom": [ 2409 + "workspace_id" 2410 + ], 2411 + "columnsTo": [ 2412 + "id" 2413 + ], 2414 + "onDelete": "no action", 2415 + "onUpdate": "no action" 2416 + }, 2417 + "monitor_run_monitor_id_monitor_id_fk": { 2418 + "name": "monitor_run_monitor_id_monitor_id_fk", 2419 + "tableFrom": "monitor_run", 2420 + "tableTo": "monitor", 2421 + "columnsFrom": [ 2422 + "monitor_id" 2423 + ], 2424 + "columnsTo": [ 2425 + "id" 2426 + ], 2427 + "onDelete": "no action", 2428 + "onUpdate": "no action" 2429 + } 2430 + }, 2431 + "compositePrimaryKeys": {}, 2432 + "uniqueConstraints": {}, 2433 + "checkConstraints": {} 2434 + }, 2435 + "private_location": { 2436 + "name": "private_location", 2437 + "columns": { 2438 + "id": { 2439 + "name": "id", 2440 + "type": "integer", 2441 + "primaryKey": true, 2442 + "notNull": true, 2443 + "autoincrement": false 2444 + }, 2445 + "name": { 2446 + "name": "name", 2447 + "type": "text", 2448 + "primaryKey": false, 2449 + "notNull": true, 2450 + "autoincrement": false 2451 + }, 2452 + "token": { 2453 + "name": "token", 2454 + "type": "text", 2455 + "primaryKey": false, 2456 + "notNull": true, 2457 + "autoincrement": false 2458 + }, 2459 + "last_seen_at": { 2460 + "name": "last_seen_at", 2461 + "type": "integer", 2462 + "primaryKey": false, 2463 + "notNull": false, 2464 + "autoincrement": false 2465 + }, 2466 + "workspace_id": { 2467 + "name": "workspace_id", 2468 + "type": "integer", 2469 + "primaryKey": false, 2470 + "notNull": false, 2471 + "autoincrement": false 2472 + }, 2473 + "created_at": { 2474 + "name": "created_at", 2475 + "type": "integer", 2476 + "primaryKey": false, 2477 + "notNull": false, 2478 + "autoincrement": false, 2479 + "default": "(strftime('%s', 'now'))" 2480 + }, 2481 + "updated_at": { 2482 + "name": "updated_at", 2483 + "type": "integer", 2484 + "primaryKey": false, 2485 + "notNull": false, 2486 + "autoincrement": false, 2487 + "default": "(strftime('%s', 'now'))" 2488 + } 2489 + }, 2490 + "indexes": {}, 2491 + "foreignKeys": { 2492 + "private_location_workspace_id_workspace_id_fk": { 2493 + "name": "private_location_workspace_id_workspace_id_fk", 2494 + "tableFrom": "private_location", 2495 + "tableTo": "workspace", 2496 + "columnsFrom": [ 2497 + "workspace_id" 2498 + ], 2499 + "columnsTo": [ 2500 + "id" 2501 + ], 2502 + "onDelete": "no action", 2503 + "onUpdate": "no action" 2504 + } 2505 + }, 2506 + "compositePrimaryKeys": {}, 2507 + "uniqueConstraints": {}, 2508 + "checkConstraints": {} 2509 + }, 2510 + "private_location_to_monitor": { 2511 + "name": "private_location_to_monitor", 2512 + "columns": { 2513 + "private_location_id": { 2514 + "name": "private_location_id", 2515 + "type": "integer", 2516 + "primaryKey": false, 2517 + "notNull": false, 2518 + "autoincrement": false 2519 + }, 2520 + "monitor_id": { 2521 + "name": "monitor_id", 2522 + "type": "integer", 2523 + "primaryKey": false, 2524 + "notNull": false, 2525 + "autoincrement": false 2526 + }, 2527 + "created_at": { 2528 + "name": "created_at", 2529 + "type": "integer", 2530 + "primaryKey": false, 2531 + "notNull": false, 2532 + "autoincrement": false, 2533 + "default": "(strftime('%s', 'now'))" 2534 + }, 2535 + "deleted_at": { 2536 + "name": "deleted_at", 2537 + "type": "integer", 2538 + "primaryKey": false, 2539 + "notNull": false, 2540 + "autoincrement": false 2541 + } 2542 + }, 2543 + "indexes": {}, 2544 + "foreignKeys": { 2545 + "private_location_to_monitor_private_location_id_private_location_id_fk": { 2546 + "name": "private_location_to_monitor_private_location_id_private_location_id_fk", 2547 + "tableFrom": "private_location_to_monitor", 2548 + "tableTo": "private_location", 2549 + "columnsFrom": [ 2550 + "private_location_id" 2551 + ], 2552 + "columnsTo": [ 2553 + "id" 2554 + ], 2555 + "onDelete": "cascade", 2556 + "onUpdate": "no action" 2557 + }, 2558 + "private_location_to_monitor_monitor_id_monitor_id_fk": { 2559 + "name": "private_location_to_monitor_monitor_id_monitor_id_fk", 2560 + "tableFrom": "private_location_to_monitor", 2561 + "tableTo": "monitor", 2562 + "columnsFrom": [ 2563 + "monitor_id" 2564 + ], 2565 + "columnsTo": [ 2566 + "id" 2567 + ], 2568 + "onDelete": "cascade", 2569 + "onUpdate": "no action" 2570 + } 2571 + }, 2572 + "compositePrimaryKeys": {}, 2573 + "uniqueConstraints": {}, 2574 + "checkConstraints": {} 2575 + } 2576 + }, 2577 + "views": {}, 2578 + "enums": {}, 2579 + "_meta": { 2580 + "schemas": {}, 2581 + "tables": {}, 2582 + "columns": {} 2583 + }, 2584 + "internal": { 2585 + "indexes": {} 2586 + } 2587 + }
+7
packages/db/drizzle/meta/_journal.json
··· 337 337 "when": 1757840904190, 338 338 "tag": "0047_nifty_roughhouse", 339 339 "breakpoints": true 340 + }, 341 + { 342 + "idx": 48, 343 + "version": "6", 344 + "when": 1760602903085, 345 + "tag": "0048_neat_tempest", 346 + "breakpoints": true 340 347 } 341 348 ] 342 349 }
+1
packages/db/src/schema/index.ts
··· 15 15 export * from "./maintenances"; 16 16 export * from "./check"; 17 17 export * from "./monitor_run"; 18 + export * from "./private_locations";
+2
packages/db/src/schema/monitors/monitor.ts
··· 13 13 import { monitorTagsToMonitors } from "../monitor_tags"; 14 14 import { notificationsToMonitors } from "../notifications"; 15 15 import { page } from "../pages"; 16 + import { privateLocationToMonitors } from "../private_locations"; 16 17 import { monitorsToStatusReport } from "../status_reports"; 17 18 import { workspace } from "../workspaces/workspace"; 18 19 import { monitorJobTypes, monitorMethods, monitorStatus } from "./constants"; ··· 82 83 maintenancesToMonitors: many(maintenancesToMonitors), 83 84 incidents: many(incidentTable), 84 85 monitorStatus: many(monitorStatusTable), 86 + privateLocationToMonitors: many(privateLocationToMonitors), 85 87 })); 86 88 87 89 export const monitorsToPages = sqliteTable(
+5 -5
packages/db/src/schema/plan/config.ts
··· 1 - import { type Region, availableRegions } from "../constants"; 1 + import { AVAILABLE_REGIONS, FREE_FLY_REGIONS } from "@openstatus/regions"; 2 2 import type { WorkspacePlan } from "../workspaces/validation"; 3 3 import type { Limits } from "./schema"; 4 4 ··· 51 51 "notification-channels": 1, 52 52 members: 1, 53 53 "audit-log": false, 54 - regions: ["ams", "gru", "iad", "jnb", "hkg", "syd"] satisfies Region[], 54 + regions: [...FREE_FLY_REGIONS], 55 55 "private-locations": false, 56 56 }, 57 57 }, ··· 89 89 "notification-channels": 10, 90 90 members: "Unlimited", 91 91 "audit-log": false, 92 - regions: [...availableRegions], 92 + regions: [...AVAILABLE_REGIONS], 93 93 "private-locations": false, 94 94 }, 95 95 }, ··· 107 107 "synthetic-checks": 300, 108 108 periodicity: ["30s", "1m", "5m", "10m", "30m", "1h"], 109 109 "multi-region": true, 110 - "max-regions": availableRegions.length, 110 + "max-regions": AVAILABLE_REGIONS.length, 111 111 "data-retention": "12 months", 112 112 "status-pages": 5, 113 113 maintenance: true, ··· 127 127 "notification-channels": 20, 128 128 members: "Unlimited", 129 129 "audit-log": true, 130 - regions: [...availableRegions], 130 + regions: [...AVAILABLE_REGIONS], 131 131 "private-locations": false, 132 132 }, 133 133 },
+2 -3
packages/db/src/schema/plan/schema.ts
··· 1 + import { FREE_FLY_REGIONS } from "@openstatus/regions"; 1 2 import { z } from "zod"; 2 3 import { monitorPeriodicitySchema, monitorRegionSchema } from "../constants"; 3 4 ··· 17 18 "data-retention": z 18 19 .enum(["14 days", "3 months", "12 months", "24 months"]) 19 20 .default("14 days"), 20 - regions: monitorRegionSchema 21 - .array() 22 - .default(["ams", "gru", "iad", "jnb", "sin", "syd"]), 21 + regions: monitorRegionSchema.array().default(FREE_FLY_REGIONS), 23 22 "private-locations": z.boolean().default(false), 24 23 screenshots: z.boolean().default(false), 25 24 "response-logs": z.boolean().default(false),
+54
packages/db/src/schema/plan/utils.ts
··· 42 42 } 43 43 return { value: planConfig.price.USD, locale: "en-US", currency: "USD" }; 44 44 } 45 + 46 + export function getPlansForLimit( 47 + currentPlan: WorkspacePlan, 48 + limit: keyof Limits, 49 + ): WorkspacePlan[] { 50 + const currentLimitValue = allPlans[currentPlan].limits[limit]; 51 + const planOrder: WorkspacePlan[] = ["free", "starter", "team"]; 52 + 53 + // Get plans that come after the current plan 54 + const availablePlans = planOrder.filter((plan) => { 55 + const planIndex = planOrder.indexOf(plan); 56 + const currentIndex = planOrder.indexOf(currentPlan); 57 + return planIndex > currentIndex; 58 + }); 59 + 60 + // Filter plans based on the limit feature value 61 + return availablePlans.filter((plan) => { 62 + const planLimitValue = allPlans[plan].limits[limit]; 63 + 64 + // For boolean limits, only show plans where the feature is enabled 65 + if (typeof currentLimitValue === "boolean") { 66 + return planLimitValue === true; 67 + } 68 + 69 + // For numeric limits, show plans with higher values 70 + if ( 71 + typeof currentLimitValue === "number" && 72 + typeof planLimitValue === "number" 73 + ) { 74 + return planLimitValue > currentLimitValue; 75 + } 76 + 77 + // For array limits (e.g., periodicity, regions), show plans with more options 78 + if (Array.isArray(currentLimitValue) && Array.isArray(planLimitValue)) { 79 + return planLimitValue.length > currentLimitValue.length; 80 + } 81 + 82 + // For string limits (e.g., data-retention), check if it's "better" 83 + // This is a simple heuristic - could be improved based on specific needs 84 + if ( 85 + typeof currentLimitValue === "string" && 86 + typeof planLimitValue === "string" 87 + ) { 88 + return planLimitValue !== currentLimitValue; 89 + } 90 + 91 + // For "Unlimited" string literal in members 92 + if (planLimitValue === "Unlimited") { 93 + return true; 94 + } 95 + 96 + return false; 97 + }); 98 + }
+2
packages/db/src/schema/private_locations/index.ts
··· 1 + export * from "./private_locations"; 2 + export * from "./validation";
+61
packages/db/src/schema/private_locations/private_locations.ts
··· 1 + import { relations } from "drizzle-orm"; 2 + import { sql } from "drizzle-orm/sql"; 3 + import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; 4 + import { monitor } from "../monitors/monitor"; 5 + import { workspace } from "../workspaces"; 6 + 7 + export const privateLocation = sqliteTable("private_location", { 8 + id: integer("id").primaryKey(), 9 + name: text("name").notNull(), 10 + token: text("token").notNull(), 11 + lastSeenAt: integer("last_seen_at", { mode: "timestamp" }), 12 + workspaceId: integer("workspace_id").references(() => workspace.id), 13 + createdAt: integer("created_at", { mode: "timestamp" }).default( 14 + sql`(strftime('%s', 'now'))`, 15 + ), 16 + updatedAt: integer("updated_at", { mode: "timestamp" }).default( 17 + sql`(strftime('%s', 'now'))`, 18 + ), 19 + }); 20 + 21 + export const privateLocationToMonitors = sqliteTable( 22 + "private_location_to_monitor", 23 + { 24 + privateLocationId: integer("private_location_id").references( 25 + () => privateLocation.id, 26 + { onDelete: "cascade" }, 27 + ), 28 + monitorId: integer("monitor_id").references(() => monitor.id, { 29 + onDelete: "cascade", 30 + }), 31 + createdAt: integer("created_at", { mode: "timestamp" }).default( 32 + sql`(strftime('%s', 'now'))`, 33 + ), 34 + deletedAt: integer("deleted_at", { mode: "timestamp" }), 35 + }, 36 + ); 37 + 38 + export const privateLocationRelation = relations( 39 + privateLocation, 40 + ({ many, one }) => ({ 41 + privateLocationToMonitors: many(privateLocationToMonitors), 42 + workspace: one(workspace, { 43 + fields: [privateLocation.workspaceId], 44 + references: [workspace.id], 45 + }), 46 + }), 47 + ); 48 + 49 + export const privateLocationToMonitorsRelation = relations( 50 + privateLocationToMonitors, 51 + ({ one }) => ({ 52 + privateLocation: one(privateLocation, { 53 + fields: [privateLocationToMonitors.privateLocationId], 54 + references: [privateLocation.id], 55 + }), 56 + monitor: one(monitor, { 57 + fields: [privateLocationToMonitors.monitorId], 58 + references: [monitor.id], 59 + }), 60 + }), 61 + );
+11
packages/db/src/schema/private_locations/validation.ts
··· 1 + import { createInsertSchema, createSelectSchema } from "drizzle-zod"; 2 + import type { z } from "zod"; 3 + 4 + import { privateLocation } from "./private_locations"; 5 + 6 + export const insertPrivateLocationSchema = createInsertSchema(privateLocation); 7 + 8 + export const selectPrivateLocationSchema = createSelectSchema(privateLocation); 9 + 10 + export type InsertPrivateLocation = z.infer<typeof insertPrivateLocationSchema>; 11 + export type PrivateLocation = z.infer<typeof selectPrivateLocationSchema>;
+43
packages/db/src/seed.mts
··· 13 13 notification, 14 14 notificationsToMonitors, 15 15 page, 16 + privateLocation, 17 + privateLocationToMonitors, 16 18 statusReport, 17 19 statusReportUpdate, 18 20 user, ··· 47 49 name: "test2", 48 50 subscriptionId: "subscriptionId2", 49 51 plan: "free", 52 + endsAt: null, 53 + paidUntil: null, 54 + }, 55 + { 56 + id: 3, 57 + slug: "test3", 58 + stripeId: "stripeId3", 59 + name: "test3", 60 + subscriptionId: "subscriptionId3", 61 + plan: "team", 50 62 endsAt: null, 51 63 paidUntil: null, 52 64 }, ··· 104 116 public: true, 105 117 otelEndpoint: "https://otel.com:4337", 106 118 otelHeaders: '[{"key":"Authorization","value":"Basic"}]', 119 + }, 120 + { 121 + id: 5, 122 + active: true, 123 + workspaceId: 3, 124 + periodicity: "10m", 125 + url: "https://openstat.us", 126 + method: "GET", 127 + regions: "ams", 128 + public: true, 107 129 }, 108 130 ]) 109 131 .onConflictDoNothing() ··· 288 310 status: "monitoring", 289 311 message: "test", 290 312 date: new Date(), 313 + }) 314 + .onConflictDoNothing() 315 + .run(); 316 + 317 + await db 318 + .insert(privateLocation) 319 + .values({ 320 + id: 1, 321 + name: "My Home", 322 + token: "my-secret-key", 323 + workspaceId: 3, 324 + createdAt: new Date(), 325 + }) 326 + .onConflictDoNothing() 327 + .run(); 328 + await db 329 + .insert(privateLocationToMonitors) 330 + .values({ 331 + privateLocationId: 1, 332 + monitorId: 5, 333 + createdAt: new Date(), 291 334 }) 292 335 .onConflictDoNothing() 293 336 .run();
+29
packages/proto/buf.gen.yaml
··· 1 + # buf.gen.yaml defines a local generation template. 2 + # For details, see https://buf.build/docs/configuration/v2/buf-gen-yaml 3 + version: v2 4 + # managed: 5 + # enabled: true 6 + # override: 7 + # - file_option: go_package_prefix 8 + # value: github.com/openstatushq/openstatus/packages/proto/gen 9 + # 10 + # This is not beautiful but while in dev let's generate it twice 11 + plugins: 12 + - local: protoc-gen-go 13 + out: ../../apps/private-location/proto 14 + opt: 15 + - paths=source_relative 16 + - local: protoc-gen-connect-go 17 + out: ../../apps/private-location/proto 18 + opt: 19 + - paths=source_relative 20 + - package_suffix 21 + - local: protoc-gen-go 22 + out: ../../apps/checker/proto 23 + opt: 24 + - paths=source_relative 25 + - local: protoc-gen-connect-go 26 + out: ../../apps/checker/proto 27 + opt: 28 + - paths=source_relative 29 + - package_suffix
+8
packages/proto/buf.yaml
··· 1 + version: v2 2 + 3 + modules: 4 + - path: . 5 + name: buf.build/openstatus/private-location 6 + lint: 7 + use: 8 + - STANDARD
+8
packages/proto/go.mod
··· 1 + module github.com/openstatushq/openstatus/packages/proto 2 + 3 + go 1.25.2 4 + 5 + require ( 6 + connectrpc.com/connect v1.19.1 7 + google.golang.org/protobuf v1.36.10 8 + )
+6
packages/proto/go.sum
··· 1 + connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= 2 + connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= 3 + github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 4 + github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 5 + google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= 6 + google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+13
packages/proto/justfile
··· 1 + set fallback := true 2 + 3 + init: 4 + go install github.com/bufbuild/buf/cmd/buf@latest 5 + go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest 6 + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 7 + go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest 8 + 9 + buf: 10 + pnpm buf 11 + 12 + gen: 13 + buf generate
+10
packages/proto/package.json
··· 1 + { 2 + "name": "@openstatus/proto", 3 + "version": "0.0.0", 4 + "scripts": { 5 + "buf": "buf generate" 6 + }, 7 + "devDependencies": { 8 + "@bufbuild/buf": "1.57.2" 9 + } 10 + }
+47
packages/proto/private_location/v1/assertions.proto
··· 1 + 2 + syntax = "proto3"; 3 + 4 + package private_location.v1; 5 + 6 + option go_package = "github.com/openstatushq/openstatus/packages/proto/private_location/v1;v1"; 7 + 8 + 9 + enum NumberComparator { 10 + NUMBER_COMPARATOR_UNSPECIFIED = 0; 11 + NUMBER_COMPARATOR_EQUAL = 1; 12 + NUMBER_COMPARATOR_NOT_EQUAL = 2; 13 + NUMBER_COMPARATOR_GREATER_THAN = 3; 14 + NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL = 4; 15 + NUMBER_COMPARATOR_LESS_THAN = 5; 16 + NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL = 6; 17 + } 18 + 19 + enum StringComparator { 20 + STRING_COMPARATOR_UNSPECIFIED = 0; 21 + STRING_COMPARATOR_CONTAINS = 1; 22 + STRING_COMPARATOR_NOT_CONTAINS = 2; 23 + STRING_COMPARATOR_EQUAL = 3; 24 + STRING_COMPARATOR_NOT_EQUAL = 4; 25 + STRING_COMPARATOR_EMPTY = 5; 26 + STRING_COMPARATOR_NOT_EMPTY = 6; 27 + STRING_COMPARATOR_GREATER_THAN = 7; 28 + STRING_COMPARATOR_GREATER_THAN_OR_EQUAL = 8; 29 + STRING_COMPARATOR_LESS_THAN = 9; 30 + STRING_COMPARATOR_LESS_THAN_OR_EQUAL = 10; 31 + } 32 + 33 + message StatusCodeAssertion { 34 + int64 target = 1; 35 + NumberComparator comparator = 2; 36 + } 37 + 38 + message BodyAssertion { 39 + string target = 1; 40 + StringComparator comparator = 2; 41 + } 42 + 43 + message HeaderAssertion { 44 + string target = 1; 45 + StringComparator comparator = 2; 46 + string key = 3; 47 + }
+32
packages/proto/private_location/v1/http_monitor.proto
··· 1 + syntax = "proto3"; 2 + 3 + package private_location.v1; 4 + 5 + import "private_location/v1/assertions.proto"; 6 + 7 + option go_package = "github.com/openstatushq/openstatus/packages/proto/private_location/v1;v1"; 8 + 9 + 10 + message Headers { 11 + string key = 1; 12 + string value = 2; 13 + } 14 + 15 + message HTTPMonitor { 16 + string id = 1; 17 + string url = 2; 18 + string periodicity = 3; 19 + string method = 4; 20 + string body = 5; 21 + int64 timeout = 6; 22 + optional int64 degraded_at = 7; 23 + int64 retry = 8; 24 + bool follow_redirects = 9; 25 + 26 + repeated Headers headers = 10; 27 + 28 + repeated StatusCodeAssertion status_code_assertions = 11; 29 + repeated BodyAssertion body_assertions = 12; 30 + repeated HeaderAssertion header_assertions = 13; 31 + 32 + }
+61
packages/proto/private_location/v1/private_location.proto
··· 1 + syntax = "proto3"; 2 + 3 + package private_location.v1; 4 + 5 + import "private_location/v1/http_monitor.proto"; 6 + import "private_location/v1/tcp_monitor.proto"; 7 + 8 + 9 + option go_package = "github.com/openstatushq/openstatus/packages/proto/private_location/v1;v1"; 10 + 11 + service PrivateLocationService { 12 + rpc Monitors(MonitorsRequest) returns (MonitorsResponse) {} 13 + rpc IngestTCP(IngestTCPRequest) returns (IngestTCPResponse) {} 14 + rpc IngestHTTP(IngestHTTPRequest) returns (IngestHTTPResponse) {} 15 + } 16 + 17 + message MonitorsRequest {} 18 + 19 + message MonitorsResponse { 20 + repeated HTTPMonitor http_monitors = 1; 21 + repeated TCPMonitor tcp_monitors = 2; 22 + } 23 + 24 + 25 + message IngestTCPRequest { 26 + string id = 1; 27 + string monitorId = 2; 28 + int64 latency = 3; 29 + int64 timestamp = 4; 30 + int64 cronTimestamp = 5; 31 + string uri = 6; 32 + string message = 7; 33 + string requestStatus = 8; 34 + int64 error = 9; 35 + string timing = 10; 36 + 37 + } 38 + 39 + message IngestTCPResponse { 40 + 41 + } 42 + 43 + message IngestHTTPRequest { 44 + string id = 1; 45 + string monitorId = 2; 46 + int64 latency = 3; 47 + int64 timestamp = 4; 48 + int64 cronTimestamp = 5; 49 + string url = 6; 50 + string requestStatus = 7; 51 + string message = 8; 52 + string body = 9; 53 + string headers = 10; 54 + string timing = 11; 55 + int64 statusCode = 12; 56 + int64 error = 13; 57 + } 58 + 59 + message IngestHTTPResponse { 60 + 61 + }
+18
packages/proto/private_location/v1/tcp_monitor.proto
··· 1 + syntax = "proto3"; 2 + 3 + package private_location.v1; 4 + 5 + option go_package = "github.com/openstatushq/openstatus/packages/proto/private_location/v1;v1"; 6 + 7 + 8 + 9 + 10 + message TCPMonitor { 11 + string id = 1; 12 + string uri = 2; 13 + int64 timeout = 3; 14 + optional int64 degraded_at = 4; 15 + string periodicity = 5; 16 + int64 retry = 6; 17 + 18 + }
+22 -3
packages/regions/index.ts
··· 83 83 flag: string; 84 84 continent: Continent; 85 85 deprecated: boolean; 86 - provider: "fly" | "koyeb" | "railway"; 86 + provider: "fly" | "koyeb" | "railway" | "private"; 87 87 }; 88 88 89 89 // TODO: we could think of doing the inverse and use "EU" as key ··· 464 464 (r) => !regionDict[r].deprecated, 465 465 ); 466 466 467 - export function formatRegionCode(region: RegionInfo | Region) { 468 - const r = typeof region === "string" ? regionDict[region] : region; 467 + export function formatRegionCode(region: RegionInfo | Region | string) { 468 + const r = typeof region === "string" ? getRegionInfo(region) : region; 469 469 const suffix = r.code.replace(/(koyeb_|railway_|fly_)/g, ""); 470 470 471 471 if (r.provider === "railway") { ··· 473 473 } 474 474 475 475 return suffix; 476 + } 477 + 478 + // NOTE: opts is used to override the location for private locations 479 + export function getRegionInfo(region: string, opts?: { location?: string }) { 480 + if (region in regionDict) { 481 + return regionDict[region as Region]; 482 + } 483 + 484 + return { 485 + code: region, 486 + location: opts?.location ?? "Private Location", 487 + flag: "๐ŸŒ", 488 + continent: "Private", 489 + deprecated: false, 490 + provider: "private", 491 + } satisfies Omit<RegionInfo, "code" | "continent"> & { 492 + code: string; 493 + continent: "Private"; 494 + }; 476 495 } 477 496 478 497 export const groupByContinent = Object.entries(regionDict).reduce<
+61 -61
packages/tinybird/src/client.ts
··· 51 51 statusCode: z.number().int().nullable(), 52 52 monitorId: z.string(), 53 53 error: z.coerce.boolean(), 54 - region: z.enum(monitorRegions), 54 + region: z.enum(monitorRegions).or(z.string()), 55 55 cronTimestamp: z.number().int(), 56 56 trigger: z.enum(triggers).nullable().default("cron"), 57 57 timestamp: z.number(), ··· 76 76 statusCode: z.number().int().nullable(), 77 77 monitorId: z.string(), 78 78 requestStatus: z.enum(["error", "success", "degraded"]).nullable(), 79 - region: z.enum(monitorRegions), 79 + region: z.enum(monitorRegions).or(z.string()), 80 80 cronTimestamp: z.number().int(), 81 81 trigger: z.enum(triggers).nullable().default("cron"), 82 82 timestamp: z.number(), ··· 98 98 statusCode: z.number().int().nullable(), 99 99 monitorId: z.string(), 100 100 error: z.coerce.boolean(), 101 - region: z.enum(monitorRegions), 101 + region: z.enum(monitorRegions).or(z.string()), 102 102 cronTimestamp: z.number().int(), 103 103 trigger: z.enum(triggers).nullable().default("cron"), 104 104 timestamp: z.number(), ··· 123 123 statusCode: z.number().int().nullable(), 124 124 monitorId: z.string(), 125 125 requestStatus: z.enum(["error", "success", "degraded"]).nullable(), 126 - region: z.enum(monitorRegions), 126 + region: z.enum(monitorRegions).or(z.string()), 127 127 cronTimestamp: z.number().int(), 128 128 trigger: z.enum(triggers).nullable().default("cron"), 129 129 timestamp: z.number(), ··· 145 145 statusCode: z.number().int().nullable(), 146 146 monitorId: z.string(), 147 147 error: z.coerce.boolean(), 148 - region: z.enum(monitorRegions), 148 + region: z.enum(monitorRegions).or(z.string()), 149 149 cronTimestamp: z.number().int(), 150 150 trigger: z.enum(triggers).nullable().default("cron"), 151 151 timestamp: z.number(), ··· 170 170 statusCode: z.number().int().nullable(), 171 171 monitorId: z.string(), 172 172 requestStatus: z.enum(["error", "success", "degraded"]).nullable(), 173 - region: z.enum(monitorRegions), 173 + region: z.enum(monitorRegions).or(z.string()), 174 174 cronTimestamp: z.number().int(), 175 175 trigger: z.enum(triggers).nullable().default("cron"), 176 176 timestamp: z.number(), ··· 184 184 return this.tb.buildPipe({ 185 185 pipe: "endpoint__http_metrics_1d__v0", 186 186 parameters: z.object({ 187 - regions: z.array(z.enum(monitorRegions)).optional(), 187 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 188 188 interval: z.number().int().optional(), 189 189 monitorId: z.string(), 190 190 }), ··· 207 207 pipe: "endpoint__http_metrics_1d__v1", 208 208 parameters: z.object({ 209 209 interval: z.number().int().optional(), 210 - regions: z.array(z.enum(monitorRegions)).optional(), 210 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 211 211 monitorId: z.string(), 212 212 }), 213 213 data: z.object({ ··· 230 230 return this.tb.buildPipe({ 231 231 pipe: "endpoint__http_metrics_7d__v0", 232 232 parameters: z.object({ 233 - regions: z.array(z.enum(monitorRegions)).optional(), 233 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 234 234 interval: z.number().int().optional(), 235 235 monitorId: z.string(), 236 236 }), ··· 253 253 pipe: "endpoint__http_metrics_7d__v1", 254 254 parameters: z.object({ 255 255 interval: z.number().int().optional(), 256 - regions: z.array(z.enum(monitorRegions)).optional(), 256 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 257 257 monitorId: z.string(), 258 258 }), 259 259 data: z.object({ ··· 276 276 return this.tb.buildPipe({ 277 277 pipe: "endpoint__http_metrics_14d__v0", 278 278 parameters: z.object({ 279 - regions: z.array(z.enum(monitorRegions)).optional(), 279 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 280 280 interval: z.number().int().optional(), 281 281 monitorId: z.string(), 282 282 }), ··· 299 299 pipe: "endpoint__http_metrics_14d__v1", 300 300 parameters: z.object({ 301 301 interval: z.number().int().optional(), 302 - regions: z.array(z.enum(monitorRegions)).optional(), 302 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 303 303 monitorId: z.string(), 304 304 }), 305 305 data: z.object({ ··· 326 326 monitorId: z.string(), 327 327 }), 328 328 data: z.object({ 329 - region: z.enum(monitorRegions), 329 + region: z.enum(monitorRegions).or(z.string()), 330 330 timestamp: z.number().int(), 331 331 p50Latency: z.number().nullable().default(0), 332 332 p75Latency: z.number().nullable().default(0), ··· 346 346 monitorId: z.string(), 347 347 }), 348 348 data: z.object({ 349 - region: z.enum(monitorRegions), 349 + region: z.enum(monitorRegions).or(z.string()), 350 350 timestamp: z.number().int(), 351 351 p50Latency: z.number().nullable().default(0), 352 352 p75Latency: z.number().nullable().default(0), ··· 366 366 monitorId: z.string(), 367 367 }), 368 368 data: z.object({ 369 - region: z.enum(monitorRegions), 369 + region: z.enum(monitorRegions).or(z.string()), 370 370 timestamp: z.number().int(), 371 371 p50Latency: z.number().nullable().default(0), 372 372 p75Latency: z.number().nullable().default(0), ··· 383 383 pipe: "endpoint__http_metrics_by_region_1d__v0", 384 384 parameters: z.object({ 385 385 monitorId: z.string(), 386 - regions: z.array(z.enum(monitorRegions)).optional(), 386 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 387 387 }), 388 388 data: z.object({ 389 - region: z.enum(monitorRegions), 389 + region: z.enum(monitorRegions).or(z.string()), 390 390 count: z.number().int(), 391 391 ok: z.number().int(), 392 392 p50Latency: z.number().nullable().default(0), ··· 404 404 pipe: "endpoint__http_metrics_by_region_7d__v0", 405 405 parameters: z.object({ 406 406 monitorId: z.string(), 407 - regions: z.array(z.enum(monitorRegions)).optional(), 407 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 408 408 }), 409 409 data: z.object({ 410 - region: z.enum(monitorRegions), 410 + region: z.enum(monitorRegions).or(z.string()), 411 411 count: z.number().int(), 412 412 ok: z.number().int(), 413 413 p50Latency: z.number().nullable().default(0), ··· 425 425 pipe: "endpoint__http_metrics_by_region_14d__v0", 426 426 parameters: z.object({ 427 427 monitorId: z.string(), 428 - regions: z.array(z.enum(monitorRegions)).optional(), 428 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 429 429 }), 430 430 data: z.object({ 431 - region: z.enum(monitorRegions), 431 + region: z.enum(monitorRegions).or(z.string()), 432 432 count: z.number().int(), 433 433 ok: z.number().int(), 434 434 p50Latency: z.number().nullable().default(0), ··· 518 518 monitorId: z.string(), 519 519 url: z.string().url(), 520 520 error: z.coerce.boolean(), 521 - region: z.enum(monitorRegions), 521 + region: z.enum(monitorRegions).or(z.string()), 522 522 cronTimestamp: z.number().int(), 523 523 message: z.string().nullable(), 524 524 headers: headersSchema, ··· 540 540 pipe: "endpoint__http_get_30d__v0", 541 541 parameters: z.object({ 542 542 monitorId: z.string(), 543 - region: z.enum(monitorRegions).optional(), 543 + region: z.enum(monitorRegions).or(z.string()).optional(), 544 544 cronTimestamp: z.number().int().optional(), 545 545 }), 546 546 data: z.object({ ··· 550 550 monitorId: z.string(), 551 551 url: z.string().url(), 552 552 error: z.coerce.boolean(), 553 - region: z.enum(monitorRegions), 553 + region: z.enum(monitorRegions).or(z.string()), 554 554 cronTimestamp: z.number().int(), 555 555 message: z.string().nullable(), 556 556 headers: headersSchema, ··· 583 583 .number() 584 584 .default(0) 585 585 .transform((val) => val !== 0), 586 - region: z.enum(monitorRegions), 586 + region: z.enum(monitorRegions).or(z.string()), 587 587 timestamp: z.number().int().optional(), 588 588 message: z.string().nullable().optional(), 589 589 timing: timingSchema, ··· 605 605 latency: z.number().int(), 606 606 monitorId: z.coerce.string(), 607 607 error: z.coerce.boolean(), 608 - region: z.enum(monitorRegions), 608 + region: z.enum(monitorRegions).or(z.string()), 609 609 cronTimestamp: z.number().int(), 610 610 trigger: z.enum(triggers).nullable().default("cron"), 611 611 timestamp: z.number(), ··· 629 629 latency: z.number().int(), 630 630 monitorId: z.coerce.string(), 631 631 requestStatus: z.enum(["error", "success", "degraded"]).nullable(), 632 - region: z.enum(monitorRegions), 632 + region: z.enum(monitorRegions).or(z.string()), 633 633 cronTimestamp: z.number().int(), 634 634 trigger: z.enum(triggers).nullable().default("cron"), 635 635 timestamp: z.number(), ··· 649 649 latency: z.number().int(), 650 650 monitorId: z.coerce.string(), 651 651 error: z.coerce.boolean(), 652 - region: z.enum(monitorRegions), 652 + region: z.enum(monitorRegions).or(z.string()), 653 653 cronTimestamp: z.number().int(), 654 654 trigger: z.enum(triggers).nullable().default("cron"), 655 655 timestamp: z.number(), ··· 673 673 latency: z.number().int(), 674 674 monitorId: z.coerce.string(), 675 675 requestStatus: z.enum(["error", "success", "degraded"]).nullable(), 676 - region: z.enum(monitorRegions), 676 + region: z.enum(monitorRegions).or(z.string()), 677 677 cronTimestamp: z.number().int(), 678 678 trigger: z.enum(triggers).nullable().default("cron"), 679 679 timestamp: z.number(), ··· 693 693 latency: z.number().int(), 694 694 monitorId: z.coerce.string(), 695 695 error: z.coerce.boolean(), 696 - region: z.enum(monitorRegions), 696 + region: z.enum(monitorRegions).or(z.string()), 697 697 cronTimestamp: z.number().int(), 698 698 trigger: z.enum(triggers).nullable().default("cron"), 699 699 timestamp: z.number(), ··· 717 717 latency: z.number().int(), 718 718 monitorId: z.coerce.string(), 719 719 requestStatus: z.enum(["error", "success", "degraded"]).nullable(), 720 - region: z.enum(monitorRegions), 720 + region: z.enum(monitorRegions).or(z.string()), 721 721 cronTimestamp: z.number().int(), 722 722 trigger: z.enum(triggers).nullable().default("cron"), 723 723 timestamp: z.number(), ··· 730 730 return this.tb.buildPipe({ 731 731 pipe: "endpoint__tcp_metrics_1d__v0", 732 732 parameters: z.object({ 733 - regions: z.array(z.enum(monitorRegions)).optional(), 733 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 734 734 interval: z.number().int().optional(), 735 735 monitorId: z.string(), 736 736 }), ··· 753 753 pipe: "endpoint__tcp_metrics_1d__v1", 754 754 parameters: z.object({ 755 755 interval: z.number().int().optional(), 756 - regions: z.array(z.enum(monitorRegions)).optional(), 756 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 757 757 monitorId: z.string(), 758 758 }), 759 759 data: z.object({ ··· 777 777 pipe: "endpoint__tcp_metrics_7d__v0", 778 778 parameters: z.object({ 779 779 interval: z.number().int().optional(), 780 - regions: z.array(z.enum(monitorRegions)).optional(), 780 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 781 781 monitorId: z.string(), 782 782 }), 783 783 data: z.object({ ··· 799 799 pipe: "endpoint__tcp_metrics_7d__v1", 800 800 parameters: z.object({ 801 801 interval: z.number().int().optional(), 802 - regions: z.array(z.enum(monitorRegions)).optional(), 802 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 803 803 monitorId: z.string(), 804 804 }), 805 805 data: z.object({ ··· 822 822 return this.tb.buildPipe({ 823 823 pipe: "endpoint__tcp_metrics_14d__v0", 824 824 parameters: z.object({ 825 - regions: z.array(z.enum(monitorRegions)).optional(), 825 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 826 826 interval: z.number().int().optional(), 827 827 monitorId: z.string(), 828 828 }), ··· 845 845 pipe: "endpoint__tcp_metrics_14d__v1", 846 846 parameters: z.object({ 847 847 interval: z.number().int().optional(), 848 - regions: z.array(z.enum(monitorRegions)).optional(), 848 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 849 849 monitorId: z.string(), 850 850 }), 851 851 data: z.object({ ··· 868 868 return this.tb.buildPipe({ 869 869 pipe: "endpoint__tcp_metrics_by_interval_1d__v0", 870 870 parameters: z.object({ 871 - regions: z.array(z.enum(monitorRegions)).optional(), 871 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 872 872 interval: z.number().int().optional(), 873 873 monitorId: z.string(), 874 874 }), 875 875 data: z.object({ 876 - region: z.enum(monitorRegions), 876 + region: z.enum(monitorRegions).or(z.string()), 877 877 timestamp: z.number().int(), 878 878 p50Latency: z.number().nullable().default(0), 879 879 p75Latency: z.number().nullable().default(0), ··· 889 889 return this.tb.buildPipe({ 890 890 pipe: "endpoint__tcp_metrics_by_interval_7d__v0", 891 891 parameters: z.object({ 892 - regions: z.array(z.enum(monitorRegions)).optional(), 892 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 893 893 interval: z.number().int().optional(), 894 894 monitorId: z.string(), 895 895 }), 896 896 data: z.object({ 897 - region: z.enum(monitorRegions), 897 + region: z.enum(monitorRegions).or(z.string()), 898 898 timestamp: z.number().int(), 899 899 p50Latency: z.number().nullable().default(0), 900 900 p75Latency: z.number().nullable().default(0), ··· 910 910 return this.tb.buildPipe({ 911 911 pipe: "endpoint__tcp_metrics_by_interval_14d__v0", 912 912 parameters: z.object({ 913 - regions: z.array(z.enum(monitorRegions)).optional(), 913 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 914 914 interval: z.number().int().optional(), 915 915 monitorId: z.string(), 916 916 }), 917 917 data: z.object({ 918 - region: z.enum(monitorRegions), 918 + region: z.enum(monitorRegions).or(z.string()), 919 919 timestamp: z.number().int(), 920 920 p50Latency: z.number().nullable().default(0), 921 921 p75Latency: z.number().nullable().default(0), ··· 932 932 pipe: "endpoint__tcp_metrics_by_region_1d__v0", 933 933 parameters: z.object({ 934 934 monitorId: z.string(), 935 - regions: z.array(z.enum(monitorRegions)).optional(), 935 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 936 936 }), 937 937 data: z.object({ 938 - region: z.enum(monitorRegions), 938 + region: z.enum(monitorRegions).or(z.string()), 939 939 count: z.number().int(), 940 940 ok: z.number().int(), 941 941 p50Latency: z.number().nullable().default(0), ··· 953 953 pipe: "endpoint__tcp_metrics_by_region_7d__v0", 954 954 parameters: z.object({ 955 955 monitorId: z.string(), 956 - regions: z.array(z.enum(monitorRegions)).optional(), 956 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 957 957 }), 958 958 data: z.object({ 959 - region: z.enum(monitorRegions), 959 + region: z.enum(monitorRegions).or(z.string()), 960 960 count: z.number().int(), 961 961 ok: z.number().int(), 962 962 p50Latency: z.number().nullable().default(0), ··· 974 974 pipe: "endpoint__tcp_metrics_by_region_14d__v0", 975 975 parameters: z.object({ 976 976 monitorId: z.string(), 977 - regions: z.array(z.enum(monitorRegions)).optional(), 977 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 978 978 }), 979 979 data: z.object({ 980 - region: z.enum(monitorRegions), 980 + region: z.enum(monitorRegions).or(z.string()), 981 981 count: z.number().int(), 982 982 ok: z.number().int(), 983 983 p50Latency: z.number().nullable().default(0), ··· 1099 1099 latency: z.number().int(), 1100 1100 monitorId: z.coerce.string(), 1101 1101 error: z.coerce.boolean(), 1102 - region: z.enum(monitorRegions), 1102 + region: z.enum(monitorRegions).or(z.string()), 1103 1103 cronTimestamp: z.number().int(), 1104 1104 trigger: z.enum(triggers).nullable().default("cron"), 1105 1105 timestamp: z.number(), ··· 1116 1116 pipe: "endpoint__tcp_get_30d__v0", 1117 1117 parameters: z.object({ 1118 1118 monitorId: z.string(), 1119 - region: z.enum(monitorRegions).optional(), 1119 + region: z.enum(monitorRegions).or(z.string()).optional(), 1120 1120 cronTimestamp: z.number().int().optional(), 1121 1121 }), 1122 1122 data: z.object({ ··· 1124 1124 latency: z.number().int(), 1125 1125 monitorId: z.string(), 1126 1126 error: z.coerce.boolean(), 1127 - region: z.enum(monitorRegions), 1127 + region: z.enum(monitorRegions).or(z.string()), 1128 1128 cronTimestamp: z.number().int(), 1129 1129 trigger: z.enum(triggers).nullable().default("cron"), 1130 1130 timestamp: z.number(), ··· 1149 1149 regions: z.string().array().optional(), 1150 1150 }), 1151 1151 data: z.object({ 1152 - region: z.enum(monitorRegions), 1152 + region: z.enum(monitorRegions).or(z.string()), 1153 1153 timestamp: z.number().int(), 1154 1154 p50Latency: z.number().nullable().default(0), 1155 1155 p75Latency: z.number().nullable().default(0), ··· 1170 1170 regions: z.string().array().optional(), 1171 1171 }), 1172 1172 data: z.object({ 1173 - region: z.enum(monitorRegions), 1173 + region: z.enum(monitorRegions).or(z.string()), 1174 1174 timestamp: z.number().int(), 1175 1175 p50Latency: z.number().nullable().default(0), 1176 1176 p75Latency: z.number().nullable().default(0), ··· 1191 1191 regions: z.string().array().optional(), 1192 1192 }), 1193 1193 data: z.object({ 1194 - region: z.enum(monitorRegions), 1194 + region: z.enum(monitorRegions).or(z.string()), 1195 1195 timestamp: z.number().int(), 1196 1196 p50Latency: z.number().nullable().default(0), 1197 1197 p75Latency: z.number().nullable().default(0), ··· 1210 1210 monitorId: z.string(), 1211 1211 fromDate: z.string().optional(), 1212 1212 toDate: z.string().optional(), 1213 - regions: z.enum(monitorRegions).array().optional(), 1213 + regions: z.enum(monitorRegions).or(z.string()).array().optional(), 1214 1214 interval: z.number().int().optional(), 1215 1215 }), 1216 1216 data: z.object({ ··· 1229 1229 monitorId: z.string(), 1230 1230 fromDate: z.string().optional(), 1231 1231 toDate: z.string().optional(), 1232 - regions: z.enum(monitorRegions).array().optional(), 1232 + regions: z.enum(monitorRegions).or(z.string()).array().optional(), 1233 1233 interval: z.number().int().optional(), 1234 1234 }), 1235 1235 data: z.object({ ··· 1248 1248 monitorId: z.string(), 1249 1249 fromDate: z.string().optional(), 1250 1250 toDate: z.string().optional(), 1251 - regions: z.enum(monitorRegions).array().optional(), 1251 + regions: z.enum(monitorRegions).or(z.string()).array().optional(), 1252 1252 interval: z.number().int().optional(), 1253 1253 }), 1254 1254 data: z.object({ ··· 1267 1267 monitorId: z.string(), 1268 1268 fromDate: z.string().optional(), 1269 1269 toDate: z.string().optional(), 1270 - regions: z.enum(monitorRegions).array().optional(), 1270 + regions: z.enum(monitorRegions).or(z.string()).array().optional(), 1271 1271 interval: z.number().int().optional(), 1272 1272 }), 1273 1273 data: z.object({ ··· 1353 1353 parameters: z.object({ 1354 1354 monitorId: z.string(), 1355 1355 interval: z.number().int().optional(), 1356 - regions: z.array(z.enum(monitorRegions)).optional(), 1356 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 1357 1357 }), 1358 1358 data: z.object({ 1359 1359 timestamp: z.number().int(), ··· 1444 1444 pipe: "endpoint__tcp_metrics_latency_1d__v1", 1445 1445 parameters: z.object({ 1446 1446 monitorId: z.string(), 1447 - regions: z.array(z.enum(monitorRegions)).optional(), 1447 + regions: z.array(z.enum(monitorRegions).or(z.string())).optional(), 1448 1448 }), 1449 1449 data: z.object({ 1450 1450 timestamp: z.number().int(),
+84
pnpm-lock.yaml
··· 1621 1621 specifier: 5.7.2 1622 1622 version: 5.7.2 1623 1623 1624 + packages/proto: 1625 + devDependencies: 1626 + '@bufbuild/buf': 1627 + specifier: 1.57.2 1628 + version: 1.57.2 1629 + 1624 1630 packages/react: 1625 1631 dependencies: 1626 1632 react: ··· 2505 2511 engines: {node: '>=14.21.3'} 2506 2512 cpu: [x64] 2507 2513 os: [win32] 2514 + 2515 + '@bufbuild/buf-darwin-arm64@1.57.2': 2516 + resolution: {integrity: sha512-ybwjZ8hFpDiSfTY8ltuNulh6e4CYcojZX+joo/VocFb4InRFyF08S+JSO+1ld+nTmJ24ziGwXZTg8qXiiN6qog==} 2517 + engines: {node: '>=12'} 2518 + cpu: [arm64] 2519 + os: [darwin] 2520 + 2521 + '@bufbuild/buf-darwin-x64@1.57.2': 2522 + resolution: {integrity: sha512-uoNjkkyjmJlzQyfPI/mwlutDvu48gFg8pYzlK0RhNshvTWCDo2vXbw1gZSaQnv1xK585N9Axmyjp0K2sYJkaDQ==} 2523 + engines: {node: '>=12'} 2524 + cpu: [x64] 2525 + os: [darwin] 2526 + 2527 + '@bufbuild/buf-linux-aarch64@1.57.2': 2528 + resolution: {integrity: sha512-JvB9M+GraP7tgYlyKUhF46+pw/hctFyAjcLvPCPxr8Lr9uo3I6uqU8KywE4AuzdFReaZ4wtdF6sUlsfDOo8Geg==} 2529 + engines: {node: '>=12'} 2530 + cpu: [arm64] 2531 + os: [linux] 2532 + 2533 + '@bufbuild/buf-linux-armv7@1.57.2': 2534 + resolution: {integrity: sha512-13ZBU/LTboW7B/qjMV6fY6z8q0uyfSpp+y2J+/PAzCrC64ewSTE30pMKFQujMQ05Po0kY5e9yWvTElJcLbzt5w==} 2535 + engines: {node: '>=12'} 2536 + cpu: [arm] 2537 + os: [linux] 2538 + 2539 + '@bufbuild/buf-linux-x64@1.57.2': 2540 + resolution: {integrity: sha512-R28Y6g0kGlM/agtVc8sAjzGhgtbA4JyJYg4w7ZPm/FaVEw2sAYZBsa5uxmMNNOmWopKfd0yr2Pgq8ndfG0QSug==} 2541 + engines: {node: '>=12'} 2542 + cpu: [x64] 2543 + os: [linux] 2544 + 2545 + '@bufbuild/buf-win32-arm64@1.57.2': 2546 + resolution: {integrity: sha512-6YlPr2Z0VLae/76raYDEcotN7YGNgn3qaNy7x5idAoNeAr0EK2WsKtlBypK0/fQO3GOrYo0HGM/0SFENXoQKbw==} 2547 + engines: {node: '>=12'} 2548 + cpu: [arm64] 2549 + os: [win32] 2550 + 2551 + '@bufbuild/buf-win32-x64@1.57.2': 2552 + resolution: {integrity: sha512-7YKz2RVPQlL+rJYzNL7JK3LpvZUz7wY6hof3QOTNX3sHY6pRD+mtWt/PITTlC3PtyjSKy17wAhbkokC0JErsHw==} 2553 + engines: {node: '>=12'} 2554 + cpu: [x64] 2555 + os: [win32] 2556 + 2557 + '@bufbuild/buf@1.57.2': 2558 + resolution: {integrity: sha512-N51MOnjROdtBX3fPU2KGdmwfiT1WWXzs8Jw1tMMkyVAmCSahWbObGIPq7e1fw7lR5i0s3bTxQyk3KRmb2/uwNg==} 2559 + engines: {node: '>=12'} 2560 + hasBin: true 2508 2561 2509 2562 '@bundled-es-modules/cookie@2.0.1': 2510 2563 resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} ··· 13116 13169 13117 13170 '@biomejs/cli-win32-x64@1.9.4': 13118 13171 optional: true 13172 + 13173 + '@bufbuild/buf-darwin-arm64@1.57.2': 13174 + optional: true 13175 + 13176 + '@bufbuild/buf-darwin-x64@1.57.2': 13177 + optional: true 13178 + 13179 + '@bufbuild/buf-linux-aarch64@1.57.2': 13180 + optional: true 13181 + 13182 + '@bufbuild/buf-linux-armv7@1.57.2': 13183 + optional: true 13184 + 13185 + '@bufbuild/buf-linux-x64@1.57.2': 13186 + optional: true 13187 + 13188 + '@bufbuild/buf-win32-arm64@1.57.2': 13189 + optional: true 13190 + 13191 + '@bufbuild/buf-win32-x64@1.57.2': 13192 + optional: true 13193 + 13194 + '@bufbuild/buf@1.57.2': 13195 + optionalDependencies: 13196 + '@bufbuild/buf-darwin-arm64': 1.57.2 13197 + '@bufbuild/buf-darwin-x64': 1.57.2 13198 + '@bufbuild/buf-linux-aarch64': 1.57.2 13199 + '@bufbuild/buf-linux-armv7': 1.57.2 13200 + '@bufbuild/buf-linux-x64': 1.57.2 13201 + '@bufbuild/buf-win32-arm64': 1.57.2 13202 + '@bufbuild/buf-win32-x64': 1.57.2 13119 13203 13120 13204 '@bundled-es-modules/cookie@2.0.1': 13121 13205 dependencies: