diff --git a/.air.toml b/.air.toml
new file mode 100644
index 0000000..e7e5c07
--- /dev/null
+++ b/.air.toml
@@ -0,0 +1,22 @@
+# Working directory
+root = "."
+tmp_dir = ".tmp"
+
+[build]
+# Just plain old shell command. You could use `make` as well.
+cmd = "make build"
+# Binary file yields from `cmd`.
+full_bin = "build/sentinel hub start -c ./config.yaml"
+# This log file places in your tmp_dir.
+log = "air_errors.log"
+# Watch these filename extensions.
+include_ext = ["go"]
+# Ignore these filename extensions or directories.
+exclude_dir = [".tmp", "sql", "frontend", "data", "schema", "scripts"]
+# It's not necessary to trigger build each time file changes if it's too frequent.
+delay = 1000 # ms
+send_interrupt = true
+kill_delay = 500000000
+
+[log]
+time = true
diff --git a/.gitignore b/.gitignore
index 6b8e310..c829d8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,9 @@ go.work.sum
# .vscode/
data
+data-agent
build
config.yaml
+config-agent.yaml
dist
+.tmp
diff --git a/Dockerfile b/Dockerfile
index 0b6fc8c..4eddaa0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,7 +16,7 @@ COPY frontend/ ./
RUN pnpm run build
# Backend build stage
-FROM golang:1.25.0-alpine AS backend-builder
+FROM golang:1.25.1-alpine AS backend-builder
# Define build arguments for version, commit, and date.
ARG VERSION="unknown"
@@ -54,4 +54,4 @@ WORKDIR /root/
COPY --from=backend-builder /app/bin/sentinel .
# Run the binary
-CMD ["./sentinel", "start"]
+ENTRYPOINT ["./sentinel"]
diff --git a/Makefile b/Makefile
index a6c5b50..b94b3a9 100644
--- a/Makefile
+++ b/Makefile
@@ -4,10 +4,11 @@
# Variables
BINARY_NAME=sentinel
-MAIN_PATH=./cmd/sentinel
+SENTINEL_PATH=./cmd/sentinel
BUILD_DIR=./build
VERSION?=dev
LDFLAGS=-ldflags="-w -s -X main.version=${VERSION}"
+MIGRATIONS_DIR = ./sql/migrations/
# Default target
help: ## Show this help message
@@ -17,36 +18,44 @@ help: ## Show this help message
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
# Development
-dev: ## Run in development mode with auto-reload
- go run $(MAIN_PATH) start
+hub: ## Run in development mode with auto-reload
+ go run $(SENTINEL_PATH) hub start -c ./config.yaml
+
+agent: ## Run in development mode with auto-reload
+ go run $(SENTINEL_PATH) agent start -c ./config-agent.yaml
+
+migrateup:
+ go run $(SENTINEL_PATH) migrations up -db-path ./data/hub/sqlite/db.sqlite
+
+migratedown:
+ go run $(SENTINEL_PATH) migrations down -db-path ./data/hub/sqlite/db.sqlite
+
+air:
+ air -c .air.toml
run: build ## Build and run the application
./$(BUILD_DIR)/$(BINARY_NAME)
-runtcpserver:
- go run ./cmd/tcpserver
-
-rungrpcserver:
- go run ./cmd/grpcserver
+runtestservers:
+ go run ./cmd/testserver -http -grpc -tcp
front:
cd frontend && pnpm dev
# Build targets
-build: deps ## Build the application
- @mkdir -p $(BUILD_DIR)
- go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) $(MAIN_PATH)
+build:
+ go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) $(SENTINEL_PATH)
build-linux: deps ## Build for Linux
@mkdir -p $(BUILD_DIR)
- GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux $(MAIN_PATH)
+ GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux $(SENTINEL_PATH)
build-all: deps ## Build for all platforms
@mkdir -p $(BUILD_DIR)
- GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 $(MAIN_PATH)
- GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-amd64 $(MAIN_PATH)
- GOOS=windows GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe $(MAIN_PATH)
+ GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 $(SENTINEL_PATH)
+ GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-amd64 $(SENTINEL_PATH)
+ GOOS=windows GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe $(SENTINEL_PATH)
# Dependencies
deps: ## Download dependencies
@@ -115,9 +124,11 @@ clean: ## Clean build artifacts
rm -f coverage.out coverage.html
docker-compose down --volumes --remove-orphans || true
-# Database
-init-db: ## Initialize database directory
- mkdir -p data
+# db-create-migration:
+# migrate create -ext sql -format unix -dir "$(MIGRATIONS_DIR)" $(filter-out $@,$(MAKECMDGOALS))
+
+db-create-migration:
+ go run ./cmd/sentinel migrations create -p ./sql/migrations -name $(filter-out $@,$(MAKECMDGOALS))
# Configuration
init-config: ## Copy example configuration
@@ -133,3 +144,26 @@ genswagger:
rm -rf ./docs/*
swag fmt -d ./internal/web
swag init -o docs/docsv1 --dir ./internal/web -g handlers.go --parseDependency
+
+genenvs:
+ go run ./cmd/sentinel config genenvs
+
+gensql:
+ pgxgen crud
+ pgxgen sqlc generate
+
+genproto: ## Generate protobuf code
+ buf lint
+ rm -rf ./internal/hub/hubserver/api/*
+ rm -rf frontend/src/api/gen/*
+ buf generate
+ rm -rf frontend/src/api/gen/sentinel/hub
+
+grpcui-hub:
+ grpcui --plaintext localhost:8080
+
+grpcui-server:
+ grpcui --plaintext localhost:8080
+
+%:
+ @:
diff --git a/README.md b/README.md
index 4b2c887..d553b98 100644
--- a/README.md
+++ b/README.md
@@ -123,9 +123,6 @@ monitoring:
default_timeout: 10s
default_retries: 5
-database:
- path: "./data/db.sqlite"
-
notifications:
enabled: true
urls:
@@ -218,7 +215,7 @@ The gRPC monitor supports three types of checks:
## Notification Setup
-Sentinel uses [Shoutrrr](https://github.com/containrrr/shoutrrr) for notifications, which supports multiple providers
+Sentinel uses [Shoutrrr](https://github.com/nicholas-fedor/shoutrrr) for notifications, which supports multiple providers
You can configure multiple notification providers simultaneously. If one provider fails, notifications will still be sent to the others:
diff --git a/buf.gen.yaml b/buf.gen.yaml
new file mode 100644
index 0000000..8f73e9d
--- /dev/null
+++ b/buf.gen.yaml
@@ -0,0 +1,19 @@
+version: v2
+managed:
+ enabled: true
+ override:
+ - file_option: go_package_prefix
+ value: github.com/sxwebdev/sentinel/internal/hub/hubserver/api
+plugins:
+ - remote: buf.build/protocolbuffers/go:v1.36.9
+ out: internal/hub/hubserver/api
+ opt: paths=source_relative
+ - remote: buf.build/connectrpc/go:v1.18.1
+ out: internal/hub/hubserver/api
+ opt: paths=source_relative
+ - remote: buf.build/bufbuild/es:v2.9.0
+ out: frontend/src/api/gen
+ opt: target=ts
+ - remote: buf.build/connectrpc/query-es:v2.2.0
+ out: frontend/src/api/gen
+ opt: target=ts
diff --git a/buf.yaml b/buf.yaml
new file mode 100644
index 0000000..ff6c1e1
--- /dev/null
+++ b/buf.yaml
@@ -0,0 +1,20 @@
+version: v2
+modules:
+ - path: schema
+lint:
+ use:
+ - COMMENT_SERVICE
+ - STANDARD
+ except:
+ - FIELD_NOT_REQUIRED
+ - PACKAGE_NO_IMPORT_CYCLE
+ disallow_comment_ignores: true
+ rpc_allow_google_protobuf_empty_requests: true
+ rpc_allow_google_protobuf_empty_responses: true
+
+breaking:
+ use:
+ - FILE
+ except:
+ - EXTENSION_NO_DELETE
+ - FIELD_SAME_DEFAULT
diff --git a/cmd/grpcserver/main.go b/cmd/grpcserver/main.go
deleted file mode 100644
index d022b9b..0000000
--- a/cmd/grpcserver/main.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package main
-
-import (
- "flag"
- "fmt"
- "log"
- "net"
- "os"
- "os/signal"
- "syscall"
-
- "google.golang.org/grpc"
- "google.golang.org/grpc/health"
- "google.golang.org/grpc/health/grpc_health_v1"
- "google.golang.org/grpc/reflection"
-)
-
-var port = flag.Int("port", 50051, "The server port")
-
-func main() {
- flag.Parse()
-
- if err := run(); err != nil {
- log.Fatalf("Failed to run server: %v", err)
- }
-}
-
-func run() error {
- addr := fmt.Sprintf("localhost:%d", *port)
- log.Printf("Starting gRPC server on %s\n", addr)
-
- lis, err := net.Listen("tcp", addr)
- if err != nil {
- return fmt.Errorf("failed to listen: %w", err)
- }
-
- // Create gRPC server
- grpcServer := grpc.NewServer()
-
- // Create and register health server
- healthServer := health.NewServer()
- grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
-
- // Register gRPC reflection service for debugging
- reflection.Register(grpcServer)
-
- // Set initial serving status
- healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
- healthServer.SetServingStatus("health", grpc_health_v1.HealthCheckResponse_SERVING)
- healthServer.SetServingStatus("test-service", grpc_health_v1.HealthCheckResponse_SERVING)
-
- // Start server in a goroutine
- serverErr := make(chan error, 1)
- go func() {
- if err := grpcServer.Serve(lis); err != nil {
- serverErr <- fmt.Errorf("failed to serve: %w", err)
- }
- }()
-
- // Wait for interrupt signal or server error
- sigChan := make(chan os.Signal, 1)
- signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
-
- select {
- case <-sigChan:
- // Graceful shutdown
- case err := <-serverErr:
- return err
- }
-
- // Graceful shutdown
- grpcServer.GracefulStop()
-
- return nil
-}
-
-// simulateServiceStatusChanges simulates service status changes for testing
-// func simulateServiceStatusChanges(healthServer *health.Server) {
-// ticker := time.NewTicker(30 * time.Second)
-// defer ticker.Stop()
-
-// status := grpc_health_v1.HealthCheckResponse_SERVING
-
-// for range ticker.C {
-// // Toggle service status for testing
-// if status == grpc_health_v1.HealthCheckResponse_SERVING {
-// status = grpc_health_v1.HealthCheckResponse_NOT_SERVING
-// log.Println("Setting test-service status to NOT_SERVING")
-// } else {
-// status = grpc_health_v1.HealthCheckResponse_SERVING
-// log.Println("Setting test-service status to SERVING")
-// }
-
-// healthServer.SetServingStatus("test-service", status)
-// }
-// }
diff --git a/cmd/sentinel/agent.go b/cmd/sentinel/agent.go
new file mode 100644
index 0000000..03cb010
--- /dev/null
+++ b/cmd/sentinel/agent.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/sxwebdev/sentinel/internal/agent"
+ "github.com/sxwebdev/sentinel/internal/config"
+ "github.com/sxwebdev/sentinel/internal/models"
+ "github.com/tkcrm/mx/launcher"
+ "github.com/tkcrm/mx/logger"
+ "github.com/tkcrm/mx/service"
+ "github.com/tkcrm/mx/service/pingpong"
+ "github.com/urfave/cli/v3"
+)
+
+func agentCMD() *cli.Command {
+ return &cli.Command{
+ Name: "agent",
+ Usage: "agent commands",
+ Commands: []*cli.Command{
+ {
+ Name: "start",
+ Usage: "start the agent",
+ Flags: []cli.Flag{cfgPathsFlag()},
+ Action: func(ctx context.Context, cl *cli.Command) error {
+ conf := new(config.ConfigAgent)
+ if err := config.Load(conf, envAgentPrefix, cl.StringSlice("config")); err != nil {
+ return fmt.Errorf("failed to load config: %w", err)
+ }
+
+ loggerOpts := append(defaultLoggerOpts(), logger.WithConfig(conf.Log))
+
+ l := logger.NewExtended(loggerOpts...)
+ defer func() {
+ _ = l.Sync()
+ }()
+
+ // init launcher
+ ln := launcher.New(
+ launcher.WithVersion(version),
+ launcher.WithName(appName),
+ launcher.WithLogger(l),
+ launcher.WithContext(ctx),
+ launcher.WithRunnerServicesSequence(launcher.RunnerServicesSequenceFifo),
+ launcher.WithOpsConfig(conf.Ops),
+ launcher.WithAppStartStopLog(true),
+ )
+
+ // check if exists data dir, if not create it
+ if _, err := os.Stat(conf.AgentDataDir()); os.IsNotExist(err) {
+ l.Infof("creating data directory in %s", conf.AgentDataDir())
+ if err := os.MkdirAll(conf.AgentDataDir(), 0o700); err != nil {
+ return fmt.Errorf("failed to create data dir: %w", err)
+ }
+ }
+
+ // get system info
+ systemInfo := models.GetSystemInfo(version, commitHash, buildDate)
+
+ // init receiver
+ // rc := receiver.New()
+
+ // init agent service
+ ag, err := agent.New(ctx, l, conf, *systemInfo)
+ if err != nil {
+ return fmt.Errorf("failed to init agent: %w", err)
+ }
+
+ // register services
+ ln.ServicesRunner().Register(
+ service.New(service.WithService(pingpong.New(l))),
+ // service.New(service.WithService(rc)),
+ service.New(service.WithService(ag)),
+ )
+
+ return ln.Run()
+ },
+ },
+ },
+ }
+}
diff --git a/cmd/sentinel/config.go b/cmd/sentinel/config.go
index 5212652..b628e32 100644
--- a/cmd/sentinel/config.go
+++ b/cmd/sentinel/config.go
@@ -8,15 +8,15 @@ import (
"github.com/goccy/go-yaml"
"github.com/sxwebdev/sentinel/internal/config"
+ "github.com/sxwebdev/xconfig"
"github.com/urfave/cli/v3"
)
-func cfgPathsFlag() *cli.StringFlag {
- return &cli.StringFlag{
+func cfgPathsFlag() *cli.StringSliceFlag {
+ return &cli.StringSliceFlag{
Name: "config",
Aliases: []string{"c"},
- Value: "config.yaml",
- Usage: "allows you to use your own paths to configuration files. by default it uses config.yaml",
+ Usage: "allows you to use your own paths to configuration files",
}
}
@@ -29,23 +29,40 @@ func configCMD() *cli.Command {
Name: "genenvs",
Usage: "generate config yaml template",
Action: func(_ context.Context, _ *cli.Command) error {
- conf := new(config.Config)
-
- conf, err := config.Load("")
- if err != nil {
- return fmt.Errorf("failed to load config: %w", err)
+ data := []struct {
+ fileName string
+ envPrefix string
+ conf any
+ }{
+ {
+ fileName: "config.template.yaml",
+ envPrefix: envHubPrefix,
+ conf: new(config.ConfigHub),
+ },
+ {
+ fileName: "config-agent.template.yaml",
+ envPrefix: envAgentPrefix,
+ conf: new(config.ConfigAgent),
+ },
}
- buf := bytes.NewBuffer(nil)
- enc := yaml.NewEncoder(buf, yaml.Indent(2))
- defer enc.Close()
+ for _, d := range data {
+ _, err := xconfig.Load(d.conf, xconfig.WithEnvPrefix(d.envPrefix))
+ if err != nil {
+ return fmt.Errorf("failed to generate markdown: %w", err)
+ }
- if err := enc.Encode(conf); err != nil {
- return fmt.Errorf("failed to encode yaml: %w", err)
- }
+ buf := bytes.NewBuffer(nil)
+ enc := yaml.NewEncoder(buf, yaml.Indent(2))
+ defer enc.Close()
+
+ if err := enc.Encode(d.conf); err != nil {
+ return fmt.Errorf("failed to encode yaml: %w", err)
+ }
- if err := os.WriteFile("config.template.yaml", buf.Bytes(), 0o600); err != nil {
- return fmt.Errorf("failed to write file: %w", err)
+ if err := os.WriteFile(d.fileName, buf.Bytes(), 0o600); err != nil {
+ return fmt.Errorf("failed to write file: %w", err)
+ }
}
return nil
diff --git a/cmd/sentinel/hub.go b/cmd/sentinel/hub.go
new file mode 100644
index 0000000..c08337d
--- /dev/null
+++ b/cmd/sentinel/hub.go
@@ -0,0 +1,224 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/sxwebdev/sentinel/internal/alertresolver"
+ "github.com/sxwebdev/sentinel/internal/config"
+ "github.com/sxwebdev/sentinel/internal/datamigrations"
+ "github.com/sxwebdev/sentinel/internal/dispatcher"
+ "github.com/sxwebdev/sentinel/internal/models"
+ "github.com/sxwebdev/sentinel/internal/receiver"
+ "github.com/sxwebdev/sentinel/internal/scheduler"
+ "github.com/sxwebdev/sentinel/internal/servers"
+ "github.com/sxwebdev/sentinel/internal/services/baseservices"
+ "github.com/sxwebdev/sentinel/internal/store"
+ "github.com/sxwebdev/sentinel/internal/store/badgerdb"
+ updater "github.com/sxwebdev/sentinel/internal/updated"
+ "github.com/sxwebdev/sentinel/internal/utils"
+ "github.com/sxwebdev/sentinel/pkg/locker"
+ "github.com/sxwebdev/sentinel/pkg/migrator"
+ "github.com/sxwebdev/sentinel/pkg/sqlite"
+ "github.com/sxwebdev/sentinel/sql"
+ "github.com/sxwebdev/tokenmanager"
+ "github.com/tkcrm/mx/launcher"
+ "github.com/tkcrm/mx/logger"
+ "github.com/tkcrm/mx/service"
+ "github.com/tkcrm/mx/service/pingpong"
+ "github.com/urfave/cli/v3"
+)
+
+func hubStartCMD() *cli.Command {
+ return &cli.Command{
+ Name: "hub",
+ Usage: "hub commands",
+ Commands: []*cli.Command{
+ {
+ Name: "start",
+ Usage: "start the server",
+ Flags: []cli.Flag{cfgPathsFlag()},
+ Action: func(ctx context.Context, cl *cli.Command) error {
+ conf := new(config.ConfigHub)
+ if err := config.Load(conf, envHubPrefix, cl.StringSlice("config")); err != nil {
+ return fmt.Errorf("failed to load config: %w", err)
+ }
+
+ loggerOpts := append(defaultLoggerOpts(), logger.WithConfig(conf.Log))
+
+ l := logger.NewExtended(loggerOpts...)
+ defer func() {
+ _ = l.Sync()
+ }()
+
+ // init launcher
+ ln := launcher.New(
+ launcher.WithVersion(version),
+ launcher.WithName(appName),
+ launcher.WithLogger(l),
+ launcher.WithContext(ctx),
+ launcher.WithRunnerServicesSequence(launcher.RunnerServicesSequenceLifo),
+ launcher.WithOpsConfig(conf.Ops),
+ launcher.WithAppStartStopLog(true),
+ )
+
+ // check if exists data dir, if not create it
+ if _, err := os.Stat(conf.HubDataDir()); os.IsNotExist(err) {
+ l.Infof("creating data directory in %s", conf.HubDataDir())
+ if err := os.MkdirAll(conf.HubDataDir(), 0o700); err != nil {
+ return fmt.Errorf("failed to create data dir: %w", err)
+ }
+ }
+
+ var authConfig config.AuthConfig
+
+ // get file datadir/hub/secrets.json
+ // if not exists create it with generated values
+ secretsFilePath := filepath.Join(conf.HubDataDir(), "secrets.json")
+ if _, err := os.Stat(secretsFilePath); os.IsNotExist(err) {
+ l.Infof("creating secrets file in %s", secretsFilePath)
+ if err := os.MkdirAll(filepath.Dir(secretsFilePath), 0o700); err != nil {
+ return fmt.Errorf("failed to create secrets dir: %w", err)
+ }
+
+ accessToken, err := utils.GenerateRandomString(48, "")
+ if err != nil {
+ return fmt.Errorf("failed to generate access token secret key: %w", err)
+ }
+
+ refreshToken, err := utils.GenerateRandomString(48, "")
+ if err != nil {
+ return fmt.Errorf("failed to generate refresh token secret key: %w", err)
+ }
+
+ authConfig = config.AuthConfig{
+ AccessTokenSecretKey: accessToken,
+ RefreshTokenSecretKey: refreshToken,
+ }
+
+ data, err := json.MarshalIndent(authConfig, "", " ")
+ if err != nil {
+ return fmt.Errorf("failed to marshal secrets: %w", err)
+ }
+
+ if err := os.WriteFile(secretsFilePath, data, 0o600); err != nil {
+ return fmt.Errorf("failed to create secrets file: %w", err)
+ }
+ } else {
+ data, err := os.ReadFile(secretsFilePath)
+ if err != nil {
+ return fmt.Errorf("failed to read secrets file: %w", err)
+ }
+
+ if err := json.Unmarshal(data, &authConfig); err != nil {
+ return fmt.Errorf("failed to unmarshal secrets file: %w", err)
+ }
+
+ if authConfig.AccessTokenSecretKey == "" || authConfig.RefreshTokenSecretKey == "" {
+ return fmt.Errorf("invalid secrets file: missing keys")
+ }
+ }
+
+ // set default timezone
+ var err error
+ time.Local, err = time.LoadLocation(conf.Timezone)
+ if err != nil {
+ return fmt.Errorf("failed to set timezone: %w", err)
+ }
+
+ sqliteDbPath := filepath.Join(conf.HubDataDir(), "sqlite", sqliteDBFile)
+
+ // init sqlite
+ sqliteDB, err := sqlite.New(ctx, sqliteDbPath)
+ if err != nil {
+ return fmt.Errorf("failed to initialize sqlite: %w", err)
+ }
+
+ // init badger
+ var kvStore tokenmanager.ITokenStore
+
+ var badgerDB *badgerdb.DB
+ if conf.KvDbEngine == "badgerdb" {
+ badgerDbPath := filepath.Join(conf.HubDataDir(), "badger")
+ badgerDB, err = badgerdb.New(l, badgerDbPath)
+ if err != nil {
+ return fmt.Errorf("failed to initialize badgerdb: %w", err)
+ }
+ kvStore = badgerDB
+ } else {
+ kvStore = tokenmanager.NewMemoryTokenStore()
+ }
+
+ l.Infof("using kv store: %s", conf.KvDbEngine)
+
+ // Print SQLite version if using SQLite storage
+ sqliteVersion, err := sqliteDB.GetSQLiteVersion(ctx)
+ if err != nil {
+ return fmt.Errorf("failed to get SQLite version: %w", err)
+ }
+ l.Infof("SQLite version: %s", sqliteVersion)
+
+ // check and run all migrations
+ m := migrator.New(l, sql.MigrationsFS, sql.MigrationsPath, datamigrations.Migrations)
+ if err := m.MigrateUpAll(ctx, sqliteDbPath); err != nil {
+ return fmt.Errorf("failed to run migrations: %w", err)
+ }
+
+ st, err := store.New(sqliteDB.DB, kvStore)
+ if err != nil {
+ return fmt.Errorf("failed to initialize store: %w", err)
+ }
+
+ systemInfo := models.GetSystemInfo(version, commitHash, buildDate)
+ systemInfo.SqliteVersion = sqliteVersion
+
+ // Init receiver
+ rc := receiver.New()
+
+ // Initialize dispatcher
+ dispatcher := dispatcher.New()
+
+ baseServices := baseservices.New(l, st, authConfig, rc, dispatcher, systemInfo)
+
+ // init alert resolver
+ ar := alertresolver.New(l, baseServices)
+
+ // Initialize scheduler
+ sched := scheduler.New(l, rc, baseServices, ar)
+
+ availableUpdateData := locker.New(models.AvailableUpdate{})
+
+ // Initialize upgrader if configured
+ updater, err := updater.New(l, conf.Updater, version, availableUpdateData)
+ if err != nil {
+ return fmt.Errorf("failed to initialize upgrader: %w", err)
+ }
+
+ srv := servers.New(ctx, l, conf.Server.Addr, baseServices, ar, systemInfo, availableUpdateData)
+
+ // register services
+ ln.ServicesRunner().Register(
+ service.New(service.WithService(pingpong.New(l))),
+ service.New(service.WithService(sqliteDB)),
+ service.New(service.WithService(updater)),
+ service.New(service.WithService(rc)),
+ service.New(service.WithService(dispatcher)),
+ service.New(service.WithService(sched)),
+ service.New(service.WithService(srv)),
+ service.New(service.WithService(baseServices.Notifications().Sender())),
+ )
+
+ if badgerDB != nil {
+ ln.ServicesRunner().Register(service.New(service.WithService(badgerDB)))
+ }
+
+ return ln.Run()
+ },
+ },
+ },
+ }
+}
diff --git a/cmd/sentinel/main.go b/cmd/sentinel/main.go
index 69ea018..452e765 100644
--- a/cmd/sentinel/main.go
+++ b/cmd/sentinel/main.go
@@ -14,10 +14,13 @@ import (
)
var (
- appName = "sentinel"
- version = "local"
- commitHash = "unknown"
- buildDate = "unknown"
+ appName = "sentinel"
+ version = "local"
+ commitHash = "unknown"
+ buildDate = "unknown"
+ envHubPrefix = "SENTINEL_"
+ envAgentPrefix = "SENTINEL_AGENT_"
+ sqliteDBFile = "db.sqlite"
)
func getBuildVersion() string {
@@ -43,15 +46,29 @@ func main() {
l := logger.NewExtended(defaultLoggerOpts()...)
+ // check if os args constains agent command
+ if len(os.Args) > 1 && os.Args[1] == "agent" {
+ appName = "sentinel-agent"
+ }
+
app := &cli.Command{
Name: appName,
Usage: "A CLI application for " + appName,
Version: getBuildVersion(),
Suggest: true,
Commands: []*cli.Command{
- startCMD(),
+ hubStartCMD(),
+ agentCMD(),
configCMD(),
+ migrationsCMD(),
versionCMD(),
+ {
+ Name: "migratedb",
+ Commands: []*cli.Command{
+ exportCmd(),
+ importCmd(),
+ },
+ },
},
}
diff --git a/cmd/sentinel/migrate_data.go b/cmd/sentinel/migrate_data.go
new file mode 100644
index 0000000..7de0b97
--- /dev/null
+++ b/cmd/sentinel/migrate_data.go
@@ -0,0 +1,359 @@
+package main
+
+import (
+ "context"
+ "database/sql"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/sxwebdev/sentinel/pkg/sqlite"
+ "github.com/urfave/cli/v3"
+ _ "modernc.org/sqlite"
+)
+
+// -------- Old schema structs (export format) --------
+
+type ServiceOld struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Protocol string `json:"protocol"`
+ Interval string `json:"interval"` // time.Duration string (e.g., "1s", "250ms")
+ Timeout string `json:"timeout"`
+ Retries int64 `json:"retries"`
+ Tags json.RawMessage `json:"tags"`
+ Config json.RawMessage `json:"config"`
+ IsEnabled bool `json:"is_enabled"`
+ CreatedAt *time.Time `json:"created_at,omitempty"`
+ UpdatedAt *time.Time `json:"updated_at,omitempty"`
+}
+
+type StateOld struct {
+ ID string `json:"id"`
+ ServiceID string `json:"service_id"`
+ Status string `json:"status"`
+ LastCheck *time.Time `json:"last_check"`
+ NextCheck *time.Time `json:"next_check"`
+ LastError *string `json:"last_error"`
+ ConsecutiveFails int64 `json:"consecutive_fails"`
+ ConsecutiveSucc int64 `json:"consecutive_success"`
+ TotalChecks int64 `json:"total_checks"`
+ ResponseTimeNS *int64 `json:"response_time_ns"`
+ CreatedAt *time.Time `json:"created_at"`
+ UpdatedAt *time.Time `json:"updated_at"`
+}
+
+type IncidentOld struct {
+ ID string `json:"id"`
+ ServiceID string `json:"service_id"`
+ StartTime time.Time `json:"start_time"`
+ EndTime *time.Time `json:"end_time"`
+ Error string `json:"error"`
+ DurationNS *int64 `json:"duration_ns"`
+ Resolved bool `json:"resolved"`
+ CreatedAt *time.Time `json:"created_at"`
+ UpdatedAt *time.Time `json:"updated_at"`
+}
+
+func parseDurationMS(s string) (int64, error) {
+ if strings.TrimSpace(s) == "" {
+ return 0, nil
+ }
+ d, err := time.ParseDuration(s)
+ if err != nil {
+ return 0, err
+ }
+ return d.Milliseconds(), nil
+}
+
+func readAll[T any](ctx context.Context, db *sql.DB, query string, scan func(*sql.Rows) (T, error)) ([]T, error) {
+ rows, err := db.QueryContext(ctx, query)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var out []T
+ for rows.Next() {
+ v, err := scan(rows)
+ if err != nil {
+ return nil, err
+ }
+ out = append(out, v)
+ }
+ return out, rows.Err()
+}
+
+func exportCmd() *cli.Command {
+ return &cli.Command{
+ Name: "export",
+ Usage: "Export old schema tables to JSON files (services/service_states/incidents)",
+ Flags: []cli.Flag{
+ &cli.StringFlag{Name: "db", Required: true, Usage: "Path to SQLite DB file"},
+ &cli.StringFlag{Name: "out", Required: true, Usage: "Output directory for JSON files"},
+ },
+ Action: func(ctx context.Context, c *cli.Command) error {
+ dbpath := c.String("db")
+ outDir := c.String("out")
+ if err := os.MkdirAll(outDir, 0o755); err != nil {
+ return err
+ }
+ db, err := sql.Open("sqlite", fmt.Sprintf("file:%s?_busy_timeout=5000&_pragma=foreign_keys(ON)", dbpath))
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
+ defer cancel()
+
+ // services
+ services, err := readAll(ctx, db, `SELECT id,name,protocol,interval,timeout,retries,tags,config,is_enabled,created_at,updated_at FROM services`, func(r *sql.Rows) (ServiceOld, error) {
+ var s ServiceOld
+ var tags, cfg []byte
+ if err := r.Scan(&s.ID, &s.Name, &s.Protocol, &s.Interval, &s.Timeout, &s.Retries, &tags, &cfg, &s.IsEnabled, &s.CreatedAt, &s.UpdatedAt); err != nil {
+ return s, err
+ }
+ s.Tags = append([]byte(nil), tags...)
+ s.Config = append([]byte(nil), cfg...)
+ return s, nil
+ })
+ if err != nil {
+ return err
+ }
+
+ // service_states
+ states, err := readAll(ctx, db, `SELECT id,service_id,status,last_check,next_check,last_error,consecutive_fails,consecutive_success,total_checks,response_time_ns,created_at,updated_at FROM service_states`, func(r *sql.Rows) (StateOld, error) {
+ var s StateOld
+ if err := r.Scan(&s.ID, &s.ServiceID, &s.Status, &s.LastCheck, &s.NextCheck, &s.LastError, &s.ConsecutiveFails, &s.ConsecutiveSucc, &s.TotalChecks, &s.ResponseTimeNS, &s.CreatedAt, &s.UpdatedAt); err != nil {
+ return s, err
+ }
+ return s, nil
+ })
+ if err != nil {
+ return err
+ }
+
+ // incidents
+ incidents, err := readAll(ctx, db, `SELECT id,service_id,start_time,end_time,error,duration_ns,resolved,created_at,updated_at FROM incidents`, func(r *sql.Rows) (IncidentOld, error) {
+ var s IncidentOld
+ if err := r.Scan(&s.ID, &s.ServiceID, &s.StartTime, &s.EndTime, &s.Error, &s.DurationNS, &s.Resolved, &s.CreatedAt, &s.UpdatedAt); err != nil {
+ return s, err
+ }
+ return s, nil
+ })
+ if err != nil {
+ return err
+ }
+
+ // write files
+ write := func(name string, v any) error {
+ b, err := json.MarshalIndent(v, "", " ")
+ if err != nil {
+ return err
+ }
+ return os.WriteFile(filepath.Join(outDir, name), b, 0o644)
+ }
+
+ if err := write("services.old.json", services); err != nil {
+ return err
+ }
+ if err := write("service_states.old.json", states); err != nil {
+ return err
+ }
+ if err := write("incidents.old.json", incidents); err != nil {
+ return err
+ }
+
+ fmt.Printf("Exported %d services, %d states, %d incidents to %s\n", len(services), len(states), len(incidents), outDir)
+ return nil
+ },
+ }
+}
+
+func importCmd() *cli.Command {
+ return &cli.Command{
+ Name: "import",
+ Usage: "Import JSON into NEW schema (converts durations to ms)",
+ Flags: []cli.Flag{
+ &cli.StringFlag{Name: "db", Required: true, Usage: "Path to NEW SQLite DB file"},
+ &cli.StringFlag{Name: "in", Required: true, Usage: "Input directory with JSON files"},
+ },
+ Action: func(ctx context.Context, c *cli.Command) error {
+ dbpath := c.String("db")
+ inDir := c.String("in")
+
+ dsn := sqlite.GetDSN(dbpath)
+ db, err := sql.Open("sqlite", dsn)
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
+ defer cancel()
+
+ tx, err := db.BeginTx(ctx, nil)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err != nil {
+ _ = tx.Rollback()
+ }
+ }()
+
+ // Load JSON
+ var svcsOld []ServiceOld
+ var stsOld []StateOld
+ var incOld []IncidentOld
+ if err = readJSON(filepath.Join(inDir, "services.old.json"), &svcsOld); err != nil {
+ return err
+ }
+ if err = readJSON(filepath.Join(inDir, "service_states.old.json"), &stsOld); err != nil {
+ return err
+ }
+ if err = readJSON(filepath.Join(inDir, "incidents.old.json"), &incOld); err != nil {
+ return err
+ }
+
+ // Convert & insert
+ if err = insertServices(ctx, tx, svcsOld); err != nil {
+ return fmt.Errorf("services: %w", err)
+ }
+ if err = insertStates(ctx, tx, stsOld); err != nil {
+ return fmt.Errorf("service_states: %w", err)
+ }
+ if err = insertIncidents(ctx, tx, incOld); err != nil {
+ return fmt.Errorf("incidents: %w", err)
+ }
+
+ if err = tx.Commit(); err != nil {
+ return err
+ }
+ _, _ = db.Exec(`PRAGMA integrity_check;`)
+ _, _ = db.Exec(`PRAGMA optimize;`)
+ fmt.Println("Import OK")
+ return nil
+ },
+ }
+}
+
+func insertServices(ctx context.Context, tx *sql.Tx, in []ServiceOld) error {
+ stmt, err := tx.PrepareContext(ctx, `
+ INSERT INTO services (id,name,protocol,interval,timeout,retries,tags,config,is_enabled,created_at,updated_at)
+ VALUES (?,?,?,?,?,?,?,?,?,COALESCE(?,CURRENT_TIMESTAMP),COALESCE(?,CURRENT_TIMESTAMP))`)
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+ for _, s := range in {
+ intervalMS, err := parseDurationMS(s.Interval)
+ if err != nil {
+ return fmt.Errorf("id=%s interval %q: %w", s.ID, s.Interval, err)
+ }
+ timeoutMS, err := parseDurationMS(s.Timeout)
+ if err != nil {
+ return fmt.Errorf("id=%s timeout %q: %w", s.ID, s.Timeout, err)
+ }
+ protocol := s.Protocol
+ if protocol == "" {
+ protocol = "unknown"
+ }
+ if s.Tags == nil {
+ s.Tags = json.RawMessage("[]")
+ }
+ if s.Config == nil {
+ s.Config = json.RawMessage("{}")
+ }
+ if _, err := stmt.ExecContext(ctx, s.ID, s.Name, protocol, intervalMS, timeoutMS, s.Retries, s.Tags, s.Config, s.IsEnabled, formatDate(s.CreatedAt), formatDate(s.UpdatedAt)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func insertStates(ctx context.Context, tx *sql.Tx, in []StateOld) error {
+ stmt, err := tx.PrepareContext(ctx, `
+ INSERT INTO service_states (id,service_id,status,last_check,last_error,consecutive_fails,consecutive_success,total_checks,avg_response_time,created_at,updated_at)
+ VALUES (?,?,?,?,?,?,?,?,?,COALESCE(?,CURRENT_TIMESTAMP),COALESCE(?,CURRENT_TIMESTAMP))`)
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+ for _, s := range in {
+ var avgMS *int64
+ if s.ResponseTimeNS != nil {
+ v := *s.ResponseTimeNS / 1_000_000
+ avgMS = &v
+ }
+ if _, err := stmt.ExecContext(ctx, s.ID, s.ServiceID, s.Status, formatDate(s.LastCheck), s.LastError, s.ConsecutiveFails, s.ConsecutiveSucc, s.TotalChecks, avgMS, formatDate(s.CreatedAt), formatDate(s.UpdatedAt)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func parseTimeFlexible(s string) (time.Time, error) {
+ formats := []string{time.RFC3339Nano, time.RFC3339, "2006-01-02 15:04:05", "2006-01-02 15:04:05Z07:00"}
+ for _, f := range formats {
+ if t, err := time.Parse(f, s); err == nil {
+ return t, nil
+ }
+ }
+ return time.Time{}, errors.New("unsupported time format: " + s)
+}
+
+func insertIncidents(ctx context.Context, tx *sql.Tx, in []IncidentOld) error {
+ stmt, err := tx.PrepareContext(ctx, `
+ INSERT INTO incidents (id,service_id,error,duration,started_at,resolved_at,created_at,updated_at)
+ VALUES (?,?,?,?,?,?,COALESCE(?,CURRENT_TIMESTAMP),COALESCE(?,CURRENT_TIMESTAMP))`)
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+ for _, s := range in {
+ var dur *int64
+ if s.DurationNS != nil {
+ v := *s.DurationNS / 1_000_000
+ dur = &v
+ } else if s.EndTime != nil && !s.EndTime.IsZero() && !s.StartTime.IsZero() {
+ if st, err1 := parseTimeFlexible(s.StartTime.String()); err1 == nil {
+ if et, err2 := parseTimeFlexible(s.EndTime.String()); err2 == nil {
+ v := et.Sub(st).Milliseconds()
+ dur = &v
+ }
+ }
+ }
+
+ var resolvedAt *string
+ if s.Resolved && s.EndTime != nil {
+ endTime := formatDate(s.EndTime)
+ resolvedAt = endTime
+ }
+
+ if _, err := stmt.ExecContext(ctx, s.ID, s.ServiceID, s.Error, dur, formatDate(&s.StartTime), resolvedAt, formatDate(s.CreatedAt), formatDate(s.UpdatedAt)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func readJSON(path string, v any) error {
+ b, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ return json.Unmarshal(b, v)
+}
+
+func formatDate(t *time.Time) *string {
+ if t == nil {
+ return nil
+ }
+ formatted := t.Format("2006-01-02 15:04:05")
+ return &formatted
+}
diff --git a/cmd/sentinel/migrations.go b/cmd/sentinel/migrations.go
new file mode 100644
index 0000000..359c35f
--- /dev/null
+++ b/cmd/sentinel/migrations.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+ "github.com/sxwebdev/sentinel/internal/datamigrations"
+ "github.com/sxwebdev/sentinel/pkg/migrator"
+ "github.com/sxwebdev/sentinel/sql"
+ "github.com/tkcrm/mx/logger"
+ "github.com/urfave/cli/v3"
+)
+
+func migrationsCMD() *cli.Command {
+ opts := append(
+ defaultLoggerOpts(),
+ logger.WithConsoleColored(true),
+ logger.WithLogFormat(logger.LoggerFormatConsole),
+ )
+ l := logger.NewExtended(opts...)
+ return migrator.CliCmd(l, sql.MigrationsFS, sql.MigrationsPath, datamigrations.Migrations)
+}
diff --git a/cmd/sentinel/start.go b/cmd/sentinel/start.go
deleted file mode 100644
index 5b3d6a7..0000000
--- a/cmd/sentinel/start.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package main
-
-import (
- "context"
- "fmt"
- "runtime"
- "time"
-
- "github.com/sxwebdev/sentinel/internal/config"
- "github.com/sxwebdev/sentinel/internal/monitor"
- "github.com/sxwebdev/sentinel/internal/notifier"
- "github.com/sxwebdev/sentinel/internal/receiver"
- "github.com/sxwebdev/sentinel/internal/scheduler"
- "github.com/sxwebdev/sentinel/internal/storage"
- "github.com/sxwebdev/sentinel/internal/upgrader"
- "github.com/sxwebdev/sentinel/internal/web"
- "github.com/tkcrm/mx/launcher"
- "github.com/tkcrm/mx/logger"
- "github.com/tkcrm/mx/service"
- "github.com/tkcrm/mx/service/pingpong"
- "github.com/urfave/cli/v3"
-)
-
-func startCMD() *cli.Command {
- return &cli.Command{
- Name: "start",
- Usage: "start the server",
- Flags: []cli.Flag{cfgPathsFlag()},
- Action: func(ctx context.Context, cl *cli.Command) error {
- conf, err := config.Load(cl.String("config"))
- if err != nil {
- return fmt.Errorf("failed to load config: %w", err)
- }
-
- loggerOpts := append(defaultLoggerOpts(), logger.WithConfig(conf.Log))
-
- l := logger.NewExtended(loggerOpts...)
- defer func() {
- _ = l.Sync()
- }()
-
- // init launcher
- ln := launcher.New(
- launcher.WithVersion(version),
- launcher.WithName(appName),
- launcher.WithLogger(l),
- launcher.WithContext(ctx),
- launcher.WithRunnerServicesSequence(launcher.RunnerServicesSequenceFifo),
- launcher.WithOpsConfig(conf.Ops),
- launcher.WithAppStartStopLog(true),
- )
-
- // set default timezone
- time.Local, err = time.LoadLocation(conf.Timezone)
- if err != nil {
- return fmt.Errorf("failed to set timezone: %w", err)
- }
-
- // Initialize storage
- store, err := storage.NewStorage(storage.StorageTypeSQLite, conf.Database.Path)
- if err != nil {
- return fmt.Errorf("failed to initialize storage: %w", err)
- }
-
- // Print SQLite version if using SQLite storage
- sqliteVersion, err := store.GetSQLiteVersion(ctx)
- if err != nil {
- return fmt.Errorf("failed to get SQLite version: %w", err)
- }
- l.Infof("SQLite version: %s", sqliteVersion)
-
- // Initialize notifier
- var notif *notifier.Notifier
- if conf.Notifications.Enabled {
- notif, err = notifier.New(l, conf.Notifications.URLs)
- if err != nil {
- return fmt.Errorf("failed to initialize notifier: %w", err)
- }
- }
-
- // Init receiver
- rc := receiver.New()
-
- // Initialize upgrader if configured
- upgr, err := upgrader.New(l, conf.Upgrader)
- if err != nil {
- return fmt.Errorf("failed to initialize upgrader: %w", err)
- }
-
- // Create monitor service
- monitorService := monitor.NewMonitorService(store, conf, notif, rc)
-
- // Initialize scheduler
- sched := scheduler.New(l, monitorService, rc)
-
- webServer, err := web.NewServer(l, conf, web.ServerInfo{
- Version: version,
- CommitHash: commitHash,
- BuildDate: buildDate,
- GoVersion: runtime.Version(),
- SqliteVersion: sqliteVersion,
- OS: runtime.GOOS,
- Arch: runtime.GOARCH,
- }, monitorService, store, rc, upgr)
- if err != nil {
- return fmt.Errorf("failed to initialize web server: %w", err)
- }
-
- // register services
- ln.ServicesRunner().Register(
- service.New(service.WithService(pingpong.New(l))),
- service.New(service.WithService(store)),
- service.New(service.WithService(rc)),
- service.New(service.WithService(sched)),
- service.New(service.WithService(webServer)),
- )
-
- if notif != nil {
- ln.ServicesRunner().Register(
- service.New(service.WithService(notif)),
- )
- }
-
- return ln.Run()
- },
- }
-}
diff --git a/cmd/tcpserver/main.go b/cmd/tcpserver/main.go
deleted file mode 100644
index 4d9d91a..0000000
--- a/cmd/tcpserver/main.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package main
-
-import (
- "bufio"
- "fmt"
- "net"
- "time"
-
- "github.com/sxwebdev/sentinel/internal/utils"
-)
-
-func main() {
- if err := run(); err != nil {
- fmt.Println("Error:", err)
- }
-}
-
-func run() error {
- listener, err := net.Listen("tcp", "127.0.0.1:12345")
- if err != nil {
- return fmt.Errorf("failed to start server: %w", err)
- }
- defer listener.Close()
-
- fmt.Println("Server is listening on 127.0.0.1:12345")
-
- for {
- conn, err := listener.Accept()
- if err != nil {
- fmt.Println("Failed to accept connection:", err)
- continue
- }
- fmt.Println("Client connected:", conn.RemoteAddr())
- go handleConnection(conn)
- }
-}
-
-func handleConnection(conn net.Conn) {
- defer func() {
- conn.Close()
- fmt.Printf("Connection closed: %s\n\n", conn.RemoteAddr())
- }()
-
- // Set connection timeout - close if no data received in 5 seconds
- conn.SetReadDeadline(time.Now().Add(5 * time.Second))
-
- var accumulated []byte
- reader := bufio.NewReader(conn)
-
- for {
- buffer := make([]byte, 1024)
- n, err := reader.Read(buffer)
- if err != nil {
- if utils.IsErrTimeout(err) {
- break
- }
-
- fmt.Println("failed to read from connection:", err)
- return
- }
-
- if n == 0 {
- break
- }
-
- // Accumulate received data
- accumulated = append(accumulated, buffer[:n]...)
-
- // If no more data buffered, client likely finished sending
- if reader.Buffered() == 0 {
- break
- }
- }
-
- // Now process the complete message
- if len(accumulated) == 0 {
- fmt.Println("No data received from client")
- return
- }
-
- msg := string(accumulated)
- fmt.Println("Complete message received:", msg)
-
- // Simple ping-pong protocol - exact match
- switch msg {
- case "ping":
- fmt.Println("Sending pong")
- _, err := conn.Write([]byte("pong"))
- if err != nil {
- fmt.Println("Failed to send response:", err)
- }
- case "noresponse":
- fmt.Println("No response expected")
- default:
- fmt.Printf("Unknown message '%s', sending ok\n", msg)
- _, err := conn.Write([]byte("ok"))
- if err != nil {
- fmt.Println("Failed to send response:", err)
- }
- }
-}
diff --git a/cmd/testapi/README.md b/cmd/testapi/README.md
deleted file mode 100644
index 6611ad6..0000000
--- a/cmd/testapi/README.md
+++ /dev/null
@@ -1,248 +0,0 @@
-# Sentinel API Integration Tests
-
-This package contains comprehensive integration tests for the Sentinel monitoring system API.
-
-## What is tested
-
-### Core API functions:
-
-- ✅ Dashboard and statistics
-- ✅ Service CRUD operations (Create, Read, Update, Delete)
-- ✅ Service filtering by various parameters
-- ✅ Result pagination
-- ✅ Incident management
-- ✅ Tag operations
-- ✅ Service statistics
-- ✅ Manual service checks
-- ✅ Error handling
-
-### Monitoring protocols:
-
-- ✅ HTTP/HTTPS monitoring with support for:
- - Multiple endpoints
- - Various HTTP methods (GET, POST, PUT, DELETE)
- - Headers and request body
- - Basic authentication
- - JSON Path validation
- - Execution conditions (all/any)
-- ✅ TCP monitoring with data sending and receiving
-- ✅ gRPC monitoring with various check types
-
-### Filters and query parameters:
-
-- ✅ Filtering by service name
-- ✅ Filtering by tags (single and multiple)
-- ✅ Filtering by status (up/down/unknown)
-- ✅ Filtering by enabled status (enabled/disabled)
-- ✅ Filtering by protocol (http/tcp/grpc)
-- ✅ Sorting by name and creation date
-- ✅ Pagination with configurable page size
-- ✅ Incident filtering by time
-- ✅ Incident filtering by resolution status
-
-### Data model validation:
-
-- ✅ Configuration structures for all protocols
-- ✅ DTO conversions
-- ✅ API response models
-- ✅ Error models
-- ✅ Pagination structures
-
-### Error handling:
-
-- ✅ Invalid JSON data
-- ✅ Missing required fields
-- ✅ Invalid parameter values
-- ✅ Operations on non-existent resources
-- ✅ Configuration validation errors
-
-## Test structure
-
-### Files:
-
-- `main.go` - Main file with basic tests and test environment setup
-- `extended_tests.go` - Extended tests for complex scenarios
-- `model_tests.go` - Data model validation tests
-
-### Test cases:
-
-#### Basic tests (main.go):
-
-1. **TestHealthCheck** - main page availability check
-2. **TestDashboardStats** - dashboard statistics API test
-3. **TestCreateServices** - creating test services of all types
-4. **TestGetServices** - getting service list
-5. **TestServiceFilters** - testing service filters
-6. **TestServiceDetail** - getting detailed service information
-7. **TestUpdateService** - service update
-8. **TestServiceStats** - getting service statistics
-9. **TestServiceCheck** - manual service check trigger
-10. **TestIncidents** - incident operations
-11. **TestIncidentFilters** - incident filtering
-12. **TestTags** - tag operations
-13. **TestPagination** - basic pagination testing
-14. **TestErrorHandling** - error handling
-15. **TestDeleteService** - service deletion
-
-#### Extended tests (extended_tests.go):
-
-1. **TestAdvancedServiceFilters** - complex filter combinations
-2. **TestServiceCRUDCompleteFlow** - complete service lifecycle
-3. **TestAdvancedIncidentManagement** - advanced incident management
-4. **TestCompleteProtocolConfigurations** - testing all protocols
-5. **TestAdvancedPaginationAndSorting** - advanced pagination and sorting
-6. **TestAdvancedErrorScenarios** - complex error scenarios
-7. **TestStatsWithDifferentParameters** - statistics with various parameters
-
-#### Model tests (model_tests.go):
-
-1. **TestModelsValidation** - monitoring configuration validation
-2. **TestServiceDTOFields** - service DTO field verification
-3. **TestIncidentFields** - incident field verification
-4. **TestResponseModels** - response model verification
-5. **TestServiceStatsModel** - service statistics model verification
-6. **TestPaginationResponseModel** - paginated response model verification
-
-## Running tests
-
-### Prerequisites:
-
-- Go 1.21+
-- Sentinel project dependencies must be installed
-
-### Run command:
-
-```bash
-cd cmd/testapi
-go run *.go test
-```
-
-### Expected output:
-
-```
-Running TestHealthCheck...
-PASS: TestHealthCheck
-Running TestDashboardStats...
-PASS: TestDashboardStats
-...
-Running TestPaginationResponseModel...
-PASS: TestPaginationResponseModel
-
-All tests passed!
-```
-
-### In case of errors:
-
-```
-Running TestCreateServices...
-FAIL: TestCreateServices - service 0: HTTP 400: Service name is required
-...
-
-2 test(s) failed
-```
-
-## Test data
-
-Tests create the following test services:
-
-1. **HTTP Test Service 1** (enabled)
-
- - Protocol: HTTP
- - Endpoint: https://httpbin.org/status/200
- - Tags: [http, production, api]
-
-2. **HTTP Test Service 2** (disabled)
-
- - Protocol: HTTP
- - Endpoint: https://httpbin.org/status/404
- - Tags: [http, staging, web]
-
-3. **TCP Test Service** (enabled)
-
- - Protocol: TCP
- - Endpoint: google.com:80
- - Tags: [tcp, database, production]
-
-4. **gRPC Test Service** (enabled)
-
- - Protocol: gRPC
- - Endpoint: grpc.example.com:443
- - Tags: [grpc, api, microservice]
-
-5. **Disabled Service** (disabled)
- - Protocol: HTTP
- - Endpoint: https://httpbin.org/status/500
- - Tags: [disabled, test]
-
-## API endpoint coverage
-
-### Dashboard
-
-- `GET /` - main page
-- `GET /api/v1/dashboard/stats` - dashboard statistics
-
-### Services
-
-- `GET /api/v1/services` - service list with filters
-- `POST /api/v1/services` - service creation
-- `GET /api/v1/services/{id}` - service details
-- `PUT /api/v1/services/{id}` - service update
-- `DELETE /api/v1/services/{id}` - service deletion
-- `POST /api/v1/services/{id}/check` - manual check
-- `POST /api/v1/services/{id}/resolve` - incident resolution
-- `GET /api/v1/services/{id}/stats` - service statistics
-
-### Incidents
-
-- `GET /api/v1/incidents` - all incidents list
-- `GET /api/v1/services/{id}/incidents` - service incidents
-- `DELETE /api/v1/services/{id}/incidents/{incidentId}` - incident deletion
-
-### Tags
-
-- `GET /api/v1/tags` - tags list
-- `GET /api/v1/tags/count` - tags with usage count
-
-## Testing features
-
-### Test isolation
-
-- Each test run uses a temporary SQLite database
-- Test server runs on port 8899
-- All resources are cleaned up after test completion
-
-### External dependencies
-
-- Tests use httpbin.org for HTTP checks
-- TCP tests use google.com:80
-- gRPC tests use mock endpoints
-
-### Concurrency
-
-- Tests run sequentially for predictability
-- Each test can create and delete its own resources
-
-## Test configuration
-
-Tests use the following configuration:
-
-- **Database**: temporary SQLite
-- **Server**: localhost:8899
-- **Monitoring interval**: 30 seconds (default)
-- **Timeout**: 5 seconds (default)
-- **Retries**: 3 (default)
-- **Timezone**: UTC
-- **Notifications**: disabled
-
-## Debugging
-
-For debugging, you can add additional logging to test code or use Go debugger. Tests output detailed error messages indicating the specific problem location.
-
-## Extending tests
-
-To add new tests:
-
-1. Create a new function in the appropriate file
-2. Add it to the test array in the main() function
-3. Ensure the test returns an error on failure
-4. Add resource cleanup if necessary
diff --git a/cmd/testapi/extended_tests.go b/cmd/testapi/extended_tests.go
deleted file mode 100644
index dcb3a7e..0000000
--- a/cmd/testapi/extended_tests.go
+++ /dev/null
@@ -1,982 +0,0 @@
-package main
-
-import (
- "bytes"
- "fmt"
- "net/http"
- "net/url"
- "time"
-
- "github.com/sxwebdev/sentinel/internal/monitors"
- "github.com/sxwebdev/sentinel/internal/storage"
- "github.com/sxwebdev/sentinel/internal/web"
- "github.com/sxwebdev/sentinel/pkg/dbutils"
-)
-
-// Extended tests for comprehensive API coverage
-
-func testAdvancedServiceFilters(s *TestSuite) error {
- // Test complex tag filtering with multiple tags
- resp, err := s.makeRequest("GET", "/api/v1/services?tags=production,api", nil)
- if err != nil {
- return err
- }
-
- var result dbutils.FindResponseWithCount[web.ServiceDTO]
- if err := s.decodeResponse(resp, &result); err != nil {
- return err
- }
-
- // Each service should have both tags
- for _, service := range result.Items {
- hasProduction := false
- hasAPI := false
- for _, tag := range service.Tags {
- if tag == "production" {
- hasProduction = true
- }
- if tag == "api" {
- hasAPI = true
- }
- }
- if !hasProduction || !hasAPI {
- return fmt.Errorf("service %s doesn't have both required tags", service.Name)
- }
- }
-
- // Test status filtering
- resp, err = s.makeRequest("GET", "/api/v1/services?status=up", nil)
- if err != nil {
- return err
- }
-
- if err := s.decodeResponse(resp, &result); err != nil {
- return err
- }
-
- for _, service := range result.Items {
- if service.Status != storage.StatusUp {
- return fmt.Errorf("service %s status is not 'up'", service.Name)
- }
- }
-
- // Test ordering by created_at
- resp, err = s.makeRequest("GET", "/api/v1/services?order_by=created_at", nil)
- if err != nil {
- return err
- }
-
- if err := s.decodeResponse(resp, &result); err != nil {
- return err
- }
-
- // Services should be ordered (assuming creation order)
- if len(result.Items) > 1 {
- fmt.Printf("Order test: found %d services\n", len(result.Items))
- }
-
- return nil
-}
-
-func testServiceCRUDCompleteFlow(s *TestSuite) error {
- // Create a new service with complex HTTP config
- complexHTTPService := web.CreateUpdateServiceRequest{
- Name: "Complex HTTP Service",
- Protocol: storage.ServiceProtocolTypeHTTP,
- Interval: 15000, // 15s
- Timeout: 10000, // 10s
- Retries: 2,
- Tags: []string{"complex", "test", "http"},
- Config: monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 8000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "Main API",
- URL: "https://httpbin.org/get",
- Method: "GET",
- ExpectedStatus: 200,
- Headers: map[string]string{
- "Authorization": "Bearer test-token",
- "User-Agent": "Sentinel-Test",
- },
- },
- {
- Name: "Health Check",
- URL: "https://httpbin.org/status/200",
- Method: "GET",
- ExpectedStatus: 200,
- },
- {
- Name: "POST Endpoint",
- URL: "https://httpbin.org/post",
- Method: "POST",
- ExpectedStatus: 200,
- Body: `{"test": "data"}`,
- Headers: map[string]string{
- "Content-Type": "application/json",
- },
- },
- },
- Condition: "all", // All endpoints must pass
- },
- },
- IsEnabled: true,
- }
-
- // Create service
- resp, err := s.makeRequest("POST", "/api/v1/services", complexHTTPService)
- if err != nil {
- return fmt.Errorf("create complex service: %w", err)
- }
-
- var createdService web.ServiceDTO
- if err := s.decodeResponse(resp, &createdService); err != nil {
- return fmt.Errorf("decode created service: %w", err)
- }
-
- serviceID := createdService.ID
-
- // Verify creation
- if createdService.Name != complexHTTPService.Name {
- return fmt.Errorf("name mismatch in created service")
- }
- if createdService.Protocol != complexHTTPService.Protocol {
- return fmt.Errorf("protocol mismatch in created service")
- }
- if createdService.Config.HTTP == nil {
- return fmt.Errorf("HTTP config is nil in created service")
- }
- if len(createdService.Config.HTTP.Endpoints) != 3 {
- return fmt.Errorf("expected 3 endpoints, got %d", len(createdService.Config.HTTP.Endpoints))
- }
-
- // Test service detail retrieval
- resp, err = s.makeRequest("GET", "/api/v1/services/"+serviceID, nil)
- if err != nil {
- return fmt.Errorf("get service detail: %w", err)
- }
-
- var serviceDetail web.ServiceDTO
- if err := s.decodeResponse(resp, &serviceDetail); err != nil {
- return fmt.Errorf("decode service detail: %w", err)
- }
-
- if serviceDetail.ID != serviceID {
- return fmt.Errorf("service detail ID mismatch")
- }
-
- // Update service - modify configuration
- updatedConfig := complexHTTPService
- updatedConfig.Name = "Updated Complex HTTP Service"
- updatedConfig.Interval = 45000 // 45s
- updatedConfig.Tags = append(updatedConfig.Tags, "updated")
-
- // Remove one endpoint
- updatedConfig.Config.HTTP.Endpoints = updatedConfig.Config.HTTP.Endpoints[:2]
- updatedConfig.Config.HTTP.Condition = "any" // Any endpoint can pass
-
- resp, err = s.makeRequest("PUT", "/api/v1/services/"+serviceID, updatedConfig)
- if err != nil {
- return fmt.Errorf("update service: %w", err)
- }
-
- var updatedService web.ServiceDTO
- if err := s.decodeResponse(resp, &updatedService); err != nil {
- return fmt.Errorf("decode updated service: %w", err)
- }
-
- // Verify updates
- if updatedService.Name != updatedConfig.Name {
- return fmt.Errorf("updated name mismatch")
- }
- if updatedService.Interval != updatedConfig.Interval {
- return fmt.Errorf("updated interval mismatch")
- }
- if len(updatedService.Config.HTTP.Endpoints) != 2 {
- return fmt.Errorf("expected 2 endpoints after update, got %d", len(updatedService.Config.HTTP.Endpoints))
- }
- if updatedService.Config.HTTP.Condition != "any" {
- return fmt.Errorf("condition not updated")
- }
-
- // Trigger manual check
- resp, err = s.makeRequest("POST", "/api/v1/services/"+serviceID+"/check", nil)
- if err != nil {
- return fmt.Errorf("trigger check: %w", err)
- }
-
- var checkResult web.SuccessResponse
- if err := s.decodeResponse(resp, &checkResult); err != nil {
- return fmt.Errorf("decode check result: %w", err)
- }
-
- if checkResult.Message == "" {
- return fmt.Errorf("empty check result message")
- }
-
- // Wait a bit and check service stats
- time.Sleep(100 * time.Millisecond)
-
- resp, err = s.makeRequest("GET", "/api/v1/services/"+serviceID+"/stats?days=1", nil)
- if err != nil {
- return fmt.Errorf("get service stats: %w", err)
- }
-
- var stats web.ServiceStats
- if err := s.decodeResponse(resp, &stats); err != nil {
- return fmt.Errorf("decode service stats: %w", err)
- }
-
- if stats.ServiceID != serviceID {
- return fmt.Errorf("stats service ID mismatch")
- }
-
- // Get service incidents
- resp, err = s.makeRequest("GET", "/api/v1/services/"+serviceID+"/incidents", nil)
- if err != nil {
- return fmt.Errorf("get service incidents: %w", err)
- }
-
- var incidents dbutils.FindResponseWithCount[web.Incident]
- if err := s.decodeResponse(resp, &incidents); err != nil {
- return fmt.Errorf("decode service incidents: %w", err)
- }
-
- // All incidents should belong to this service
- for _, incident := range incidents.Items {
- if incident.ServiceID != serviceID {
- return fmt.Errorf("incident %s doesn't belong to service %s", incident.ID, serviceID)
- }
- }
-
- // Delete the service
- resp, err = s.makeRequest("DELETE", "/api/v1/services/"+serviceID, nil)
- if err != nil {
- return fmt.Errorf("delete service: %w", err)
- }
-
- if resp.StatusCode != http.StatusNoContent {
- return fmt.Errorf("delete service: expected 204, got %d", resp.StatusCode)
- }
-
- // Verify deletion
- resp, err = s.makeRequest("GET", "/api/v1/services/"+serviceID, nil)
- if err != nil {
- return fmt.Errorf("verify deletion: %w", err)
- }
-
- if resp.StatusCode == http.StatusOK {
- return fmt.Errorf("service still exists after deletion")
- }
-
- return nil
-}
-
-func testAdvancedIncidentManagement(s *TestSuite) error {
- // Create a dedicated service for incident testing
- testService := web.CreateUpdateServiceRequest{
- Name: "Incident Management Test Service",
- Protocol: storage.ServiceProtocolTypeHTTP,
- Interval: 30000,
- Timeout: 5000,
- Retries: 3,
- Tags: []string{"incident-test"},
- Config: monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 5000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "Test Endpoint",
- URL: "https://httpbin.org/status/200",
- Method: "GET",
- ExpectedStatus: 200,
- },
- },
- },
- },
- IsEnabled: true,
- }
-
- // Create the test service
- resp, err := s.makeRequest("POST", "/api/v1/services", testService)
- if err != nil {
- return fmt.Errorf("create incident test service: %w", err)
- }
-
- var createdService web.ServiceDTO
- if err := s.decodeResponse(resp, &createdService); err != nil {
- return fmt.Errorf("decode incident test service: %w", err)
- }
-
- serviceID := createdService.ID
-
- // Ensure cleanup
- defer func() {
- s.makeRequest("DELETE", "/api/v1/services/"+serviceID, nil)
- }()
-
- // Get service incidents with various filters
- resp, err = s.makeRequest("GET", "/api/v1/services/"+serviceID+"/incidents?resolved=false", nil)
- if err != nil {
- return err
- }
-
- var unresolvedIncidents dbutils.FindResponseWithCount[web.Incident]
- if err := s.decodeResponse(resp, &unresolvedIncidents); err != nil {
- return err
- }
-
- // All incidents should be unresolved
- for _, incident := range unresolvedIncidents.Items {
- if incident.Resolved {
- return fmt.Errorf("found resolved incident when filtering for unresolved")
- }
- }
-
- // Get resolved incidents
- resp, err = s.makeRequest("GET", "/api/v1/services/"+serviceID+"/incidents?resolved=true", nil)
- if err != nil {
- return err
- }
-
- var resolvedIncidents dbutils.FindResponseWithCount[web.Incident]
- if err := s.decodeResponse(resp, &resolvedIncidents); err != nil {
- return err
- }
-
- // All incidents should be resolved
- for _, incident := range resolvedIncidents.Items {
- if !incident.Resolved {
- return fmt.Errorf("found unresolved incident when filtering for resolved")
- }
- }
-
- // Test time-based filtering
- now := time.Now()
- yesterday := now.AddDate(0, 0, -1)
- tomorrow := now.AddDate(0, 0, 1)
-
- // Filter by time range
- timeParams := fmt.Sprintf("start_time=%s&end_time=%s",
- url.QueryEscape(yesterday.Format(time.RFC3339)),
- url.QueryEscape(tomorrow.Format(time.RFC3339)))
-
- resp, err = s.makeRequest("GET", "/api/v1/services/"+serviceID+"/incidents?"+timeParams, nil)
- if err != nil {
- return err
- }
-
- var timeFilteredIncidents dbutils.FindResponseWithCount[web.Incident]
- if err := s.decodeResponse(resp, &timeFilteredIncidents); err != nil {
- return err
- }
-
- // Test pagination for incidents
- resp, err = s.makeRequest("GET", "/api/v1/services/"+serviceID+"/incidents?page=1&page_size=5", nil)
- if err != nil {
- return err
- }
-
- var paginatedIncidents dbutils.FindResponseWithCount[web.Incident]
- if err := s.decodeResponse(resp, &paginatedIncidents); err != nil {
- return err
- }
-
- if len(paginatedIncidents.Items) > 5 {
- return fmt.Errorf("page size not respected for incidents: got %d items", len(paginatedIncidents.Items))
- }
-
- // Test resolve service incidents
- resp, err = s.makeRequest("POST", "/api/v1/services/"+serviceID+"/resolve", nil)
- if err != nil {
- return err
- }
-
- var resolveResult web.SuccessResponse
- if err := s.decodeResponse(resp, &resolveResult); err != nil {
- return err
- }
-
- if resolveResult.Message == "" {
- return fmt.Errorf("empty resolve result message")
- }
-
- // Test global incident search
- resp, err = s.makeRequest("GET", "/api/v1/incidents?search="+serviceID, nil)
- if err != nil {
- return err
- }
-
- var searchResults dbutils.FindResponseWithCount[web.Incident]
- if err := s.decodeResponse(resp, &searchResults); err != nil {
- return err
- }
-
- // All results should be related to the searched service
- for _, incident := range searchResults.Items {
- if incident.ServiceID != serviceID && incident.ID != serviceID {
- // Check if the search term appears in service name or incident data
- hasSearchTerm := false
- if incident.ServiceID == serviceID || incident.ID == serviceID {
- hasSearchTerm = true
- }
- if !hasSearchTerm {
- return fmt.Errorf("search result doesn't match search criteria")
- }
- }
- }
-
- return nil
-}
-
-func testCompleteProtocolConfigurations(s *TestSuite) error {
- // Test TCP service with advanced configuration
- tcpService := web.CreateUpdateServiceRequest{
- Name: "Advanced TCP Service",
- Protocol: storage.ServiceProtocolTypeTCP,
- Interval: 20000,
- Timeout: 8000,
- Retries: 2,
- Tags: []string{"tcp", "advanced", "database"},
- Config: monitors.Config{
- TCP: &monitors.TCPConfig{
- Endpoint: "google.com:80",
- SendData: "GET / HTTP/1.1\r\nHost: google.com\r\n\r\n",
- ExpectData: "HTTP/1.1",
- },
- },
- IsEnabled: true,
- }
-
- resp, err := s.makeRequest("POST", "/api/v1/services", tcpService)
- if err != nil {
- return fmt.Errorf("create TCP service: %w", err)
- }
-
- var createdTCPService web.ServiceDTO
- if err := s.decodeResponse(resp, &createdTCPService); err != nil {
- return fmt.Errorf("decode TCP service: %w", err)
- }
-
- if createdTCPService.Config.TCP == nil {
- return fmt.Errorf("TCP config is nil")
- }
- if createdTCPService.Config.TCP.Endpoint != tcpService.Config.TCP.Endpoint {
- return fmt.Errorf("TCP endpoint mismatch")
- }
- if createdTCPService.Config.TCP.SendData != tcpService.Config.TCP.SendData {
- return fmt.Errorf("TCP send data mismatch")
- }
-
- tcpServiceID := createdTCPService.ID
-
- // Test gRPC service with advanced configuration
- grpcService := web.CreateUpdateServiceRequest{
- Name: "Advanced gRPC Service",
- Protocol: storage.ServiceProtocolTypeGRPC,
- Interval: 25000,
- Timeout: 10000,
- Retries: 3,
- Tags: []string{"grpc", "advanced", "microservice"},
- Config: monitors.Config{
- GRPC: &monitors.GRPCConfig{
- Endpoint: "grpc.example.com:443",
- CheckType: "health",
- ServiceName: "example.HealthService",
- TLS: true,
- InsecureTLS: false,
- },
- },
- IsEnabled: true,
- }
-
- resp, err = s.makeRequest("POST", "/api/v1/services", grpcService)
- if err != nil {
- return fmt.Errorf("create gRPC service: %w", err)
- }
-
- var createdGRPCService web.ServiceDTO
- if err := s.decodeResponse(resp, &createdGRPCService); err != nil {
- return fmt.Errorf("decode gRPC service: %w", err)
- }
-
- if createdGRPCService.Config.GRPC == nil {
- return fmt.Errorf("gRPC config is nil")
- }
- if createdGRPCService.Config.GRPC.Endpoint != grpcService.Config.GRPC.Endpoint {
- return fmt.Errorf("gRPC endpoint mismatch")
- }
- if createdGRPCService.Config.GRPC.CheckType != grpcService.Config.GRPC.CheckType {
- return fmt.Errorf("gRPC check type mismatch")
- }
- if createdGRPCService.Config.GRPC.TLS != grpcService.Config.GRPC.TLS {
- return fmt.Errorf("gRPC TLS setting mismatch")
- }
-
- grpcServiceID := createdGRPCService.ID
-
- // Test both services individually
- services := []struct {
- id string
- protocol storage.ServiceProtocolType
- }{
- {tcpServiceID, storage.ServiceProtocolTypeTCP},
- {grpcServiceID, storage.ServiceProtocolTypeGRPC},
- }
-
- for _, svc := range services {
- // Test service detail
- resp, err = s.makeRequest("GET", "/api/v1/services/"+svc.id, nil)
- if err != nil {
- return fmt.Errorf("get %s service detail: %w", svc.protocol, err)
- }
-
- var serviceDetail web.ServiceDTO
- if err := s.decodeResponse(resp, &serviceDetail); err != nil {
- return fmt.Errorf("decode %s service detail: %w", svc.protocol, err)
- }
-
- if serviceDetail.Protocol != svc.protocol {
- return fmt.Errorf("%s service protocol mismatch", svc.protocol)
- }
-
- // Test service check
- resp, err = s.makeRequest("POST", "/api/v1/services/"+svc.id+"/check", nil)
- if err != nil {
- return fmt.Errorf("trigger %s service check: %w", svc.protocol, err)
- }
-
- var checkResult web.SuccessResponse
- if err := s.decodeResponse(resp, &checkResult); err != nil {
- return fmt.Errorf("decode %s check result: %w", svc.protocol, err)
- }
-
- // Test service stats
- resp, err = s.makeRequest("GET", "/api/v1/services/"+svc.id+"/stats", nil)
- if err != nil {
- return fmt.Errorf("get %s service stats: %w", svc.protocol, err)
- }
-
- var stats web.ServiceStats
- if err := s.decodeResponse(resp, &stats); err != nil {
- return fmt.Errorf("decode %s service stats: %w", svc.protocol, err)
- }
-
- if stats.ServiceID != svc.id {
- return fmt.Errorf("%s service stats ID mismatch", svc.protocol)
- }
- }
-
- // Clean up - delete both services
- for _, svc := range services {
- resp, err = s.makeRequest("DELETE", "/api/v1/services/"+svc.id, nil)
- if err != nil {
- return fmt.Errorf("delete %s service: %w", svc.protocol, err)
- }
-
- if resp.StatusCode != http.StatusNoContent {
- return fmt.Errorf("delete %s service: expected 204, got %d", svc.protocol, resp.StatusCode)
- }
- }
-
- return nil
-}
-
-func testAdvancedPaginationAndSorting(s *TestSuite) error {
- // Create multiple services for better pagination testing
- testServices := []web.CreateUpdateServiceRequest{}
- for i := 0; i < 10; i++ {
- service := web.CreateUpdateServiceRequest{
- Name: fmt.Sprintf("Pagination Test Service %02d", i),
- Protocol: storage.ServiceProtocolTypeHTTP,
- Interval: 30000,
- Timeout: 5000,
- Retries: 3,
- Tags: []string{fmt.Sprintf("page-test-%d", i%3), "pagination"},
- Config: monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 5000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "Test Endpoint",
- URL: "https://httpbin.org/status/200",
- Method: "GET",
- ExpectedStatus: 200,
- },
- },
- },
- },
- IsEnabled: i%2 == 0, // Alternate enabled/disabled
- }
- testServices = append(testServices, service)
- }
-
- // Create all test services
- createdIDs := []string{}
- for i, service := range testServices {
- resp, err := s.makeRequest("POST", "/api/v1/services", service)
- if err != nil {
- return fmt.Errorf("create pagination test service %d: %w", i, err)
- }
-
- var created web.ServiceDTO
- if err := s.decodeResponse(resp, &created); err != nil {
- return fmt.Errorf("decode pagination test service %d: %w", i, err)
- }
-
- createdIDs = append(createdIDs, created.ID)
- }
-
- // Test various page sizes
- pageSizes := []int{3, 5, 7}
- for _, pageSize := range pageSizes {
- page := 1
- allItems := []web.ServiceDTO{}
- seenIDs := make(map[string]bool)
-
- for {
- resp, err := s.makeRequest("GET",
- fmt.Sprintf("/api/v1/services?page=%d&page_size=%d&order_by=name", page, pageSize), nil)
- if err != nil {
- return fmt.Errorf("pagination test page %d size %d: %w", page, pageSize, err)
- }
-
- var result dbutils.FindResponseWithCount[web.ServiceDTO]
- if err := s.decodeResponse(resp, &result); err != nil {
- return fmt.Errorf("decode pagination test page %d size %d: %w", page, pageSize, err)
- }
-
- if len(result.Items) == 0 {
- break // No more items
- }
-
- if len(result.Items) > pageSize {
- return fmt.Errorf("page size exceeded: requested %d, got %d", pageSize, len(result.Items))
- }
-
- // Check for duplicates
- for _, item := range result.Items {
- if seenIDs[item.ID] {
- return fmt.Errorf("duplicate item %s found across pages", item.ID)
- }
- seenIDs[item.ID] = true
- allItems = append(allItems, item)
- }
-
- page++
- if page > 20 { // Safety break
- break
- }
- }
-
- // Check if items are properly sorted by name
- for i := 1; i < len(allItems); i++ {
- if allItems[i-1].Name > allItems[i].Name {
- return fmt.Errorf("items not sorted by name: %s > %s", allItems[i-1].Name, allItems[i].Name)
- }
- }
- }
-
- // Test ordering by created_at
- resp, err := s.makeRequest("GET", "/api/v1/services?order_by=created_at&page_size=20", nil)
- if err != nil {
- return fmt.Errorf("order by created_at test: %w", err)
- }
-
- var orderedResult dbutils.FindResponseWithCount[web.ServiceDTO]
- if err := s.decodeResponse(resp, &orderedResult); err != nil {
- return fmt.Errorf("decode order by created_at test: %w", err)
- }
-
- // Clean up - delete all test services
- for _, id := range createdIDs {
- _, err := s.makeRequest("DELETE", "/api/v1/services/"+id, nil)
- if err != nil {
- // Log error but continue cleanup
- fmt.Printf("Warning: failed to delete test service %s: %v\n", id, err)
- }
- }
-
- return nil
-}
-
-func testAdvancedErrorScenarios(s *TestSuite) error {
- // Test various error conditions
-
- // 1. Invalid JSON in request body
- invalidJSON := bytes.NewBuffer([]byte(`{"name": "test", "protocol": "http", invalid json`))
- req, _ := http.NewRequest("POST", s.baseURL+"/api/v1/services", invalidJSON)
- req.Header.Set("Content-Type", "application/json")
- resp, err := s.client.Do(req)
- if err != nil {
- return fmt.Errorf("invalid JSON test request: %w", err)
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusBadRequest {
- return fmt.Errorf("invalid JSON: expected 400, got %d", resp.StatusCode)
- }
-
- // 2. Missing required fields
- incompleteService := map[string]interface{}{
- "protocol": "http",
- // Missing name
- }
-
- resp, err = s.makeRequest("POST", "/api/v1/services", incompleteService)
- if err != nil {
- return fmt.Errorf("incomplete service test: %w", err)
- }
-
- if resp.StatusCode != http.StatusBadRequest {
- return fmt.Errorf("incomplete service: expected 400, got %d", resp.StatusCode)
- }
-
- // 3. Invalid protocol
- invalidProtocolService := web.CreateUpdateServiceRequest{
- Name: "Invalid Protocol Service",
- Protocol: "invalid-protocol",
- Config: monitors.Config{},
- }
-
- resp, err = s.makeRequest("POST", "/api/v1/services", invalidProtocolService)
- if err != nil {
- return fmt.Errorf("invalid protocol test: %w", err)
- }
-
- if resp.StatusCode != http.StatusBadRequest {
- return fmt.Errorf("invalid protocol: expected 400, got %d", resp.StatusCode)
- }
-
- // 4. Invalid query parameters
- invalidQueries := []string{
- "?status=invalid-status",
- "?protocol=invalid-protocol",
- "?order_by=invalid-field",
- "?page=0",
- "?page_size=0",
- "?page_size=1000",
- }
-
- for _, query := range invalidQueries {
- resp, err = s.makeRequest("GET", "/api/v1/services"+query, nil)
- if err != nil {
- return fmt.Errorf("invalid query %s test: %w", query, err)
- }
-
- if resp.StatusCode != http.StatusBadRequest {
- return fmt.Errorf("invalid query %s: expected 400, got %d", query, resp.StatusCode)
- }
- }
-
- // 5. Operations on non-existent service
- nonExistentID := "non-existent-service-id"
-
- // GET non-existent service
- resp, err = s.makeRequest("GET", "/api/v1/services/"+nonExistentID, nil)
- if err != nil {
- return fmt.Errorf("get non-existent service test: %w", err)
- }
-
- if resp.StatusCode != http.StatusInternalServerError {
- return fmt.Errorf("get non-existent service: expected 500, got %d", resp.StatusCode)
- }
-
- // UPDATE non-existent service
- updateReq := web.CreateUpdateServiceRequest{
- Name: "Updated Service",
- Protocol: storage.ServiceProtocolTypeHTTP,
- Interval: 60000,
- Timeout: 10000,
- Retries: 3,
- Config: monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 30000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "test",
- URL: "https://httpbin.org/status/200",
- Method: "GET",
- ExpectedStatus: 200,
- },
- },
- },
- },
- IsEnabled: true,
- }
-
- resp, err = s.makeRequest("PUT", "/api/v1/services/"+nonExistentID, updateReq)
- if err != nil {
- return fmt.Errorf("update non-existent service test: %w", err)
- }
-
- if resp.StatusCode != http.StatusInternalServerError {
- return fmt.Errorf("update non-existent service: expected 500, got %d", resp.StatusCode)
- }
-
- // DELETE non-existent service
- resp, err = s.makeRequest("DELETE", "/api/v1/services/"+nonExistentID, nil)
- if err != nil {
- return fmt.Errorf("delete non-existent service test: %w", err)
- }
-
- if resp.StatusCode != http.StatusInternalServerError {
- return fmt.Errorf("delete non-existent service: expected 500, got %d", resp.StatusCode)
- }
-
- // CHECK non-existent service
- resp, err = s.makeRequest("POST", "/api/v1/services/"+nonExistentID+"/check", nil)
- if err != nil {
- return fmt.Errorf("check non-existent service test: %w", err)
- }
-
- if resp.StatusCode != http.StatusNotFound {
- return fmt.Errorf("check non-existent service: expected 404, got %d", resp.StatusCode)
- }
-
- // RESOLVE non-existent service
- resp, err = s.makeRequest("POST", "/api/v1/services/"+nonExistentID+"/resolve", nil)
- if err != nil {
- return fmt.Errorf("resolve non-existent service test: %w", err)
- }
-
- if resp.StatusCode != http.StatusInternalServerError {
- return fmt.Errorf("resolve non-existent service: expected 500, got %d", resp.StatusCode)
- }
-
- // STATS for non-existent service
- resp, err = s.makeRequest("GET", "/api/v1/services/"+nonExistentID+"/stats", nil)
- if err != nil {
- return fmt.Errorf("stats non-existent service test: %w", err)
- }
-
- if resp.StatusCode != http.StatusInternalServerError {
- return fmt.Errorf("stats non-existent service: expected 500, got %d", resp.StatusCode)
- }
-
- // INCIDENTS for non-existent service
- resp, err = s.makeRequest("GET", "/api/v1/services/"+nonExistentID+"/incidents", nil)
- if err != nil {
- return fmt.Errorf("incidents non-existent service test: %w", err)
- }
-
- if resp.StatusCode != http.StatusBadRequest {
- return fmt.Errorf("incidents non-existent service: expected 400, got %d", resp.StatusCode)
- }
-
- return nil
-}
-
-func testStatsWithDifferentParameters(s *TestSuite) error {
- // Create a dedicated service for stats testing
- testService := web.CreateUpdateServiceRequest{
- Name: "Stats Test Service",
- Protocol: storage.ServiceProtocolTypeHTTP,
- Interval: 30000,
- Timeout: 5000,
- Retries: 3,
- Tags: []string{"stats-test"},
- Config: monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 5000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "Test Endpoint",
- URL: "https://httpbin.org/status/200",
- Method: "GET",
- ExpectedStatus: 200,
- },
- },
- },
- },
- IsEnabled: true,
- }
-
- // Create the test service
- resp, err := s.makeRequest("POST", "/api/v1/services", testService)
- if err != nil {
- return fmt.Errorf("create stats test service: %w", err)
- }
-
- var createdService web.ServiceDTO
- if err := s.decodeResponse(resp, &createdService); err != nil {
- return fmt.Errorf("decode stats test service: %w", err)
- }
-
- serviceID := createdService.ID
-
- // Ensure cleanup
- defer func() {
- s.makeRequest("DELETE", "/api/v1/services/"+serviceID, nil)
- }()
-
- // Test different days parameters
- daysTests := []struct {
- days string
- expected bool
- }{
- {"1", true},
- {"7", true},
- {"30", true},
- {"365", true},
- {"0", true}, // Should work with 0 days
- {"-1", true}, // Negative should be handled gracefully
- {"abc", true}, // Invalid string should default to 30
- {"", true}, // Empty should default to 30
- }
-
- for _, test := range daysTests {
- path := "/api/v1/services/" + serviceID + "/stats"
- if test.days != "" {
- path += "?days=" + test.days
- }
-
- resp, err := s.makeRequest("GET", path, nil)
- if err != nil {
- if test.expected {
- return fmt.Errorf("stats with days=%s failed: %w", test.days, err)
- }
- continue // Expected to fail
- }
-
- if !test.expected {
- return fmt.Errorf("stats with days=%s should have failed but didn't", test.days)
- }
-
- var stats web.ServiceStats
- if err := s.decodeResponse(resp, &stats); err != nil {
- return fmt.Errorf("decode stats with days=%s: %w", test.days, err)
- }
-
- if stats.ServiceID != serviceID {
- return fmt.Errorf("stats service ID mismatch for days=%s", test.days)
- }
-
- // Basic validation of stats
- if stats.UptimePercentage < 0 || stats.UptimePercentage > 100 {
- return fmt.Errorf("invalid uptime percentage for days=%s: %f", test.days, stats.UptimePercentage)
- }
- }
-
- return nil
-}
-
-// Helper function to add extended tests to the main test suite
-func getExtendedTests() []struct {
- name string
- fn func(*TestSuite) error
-} {
- return []struct {
- name string
- fn func(*TestSuite) error
- }{
- {"TestAdvancedServiceFilters", testAdvancedServiceFilters},
- {"TestServiceCRUDCompleteFlow", testServiceCRUDCompleteFlow},
- {"TestAdvancedIncidentManagement", testAdvancedIncidentManagement},
- {"TestCompleteProtocolConfigurations", testCompleteProtocolConfigurations},
- {"TestAdvancedPaginationAndSorting", testAdvancedPaginationAndSorting},
- {"TestAdvancedErrorScenarios", testAdvancedErrorScenarios},
- {"TestStatsWithDifferentParameters", testStatsWithDifferentParameters},
- }
-}
diff --git a/cmd/testapi/main.go b/cmd/testapi/main.go
deleted file mode 100644
index 2c08c5c..0000000
--- a/cmd/testapi/main.go
+++ /dev/null
@@ -1,955 +0,0 @@
-package main
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "log"
- "net/http"
- "net/url"
- "os"
- "path/filepath"
- "reflect"
- "time"
-
- "github.com/sxwebdev/sentinel/internal/config"
- "github.com/sxwebdev/sentinel/internal/monitor"
- "github.com/sxwebdev/sentinel/internal/monitors"
- "github.com/sxwebdev/sentinel/internal/notifier"
- "github.com/sxwebdev/sentinel/internal/receiver"
- "github.com/sxwebdev/sentinel/internal/storage"
- "github.com/sxwebdev/sentinel/internal/upgrader"
- "github.com/sxwebdev/sentinel/internal/web"
- "github.com/sxwebdev/sentinel/pkg/dbutils"
- "github.com/tkcrm/mx/logger"
-)
-
-type TestSuite struct {
- server *web.Server
- baseURL string
- client *http.Client
- stor storage.Storage
- ctx context.Context
- services map[string]*web.ServiceDTO
- incidents map[string]*web.Incident
- testServices []TestService
-}
-
-type TestService struct {
- Name string
- Protocol storage.ServiceProtocolType
- Tags []string
- Config monitors.Config
- Enabled bool
-}
-
-var testServices = []TestService{
- {
- Name: "HTTP Test Service 1",
- Protocol: storage.ServiceProtocolTypeHTTP,
- Tags: []string{"http", "production", "api"},
- Config: monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 5000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "Health Check",
- URL: "https://httpbin.org/status/200",
- Method: "GET",
- ExpectedStatus: 200,
- },
- },
- },
- },
- Enabled: true,
- },
- {
- Name: "HTTP Test Service 2",
- Protocol: storage.ServiceProtocolTypeHTTP,
- Tags: []string{"http", "staging", "web"},
- Config: monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 3000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "Home Page",
- URL: "https://httpbin.org/status/404",
- Method: "GET",
- ExpectedStatus: 404,
- },
- },
- },
- },
- Enabled: false,
- },
- {
- Name: "TCP Test Service",
- Protocol: storage.ServiceProtocolTypeTCP,
- Tags: []string{"tcp", "database", "production"},
- Config: monitors.Config{
- TCP: &monitors.TCPConfig{
- Endpoint: "google.com:80",
- },
- },
- Enabled: true,
- },
- {
- Name: "gRPC Test Service",
- Protocol: storage.ServiceProtocolTypeGRPC,
- Tags: []string{"grpc", "api", "microservice"},
- Config: monitors.Config{
- GRPC: &monitors.GRPCConfig{
- Endpoint: "grpc.example.com:443",
- CheckType: "connectivity",
- TLS: true,
- },
- },
- Enabled: true,
- },
- {
- Name: "Disabled Service",
- Protocol: storage.ServiceProtocolTypeHTTP,
- Tags: []string{"disabled", "test"},
- Config: monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 5000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "Test Endpoint",
- URL: "https://httpbin.org/status/500",
- Method: "GET",
- ExpectedStatus: 200,
- },
- },
- },
- },
- Enabled: false,
- },
-}
-
-func main() {
- if len(os.Args) < 2 || os.Args[1] != "test" {
- fmt.Println("Usage: go run main.go test")
- os.Exit(1)
- }
-
- // Run tests
- suite, err := setupTestSuite()
- if err != nil {
- log.Fatalf("Failed to setup test suite: %v", err)
- }
- defer suite.cleanup()
-
- // Run all tests
- tests := []struct {
- name string
- fn func(*TestSuite) error
- }{
- {"TestHealthCheck", testHealthCheck},
- {"TestDashboardStats", testDashboardStats},
- {"TestCreateServices", testCreateServices},
- {"TestGetServices", testGetServices},
- {"TestServiceFilters", testServiceFilters},
- {"TestServiceDetail", testServiceDetail},
- {"TestUpdateService", testUpdateService},
- {"TestServiceStats", testServiceStats},
- {"TestServiceCheck", testServiceCheck},
- {"TestIncidents", testIncidents},
- {"TestIncidentFilters", testIncidentFilters},
- {"TestTags", testTags},
- {"TestPagination", testPagination},
- {"TestErrorHandling", testErrorHandling},
- {"TestDeleteService", testDeleteService},
- }
-
- // Add extended tests
- extendedTests := getExtendedTests()
- tests = append(tests, extendedTests...)
-
- // Add model validation tests
- modelTests := getModelValidationTests()
- tests = append(tests, modelTests...)
-
- failed := 0
- for _, test := range tests {
- fmt.Printf("Running %s...\n", test.name)
- if err := test.fn(suite); err != nil {
- fmt.Printf("FAIL: %s - %v\n", test.name, err)
- failed++
- } else {
- fmt.Printf("PASS: %s\n", test.name)
- }
- }
-
- if failed > 0 {
- fmt.Printf("\n%d test(s) failed\n", failed)
- os.Exit(1)
- } else {
- fmt.Println("\nAll tests passed!")
- }
-}
-
-func setupTestSuite() (*TestSuite, error) {
- ctx := context.Background()
-
- // Create temporary database file
- tmpDir, err := os.MkdirTemp("", "sentinel_test_*")
- if err != nil {
- return nil, fmt.Errorf("failed to create temp dir: %w", err)
- }
-
- dbPath := filepath.Join(tmpDir, "test.db")
-
- // Load config
- cfg := &config.Config{
- Database: config.DatabaseConfig{
- Path: dbPath,
- },
- Monitoring: config.MonitoringConfig{
- Global: config.GlobalConfig{
- DefaultInterval: time.Minute,
- DefaultTimeout: 5 * time.Second,
- DefaultRetries: 5,
- },
- },
- Server: config.ServerConfig{
- Host: "localhost",
- Port: 8899, // Use different port for testing
- BaseHost: "localhost:8899",
- },
- Timezone: "UTC",
- }
-
- l := logger.Default()
-
- // Initialize storage
- stor, err := storage.NewStorage(storage.StorageTypeSQLite, dbPath)
- if err != nil {
- return nil, fmt.Errorf("failed to initialize storage: %w", err)
- }
-
- // Initialize notifier (disabled for tests)
- var notif *notifier.Notifier
-
- // Initialize receiver
- rc := receiver.New()
- if err := rc.Start(ctx); err != nil {
- return nil, fmt.Errorf("failed to start receiver: %w", err)
- }
-
- // Initialize upgrader if configured
- upgr, err := upgrader.New(l, cfg.Upgrader)
- if err != nil {
- return nil, fmt.Errorf("failed to initialize upgrader: %w", err)
- }
-
- // Create monitor service
- monitorService := monitor.NewMonitorService(stor, cfg, notif, rc)
-
- // Create web server
- webServer, err := web.NewServer(l, cfg, web.ServerInfo{}, monitorService, stor, rc, upgr)
- if err != nil {
- return nil, fmt.Errorf("failed to create web server: %w", err)
- }
-
- // Start server in background
- go func() {
- if err := webServer.App().Listen(fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)); err != nil {
- log.Printf("Server error: %v", err)
- }
- }()
-
- // Wait a bit for server to start
- time.Sleep(100 * time.Millisecond)
-
- suite := &TestSuite{
- server: webServer,
- baseURL: fmt.Sprintf("http://%s:%d", cfg.Server.Host, cfg.Server.Port),
- client: &http.Client{Timeout: 10 * time.Second},
- stor: stor,
- ctx: ctx,
- services: make(map[string]*web.ServiceDTO),
- incidents: make(map[string]*web.Incident),
- testServices: testServices,
- }
-
- return suite, nil
-}
-
-func (s *TestSuite) cleanup() {
- if s.stor != nil {
- s.stor.Stop(context.Background())
- }
-}
-
-func (s *TestSuite) makeRequest(method, path string, body interface{}) (*http.Response, error) {
- var reqBody io.Reader
- if body != nil {
- jsonBody, err := json.Marshal(body)
- if err != nil {
- return nil, err
- }
- reqBody = bytes.NewBuffer(jsonBody)
- }
-
- req, err := http.NewRequest(method, s.baseURL+path, reqBody)
- if err != nil {
- return nil, err
- }
-
- if body != nil {
- req.Header.Set("Content-Type", "application/json")
- }
-
- return s.client.Do(req)
-}
-
-func (s *TestSuite) decodeResponse(resp *http.Response, target interface{}) error {
- defer resp.Body.Close()
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return err
- }
-
- if resp.StatusCode >= 400 {
- var errResp web.ErrorResponse
- if err := json.Unmarshal(body, &errResp); err != nil {
- return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
- }
- return fmt.Errorf("HTTP %d: %s", resp.StatusCode, errResp.Error)
- }
-
- return json.Unmarshal(body, target)
-}
-
-// Test cases implementation
-
-func testHealthCheck(s *TestSuite) error {
- resp, err := s.makeRequest("GET", "/", nil)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return fmt.Errorf("expected status 200, got %d", resp.StatusCode)
- }
-
- return nil
-}
-
-func testDashboardStats(s *TestSuite) error {
- resp, err := s.makeRequest("GET", "/api/v1/dashboard/stats", nil)
- if err != nil {
- return err
- }
-
- var stats web.DashboardStats
- if err := s.decodeResponse(resp, &stats); err != nil {
- return err
- }
-
- // Basic validation
- if stats.TotalServices < 0 {
- return fmt.Errorf("total services should be >= 0, got %d", stats.TotalServices)
- }
-
- return nil
-}
-
-func testCreateServices(s *TestSuite) error {
- for i, testSvc := range s.testServices {
- createReq := web.CreateUpdateServiceRequest{
- Name: testSvc.Name,
- Protocol: testSvc.Protocol,
- Interval: 30000, // 30s
- Timeout: 5000, // 5s
- Retries: 3,
- Tags: testSvc.Tags,
- Config: testSvc.Config,
- IsEnabled: testSvc.Enabled,
- }
-
- resp, err := s.makeRequest("POST", "/api/v1/services", createReq)
- if err != nil {
- return fmt.Errorf("service %d: %w", i, err)
- }
-
- var service web.ServiceDTO
- if err := s.decodeResponse(resp, &service); err != nil {
- return fmt.Errorf("service %d: %w", i, err)
- }
-
- // Validate response
- if service.ID == "" {
- return fmt.Errorf("service %d: missing ID", i)
- }
- if service.Name != testSvc.Name {
- return fmt.Errorf("service %d: name mismatch", i)
- }
- if service.Protocol != testSvc.Protocol {
- return fmt.Errorf("service %d: protocol mismatch", i)
- }
- if !reflect.DeepEqual(service.Tags, testSvc.Tags) {
- return fmt.Errorf("service %d: tags mismatch", i)
- }
- if service.IsEnabled != testSvc.Enabled {
- return fmt.Errorf("service %d: enabled status mismatch", i)
- }
-
- s.services[service.Name] = &service
- }
-
- return nil
-}
-
-func testGetServices(s *TestSuite) error {
- resp, err := s.makeRequest("GET", "/api/v1/services", nil)
- if err != nil {
- return err
- }
-
- var result dbutils.FindResponseWithCount[web.ServiceDTO]
- if err := s.decodeResponse(resp, &result); err != nil {
- return err
- }
-
- if int(result.Count) != len(s.testServices) {
- return fmt.Errorf("expected %d services, got %d", len(s.testServices), result.Count)
- }
-
- if len(result.Items) != len(s.testServices) {
- return fmt.Errorf("expected %d items, got %d", len(s.testServices), len(result.Items))
- }
-
- return nil
-}
-
-func testServiceFilters(s *TestSuite) error {
- // Test filter by name
- resp, err := s.makeRequest("GET", "/api/v1/services?name=HTTP Test Service 1", nil)
- if err != nil {
- return err
- }
-
- var result dbutils.FindResponseWithCount[web.ServiceDTO]
- if err := s.decodeResponse(resp, &result); err != nil {
- return err
- }
-
- if result.Count != 1 {
- return fmt.Errorf("name filter: expected 1 service, got %d", result.Count)
- }
- if result.Items[0].Name != "HTTP Test Service 1" {
- return fmt.Errorf("name filter: wrong service returned")
- }
-
- // Test filter by protocol
- resp, err = s.makeRequest("GET", "/api/v1/services?protocol=http", nil)
- if err != nil {
- return err
- }
-
- if err := s.decodeResponse(resp, &result); err != nil {
- return err
- }
-
- expectedHTTPServices := 0
- for _, svc := range s.testServices {
- if svc.Protocol == storage.ServiceProtocolTypeHTTP {
- expectedHTTPServices++
- }
- }
-
- if int(result.Count) != expectedHTTPServices {
- return fmt.Errorf("protocol filter: expected %d HTTP services, got %d", expectedHTTPServices, result.Count)
- }
-
- // Test filter by tags
- resp, err = s.makeRequest("GET", "/api/v1/services?tags=production", nil)
- if err != nil {
- return err
- }
-
- if err := s.decodeResponse(resp, &result); err != nil {
- return err
- }
-
- expectedProdServices := 0
- for _, svc := range s.testServices {
- for _, tag := range svc.Tags {
- if tag == "production" {
- expectedProdServices++
- break
- }
- }
- }
-
- if int(result.Count) != expectedProdServices {
- return fmt.Errorf("tags filter: expected %d production services, got %d", expectedProdServices, result.Count)
- }
-
- // Test filter by enabled status
- resp, err = s.makeRequest("GET", "/api/v1/services?is_enabled=true", nil)
- if err != nil {
- return err
- }
-
- if err := s.decodeResponse(resp, &result); err != nil {
- return err
- }
-
- expectedEnabledServices := 0
- for _, svc := range s.testServices {
- if svc.Enabled {
- expectedEnabledServices++
- }
- }
-
- if int(result.Count) != expectedEnabledServices {
- return fmt.Errorf("enabled filter: expected %d enabled services, got %d", expectedEnabledServices, result.Count)
- }
-
- // Test ordering
- resp, err = s.makeRequest("GET", "/api/v1/services?order_by=name", nil)
- if err != nil {
- return err
- }
-
- if err := s.decodeResponse(resp, &result); err != nil {
- return err
- }
-
- // Check if results are ordered by name
- for i := 1; i < len(result.Items); i++ {
- if result.Items[i-1].Name > result.Items[i].Name {
- return fmt.Errorf("services are not ordered by name")
- }
- }
-
- // Test multiple filters
- resp, err = s.makeRequest("GET", "/api/v1/services?protocol=http&tags=production&is_enabled=true", nil)
- if err != nil {
- return err
- }
-
- if err := s.decodeResponse(resp, &result); err != nil {
- return err
- }
-
- // Validate each service matches all filters
- for _, item := range result.Items {
- if item.Protocol != storage.ServiceProtocolTypeHTTP {
- return fmt.Errorf("multiple filters: service %s doesn't match protocol filter", item.Name)
- }
- if !item.IsEnabled {
- return fmt.Errorf("multiple filters: service %s doesn't match enabled filter", item.Name)
- }
- hasProdTag := false
- for _, tag := range item.Tags {
- if tag == "production" {
- hasProdTag = true
- break
- }
- }
- if !hasProdTag {
- return fmt.Errorf("multiple filters: service %s doesn't have production tag", item.Name)
- }
- }
-
- return nil
-}
-
-func testServiceDetail(s *TestSuite) error {
- // Get first service
- var serviceID string
- for _, svc := range s.services {
- serviceID = svc.ID
- break
- }
-
- resp, err := s.makeRequest("GET", "/api/v1/services/"+serviceID, nil)
- if err != nil {
- return err
- }
-
- var service web.ServiceDTO
- if err := s.decodeResponse(resp, &service); err != nil {
- return err
- }
-
- if service.ID != serviceID {
- return fmt.Errorf("service detail: ID mismatch")
- }
-
- // Test non-existent service
- resp, err = s.makeRequest("GET", "/api/v1/services/non-existent", nil)
- if err != nil {
- return err
- }
-
- if resp.StatusCode != http.StatusInternalServerError {
- return fmt.Errorf("expected 500 for non-existent service, got %d", resp.StatusCode)
- }
-
- return nil
-}
-
-func testUpdateService(s *TestSuite) error {
- // Get first service
- var service *web.ServiceDTO
- for _, svc := range s.services {
- service = svc
- break
- }
-
- // Update service
- updateReq := web.CreateUpdateServiceRequest{
- Name: service.Name + " Updated",
- Protocol: service.Protocol,
- Interval: 60000, // 60s
- Timeout: 10000, // 10s
- Retries: 5,
- Tags: append(service.Tags, "updated"),
- Config: service.Config,
- IsEnabled: !service.IsEnabled,
- }
-
- resp, err := s.makeRequest("PUT", "/api/v1/services/"+service.ID, updateReq)
- if err != nil {
- return err
- }
-
- var updatedService web.ServiceDTO
- if err := s.decodeResponse(resp, &updatedService); err != nil {
- return err
- }
-
- // Validate updates
- if updatedService.Name != updateReq.Name {
- return fmt.Errorf("update: name not updated")
- }
- if updatedService.Interval != updateReq.Interval {
- return fmt.Errorf("update: interval not updated")
- }
- if updatedService.Timeout != updateReq.Timeout {
- return fmt.Errorf("update: timeout not updated")
- }
- if updatedService.Retries != updateReq.Retries {
- return fmt.Errorf("update: retries not updated")
- }
- if updatedService.IsEnabled != updateReq.IsEnabled {
- return fmt.Errorf("update: enabled status not updated")
- }
-
- return nil
-}
-
-func testServiceStats(s *TestSuite) error {
- // Get first service
- var serviceID string
- for _, svc := range s.services {
- serviceID = svc.ID
- break
- }
-
- // Test service stats
- resp, err := s.makeRequest("GET", "/api/v1/services/"+serviceID+"/stats", nil)
- if err != nil {
- return err
- }
-
- var stats web.ServiceStats
- if err := s.decodeResponse(resp, &stats); err != nil {
- return err
- }
-
- if stats.ServiceID != serviceID {
- return fmt.Errorf("stats: service ID mismatch")
- }
-
- // Test with custom days parameter
- resp, err = s.makeRequest("GET", "/api/v1/services/"+serviceID+"/stats?days=7", nil)
- if err != nil {
- return err
- }
-
- if err := s.decodeResponse(resp, &stats); err != nil {
- return err
- }
-
- return nil
-}
-
-func testServiceCheck(s *TestSuite) error {
- // Get first service
- var serviceID string
- for _, svc := range s.services {
- serviceID = svc.ID
- break
- }
-
- // Trigger check
- resp, err := s.makeRequest("POST", "/api/v1/services/"+serviceID+"/check", nil)
- if err != nil {
- return err
- }
-
- var result web.SuccessResponse
- if err := s.decodeResponse(resp, &result); err != nil {
- return err
- }
-
- if result.Message == "" {
- return fmt.Errorf("check: empty success message")
- }
-
- return nil
-}
-
-func testIncidents(s *TestSuite) error {
- // Test get all incidents
- resp, err := s.makeRequest("GET", "/api/v1/incidents", nil)
- if err != nil {
- return err
- }
-
- var incidents dbutils.FindResponseWithCount[web.Incident]
- if err := s.decodeResponse(resp, &incidents); err != nil {
- return err
- }
-
- // Get first service for service-specific incidents
- var serviceID string
- for _, svc := range s.services {
- serviceID = svc.ID
- break
- }
-
- // Test service incidents
- resp, err = s.makeRequest("GET", "/api/v1/services/"+serviceID+"/incidents", nil)
- if err != nil {
- return err
- }
-
- var serviceIncidents dbutils.FindResponseWithCount[web.Incident]
- if err := s.decodeResponse(resp, &serviceIncidents); err != nil {
- return err
- }
-
- // All incidents should belong to the service
- for _, incident := range serviceIncidents.Items {
- if incident.ServiceID != serviceID {
- return fmt.Errorf("incident %s doesn't belong to service %s", incident.ID, serviceID)
- }
- }
-
- return nil
-}
-
-func testIncidentFilters(s *TestSuite) error {
- // Get first service
- var serviceID string
- for _, svc := range s.services {
- serviceID = svc.ID
- break
- }
-
- // Test filter by resolved status
- resp, err := s.makeRequest("GET", "/api/v1/services/"+serviceID+"/incidents?resolved=false", nil)
- if err != nil {
- return err
- }
-
- var incidents dbutils.FindResponseWithCount[web.Incident]
- if err := s.decodeResponse(resp, &incidents); err != nil {
- return err
- }
-
- // All incidents should be unresolved
- for _, incident := range incidents.Items {
- if incident.Resolved {
- return fmt.Errorf("incident filter: found resolved incident when filtering for unresolved")
- }
- }
-
- // Test time-based filtering
- now := time.Now()
- yesterday := now.AddDate(0, 0, -1)
- resp, err = s.makeRequest("GET", "/api/v1/incidents?start_time="+url.QueryEscape(yesterday.Format(time.RFC3339)), nil)
- if err != nil {
- return err
- }
-
- if err := s.decodeResponse(resp, &incidents); err != nil {
- return err
- }
-
- return nil
-}
-
-func testTags(s *TestSuite) error {
- // Test get all tags
- resp, err := s.makeRequest("GET", "/api/v1/tags", nil)
- if err != nil {
- return err
- }
-
- var tags []string
- if err := s.decodeResponse(resp, &tags); err != nil {
- return err
- }
-
- // Collect expected tags
- expectedTags := make(map[string]bool)
- for _, svc := range s.testServices {
- for _, tag := range svc.Tags {
- expectedTags[tag] = true
- }
- }
-
- // Check if all expected tags are present
- tagSet := make(map[string]bool)
- for _, tag := range tags {
- tagSet[tag] = true
- }
-
- for expectedTag := range expectedTags {
- if !tagSet[expectedTag] {
- return fmt.Errorf("tags: missing expected tag %s", expectedTag)
- }
- }
-
- // Test get tags with count
- resp, err = s.makeRequest("GET", "/api/v1/tags/count", nil)
- if err != nil {
- return err
- }
-
- var tagsWithCount map[string]int
- if err := s.decodeResponse(resp, &tagsWithCount); err != nil {
- return err
- }
-
- // Validate counts
- for tag, count := range tagsWithCount {
- if count <= 0 {
- return fmt.Errorf("tags count: tag %s has invalid count %d", tag, count)
- }
- }
-
- return nil
-}
-
-func testPagination(s *TestSuite) error {
- // Test services pagination
- resp, err := s.makeRequest("GET", "/api/v1/services?page=1&page_size=2", nil)
- if err != nil {
- return err
- }
-
- var result dbutils.FindResponseWithCount[web.ServiceDTO]
- if err := s.decodeResponse(resp, &result); err != nil {
- return err
- }
-
- if len(result.Items) > 2 {
- return fmt.Errorf("pagination: page size not respected, got %d items", len(result.Items))
- }
-
- // Test second page
- resp, err = s.makeRequest("GET", "/api/v1/services?page=2&page_size=2", nil)
- if err != nil {
- return err
- }
-
- var result2 dbutils.FindResponseWithCount[web.ServiceDTO]
- if err := s.decodeResponse(resp, &result2); err != nil {
- return err
- }
-
- // Make sure pages don't overlap
- if len(result.Items) > 0 && len(result2.Items) > 0 {
- for _, item1 := range result.Items {
- for _, item2 := range result2.Items {
- if item1.ID == item2.ID {
- return fmt.Errorf("pagination: services overlap between pages")
- }
- }
- }
- }
-
- return nil
-}
-
-func testErrorHandling(s *TestSuite) error {
- // Test invalid service creation
- invalidService := web.CreateUpdateServiceRequest{
- Name: "", // Empty name should fail
- Protocol: storage.ServiceProtocolTypeHTTP,
- }
-
- resp, err := s.makeRequest("POST", "/api/v1/services", invalidService)
- if err != nil {
- return err
- }
-
- if resp.StatusCode != http.StatusBadRequest {
- return fmt.Errorf("error handling: expected 400 for invalid service, got %d", resp.StatusCode)
- }
-
- // Test invalid query parameters
- resp, err = s.makeRequest("GET", "/api/v1/services?status=invalid", nil)
- if err != nil {
- return err
- }
-
- if resp.StatusCode != http.StatusBadRequest {
- return fmt.Errorf("error handling: expected 400 for invalid status filter, got %d", resp.StatusCode)
- }
-
- // Test invalid pagination
- resp, err = s.makeRequest("GET", "/api/v1/services?page=0", nil)
- if err != nil {
- return err
- }
-
- if resp.StatusCode != http.StatusBadRequest {
- return fmt.Errorf("error handling: expected 400 for invalid page, got %d", resp.StatusCode)
- }
-
- return nil
-}
-
-func testDeleteService(s *TestSuite) error {
- // Get a service to delete
- var serviceID string
- for _, svc := range s.services {
- serviceID = svc.ID
- break
- }
-
- // Delete service
- resp, err := s.makeRequest("DELETE", "/api/v1/services/"+serviceID, nil)
- if err != nil {
- return err
- }
-
- if resp.StatusCode != http.StatusNoContent {
- return fmt.Errorf("delete: expected 204, got %d", resp.StatusCode)
- }
-
- // Verify service is deleted
- resp, err = s.makeRequest("GET", "/api/v1/services/"+serviceID, nil)
- if err != nil {
- return err
- }
-
- if resp.StatusCode != http.StatusInternalServerError {
- return fmt.Errorf("delete: service still exists after deletion")
- }
-
- return nil
-}
diff --git a/cmd/testapi/model_tests.go b/cmd/testapi/model_tests.go
deleted file mode 100644
index b0d8275..0000000
--- a/cmd/testapi/model_tests.go
+++ /dev/null
@@ -1,602 +0,0 @@
-package main
-
-import (
- "fmt"
- "reflect"
-
- "github.com/sxwebdev/sentinel/internal/monitors"
- "github.com/sxwebdev/sentinel/internal/storage"
- "github.com/sxwebdev/sentinel/internal/web"
- "github.com/sxwebdev/sentinel/pkg/dbutils"
-)
-
-// Model validation tests
-
-func testModelsValidation(s *TestSuite) error {
- // Test HTTP config validation
- httpConfig := monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 5000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "Valid Endpoint",
- URL: "https://example.com/api",
- Method: "GET",
- ExpectedStatus: 200,
- Headers: map[string]string{
- "Authorization": "Bearer token",
- "Content-Type": "application/json",
- },
- Body: `{"test": true}`,
- JSONPath: "$.status",
- Username: "user",
- Password: "pass",
- },
- },
- Condition: "all",
- },
- }
-
- if err := httpConfig.Validate(storage.ServiceProtocolTypeHTTP); err != nil {
- return fmt.Errorf("valid HTTP config should not fail validation: %w", err)
- }
-
- // Test invalid HTTP config - missing endpoints
- invalidHTTPConfig := monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 5000,
- Endpoints: []monitors.EndpointConfig{}, // Empty endpoints
- },
- }
-
- if err := invalidHTTPConfig.Validate(storage.ServiceProtocolTypeHTTP); err == nil {
- return fmt.Errorf("HTTP config with empty endpoints should fail validation")
- }
-
- // Test invalid HTTP config - invalid endpoint
- invalidEndpointConfig := monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 5000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "", // Empty name
- URL: "invalid-url",
- Method: "INVALID",
- ExpectedStatus: 999, // Invalid status
- },
- },
- },
- }
-
- if err := invalidEndpointConfig.Validate(storage.ServiceProtocolTypeHTTP); err == nil {
- return fmt.Errorf("HTTP config with invalid endpoint should fail validation")
- }
-
- // Test TCP config validation
- tcpConfig := monitors.Config{
- TCP: &monitors.TCPConfig{
- Endpoint: "example.com:80",
- SendData: "test data",
- ExpectData: "expected response",
- },
- }
-
- if err := tcpConfig.Validate(storage.ServiceProtocolTypeTCP); err != nil {
- return fmt.Errorf("valid TCP config should not fail validation: %w", err)
- }
-
- // Test invalid TCP config - missing endpoint
- invalidTCPConfig := monitors.Config{
- TCP: &monitors.TCPConfig{
- Endpoint: "", // Empty endpoint
- },
- }
-
- if err := invalidTCPConfig.Validate(storage.ServiceProtocolTypeTCP); err == nil {
- return fmt.Errorf("TCP config with empty endpoint should fail validation")
- }
-
- // Test gRPC config validation
- grpcConfig := monitors.Config{
- GRPC: &monitors.GRPCConfig{
- Endpoint: "grpc.example.com:443",
- CheckType: "health",
- ServiceName: "ExampleService",
- TLS: true,
- InsecureTLS: false,
- },
- }
-
- if err := grpcConfig.Validate(storage.ServiceProtocolTypeGRPC); err != nil {
- return fmt.Errorf("valid gRPC config should not fail validation: %w", err)
- }
-
- // Test invalid gRPC config - missing endpoint
- invalidGRPCConfig := monitors.Config{
- GRPC: &monitors.GRPCConfig{
- Endpoint: "", // Empty endpoint
- CheckType: "invalid-type",
- },
- }
-
- if err := invalidGRPCConfig.Validate(storage.ServiceProtocolTypeGRPC); err == nil {
- return fmt.Errorf("gRPC config with empty endpoint should fail validation")
- }
-
- // Test config with wrong protocol
- httpConfigForTCP := monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 5000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "Test",
- URL: "https://example.com",
- Method: "GET",
- ExpectedStatus: 200,
- },
- },
- },
- }
-
- if err := httpConfigForTCP.Validate(storage.ServiceProtocolTypeTCP); err == nil {
- return fmt.Errorf("HTTP config should fail validation for TCP protocol")
- }
-
- return nil
-}
-
-func testServiceDTOFields(s *TestSuite) error {
- // Create a test service to validate DTO conversion
- testService := web.CreateUpdateServiceRequest{
- Name: "DTO Test Service",
- Protocol: storage.ServiceProtocolTypeHTTP,
- Interval: 30000,
- Timeout: 5000,
- Retries: 3,
- Tags: []string{"dto", "test", "validation"},
- Config: monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 5000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "Test Endpoint",
- URL: "https://httpbin.org/status/200",
- Method: "GET",
- ExpectedStatus: 200,
- },
- },
- },
- },
- IsEnabled: true,
- }
-
- // Create service
- resp, err := s.makeRequest("POST", "/api/v1/services", testService)
- if err != nil {
- return fmt.Errorf("create DTO test service: %w", err)
- }
-
- var service web.ServiceDTO
- if err := s.decodeResponse(resp, &service); err != nil {
- return fmt.Errorf("decode DTO test service: %w", err)
- }
-
- // Validate all DTO fields
- if service.ID == "" {
- return fmt.Errorf("service ID should not be empty")
- }
- if service.Name != testService.Name {
- return fmt.Errorf("service name mismatch")
- }
- if service.Protocol != testService.Protocol {
- return fmt.Errorf("service protocol mismatch")
- }
- if service.Interval != testService.Interval {
- return fmt.Errorf("service interval mismatch")
- }
- if service.Timeout != testService.Timeout {
- return fmt.Errorf("service timeout mismatch")
- }
- if service.Retries != testService.Retries {
- return fmt.Errorf("service retries mismatch")
- }
- if !reflect.DeepEqual(service.Tags, testService.Tags) {
- return fmt.Errorf("service tags mismatch")
- }
- if service.IsEnabled != testService.IsEnabled {
- return fmt.Errorf("service enabled status mismatch")
- }
-
- // Check config conversion
- if service.Config.HTTP == nil {
- return fmt.Errorf("HTTP config should not be nil")
- }
- if service.Config.HTTP.Timeout != testService.Config.HTTP.Timeout {
- return fmt.Errorf("HTTP config timeout mismatch")
- }
- if len(service.Config.HTTP.Endpoints) != len(testService.Config.HTTP.Endpoints) {
- return fmt.Errorf("HTTP endpoints count mismatch")
- }
-
- // Check endpoint details
- endpoint := service.Config.HTTP.Endpoints[0]
- originalEndpoint := testService.Config.HTTP.Endpoints[0]
- if endpoint.Name != originalEndpoint.Name {
- return fmt.Errorf("endpoint name mismatch")
- }
- if endpoint.URL != originalEndpoint.URL {
- return fmt.Errorf("endpoint URL mismatch")
- }
- if endpoint.Method != originalEndpoint.Method {
- return fmt.Errorf("endpoint method mismatch")
- }
- if endpoint.ExpectedStatus != originalEndpoint.ExpectedStatus {
- return fmt.Errorf("endpoint expected status mismatch")
- }
-
- // Check state fields are present (even if default values)
- if service.ActiveIncidents < 0 {
- return fmt.Errorf("active incidents should be >= 0")
- }
- if service.TotalIncidents < 0 {
- return fmt.Errorf("total incidents should be >= 0")
- }
- if service.ConsecutiveFails < 0 {
- return fmt.Errorf("consecutive fails should be >= 0")
- }
- if service.ConsecutiveSuccess < 0 {
- return fmt.Errorf("consecutive success should be >= 0")
- }
- if service.TotalChecks < 0 {
- return fmt.Errorf("total checks should be >= 0")
- }
-
- // Status should be one of the valid values
- validStatuses := []storage.ServiceStatus{
- storage.StatusUnknown,
- storage.StatusUp,
- storage.StatusDown,
- storage.StatusMaintenance,
- }
- isValidStatus := false
- for _, validStatus := range validStatuses {
- if service.Status == validStatus {
- isValidStatus = true
- break
- }
- }
- if !isValidStatus {
- return fmt.Errorf("invalid service status: %s", service.Status)
- }
-
- // Clean up
- if _, err := s.makeRequest("DELETE", "/api/v1/services/"+service.ID, nil); err != nil {
- return fmt.Errorf("cleanup DTO test service: %w", err)
- }
-
- return nil
-}
-
-func testIncidentFields(s *TestSuite) error {
- // Get incidents to validate structure
- resp, err := s.makeRequest("GET", "/api/v1/incidents?page_size=1", nil)
- if err != nil {
- return fmt.Errorf("get incidents for validation: %w", err)
- }
-
- var incidents dbutils.FindResponseWithCount[web.Incident]
- if err := s.decodeResponse(resp, &incidents); err != nil {
- return fmt.Errorf("decode incidents for validation: %w", err)
- }
-
- if len(incidents.Items) > 0 {
- incident := incidents.Items[0]
-
- // Validate incident fields
- if incident.ID == "" {
- return fmt.Errorf("incident ID should not be empty")
- }
- if incident.ServiceID == "" {
- return fmt.Errorf("incident service ID should not be empty")
- }
- if incident.ServiceName == "" {
- return fmt.Errorf("incident service name should not be empty")
- }
- if incident.Status == "" {
- return fmt.Errorf("incident status should not be empty")
- }
- if incident.Message == "" {
- return fmt.Errorf("incident message should not be empty")
- }
-
- // StartedAt should be a valid time
- if incident.StartedAt.IsZero() {
- return fmt.Errorf("incident started at should not be zero")
- }
-
- // If resolved, ResolvedAt should not be nil
- if incident.Resolved && incident.ResolvedAt == nil {
- return fmt.Errorf("resolved incident should have resolved at time")
- }
-
- // If not resolved, ResolvedAt should be nil
- if !incident.Resolved && incident.ResolvedAt != nil {
- return fmt.Errorf("unresolved incident should not have resolved at time")
- }
-
- // Duration should be a valid string
- if incident.Duration == "" {
- return fmt.Errorf("incident duration should not be empty")
- }
- }
-
- return nil
-}
-
-func testResponseModels(s *TestSuite) error {
- // Test ErrorResponse
- resp, err := s.makeRequest("GET", "/api/v1/services/non-existent", nil)
- if err != nil {
- return fmt.Errorf("test error response: %w", err)
- }
-
- if resp.StatusCode >= 400 {
- var errorResp web.ErrorResponse
- if err := s.decodeResponse(resp, &errorResp); err == nil {
- if errorResp.Error == "" {
- return fmt.Errorf("error response should have non-empty error message")
- }
- }
- }
-
- // Test SuccessResponse by triggering a check
- // Create a test service first to ensure we have a valid service ID
- createReq := web.CreateUpdateServiceRequest{
- Name: "Test Service for Response Models",
- Protocol: storage.ServiceProtocolTypeHTTP,
- Interval: 60000,
- Timeout: 10000,
- Retries: 3,
- Tags: []string{"test", "response-models"},
- Config: monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 30000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "test",
- URL: "https://httpbin.org/status/200",
- Method: "GET",
- ExpectedStatus: 200,
- },
- },
- },
- },
- IsEnabled: true,
- }
-
- resp, err = s.makeRequest("POST", "/api/v1/services", createReq)
- if err != nil {
- return fmt.Errorf("create test service: %w", err)
- }
-
- var createResponse web.ServiceDTO
- if err := s.decodeResponse(resp, &createResponse); err != nil {
- return fmt.Errorf("decode create service response: %w", err)
- }
-
- serviceID := createResponse.ID
-
- // Now test the service check endpoint
- resp, err = s.makeRequest("POST", "/api/v1/services/"+serviceID+"/check", nil)
- if err != nil {
- return fmt.Errorf("test success response: %w", err)
- }
-
- var successResp web.SuccessResponse
- if err := s.decodeResponse(resp, &successResp); err != nil {
- return fmt.Errorf("decode success response: %w", err)
- }
-
- if successResp.Message == "" {
- return fmt.Errorf("success response should have non-empty message")
- }
-
- // Clean up: delete the test service
- _, err = s.makeRequest("DELETE", "/api/v1/services/"+serviceID, nil)
- if err != nil {
- return fmt.Errorf("cleanup test service: %w", err)
- }
-
- // Test DashboardStats
- resp, err = s.makeRequest("GET", "/api/v1/dashboard/stats", nil)
- if err != nil {
- return fmt.Errorf("test dashboard stats: %w", err)
- }
-
- var stats web.DashboardStats
- if err := s.decodeResponse(resp, &stats); err != nil {
- return fmt.Errorf("decode dashboard stats: %w", err)
- }
-
- // Validate stats fields
- if stats.TotalServices < 0 {
- return fmt.Errorf("total services should be >= 0")
- }
- if stats.ServicesUp < 0 {
- return fmt.Errorf("services up should be >= 0")
- }
- if stats.ServicesDown < 0 {
- return fmt.Errorf("services down should be >= 0")
- }
- if stats.ServicesUnknown < 0 {
- return fmt.Errorf("services unknown should be >= 0")
- }
- if stats.ActiveIncidents < 0 {
- return fmt.Errorf("active incidents should be >= 0")
- }
- if stats.UptimePercentage < 0 || stats.UptimePercentage > 100 {
- return fmt.Errorf("uptime percentage should be between 0 and 100")
- }
- if stats.TotalChecks < 0 {
- return fmt.Errorf("total checks should be >= 0")
- }
- if stats.ChecksPerMinute < 0 {
- return fmt.Errorf("checks per minute should be >= 0")
- }
-
- // Protocols map should contain valid protocols
- for protocol, count := range stats.Protocols {
- validProtocols := []storage.ServiceProtocolType{
- storage.ServiceProtocolTypeHTTP,
- storage.ServiceProtocolTypeTCP,
- storage.ServiceProtocolTypeGRPC,
- }
- isValid := false
- for _, validProtocol := range validProtocols {
- if protocol == validProtocol {
- isValid = true
- break
- }
- }
- if !isValid {
- return fmt.Errorf("invalid protocol in stats: %s", protocol)
- }
- if count < 0 {
- return fmt.Errorf("protocol count should be >= 0")
- }
- }
-
- return nil
-}
-
-func testServiceStatsModel(s *TestSuite) error {
- // Create a dedicated service for stats model testing
- testService := web.CreateUpdateServiceRequest{
- Name: "Stats Model Test Service",
- Protocol: storage.ServiceProtocolTypeHTTP,
- Interval: 30000,
- Timeout: 5000,
- Retries: 3,
- Tags: []string{"stats-model-test"},
- Config: monitors.Config{
- HTTP: &monitors.HTTPConfig{
- Timeout: 5000,
- Endpoints: []monitors.EndpointConfig{
- {
- Name: "Test Endpoint",
- URL: "https://httpbin.org/status/200",
- Method: "GET",
- ExpectedStatus: 200,
- },
- },
- },
- },
- IsEnabled: true,
- }
-
- // Create the test service
- resp, err := s.makeRequest("POST", "/api/v1/services", testService)
- if err != nil {
- return fmt.Errorf("create stats model test service: %w", err)
- }
-
- var createdService web.ServiceDTO
- if err := s.decodeResponse(resp, &createdService); err != nil {
- return fmt.Errorf("decode stats model test service: %w", err)
- }
-
- serviceID := createdService.ID
-
- // Ensure cleanup
- defer func() {
- s.makeRequest("DELETE", "/api/v1/services/"+serviceID, nil)
- }()
-
- resp, err = s.makeRequest("GET", "/api/v1/services/"+serviceID+"/stats", nil)
- if err != nil {
- return fmt.Errorf("get service stats: %w", err)
- }
-
- var stats web.ServiceStats
- if err := s.decodeResponse(resp, &stats); err != nil {
- return fmt.Errorf("decode service stats: %w", err)
- }
-
- // Validate stats fields
- if stats.ServiceID != serviceID {
- return fmt.Errorf("service stats service ID mismatch")
- }
- if stats.TotalIncidents < 0 {
- return fmt.Errorf("total incidents should be >= 0")
- }
- if stats.UptimePercentage < 0 || stats.UptimePercentage > 100 {
- return fmt.Errorf("uptime percentage should be between 0 and 100")
- }
- if stats.TotalDowntime < 0 {
- return fmt.Errorf("total downtime should be >= 0")
- }
- if stats.Period <= 0 {
- return fmt.Errorf("period should be > 0")
- }
- if stats.AvgResponseTime < 0 {
- return fmt.Errorf("avg response time should be >= 0")
- }
-
- return nil
-}
-
-func testPaginationResponseModel(s *TestSuite) error {
- // Test services pagination response
- resp, err := s.makeRequest("GET", "/api/v1/services?page=1&page_size=3", nil)
- if err != nil {
- return fmt.Errorf("get paginated services: %w", err)
- }
-
- var result dbutils.FindResponseWithCount[web.ServiceDTO]
- if err := s.decodeResponse(resp, &result); err != nil {
- return fmt.Errorf("decode paginated services: %w", err)
- }
-
- // Validate pagination structure
- // Count is uint32, so it's always >= 0
- if len(result.Items) > 3 {
- return fmt.Errorf("items should not exceed page size")
- }
-
- // Test incidents pagination response
- resp, err = s.makeRequest("GET", "/api/v1/incidents?page=1&page_size=5", nil)
- if err != nil {
- return fmt.Errorf("get paginated incidents: %w", err)
- }
-
- var incidentResult dbutils.FindResponseWithCount[web.Incident]
- if err := s.decodeResponse(resp, &incidentResult); err != nil {
- return fmt.Errorf("decode paginated incidents: %w", err)
- }
-
- // Validate pagination structure
- // Count is uint32, so it's always >= 0
- if len(incidentResult.Items) > 5 {
- return fmt.Errorf("incident items should not exceed page size")
- }
-
- return nil
-}
-
-// Helper function to add model validation tests
-func getModelValidationTests() []struct {
- name string
- fn func(*TestSuite) error
-} {
- return []struct {
- name string
- fn func(*TestSuite) error
- }{
- {"TestModelsValidation", testModelsValidation},
- {"TestServiceDTOFields", testServiceDTOFields},
- {"TestIncidentFields", testIncidentFields},
- {"TestResponseModels", testResponseModels},
- {"TestServiceStatsModel", testServiceStatsModel},
- {"TestPaginationResponseModel", testPaginationResponseModel},
- }
-}
diff --git a/cmd/testserver/main.go b/cmd/testserver/main.go
new file mode 100644
index 0000000..5de25d3
--- /dev/null
+++ b/cmd/testserver/main.go
@@ -0,0 +1,300 @@
+package main
+
+import (
+ "bufio"
+ "context"
+ "flag"
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "os/signal"
+ "sync"
+ "syscall"
+ "time"
+
+ "github.com/sxwebdev/sentinel/internal/utils"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/health"
+ "google.golang.org/grpc/health/grpc_health_v1"
+ "google.golang.org/grpc/reflection"
+)
+
+var (
+ enableTCP = flag.Bool("tcp", false, "Enable TCP server")
+ enableGRPC = flag.Bool("grpc", false, "Enable gRPC server")
+ enableHTTP = flag.Bool("http", false, "Enable HTTP server")
+ tcpPort = flag.Int("tcp-port", 12345, "TCP server port")
+ grpcPort = flag.Int("grpc-port", 50051, "gRPC server port")
+ httpPort = flag.Int("http-port", 8085, "HTTP server port")
+)
+
+func main() {
+ flag.Parse()
+
+ // Check if at least one server is enabled
+ if !*enableTCP && !*enableGRPC && !*enableHTTP {
+ log.Fatal("At least one server must be enabled. Use -tcp, -grpc, or -http flags.")
+ }
+
+ if err := run(); err != nil {
+ log.Fatalf("Failed to run servers: %v", err)
+ }
+}
+
+func run() error {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ var wg sync.WaitGroup
+ errChan := make(chan error, 3) // Buffer for all possible servers
+
+ // Start enabled servers
+ if *enableTCP {
+ wg.Go(func() {
+ log.Printf("Starting TCP server on port %d", *tcpPort)
+ if err := runTCPServer(ctx, *tcpPort); err != nil {
+ errChan <- fmt.Errorf("TCP server error: %w", err)
+ }
+ })
+ }
+
+ if *enableGRPC {
+ wg.Go(func() {
+ log.Printf("Starting gRPC server on port %d", *grpcPort)
+ if err := runGRPCServer(ctx, *grpcPort); err != nil {
+ errChan <- fmt.Errorf("gRPC server error: %w", err)
+ }
+ })
+ }
+
+ if *enableHTTP {
+ wg.Go(func() {
+ log.Printf("Starting HTTP server on port %d", *httpPort)
+ if err := runHTTPServer(ctx, *httpPort); err != nil {
+ errChan <- fmt.Errorf("HTTP server error: %w", err)
+ }
+ })
+ }
+
+ // Wait for interrupt signal or server error
+ sigChan := make(chan os.Signal, 1)
+ signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+
+ select {
+ case <-sigChan:
+ log.Println("Received interrupt signal, shutting down...")
+ cancel() // Signal all servers to stop
+ case err := <-errChan:
+ log.Printf("Server error: %v", err)
+ cancel()
+ return err
+ }
+
+ // Wait for all servers to shut down gracefully
+ done := make(chan struct{})
+ go func() {
+ wg.Wait()
+ close(done)
+ }()
+
+ select {
+ case <-done:
+ log.Println("All servers shut down gracefully")
+ case <-time.After(10 * time.Second):
+ log.Println("Timeout waiting for servers to shut down")
+ }
+
+ return nil
+}
+
+// runTCPServer runs the TCP server with original logic from cmd/tcpserver/main.go
+func runTCPServer(ctx context.Context, port int) error {
+ addr := fmt.Sprintf("localhost:%d", port)
+ listener, err := net.Listen("tcp", addr)
+ if err != nil {
+ return fmt.Errorf("failed to start TCP server: %w", err)
+ }
+ defer listener.Close()
+
+ // Channel to signal when to stop accepting new connections
+ stopChan := make(chan struct{})
+
+ // Goroutine to handle context cancellation
+ go func() {
+ <-ctx.Done()
+ close(stopChan)
+ listener.Close() // This will cause Accept() to return an error
+ }()
+
+ for {
+ select {
+ case <-stopChan:
+ return nil
+ default:
+ }
+
+ conn, err := listener.Accept()
+ if err != nil {
+ select {
+ case <-stopChan:
+ return nil // Expected error due to context cancellation
+ default:
+ log.Printf("Failed to accept TCP connection: %v", err)
+ continue
+ }
+ }
+
+ // log.Printf("TCP client connected: %s", conn.RemoteAddr())
+ go handleTCPConnection(conn)
+ }
+}
+
+// handleTCPConnection handles individual TCP connections (original logic from cmd/tcpserver/main.go)
+func handleTCPConnection(conn net.Conn) {
+ defer func() {
+ conn.Close()
+ // log.Printf("TCP connection closed: %s\n", conn.RemoteAddr())
+ }()
+
+ // Set connection timeout - close if no data received in 5 seconds
+ conn.SetReadDeadline(time.Now().Add(5 * time.Second))
+
+ var accumulated []byte
+ reader := bufio.NewReader(conn)
+
+ for {
+ buffer := make([]byte, 1024)
+ n, err := reader.Read(buffer)
+ if err != nil {
+ if utils.IsErrTimeout(err) {
+ break
+ }
+ log.Printf("Failed to read from TCP connection: %v", err)
+ return
+ }
+
+ if n == 0 {
+ break
+ }
+
+ // Accumulate received data
+ accumulated = append(accumulated, buffer[:n]...)
+
+ // If no more data buffered, client likely finished sending
+ if reader.Buffered() == 0 {
+ break
+ }
+ }
+
+ // Now process the complete message
+ if len(accumulated) == 0 {
+ log.Println("No data received from TCP client")
+ return
+ }
+
+ msg := string(accumulated)
+ // log.Printf("TCP complete message received: %s", msg)
+
+ // Simple ping-pong protocol - exact match
+ switch msg {
+ case "ping":
+ // log.Println("TCP: Sending pong")
+ _, err := conn.Write([]byte("pong"))
+ if err != nil {
+ log.Printf("Failed to send TCP response: %v", err)
+ }
+ case "noresponse":
+ log.Println("TCP: No response expected")
+ default:
+ log.Printf("TCP: Unknown message '%s', sending ok", msg)
+ _, err := conn.Write([]byte("ok"))
+ if err != nil {
+ log.Printf("Failed to send TCP response: %v", err)
+ }
+ }
+}
+
+// runGRPCServer runs the gRPC server with original logic from cmd/grpcserver/main.go
+func runGRPCServer(ctx context.Context, port int) error {
+ addr := fmt.Sprintf("localhost:%d", port)
+
+ lis, err := net.Listen("tcp", addr)
+ if err != nil {
+ return fmt.Errorf("failed to listen: %w", err)
+ }
+
+ // Create gRPC server
+ grpcServer := grpc.NewServer()
+
+ // Create and register health server
+ healthServer := health.NewServer()
+ grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
+
+ // Register gRPC reflection service for debugging
+ reflection.Register(grpcServer)
+
+ // Set initial serving status
+ healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
+ healthServer.SetServingStatus("health", grpc_health_v1.HealthCheckResponse_SERVING)
+ healthServer.SetServingStatus("test-service", grpc_health_v1.HealthCheckResponse_SERVING)
+
+ // Start server in a goroutine
+ serverErr := make(chan error, 1)
+ go func() {
+ if err := grpcServer.Serve(lis); err != nil {
+ serverErr <- fmt.Errorf("failed to serve: %w", err)
+ }
+ }()
+
+ // Wait for context cancellation or server error
+ select {
+ case <-ctx.Done():
+ // Graceful shutdown
+ log.Println("Shutting down gRPC server...")
+ grpcServer.GracefulStop()
+ return nil
+ case err := <-serverErr:
+ return err
+ }
+}
+
+// runHTTPServer runs a simple HTTP server using standard library
+func runHTTPServer(ctx context.Context, port int) error {
+ mux := http.NewServeMux()
+
+ // Simple handler that returns {"ok":true}
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"ok":true}`))
+ })
+
+ server := &http.Server{
+ Addr: fmt.Sprintf(":%d", port),
+ Handler: mux,
+ }
+
+ // Start server in a goroutine
+ serverErr := make(chan error, 1)
+ go func() {
+ if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ serverErr <- fmt.Errorf("failed to start HTTP server: %w", err)
+ }
+ }()
+
+ // Wait for context cancellation or server error
+ select {
+ case <-ctx.Done():
+ log.Println("Shutting down HTTP server...")
+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ if err := server.Shutdown(shutdownCtx); err != nil {
+ return fmt.Errorf("failed to shutdown HTTP server: %w", err)
+ }
+ return nil
+ case err := <-serverErr:
+ return err
+ }
+}
diff --git a/config-agent.template.yaml b/config-agent.template.yaml
new file mode 100644
index 0000000..436b3d1
--- /dev/null
+++ b/config-agent.template.yaml
@@ -0,0 +1,33 @@
+log:
+ format: json
+ level: info
+ console_colored: false
+ trace: fatal
+ with_caller: false
+ with_stack_trace: false
+ops:
+ enabled: false
+ network: tcp
+ tracing_enabled: false
+ metrics:
+ enabled: false
+ path: /metrics
+ port: "10000"
+ basic_auth:
+ enabled: false
+ username: ""
+ password: ""
+ healthy:
+ enabled: false
+ path: /healthy
+ port: "10000"
+ profiler:
+ enabled: false
+ path: /debug/pprof
+ port: "10000"
+ write_timeout: 60
+hub_server:
+ name: connectrpc-client
+ addr: ""
+data_dir: ./data
+token: ""
diff --git a/config.template.yaml b/config.template.yaml
index 8f88de6..c726991 100644
--- a/config.template.yaml
+++ b/config.template.yaml
@@ -1,49 +1,39 @@
log:
- format: ""
- level: ""
+ format: json
+ level: info
console_colored: false
- trace: ""
+ trace: fatal
with_caller: false
with_stack_trace: false
ops:
enabled: false
- network: ""
+ network: tcp
tracing_enabled: false
metrics:
enabled: false
- path: ""
- port: ""
+ path: /metrics
+ port: "10000"
basic_auth:
enabled: false
username: ""
password: ""
healthy:
enabled: false
- path: ""
- port: ""
+ path: /healthy
+ port: "10000"
profiler:
enabled: false
- path: ""
- port: ""
- write_timeout: 0
+ path: /debug/pprof
+ port: "10000"
+ write_timeout: 60
+data_dir: ./data
server:
- port: 8080
- host: 0.0.0.0
- base_host: localhost:8080
- frontend:
- base_url: http://localhost:8080/api/v1
- socket_url: ws://localhost:8080/ws
+ addr: :8080
auth:
- enabled: false
- users: []
-monitoring:
- global:
- default_interval: 60s
- default_timeout: 10s
- default_retries: 5
-database:
- path: ./data/db.sqlite
-notifications:
- enabled: false
- urls: []
+ access_token_secret_key: ""
+ refresh_token_secret_key: ""
timezone: UTC
+updater:
+ is_enabled: false
+ command: ""
+kv_db_engine: inmemory
diff --git a/docker-compose.local.yml b/docker-compose.local.yml
index 7bfb380..5c3728d 100644
--- a/docker-compose.local.yml
+++ b/docker-compose.local.yml
@@ -1,5 +1,6 @@
services:
sentinel:
+ container_name: sentinel
build:
context: .
dockerfile: Dockerfile
@@ -7,6 +8,7 @@ services:
VERSION: local-dev
COMMIT_HASH: local
BUILD_DATE: local
+ command: ["hub", "start", "--config", "./config.yaml"]
ports:
- "8080:8080"
volumes:
diff --git a/docker-compose.yml b/docker-compose.yml
index 3349c6c..493c0d0 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,6 +1,8 @@
services:
sentinel:
+ container_name: sentinel
image: sxwebdev/sentinel:latest
+ command: ["hub", "start", "--config", "./config.yaml"]
ports:
- "8080:8080"
volumes:
diff --git a/docs/docsv1/docs.go b/docs/docsv1/docs.go
deleted file mode 100644
index 9fa6fa5..0000000
--- a/docs/docsv1/docs.go
+++ /dev/null
@@ -1,1412 +0,0 @@
-// Package docsv1 Code generated by swaggo/swag. DO NOT EDIT
-package docsv1
-
-import "github.com/swaggo/swag"
-
-const docTemplate = `{
- "schemes": {{ marshal .Schemes }},
- "swagger": "2.0",
- "info": {
- "description": "{{escape .Description}}",
- "title": "{{.Title}}",
- "termsOfService": "http://swagger.io/terms/",
- "contact": {
- "name": "API Support",
- "url": "http://www.swagger.io/support",
- "email": "support@swagger.io"
- },
- "license": {
- "name": "Apache 2.0",
- "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
- },
- "version": "{{.Version}}"
- },
- "host": "{{.Host}}",
- "basePath": "{{.BasePath}}",
- "paths": {
- "/dashboard/stats": {
- "get": {
- "description": "Returns statistics for the dashboard",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "dashboard"
- ],
- "summary": "Get dashboard statistics",
- "responses": {
- "200": {
- "description": "Dashboard statistics",
- "schema": {
- "$ref": "#/definitions/web.DashboardStats"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/incidents": {
- "get": {
- "description": "Returns a list of recent incidents across all services",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "incidents"
- ],
- "summary": "Get recent incidents",
- "parameters": [
- {
- "type": "string",
- "description": "Filter by service ID or incident ID",
- "name": "search",
- "in": "query"
- },
- {
- "type": "boolean",
- "description": "Filter by resolved status",
- "name": "resolved",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Page number (default 1)",
- "name": "page",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Number of items per page (default 100)",
- "name": "page_size",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "List of incidents",
- "schema": {
- "$ref": "#/definitions/dbutils.FindResponseWithCount-storage_Incident"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/incidents/stats": {
- "get": {
- "description": "Returns the stats of incidents by date range",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "incidents"
- ],
- "summary": "Get incidents stats by date range",
- "parameters": [
- {
- "type": "string",
- "description": "Start time (RFC3339 format)",
- "name": "start_time",
- "in": "query",
- "required": true
- },
- {
- "type": "string",
- "description": "End time (RFC3339 format)",
- "name": "end_time",
- "in": "query",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Incidents stats by date range",
- "schema": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/web.getIncidentsStatsItem"
- }
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/server/health": {
- "get": {
- "description": "Checks the health of the server",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "server"
- ],
- "summary": "Health check",
- "responses": {
- "200": {
- "description": "Health check successful",
- "schema": {
- "$ref": "#/definitions/web.healthCheckResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/server/info": {
- "get": {
- "description": "Returns basic information about the server",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "server"
- ],
- "summary": "Get server info",
- "responses": {
- "200": {
- "description": "Server information",
- "schema": {
- "$ref": "#/definitions/web.ServerInfoResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/server/upgrade": {
- "get": {
- "description": "Triggers a manual upgrade of the server",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "server"
- ],
- "summary": "Manual upgrade",
- "responses": {
- "200": {
- "description": "Upgrade initiated successfully",
- "schema": {
- "$ref": "#/definitions/web.SuccessResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services": {
- "get": {
- "description": "Returns a list of all services with their current states",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "services"
- ],
- "summary": "Get all services",
- "parameters": [
- {
- "type": "string",
- "description": "Filter by service name",
- "name": "name",
- "in": "query"
- },
- {
- "type": "array",
- "items": {
- "type": "string"
- },
- "collectionFormat": "csv",
- "description": "Filter by service tags",
- "name": "tags",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by service status",
- "name": "status",
- "in": "query"
- },
- {
- "type": "boolean",
- "description": "Filter by enabled status",
- "name": "is_enabled",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by protocol",
- "name": "protocol",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Order by field",
- "name": "order_by",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Page number (for pagination)",
- "name": "page",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Number of items per page (default 20)",
- "name": "page_size",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "List of services with states",
- "schema": {
- "$ref": "#/definitions/dbutils.FindResponseWithCount-web_ServiceDTO"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- },
- "post": {
- "description": "Creates a new service for monitoring",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "services"
- ],
- "summary": "Create new service",
- "parameters": [
- {
- "description": "Service configuration",
- "name": "service",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/web.CreateUpdateServiceRequest"
- }
- }
- ],
- "responses": {
- "201": {
- "description": "Service created",
- "schema": {
- "$ref": "#/definitions/web.ServiceDTO"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services/{id}": {
- "get": {
- "description": "Returns detailed information about a specific service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "services"
- ],
- "summary": "Get service details",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Service details with state",
- "schema": {
- "$ref": "#/definitions/web.ServiceDTO"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "404": {
- "description": "Service not found",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- },
- "put": {
- "description": "Updates an existing service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "services"
- ],
- "summary": "Update service",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- },
- {
- "description": "New service configuration",
- "name": "service",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/web.CreateUpdateServiceRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Service updated",
- "schema": {
- "$ref": "#/definitions/web.ServiceDTO"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "404": {
- "description": "Service not found",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- },
- "delete": {
- "description": "Deletes a service from the monitoring system",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "services"
- ],
- "summary": "Delete service",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "204": {
- "description": "Service deleted"
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services/{id}/check": {
- "post": {
- "description": "Triggers a manual check of service status",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "services"
- ],
- "summary": "Trigger service check",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Check triggered successfully",
- "schema": {
- "$ref": "#/definitions/web.SuccessResponse"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "404": {
- "description": "Service not found",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services/{id}/incidents": {
- "get": {
- "description": "Returns a list of incidents for a specific service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "incidents"
- ],
- "summary": "Get service incidents",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Filter by incident ID",
- "name": "incident_id",
- "in": "query"
- },
- {
- "type": "boolean",
- "description": "Filter by resolved status",
- "name": "resolved",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Page number (for pagination)",
- "name": "page",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Number of items per page (default 20)",
- "name": "page_size",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "List of incidents",
- "schema": {
- "$ref": "#/definitions/dbutils.FindResponseWithCount-storage_Incident"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services/{id}/incidents/{incidentId}": {
- "delete": {
- "description": "Deletes a specific incident for a service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "incidents"
- ],
- "summary": "Delete incident",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Incident ID",
- "name": "incidentId",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "204": {
- "description": "Incident deleted"
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "404": {
- "description": "Incident not found",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services/{id}/resolve": {
- "post": {
- "description": "Forcefully resolves all active incidents for a service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "incidents"
- ],
- "summary": "Resolve service incidents",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Incidents resolved successfully",
- "schema": {
- "$ref": "#/definitions/web.SuccessResponse"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services/{id}/stats": {
- "get": {
- "description": "Returns service statistics for the specified period",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "statistics"
- ],
- "summary": "Get service statistics",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- },
- {
- "type": "integer",
- "description": "Number of days (default 30)",
- "name": "days",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "Service statistics",
- "schema": {
- "$ref": "#/definitions/storage.ServiceStats"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/tags": {
- "get": {
- "description": "Retrieves all unique tags used across services",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "tags"
- ],
- "summary": "Get all tags",
- "responses": {
- "200": {
- "description": "List of unique tags",
- "schema": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/tags/count": {
- "get": {
- "description": "Retrieves all unique tags along with their usage count across services",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "tags"
- ],
- "summary": "Get all tags with usage count",
- "responses": {
- "200": {
- "description": "Map of tags with their usage count",
- "schema": {
- "type": "object",
- "additionalProperties": {
- "type": "integer"
- }
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- }
- },
- "definitions": {
- "dbutils.FindResponseWithCount-storage_Incident": {
- "type": "object",
- "properties": {
- "count": {
- "type": "integer"
- },
- "items": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/storage.Incident"
- }
- }
- }
- },
- "dbutils.FindResponseWithCount-web_ServiceDTO": {
- "type": "object",
- "properties": {
- "count": {
- "type": "integer"
- },
- "items": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/web.ServiceDTO"
- }
- }
- }
- },
- "monitors.Config": {
- "type": "object",
- "properties": {
- "grpc": {
- "$ref": "#/definitions/monitors.GRPCConfig"
- },
- "http": {
- "$ref": "#/definitions/monitors.HTTPConfig"
- },
- "tcp": {
- "$ref": "#/definitions/monitors.TCPConfig"
- }
- }
- },
- "monitors.EndpointConfig": {
- "type": "object",
- "required": [
- "expected_status",
- "method",
- "name",
- "url"
- ],
- "properties": {
- "body": {
- "type": "string"
- },
- "expected_status": {
- "type": "integer",
- "maximum": 599,
- "minimum": 100
- },
- "headers": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "json_path": {
- "description": "Path to extract value from JSON response",
- "type": "string"
- },
- "method": {
- "type": "string",
- "enum": [
- "GET",
- "POST",
- "PUT",
- "DELETE",
- "HEAD",
- "OPTIONS"
- ]
- },
- "name": {
- "type": "string"
- },
- "password": {
- "description": "Basic Auth password",
- "type": "string"
- },
- "url": {
- "type": "string"
- },
- "username": {
- "description": "Basic Auth username",
- "type": "string"
- }
- }
- },
- "monitors.GRPCConfig": {
- "type": "object",
- "required": [
- "check_type",
- "endpoint"
- ],
- "properties": {
- "check_type": {
- "type": "string",
- "enum": [
- "health",
- "reflection",
- "connectivity"
- ]
- },
- "endpoint": {
- "type": "string"
- },
- "insecure_tls": {
- "type": "boolean"
- },
- "service_name": {
- "type": "string"
- },
- "tls": {
- "type": "boolean"
- }
- }
- },
- "monitors.HTTPConfig": {
- "type": "object",
- "required": [
- "endpoints"
- ],
- "properties": {
- "condition": {
- "type": "string"
- },
- "endpoints": {
- "type": "array",
- "minItems": 1,
- "items": {
- "$ref": "#/definitions/monitors.EndpointConfig"
- }
- },
- "timeout": {
- "type": "integer",
- "example": 30000
- }
- }
- },
- "monitors.TCPConfig": {
- "type": "object",
- "required": [
- "endpoint"
- ],
- "properties": {
- "endpoint": {
- "type": "string"
- },
- "expect_data": {
- "type": "string"
- },
- "send_data": {
- "type": "string"
- }
- }
- },
- "storage.Incident": {
- "type": "object",
- "properties": {
- "duration": {
- "type": "integer"
- },
- "end_time": {
- "type": "string"
- },
- "error": {
- "type": "string"
- },
- "id": {
- "type": "string"
- },
- "resolved": {
- "type": "boolean"
- },
- "service_id": {
- "type": "string"
- },
- "start_time": {
- "type": "string"
- }
- }
- },
- "storage.ServiceProtocolType": {
- "type": "string",
- "enum": [
- "http",
- "tcp",
- "grpc"
- ],
- "x-enum-varnames": [
- "ServiceProtocolTypeHTTP",
- "ServiceProtocolTypeTCP",
- "ServiceProtocolTypeGRPC"
- ]
- },
- "storage.ServiceStats": {
- "type": "object",
- "properties": {
- "avg_response_time": {
- "type": "integer"
- },
- "period": {
- "type": "integer"
- },
- "service_id": {
- "type": "string"
- },
- "total_downtime": {
- "type": "integer"
- },
- "total_incidents": {
- "type": "integer"
- },
- "uptime_percentage": {
- "type": "number"
- }
- }
- },
- "storage.ServiceStatus": {
- "type": "string",
- "enum": [
- "unknown",
- "up",
- "down",
- "maintenance"
- ],
- "x-enum-varnames": [
- "StatusUnknown",
- "StatusUp",
- "StatusDown",
- "StatusMaintenance"
- ]
- },
- "web.AvailableUpdate": {
- "type": "object",
- "properties": {
- "description": {
- "type": "string"
- },
- "is_available_manual": {
- "type": "boolean"
- },
- "tag_name": {
- "type": "string"
- },
- "url": {
- "type": "string"
- }
- }
- },
- "web.CreateUpdateServiceRequest": {
- "type": "object",
- "properties": {
- "config": {
- "$ref": "#/definitions/monitors.Config"
- },
- "interval": {
- "type": "integer",
- "example": 60000
- },
- "is_enabled": {
- "type": "boolean",
- "example": true
- },
- "name": {
- "type": "string",
- "example": "Web Server"
- },
- "protocol": {
- "allOf": [
- {
- "$ref": "#/definitions/storage.ServiceProtocolType"
- }
- ],
- "example": "http"
- },
- "retries": {
- "type": "integer",
- "example": 5
- },
- "tags": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "example": [
- "web",
- "production"
- ]
- },
- "timeout": {
- "type": "integer",
- "example": 10000
- }
- }
- },
- "web.DashboardStats": {
- "description": "Dashboard statistics",
- "type": "object",
- "properties": {
- "active_incidents": {
- "type": "integer",
- "example": 2
- },
- "avg_response_time": {
- "type": "integer",
- "example": 150
- },
- "checks_per_minute": {
- "type": "integer",
- "example": 60
- },
- "last_check_time": {
- "type": "string"
- },
- "protocols": {
- "type": "object",
- "additionalProperties": {
- "type": "integer"
- }
- },
- "services_down": {
- "type": "integer",
- "example": 1
- },
- "services_unknown": {
- "type": "integer",
- "example": 1
- },
- "services_up": {
- "type": "integer",
- "example": 8
- },
- "total_checks": {
- "type": "integer",
- "example": 1000
- },
- "total_services": {
- "type": "integer",
- "example": 10
- },
- "uptime_percentage": {
- "type": "number",
- "example": 95.5
- }
- }
- },
- "web.ErrorResponse": {
- "description": "Error response",
- "type": "object",
- "properties": {
- "error": {
- "type": "string",
- "example": "Error description"
- }
- }
- },
- "web.ServerInfoResponse": {
- "type": "object",
- "properties": {
- "arch": {
- "type": "string",
- "example": "amd64"
- },
- "available_update": {
- "$ref": "#/definitions/web.AvailableUpdate"
- },
- "build_date": {
- "type": "string",
- "example": "2023-10-01T12:00:00Z"
- },
- "commit_hash": {
- "type": "string",
- "example": "abc123def456"
- },
- "go_version": {
- "type": "string",
- "example": "go1.24.4"
- },
- "os": {
- "type": "string",
- "example": "linux"
- },
- "sqlite_version": {
- "type": "string",
- "example": "3.50.1"
- },
- "version": {
- "type": "string",
- "example": "1.0.0"
- }
- }
- },
- "web.ServiceDTO": {
- "type": "object",
- "properties": {
- "active_incidents": {
- "type": "integer",
- "example": 2
- },
- "config": {
- "$ref": "#/definitions/monitors.Config"
- },
- "consecutive_fails": {
- "type": "integer",
- "example": 1
- },
- "consecutive_success": {
- "type": "integer",
- "example": 5
- },
- "id": {
- "type": "string",
- "example": "service-1"
- },
- "interval": {
- "type": "integer",
- "example": 60000
- },
- "is_enabled": {
- "type": "boolean",
- "example": true
- },
- "last_check": {
- "type": "string",
- "example": "2023-10-01T12:00:00Z"
- },
- "last_error": {
- "type": "string",
- "example": "Connection timeout"
- },
- "name": {
- "type": "string",
- "example": "Web Server"
- },
- "next_check": {
- "type": "string",
- "example": "2023-10-01T12:05:00Z"
- },
- "protocol": {
- "allOf": [
- {
- "$ref": "#/definitions/storage.ServiceProtocolType"
- }
- ],
- "example": "http"
- },
- "response_time": {
- "type": "integer",
- "example": 150000000
- },
- "retries": {
- "type": "integer",
- "example": 5
- },
- "status": {
- "allOf": [
- {
- "$ref": "#/definitions/storage.ServiceStatus"
- }
- ],
- "example": "up / down / unknown"
- },
- "tags": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "example": [
- "web",
- "production"
- ]
- },
- "timeout": {
- "type": "integer",
- "example": 10000
- },
- "total_checks": {
- "type": "integer",
- "example": 100
- },
- "total_incidents": {
- "type": "integer",
- "example": 10
- }
- }
- },
- "web.SuccessResponse": {
- "description": "Successful response",
- "type": "object",
- "properties": {
- "message": {
- "type": "string",
- "example": "Operation completed successfully"
- }
- }
- },
- "web.getIncidentsStatsItem": {
- "type": "object",
- "properties": {
- "avg_duration": {
- "type": "integer"
- },
- "avg_duration_human": {
- "type": "string"
- },
- "count": {
- "type": "integer"
- },
- "date": {
- "type": "string"
- },
- "total_duration": {
- "type": "integer"
- },
- "total_duration_human": {
- "type": "string"
- }
- }
- },
- "web.healthCheckResponse": {
- "type": "object",
- "properties": {
- "status": {
- "type": "string",
- "example": "healthy"
- }
- }
- }
- },
- "securityDefinitions": {
- "BasicAuth": {
- "type": "basic"
- }
- }
-}`
-
-// SwaggerInfo holds exported Swagger Info so clients can modify it
-var SwaggerInfo = &swag.Spec{
- Version: "1.0",
- Host: "",
- BasePath: "/api/v1",
- Schemes: []string{},
- Title: "Sentinel Monitoring API",
- Description: "API for service monitoring and incident management",
- InfoInstanceName: "swagger",
- SwaggerTemplate: docTemplate,
- LeftDelim: "{{",
- RightDelim: "}}",
-}
-
-func init() {
- swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
-}
diff --git a/docs/docsv1/swagger.json b/docs/docsv1/swagger.json
deleted file mode 100644
index 382fb79..0000000
--- a/docs/docsv1/swagger.json
+++ /dev/null
@@ -1,1387 +0,0 @@
-{
- "swagger": "2.0",
- "info": {
- "description": "API for service monitoring and incident management",
- "title": "Sentinel Monitoring API",
- "termsOfService": "http://swagger.io/terms/",
- "contact": {
- "name": "API Support",
- "url": "http://www.swagger.io/support",
- "email": "support@swagger.io"
- },
- "license": {
- "name": "Apache 2.0",
- "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
- },
- "version": "1.0"
- },
- "basePath": "/api/v1",
- "paths": {
- "/dashboard/stats": {
- "get": {
- "description": "Returns statistics for the dashboard",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "dashboard"
- ],
- "summary": "Get dashboard statistics",
- "responses": {
- "200": {
- "description": "Dashboard statistics",
- "schema": {
- "$ref": "#/definitions/web.DashboardStats"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/incidents": {
- "get": {
- "description": "Returns a list of recent incidents across all services",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "incidents"
- ],
- "summary": "Get recent incidents",
- "parameters": [
- {
- "type": "string",
- "description": "Filter by service ID or incident ID",
- "name": "search",
- "in": "query"
- },
- {
- "type": "boolean",
- "description": "Filter by resolved status",
- "name": "resolved",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Page number (default 1)",
- "name": "page",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Number of items per page (default 100)",
- "name": "page_size",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "List of incidents",
- "schema": {
- "$ref": "#/definitions/dbutils.FindResponseWithCount-storage_Incident"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/incidents/stats": {
- "get": {
- "description": "Returns the stats of incidents by date range",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "incidents"
- ],
- "summary": "Get incidents stats by date range",
- "parameters": [
- {
- "type": "string",
- "description": "Start time (RFC3339 format)",
- "name": "start_time",
- "in": "query",
- "required": true
- },
- {
- "type": "string",
- "description": "End time (RFC3339 format)",
- "name": "end_time",
- "in": "query",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Incidents stats by date range",
- "schema": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/web.getIncidentsStatsItem"
- }
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/server/health": {
- "get": {
- "description": "Checks the health of the server",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "server"
- ],
- "summary": "Health check",
- "responses": {
- "200": {
- "description": "Health check successful",
- "schema": {
- "$ref": "#/definitions/web.healthCheckResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/server/info": {
- "get": {
- "description": "Returns basic information about the server",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "server"
- ],
- "summary": "Get server info",
- "responses": {
- "200": {
- "description": "Server information",
- "schema": {
- "$ref": "#/definitions/web.ServerInfoResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/server/upgrade": {
- "get": {
- "description": "Triggers a manual upgrade of the server",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "server"
- ],
- "summary": "Manual upgrade",
- "responses": {
- "200": {
- "description": "Upgrade initiated successfully",
- "schema": {
- "$ref": "#/definitions/web.SuccessResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services": {
- "get": {
- "description": "Returns a list of all services with their current states",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "services"
- ],
- "summary": "Get all services",
- "parameters": [
- {
- "type": "string",
- "description": "Filter by service name",
- "name": "name",
- "in": "query"
- },
- {
- "type": "array",
- "items": {
- "type": "string"
- },
- "collectionFormat": "csv",
- "description": "Filter by service tags",
- "name": "tags",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by service status",
- "name": "status",
- "in": "query"
- },
- {
- "type": "boolean",
- "description": "Filter by enabled status",
- "name": "is_enabled",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Filter by protocol",
- "name": "protocol",
- "in": "query"
- },
- {
- "type": "string",
- "description": "Order by field",
- "name": "order_by",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Page number (for pagination)",
- "name": "page",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Number of items per page (default 20)",
- "name": "page_size",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "List of services with states",
- "schema": {
- "$ref": "#/definitions/dbutils.FindResponseWithCount-web_ServiceDTO"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- },
- "post": {
- "description": "Creates a new service for monitoring",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "services"
- ],
- "summary": "Create new service",
- "parameters": [
- {
- "description": "Service configuration",
- "name": "service",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/web.CreateUpdateServiceRequest"
- }
- }
- ],
- "responses": {
- "201": {
- "description": "Service created",
- "schema": {
- "$ref": "#/definitions/web.ServiceDTO"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services/{id}": {
- "get": {
- "description": "Returns detailed information about a specific service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "services"
- ],
- "summary": "Get service details",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Service details with state",
- "schema": {
- "$ref": "#/definitions/web.ServiceDTO"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "404": {
- "description": "Service not found",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- },
- "put": {
- "description": "Updates an existing service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "services"
- ],
- "summary": "Update service",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- },
- {
- "description": "New service configuration",
- "name": "service",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/web.CreateUpdateServiceRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Service updated",
- "schema": {
- "$ref": "#/definitions/web.ServiceDTO"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "404": {
- "description": "Service not found",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- },
- "delete": {
- "description": "Deletes a service from the monitoring system",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "services"
- ],
- "summary": "Delete service",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "204": {
- "description": "Service deleted"
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services/{id}/check": {
- "post": {
- "description": "Triggers a manual check of service status",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "services"
- ],
- "summary": "Trigger service check",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Check triggered successfully",
- "schema": {
- "$ref": "#/definitions/web.SuccessResponse"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "404": {
- "description": "Service not found",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services/{id}/incidents": {
- "get": {
- "description": "Returns a list of incidents for a specific service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "incidents"
- ],
- "summary": "Get service incidents",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Filter by incident ID",
- "name": "incident_id",
- "in": "query"
- },
- {
- "type": "boolean",
- "description": "Filter by resolved status",
- "name": "resolved",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Page number (for pagination)",
- "name": "page",
- "in": "query"
- },
- {
- "type": "integer",
- "description": "Number of items per page (default 20)",
- "name": "page_size",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "List of incidents",
- "schema": {
- "$ref": "#/definitions/dbutils.FindResponseWithCount-storage_Incident"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services/{id}/incidents/{incidentId}": {
- "delete": {
- "description": "Deletes a specific incident for a service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "incidents"
- ],
- "summary": "Delete incident",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- },
- {
- "type": "string",
- "description": "Incident ID",
- "name": "incidentId",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "204": {
- "description": "Incident deleted"
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "404": {
- "description": "Incident not found",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services/{id}/resolve": {
- "post": {
- "description": "Forcefully resolves all active incidents for a service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "incidents"
- ],
- "summary": "Resolve service incidents",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- }
- ],
- "responses": {
- "200": {
- "description": "Incidents resolved successfully",
- "schema": {
- "$ref": "#/definitions/web.SuccessResponse"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/services/{id}/stats": {
- "get": {
- "description": "Returns service statistics for the specified period",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "statistics"
- ],
- "summary": "Get service statistics",
- "parameters": [
- {
- "type": "string",
- "description": "Service ID",
- "name": "id",
- "in": "path",
- "required": true
- },
- {
- "type": "integer",
- "description": "Number of days (default 30)",
- "name": "days",
- "in": "query"
- }
- ],
- "responses": {
- "200": {
- "description": "Service statistics",
- "schema": {
- "$ref": "#/definitions/storage.ServiceStats"
- }
- },
- "400": {
- "description": "Bad request",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/tags": {
- "get": {
- "description": "Retrieves all unique tags used across services",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "tags"
- ],
- "summary": "Get all tags",
- "responses": {
- "200": {
- "description": "List of unique tags",
- "schema": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- },
- "/tags/count": {
- "get": {
- "description": "Retrieves all unique tags along with their usage count across services",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "tags"
- ],
- "summary": "Get all tags with usage count",
- "responses": {
- "200": {
- "description": "Map of tags with their usage count",
- "schema": {
- "type": "object",
- "additionalProperties": {
- "type": "integer"
- }
- }
- },
- "500": {
- "description": "Internal server error",
- "schema": {
- "$ref": "#/definitions/web.ErrorResponse"
- }
- }
- }
- }
- }
- },
- "definitions": {
- "dbutils.FindResponseWithCount-storage_Incident": {
- "type": "object",
- "properties": {
- "count": {
- "type": "integer"
- },
- "items": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/storage.Incident"
- }
- }
- }
- },
- "dbutils.FindResponseWithCount-web_ServiceDTO": {
- "type": "object",
- "properties": {
- "count": {
- "type": "integer"
- },
- "items": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/web.ServiceDTO"
- }
- }
- }
- },
- "monitors.Config": {
- "type": "object",
- "properties": {
- "grpc": {
- "$ref": "#/definitions/monitors.GRPCConfig"
- },
- "http": {
- "$ref": "#/definitions/monitors.HTTPConfig"
- },
- "tcp": {
- "$ref": "#/definitions/monitors.TCPConfig"
- }
- }
- },
- "monitors.EndpointConfig": {
- "type": "object",
- "required": [
- "expected_status",
- "method",
- "name",
- "url"
- ],
- "properties": {
- "body": {
- "type": "string"
- },
- "expected_status": {
- "type": "integer",
- "maximum": 599,
- "minimum": 100
- },
- "headers": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "json_path": {
- "description": "Path to extract value from JSON response",
- "type": "string"
- },
- "method": {
- "type": "string",
- "enum": [
- "GET",
- "POST",
- "PUT",
- "DELETE",
- "HEAD",
- "OPTIONS"
- ]
- },
- "name": {
- "type": "string"
- },
- "password": {
- "description": "Basic Auth password",
- "type": "string"
- },
- "url": {
- "type": "string"
- },
- "username": {
- "description": "Basic Auth username",
- "type": "string"
- }
- }
- },
- "monitors.GRPCConfig": {
- "type": "object",
- "required": [
- "check_type",
- "endpoint"
- ],
- "properties": {
- "check_type": {
- "type": "string",
- "enum": [
- "health",
- "reflection",
- "connectivity"
- ]
- },
- "endpoint": {
- "type": "string"
- },
- "insecure_tls": {
- "type": "boolean"
- },
- "service_name": {
- "type": "string"
- },
- "tls": {
- "type": "boolean"
- }
- }
- },
- "monitors.HTTPConfig": {
- "type": "object",
- "required": [
- "endpoints"
- ],
- "properties": {
- "condition": {
- "type": "string"
- },
- "endpoints": {
- "type": "array",
- "minItems": 1,
- "items": {
- "$ref": "#/definitions/monitors.EndpointConfig"
- }
- },
- "timeout": {
- "type": "integer",
- "example": 30000
- }
- }
- },
- "monitors.TCPConfig": {
- "type": "object",
- "required": [
- "endpoint"
- ],
- "properties": {
- "endpoint": {
- "type": "string"
- },
- "expect_data": {
- "type": "string"
- },
- "send_data": {
- "type": "string"
- }
- }
- },
- "storage.Incident": {
- "type": "object",
- "properties": {
- "duration": {
- "type": "integer"
- },
- "end_time": {
- "type": "string"
- },
- "error": {
- "type": "string"
- },
- "id": {
- "type": "string"
- },
- "resolved": {
- "type": "boolean"
- },
- "service_id": {
- "type": "string"
- },
- "start_time": {
- "type": "string"
- }
- }
- },
- "storage.ServiceProtocolType": {
- "type": "string",
- "enum": [
- "http",
- "tcp",
- "grpc"
- ],
- "x-enum-varnames": [
- "ServiceProtocolTypeHTTP",
- "ServiceProtocolTypeTCP",
- "ServiceProtocolTypeGRPC"
- ]
- },
- "storage.ServiceStats": {
- "type": "object",
- "properties": {
- "avg_response_time": {
- "type": "integer"
- },
- "period": {
- "type": "integer"
- },
- "service_id": {
- "type": "string"
- },
- "total_downtime": {
- "type": "integer"
- },
- "total_incidents": {
- "type": "integer"
- },
- "uptime_percentage": {
- "type": "number"
- }
- }
- },
- "storage.ServiceStatus": {
- "type": "string",
- "enum": [
- "unknown",
- "up",
- "down",
- "maintenance"
- ],
- "x-enum-varnames": [
- "StatusUnknown",
- "StatusUp",
- "StatusDown",
- "StatusMaintenance"
- ]
- },
- "web.AvailableUpdate": {
- "type": "object",
- "properties": {
- "description": {
- "type": "string"
- },
- "is_available_manual": {
- "type": "boolean"
- },
- "tag_name": {
- "type": "string"
- },
- "url": {
- "type": "string"
- }
- }
- },
- "web.CreateUpdateServiceRequest": {
- "type": "object",
- "properties": {
- "config": {
- "$ref": "#/definitions/monitors.Config"
- },
- "interval": {
- "type": "integer",
- "example": 60000
- },
- "is_enabled": {
- "type": "boolean",
- "example": true
- },
- "name": {
- "type": "string",
- "example": "Web Server"
- },
- "protocol": {
- "allOf": [
- {
- "$ref": "#/definitions/storage.ServiceProtocolType"
- }
- ],
- "example": "http"
- },
- "retries": {
- "type": "integer",
- "example": 5
- },
- "tags": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "example": [
- "web",
- "production"
- ]
- },
- "timeout": {
- "type": "integer",
- "example": 10000
- }
- }
- },
- "web.DashboardStats": {
- "description": "Dashboard statistics",
- "type": "object",
- "properties": {
- "active_incidents": {
- "type": "integer",
- "example": 2
- },
- "avg_response_time": {
- "type": "integer",
- "example": 150
- },
- "checks_per_minute": {
- "type": "integer",
- "example": 60
- },
- "last_check_time": {
- "type": "string"
- },
- "protocols": {
- "type": "object",
- "additionalProperties": {
- "type": "integer"
- }
- },
- "services_down": {
- "type": "integer",
- "example": 1
- },
- "services_unknown": {
- "type": "integer",
- "example": 1
- },
- "services_up": {
- "type": "integer",
- "example": 8
- },
- "total_checks": {
- "type": "integer",
- "example": 1000
- },
- "total_services": {
- "type": "integer",
- "example": 10
- },
- "uptime_percentage": {
- "type": "number",
- "example": 95.5
- }
- }
- },
- "web.ErrorResponse": {
- "description": "Error response",
- "type": "object",
- "properties": {
- "error": {
- "type": "string",
- "example": "Error description"
- }
- }
- },
- "web.ServerInfoResponse": {
- "type": "object",
- "properties": {
- "arch": {
- "type": "string",
- "example": "amd64"
- },
- "available_update": {
- "$ref": "#/definitions/web.AvailableUpdate"
- },
- "build_date": {
- "type": "string",
- "example": "2023-10-01T12:00:00Z"
- },
- "commit_hash": {
- "type": "string",
- "example": "abc123def456"
- },
- "go_version": {
- "type": "string",
- "example": "go1.24.4"
- },
- "os": {
- "type": "string",
- "example": "linux"
- },
- "sqlite_version": {
- "type": "string",
- "example": "3.50.1"
- },
- "version": {
- "type": "string",
- "example": "1.0.0"
- }
- }
- },
- "web.ServiceDTO": {
- "type": "object",
- "properties": {
- "active_incidents": {
- "type": "integer",
- "example": 2
- },
- "config": {
- "$ref": "#/definitions/monitors.Config"
- },
- "consecutive_fails": {
- "type": "integer",
- "example": 1
- },
- "consecutive_success": {
- "type": "integer",
- "example": 5
- },
- "id": {
- "type": "string",
- "example": "service-1"
- },
- "interval": {
- "type": "integer",
- "example": 60000
- },
- "is_enabled": {
- "type": "boolean",
- "example": true
- },
- "last_check": {
- "type": "string",
- "example": "2023-10-01T12:00:00Z"
- },
- "last_error": {
- "type": "string",
- "example": "Connection timeout"
- },
- "name": {
- "type": "string",
- "example": "Web Server"
- },
- "next_check": {
- "type": "string",
- "example": "2023-10-01T12:05:00Z"
- },
- "protocol": {
- "allOf": [
- {
- "$ref": "#/definitions/storage.ServiceProtocolType"
- }
- ],
- "example": "http"
- },
- "response_time": {
- "type": "integer",
- "example": 150000000
- },
- "retries": {
- "type": "integer",
- "example": 5
- },
- "status": {
- "allOf": [
- {
- "$ref": "#/definitions/storage.ServiceStatus"
- }
- ],
- "example": "up / down / unknown"
- },
- "tags": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "example": [
- "web",
- "production"
- ]
- },
- "timeout": {
- "type": "integer",
- "example": 10000
- },
- "total_checks": {
- "type": "integer",
- "example": 100
- },
- "total_incidents": {
- "type": "integer",
- "example": 10
- }
- }
- },
- "web.SuccessResponse": {
- "description": "Successful response",
- "type": "object",
- "properties": {
- "message": {
- "type": "string",
- "example": "Operation completed successfully"
- }
- }
- },
- "web.getIncidentsStatsItem": {
- "type": "object",
- "properties": {
- "avg_duration": {
- "type": "integer"
- },
- "avg_duration_human": {
- "type": "string"
- },
- "count": {
- "type": "integer"
- },
- "date": {
- "type": "string"
- },
- "total_duration": {
- "type": "integer"
- },
- "total_duration_human": {
- "type": "string"
- }
- }
- },
- "web.healthCheckResponse": {
- "type": "object",
- "properties": {
- "status": {
- "type": "string",
- "example": "healthy"
- }
- }
- }
- },
- "securityDefinitions": {
- "BasicAuth": {
- "type": "basic"
- }
- }
-}
\ No newline at end of file
diff --git a/docs/docsv1/swagger.yaml b/docs/docsv1/swagger.yaml
deleted file mode 100644
index 2ad658e..0000000
--- a/docs/docsv1/swagger.yaml
+++ /dev/null
@@ -1,934 +0,0 @@
-basePath: /api/v1
-definitions:
- dbutils.FindResponseWithCount-storage_Incident:
- properties:
- count:
- type: integer
- items:
- items:
- $ref: '#/definitions/storage.Incident'
- type: array
- type: object
- dbutils.FindResponseWithCount-web_ServiceDTO:
- properties:
- count:
- type: integer
- items:
- items:
- $ref: '#/definitions/web.ServiceDTO'
- type: array
- type: object
- monitors.Config:
- properties:
- grpc:
- $ref: '#/definitions/monitors.GRPCConfig'
- http:
- $ref: '#/definitions/monitors.HTTPConfig'
- tcp:
- $ref: '#/definitions/monitors.TCPConfig'
- type: object
- monitors.EndpointConfig:
- properties:
- body:
- type: string
- expected_status:
- maximum: 599
- minimum: 100
- type: integer
- headers:
- additionalProperties:
- type: string
- type: object
- json_path:
- description: Path to extract value from JSON response
- type: string
- method:
- enum:
- - GET
- - POST
- - PUT
- - DELETE
- - HEAD
- - OPTIONS
- type: string
- name:
- type: string
- password:
- description: Basic Auth password
- type: string
- url:
- type: string
- username:
- description: Basic Auth username
- type: string
- required:
- - expected_status
- - method
- - name
- - url
- type: object
- monitors.GRPCConfig:
- properties:
- check_type:
- enum:
- - health
- - reflection
- - connectivity
- type: string
- endpoint:
- type: string
- insecure_tls:
- type: boolean
- service_name:
- type: string
- tls:
- type: boolean
- required:
- - check_type
- - endpoint
- type: object
- monitors.HTTPConfig:
- properties:
- condition:
- type: string
- endpoints:
- items:
- $ref: '#/definitions/monitors.EndpointConfig'
- minItems: 1
- type: array
- timeout:
- example: 30000
- type: integer
- required:
- - endpoints
- type: object
- monitors.TCPConfig:
- properties:
- endpoint:
- type: string
- expect_data:
- type: string
- send_data:
- type: string
- required:
- - endpoint
- type: object
- storage.Incident:
- properties:
- duration:
- type: integer
- end_time:
- type: string
- error:
- type: string
- id:
- type: string
- resolved:
- type: boolean
- service_id:
- type: string
- start_time:
- type: string
- type: object
- storage.ServiceProtocolType:
- enum:
- - http
- - tcp
- - grpc
- type: string
- x-enum-varnames:
- - ServiceProtocolTypeHTTP
- - ServiceProtocolTypeTCP
- - ServiceProtocolTypeGRPC
- storage.ServiceStats:
- properties:
- avg_response_time:
- type: integer
- period:
- type: integer
- service_id:
- type: string
- total_downtime:
- type: integer
- total_incidents:
- type: integer
- uptime_percentage:
- type: number
- type: object
- storage.ServiceStatus:
- enum:
- - unknown
- - up
- - down
- - maintenance
- type: string
- x-enum-varnames:
- - StatusUnknown
- - StatusUp
- - StatusDown
- - StatusMaintenance
- web.AvailableUpdate:
- properties:
- description:
- type: string
- is_available_manual:
- type: boolean
- tag_name:
- type: string
- url:
- type: string
- type: object
- web.CreateUpdateServiceRequest:
- properties:
- config:
- $ref: '#/definitions/monitors.Config'
- interval:
- example: 60000
- type: integer
- is_enabled:
- example: true
- type: boolean
- name:
- example: Web Server
- type: string
- protocol:
- allOf:
- - $ref: '#/definitions/storage.ServiceProtocolType'
- example: http
- retries:
- example: 5
- type: integer
- tags:
- example:
- - web
- - production
- items:
- type: string
- type: array
- timeout:
- example: 10000
- type: integer
- type: object
- web.DashboardStats:
- description: Dashboard statistics
- properties:
- active_incidents:
- example: 2
- type: integer
- avg_response_time:
- example: 150
- type: integer
- checks_per_minute:
- example: 60
- type: integer
- last_check_time:
- type: string
- protocols:
- additionalProperties:
- type: integer
- type: object
- services_down:
- example: 1
- type: integer
- services_unknown:
- example: 1
- type: integer
- services_up:
- example: 8
- type: integer
- total_checks:
- example: 1000
- type: integer
- total_services:
- example: 10
- type: integer
- uptime_percentage:
- example: 95.5
- type: number
- type: object
- web.ErrorResponse:
- description: Error response
- properties:
- error:
- example: Error description
- type: string
- type: object
- web.ServerInfoResponse:
- properties:
- arch:
- example: amd64
- type: string
- available_update:
- $ref: '#/definitions/web.AvailableUpdate'
- build_date:
- example: "2023-10-01T12:00:00Z"
- type: string
- commit_hash:
- example: abc123def456
- type: string
- go_version:
- example: go1.24.4
- type: string
- os:
- example: linux
- type: string
- sqlite_version:
- example: 3.50.1
- type: string
- version:
- example: 1.0.0
- type: string
- type: object
- web.ServiceDTO:
- properties:
- active_incidents:
- example: 2
- type: integer
- config:
- $ref: '#/definitions/monitors.Config'
- consecutive_fails:
- example: 1
- type: integer
- consecutive_success:
- example: 5
- type: integer
- id:
- example: service-1
- type: string
- interval:
- example: 60000
- type: integer
- is_enabled:
- example: true
- type: boolean
- last_check:
- example: "2023-10-01T12:00:00Z"
- type: string
- last_error:
- example: Connection timeout
- type: string
- name:
- example: Web Server
- type: string
- next_check:
- example: "2023-10-01T12:05:00Z"
- type: string
- protocol:
- allOf:
- - $ref: '#/definitions/storage.ServiceProtocolType'
- example: http
- response_time:
- example: 150000000
- type: integer
- retries:
- example: 5
- type: integer
- status:
- allOf:
- - $ref: '#/definitions/storage.ServiceStatus'
- example: up / down / unknown
- tags:
- example:
- - web
- - production
- items:
- type: string
- type: array
- timeout:
- example: 10000
- type: integer
- total_checks:
- example: 100
- type: integer
- total_incidents:
- example: 10
- type: integer
- type: object
- web.SuccessResponse:
- description: Successful response
- properties:
- message:
- example: Operation completed successfully
- type: string
- type: object
- web.getIncidentsStatsItem:
- properties:
- avg_duration:
- type: integer
- avg_duration_human:
- type: string
- count:
- type: integer
- date:
- type: string
- total_duration:
- type: integer
- total_duration_human:
- type: string
- type: object
- web.healthCheckResponse:
- properties:
- status:
- example: healthy
- type: string
- type: object
-info:
- contact:
- email: support@swagger.io
- name: API Support
- url: http://www.swagger.io/support
- description: API for service monitoring and incident management
- license:
- name: Apache 2.0
- url: http://www.apache.org/licenses/LICENSE-2.0.html
- termsOfService: http://swagger.io/terms/
- title: Sentinel Monitoring API
- version: "1.0"
-paths:
- /dashboard/stats:
- get:
- consumes:
- - application/json
- description: Returns statistics for the dashboard
- produces:
- - application/json
- responses:
- "200":
- description: Dashboard statistics
- schema:
- $ref: '#/definitions/web.DashboardStats'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Get dashboard statistics
- tags:
- - dashboard
- /incidents:
- get:
- consumes:
- - application/json
- description: Returns a list of recent incidents across all services
- parameters:
- - description: Filter by service ID or incident ID
- in: query
- name: search
- type: string
- - description: Filter by resolved status
- in: query
- name: resolved
- type: boolean
- - description: Page number (default 1)
- in: query
- name: page
- type: integer
- - description: Number of items per page (default 100)
- in: query
- name: page_size
- type: integer
- produces:
- - application/json
- responses:
- "200":
- description: List of incidents
- schema:
- $ref: '#/definitions/dbutils.FindResponseWithCount-storage_Incident'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Get recent incidents
- tags:
- - incidents
- /incidents/stats:
- get:
- consumes:
- - application/json
- description: Returns the stats of incidents by date range
- parameters:
- - description: Start time (RFC3339 format)
- in: query
- name: start_time
- required: true
- type: string
- - description: End time (RFC3339 format)
- in: query
- name: end_time
- required: true
- type: string
- produces:
- - application/json
- responses:
- "200":
- description: Incidents stats by date range
- schema:
- items:
- $ref: '#/definitions/web.getIncidentsStatsItem'
- type: array
- "400":
- description: Bad request
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Get incidents stats by date range
- tags:
- - incidents
- /server/health:
- get:
- consumes:
- - application/json
- description: Checks the health of the server
- produces:
- - application/json
- responses:
- "200":
- description: Health check successful
- schema:
- $ref: '#/definitions/web.healthCheckResponse'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Health check
- tags:
- - server
- /server/info:
- get:
- consumes:
- - application/json
- description: Returns basic information about the server
- produces:
- - application/json
- responses:
- "200":
- description: Server information
- schema:
- $ref: '#/definitions/web.ServerInfoResponse'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Get server info
- tags:
- - server
- /server/upgrade:
- get:
- consumes:
- - application/json
- description: Triggers a manual upgrade of the server
- produces:
- - application/json
- responses:
- "200":
- description: Upgrade initiated successfully
- schema:
- $ref: '#/definitions/web.SuccessResponse'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Manual upgrade
- tags:
- - server
- /services:
- get:
- consumes:
- - application/json
- description: Returns a list of all services with their current states
- parameters:
- - description: Filter by service name
- in: query
- name: name
- type: string
- - collectionFormat: csv
- description: Filter by service tags
- in: query
- items:
- type: string
- name: tags
- type: array
- - description: Filter by service status
- in: query
- name: status
- type: string
- - description: Filter by enabled status
- in: query
- name: is_enabled
- type: boolean
- - description: Filter by protocol
- in: query
- name: protocol
- type: string
- - description: Order by field
- in: query
- name: order_by
- type: string
- - description: Page number (for pagination)
- in: query
- name: page
- type: integer
- - description: Number of items per page (default 20)
- in: query
- name: page_size
- type: integer
- produces:
- - application/json
- responses:
- "200":
- description: List of services with states
- schema:
- $ref: '#/definitions/dbutils.FindResponseWithCount-web_ServiceDTO'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Get all services
- tags:
- - services
- post:
- consumes:
- - application/json
- description: Creates a new service for monitoring
- parameters:
- - description: Service configuration
- in: body
- name: service
- required: true
- schema:
- $ref: '#/definitions/web.CreateUpdateServiceRequest'
- produces:
- - application/json
- responses:
- "201":
- description: Service created
- schema:
- $ref: '#/definitions/web.ServiceDTO'
- "400":
- description: Bad request
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Create new service
- tags:
- - services
- /services/{id}:
- delete:
- consumes:
- - application/json
- description: Deletes a service from the monitoring system
- parameters:
- - description: Service ID
- in: path
- name: id
- required: true
- type: string
- produces:
- - application/json
- responses:
- "204":
- description: Service deleted
- "400":
- description: Bad request
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Delete service
- tags:
- - services
- get:
- consumes:
- - application/json
- description: Returns detailed information about a specific service
- parameters:
- - description: Service ID
- in: path
- name: id
- required: true
- type: string
- produces:
- - application/json
- responses:
- "200":
- description: Service details with state
- schema:
- $ref: '#/definitions/web.ServiceDTO'
- "400":
- description: Bad request
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "404":
- description: Service not found
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Get service details
- tags:
- - services
- put:
- consumes:
- - application/json
- description: Updates an existing service
- parameters:
- - description: Service ID
- in: path
- name: id
- required: true
- type: string
- - description: New service configuration
- in: body
- name: service
- required: true
- schema:
- $ref: '#/definitions/web.CreateUpdateServiceRequest'
- produces:
- - application/json
- responses:
- "200":
- description: Service updated
- schema:
- $ref: '#/definitions/web.ServiceDTO'
- "400":
- description: Bad request
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "404":
- description: Service not found
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Update service
- tags:
- - services
- /services/{id}/check:
- post:
- consumes:
- - application/json
- description: Triggers a manual check of service status
- parameters:
- - description: Service ID
- in: path
- name: id
- required: true
- type: string
- produces:
- - application/json
- responses:
- "200":
- description: Check triggered successfully
- schema:
- $ref: '#/definitions/web.SuccessResponse'
- "400":
- description: Bad request
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "404":
- description: Service not found
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Trigger service check
- tags:
- - services
- /services/{id}/incidents:
- get:
- consumes:
- - application/json
- description: Returns a list of incidents for a specific service
- parameters:
- - description: Service ID
- in: path
- name: id
- required: true
- type: string
- - description: Filter by incident ID
- in: query
- name: incident_id
- type: string
- - description: Filter by resolved status
- in: query
- name: resolved
- type: boolean
- - description: Page number (for pagination)
- in: query
- name: page
- type: integer
- - description: Number of items per page (default 20)
- in: query
- name: page_size
- type: integer
- produces:
- - application/json
- responses:
- "200":
- description: List of incidents
- schema:
- $ref: '#/definitions/dbutils.FindResponseWithCount-storage_Incident'
- "400":
- description: Bad request
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Get service incidents
- tags:
- - incidents
- /services/{id}/incidents/{incidentId}:
- delete:
- consumes:
- - application/json
- description: Deletes a specific incident for a service
- parameters:
- - description: Service ID
- in: path
- name: id
- required: true
- type: string
- - description: Incident ID
- in: path
- name: incidentId
- required: true
- type: string
- produces:
- - application/json
- responses:
- "204":
- description: Incident deleted
- "400":
- description: Bad request
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "404":
- description: Incident not found
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Delete incident
- tags:
- - incidents
- /services/{id}/resolve:
- post:
- consumes:
- - application/json
- description: Forcefully resolves all active incidents for a service
- parameters:
- - description: Service ID
- in: path
- name: id
- required: true
- type: string
- produces:
- - application/json
- responses:
- "200":
- description: Incidents resolved successfully
- schema:
- $ref: '#/definitions/web.SuccessResponse'
- "400":
- description: Bad request
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Resolve service incidents
- tags:
- - incidents
- /services/{id}/stats:
- get:
- consumes:
- - application/json
- description: Returns service statistics for the specified period
- parameters:
- - description: Service ID
- in: path
- name: id
- required: true
- type: string
- - description: Number of days (default 30)
- in: query
- name: days
- type: integer
- produces:
- - application/json
- responses:
- "200":
- description: Service statistics
- schema:
- $ref: '#/definitions/storage.ServiceStats'
- "400":
- description: Bad request
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Get service statistics
- tags:
- - statistics
- /tags:
- get:
- consumes:
- - application/json
- description: Retrieves all unique tags used across services
- produces:
- - application/json
- responses:
- "200":
- description: List of unique tags
- schema:
- items:
- type: string
- type: array
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Get all tags
- tags:
- - tags
- /tags/count:
- get:
- consumes:
- - application/json
- description: Retrieves all unique tags along with their usage count across services
- produces:
- - application/json
- responses:
- "200":
- description: Map of tags with their usage count
- schema:
- additionalProperties:
- type: integer
- type: object
- "500":
- description: Internal server error
- schema:
- $ref: '#/definitions/web.ErrorResponse'
- summary: Get all tags with usage count
- tags:
- - tags
-securityDefinitions:
- BasicAuth:
- type: basic
-swagger: "2.0"
diff --git a/frontend/index.html b/frontend/index.html
index d17235f..6b3127d 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -2,14 +2,14 @@
-
+
Sentinel
-
-
+
+
diff --git a/frontend/orval.config.ts b/frontend/orval.config.ts
deleted file mode 100644
index 2db046e..0000000
--- a/frontend/orval.config.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { defineConfig } from "orval";
-
-export default defineConfig({
- api: {
- input: "../docs/docsv1/swagger.json", // путь к Swagger JSON
- output: {
- mode: "tags-split", // или 'split' / 'single'
- target: "./src/shared/api/generated.ts", // куда будет сгенерировано
- schemas: "./src/shared/types/model", // типы
- client: "axios",
- override: {
- mutator: {
- path: "./src/shared/api/baseApi.ts",
- name: "customFetcher",
- },
- },
- },
- hooks: {
- afterAllFilesWrite: "pnpm format",
- },
- },
-});
diff --git a/frontend/package.json b/frontend/package.json
index 92cffb4..edfea1d 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -4,26 +4,31 @@
"version": "0.0.0",
"type": "module",
"scripts": {
- "dev": "vite --host",
+ "dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"format": "prettier --config .prettierrc --write .",
- "format-check": "prettier --config .prettierrc --check .",
- "gen-api": "rm src/shared/types/model/* && npx orval"
+ "format-check": "prettier --config .prettierrc --check ."
},
"dependencies": {
+ "@bufbuild/protobuf": "^2.9.0",
+ "@connectrpc/connect": "^2.1.0",
+ "@connectrpc/connect-query": "^2.2.0",
+ "@connectrpc/connect-web": "^2.1.0",
+ "@tanstack/react-form": "^1.23.7",
+ "@tanstack/react-query": "^5.90.2",
"@tanstack/react-router": "^1.131.36",
"@tanstack/react-router-devtools": "^1.131.36",
"@tanstack/react-table": "^8.21.3",
- "axios": "^1.11.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"emblor": "^1.4.8",
"formik": "^2.4.6",
"lucide-react": "^0.543.0",
- "radix-ui": "^1.4.3",
+ "prism-react-renderer": "^2.4.1",
+ "radix-ui": "latest",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-markdown": "^10.1.0",
@@ -33,13 +38,14 @@
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.13",
+ "tw-animate-css": "^1.3.8",
"yup": "^1.7.0",
- "zustand": "^5.0.8",
- "tw-animate-css": "^1.3.8"
+ "zod": "^4.1.11",
+ "zustand": "^5.0.8"
},
"devDependencies": {
- "@tailwindcss/vite": "^4.1.13",
"@eslint/js": "^9.35.0",
+ "@tailwindcss/vite": "^4.1.13",
"@tanstack/router-plugin": "^1.131.36",
"@types/node": "^24.3.1",
"@types/react": "^19.1.12",
@@ -49,7 +55,6 @@
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0",
- "orval": "^7.11.2",
"prettier": "3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"typescript": "~5.9.2",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index bc68f50..24cbeca 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -8,6 +8,24 @@ importers:
.:
dependencies:
+ '@bufbuild/protobuf':
+ specifier: ^2.9.0
+ version: 2.9.0
+ '@connectrpc/connect':
+ specifier: ^2.1.0
+ version: 2.1.0(@bufbuild/protobuf@2.9.0)
+ '@connectrpc/connect-query':
+ specifier: ^2.2.0
+ version: 2.2.0(@bufbuild/protobuf@2.9.0)(@connectrpc/connect@2.1.0(@bufbuild/protobuf@2.9.0))(@tanstack/query-core@5.90.2)(@tanstack/react-query@5.90.2(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@connectrpc/connect-web':
+ specifier: ^2.1.0
+ version: 2.1.0(@bufbuild/protobuf@2.9.0)(@connectrpc/connect@2.1.0(@bufbuild/protobuf@2.9.0))
+ '@tanstack/react-form':
+ specifier: ^1.23.7
+ version: 1.23.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@tanstack/react-query':
+ specifier: ^5.90.2
+ version: 5.90.2(react@19.1.1)
'@tanstack/react-router':
specifier: ^1.131.36
version: 1.131.36(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -17,9 +35,6 @@ importers:
'@tanstack/react-table':
specifier: ^8.21.3
version: 8.21.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- axios:
- specifier: ^1.11.0
- version: 1.11.0
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -38,8 +53,11 @@ importers:
lucide-react:
specifier: ^0.543.0
version: 0.543.0(react@19.1.1)
+ prism-react-renderer:
+ specifier: ^2.4.1
+ version: 2.4.1(react@19.1.1)
radix-ui:
- specifier: ^1.4.3
+ specifier: latest
version: 1.4.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react:
specifier: ^19.1.1
@@ -74,6 +92,9 @@ importers:
yup:
specifier: ^1.7.0
version: 1.7.0
+ zod:
+ specifier: ^4.1.11
+ version: 4.1.11
zustand:
specifier: ^5.0.8
version: 5.0.8(@types/react@19.1.12)(immer@10.1.1)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1))
@@ -111,9 +132,6 @@ importers:
globals:
specifier: ^16.3.0
version: 16.3.0
- orval:
- specifier: ^7.11.2
- version: 7.11.2(openapi-types@12.1.3)
prettier:
specifier: 3.6.2
version: 3.6.2
@@ -136,25 +154,6 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
- '@apidevtools/json-schema-ref-parser@11.7.2':
- resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==}
- engines: {node: '>= 16'}
-
- '@apidevtools/openapi-schemas@2.1.0':
- resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==}
- engines: {node: '>=10'}
-
- '@apidevtools/swagger-methods@3.0.2':
- resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==}
-
- '@apidevtools/swagger-parser@10.1.1':
- resolution: {integrity: sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA==}
- peerDependencies:
- openapi-types: '>=7'
-
- '@asyncapi/specs@6.8.1':
- resolution: {integrity: sha512-czHoAk3PeXTLR+X8IUaD+IpT+g+zUvkcgMDJVothBsan+oHN3jfcFcFUNdOPAAFoUCQN1hXF1dWuphWy05THlA==}
-
'@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'}
@@ -288,6 +287,36 @@ packages:
resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==}
engines: {node: '>=6.9.0'}
+ '@bufbuild/protobuf@2.9.0':
+ resolution: {integrity: sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA==}
+
+ '@connectrpc/connect-query-core@2.2.0':
+ resolution: {integrity: sha512-t/CuxW/vP84y2iyS+PnbAnBwgOTYMzHXTSoBUKC1vIn706aNiZP40Y6mGJybglyH63RhAPcOdUgzG7DjzaAHCw==}
+ peerDependencies:
+ '@bufbuild/protobuf': 2.x
+ '@connectrpc/connect': ^2.0.1
+ '@tanstack/query-core': '>=5.62.0'
+
+ '@connectrpc/connect-query@2.2.0':
+ resolution: {integrity: sha512-oQ+coXOwBmfl4/t6EOrTfzW0zdoGDe3kvUYqZHrbzORkRFd693Cz8PxuDBjRCjmBPhDRCQMxFhR1jn2+SbgEKQ==}
+ peerDependencies:
+ '@bufbuild/protobuf': 2.x
+ '@connectrpc/connect': ^2.0.1
+ '@tanstack/react-query': '>=5.62.0'
+ react: ^18 || ^19
+ react-dom: ^18 || ^19
+
+ '@connectrpc/connect-web@2.1.0':
+ resolution: {integrity: sha512-4IBFeMeXS1RVtmmFE/MwH+vWq/5vDRKys70va+DAaWDh83Rdy0iUQOJbITUDzvonlY5as3vwfs5yy9Yp2miHSw==}
+ peerDependencies:
+ '@bufbuild/protobuf': ^2.7.0
+ '@connectrpc/connect': 2.1.0
+
+ '@connectrpc/connect@2.1.0':
+ resolution: {integrity: sha512-xhiwnYlJNHzmFsRw+iSPIwXR/xweTvTw8x5HiwWp10sbVtd4OpOXbRgE7V58xs1EC17fzusF1f5uOAy24OkBuA==}
+ peerDependencies:
+ '@bufbuild/protobuf': ^2.7.0
+
'@esbuild/aix-ppc64@0.25.9':
resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==}
engines: {node: '>=18'}
@@ -488,9 +517,6 @@ packages:
resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@exodus/schemasafe@1.3.0':
- resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==}
-
'@floating-ui/core@1.7.2':
resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==}
@@ -506,9 +532,6 @@ packages:
'@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
- '@gerrit0/mini-shiki@3.8.1':
- resolution: {integrity: sha512-HVZW+8pxoOExr5ZMPK15U79jQAZTO/S6i5byQyyZGjtNj+qaYd82cizTncwFzTQgiLo8uUBym6vh+/1tfJklTw==}
-
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
@@ -529,14 +552,6 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
- '@ibm-cloud/openapi-ruleset-utilities@1.9.0':
- resolution: {integrity: sha512-AoFbSarOqFBYH+1TZ9Ahkm2IWYSi5v0pBk88fpV+5b3qGJukypX8PwvCWADjuyIccKg48/F73a6hTTkBzDQ2UA==}
- engines: {node: '>=16.0.0'}
-
- '@ibm-cloud/openapi-ruleset@1.31.1':
- resolution: {integrity: sha512-3WK2FREmDA2aadCjD71PE7tx5evyvmhg80ts1kXp2IzXIA0ZJ7guGM66tj40kxaqwpMSGchwEnnfYswntav76g==}
- engines: {node: '>=16.0.0'}
-
'@isaacs/fs-minipass@4.0.1':
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
engines: {node: '>=18.0.0'}
@@ -560,27 +575,6 @@ packages:
'@jridgewell/trace-mapping@0.3.29':
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
- '@jsdevtools/ono@7.1.3':
- resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
-
- '@jsep-plugin/assignment@1.3.0':
- resolution: {integrity: sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==}
- engines: {node: '>= 10.16.0'}
- peerDependencies:
- jsep: ^0.4.0||^1.0.0
-
- '@jsep-plugin/regex@1.0.4':
- resolution: {integrity: sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==}
- engines: {node: '>= 10.16.0'}
- peerDependencies:
- jsep: ^0.4.0||^1.0.0
-
- '@jsep-plugin/ternary@1.1.4':
- resolution: {integrity: sha512-ck5wiqIbqdMX6WRQztBL7ASDty9YLgJ3sSAK5ZpBzXeySvFGCzIvM6UiAI4hTZ22fEcYQVV/zhUbNscggW+Ukg==}
- engines: {node: '>= 10.16.0'}
- peerDependencies:
- jsep: ^0.4.0||^1.0.0
-
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -593,36 +587,6 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
- '@orval/angular@7.11.2':
- resolution: {integrity: sha512-v7I3MXlc1DTFHZlCo10uqBmss/4puXi1EbYdlYGfeZ2sYQiwtRFEYAMnSIxHzMtdtI4jd7iDEH0fZRA7W6yloA==}
-
- '@orval/axios@7.11.2':
- resolution: {integrity: sha512-X5TJTFofCeJrQcHWoH0wz/032DBhPOQuZUUOPYO3DItOnq9/nfHJYKnUfg13wtYw0LVjCxyTZpeGLUBZnY804A==}
-
- '@orval/core@7.11.2':
- resolution: {integrity: sha512-5k2j4ro53yZ3J+tGMu3LpLgVb2OBtxNDgyrJik8qkrFyuORBLx/a+AJRFoPYwZmtnMZzzRXoH4J/fbpW5LXIyg==}
-
- '@orval/fetch@7.11.2':
- resolution: {integrity: sha512-FuupASqk4Dn8ZET7u5Ra5djKy22KfRfec60zRR/o5+L5iQkWKEe/A5DBT1PwjTMnp9789PEGlFPQjZNwMG98Tg==}
-
- '@orval/hono@7.11.2':
- resolution: {integrity: sha512-SddhKMYMB/dJH3YQx3xi0Zd+4tfhrEkqJdqQaYLXgENJiw0aGbdaZTdY6mb/e6qP38TTK6ME2PkYOqwkl2DQ7g==}
-
- '@orval/mcp@7.11.2':
- resolution: {integrity: sha512-9kGKko8wLuCbeETp8Pd8lXLtBpLzEJfR2kl2m19AI3nAoHXE/Tnn3KgjMIg0qvCcsRXGXdYJB7wfxy2URdAxVA==}
-
- '@orval/mock@7.11.2':
- resolution: {integrity: sha512-+uRq6BT6NU2z0UQtgeD6FMuLAxQ5bjJ5PZK3AsbDYFRSmAWUWoeaQcoWyF38F4t7ez779beGs3AlUg+z0Ec4rQ==}
-
- '@orval/query@7.11.2':
- resolution: {integrity: sha512-C/it+wNfcDtuvpB6h/78YwWU+Rjk7eU1Av8jAoGnvxMRli4nnzhSZ83HMILGhYQbE9WcfNZxQJ6OaBoTWqACPg==}
-
- '@orval/swr@7.11.2':
- resolution: {integrity: sha512-95GkKLVy67xJvsiVvK4nTOsCpebWM54FvQdKQaqlJ0FGCNUbqDjVRwBKbjP6dLc/B3wTmBAWlFSLbdVmjGCTYg==}
-
- '@orval/zod@7.11.2':
- resolution: {integrity: sha512-4MzTg5Wms8/LlM3CbYu80dvCbP88bVlQjnYsBdFXuEv0K2GYkBCAhVOrmXCVrPXE89neV6ABkvWQeuKZQpkdxQ==}
-
'@radix-ui/number@1.1.1':
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
@@ -833,19 +797,6 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-dialog@1.1.14':
- resolution: {integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
'@radix-ui/react-dialog@1.1.15':
resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==}
peerDependencies:
@@ -1739,94 +1690,6 @@ packages:
cpu: [x64]
os: [win32]
- '@shikijs/engine-oniguruma@3.8.1':
- resolution: {integrity: sha512-KGQJZHlNY7c656qPFEQpIoqOuC4LrxjyNndRdzk5WKB/Ie87+NJCF1xo9KkOUxwxylk7rT6nhlZyTGTC4fCe1g==}
-
- '@shikijs/langs@3.8.1':
- resolution: {integrity: sha512-TjOFg2Wp1w07oKnXjs0AUMb4kJvujML+fJ1C5cmEj45lhjbUXtziT1x2bPQb9Db6kmPhkG5NI2tgYW1/DzhUuQ==}
-
- '@shikijs/themes@3.8.1':
- resolution: {integrity: sha512-Vu3t3BBLifc0GB0UPg2Pox1naTemrrvyZv2lkiSw3QayVV60me1ujFQwPZGgUTmwXl1yhCPW8Lieesm0CYruLQ==}
-
- '@shikijs/types@3.8.1':
- resolution: {integrity: sha512-5C39Q8/8r1I26suLh+5TPk1DTrbY/kn3IdWA5HdizR0FhlhD05zx5nKCqhzSfDHH3p4S0ZefxWd77DLV+8FhGg==}
-
- '@shikijs/vscode-textmate@10.0.2':
- resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
-
- '@stoplight/better-ajv-errors@1.0.3':
- resolution: {integrity: sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA==}
- engines: {node: ^12.20 || >= 14.13}
- peerDependencies:
- ajv: '>=8'
-
- '@stoplight/json-ref-readers@1.2.2':
- resolution: {integrity: sha512-nty0tHUq2f1IKuFYsLM4CXLZGHdMn+X/IwEUIpeSOXt0QjMUbL0Em57iJUDzz+2MkWG83smIigNZ3fauGjqgdQ==}
- engines: {node: '>=8.3.0'}
-
- '@stoplight/json-ref-resolver@3.1.6':
- resolution: {integrity: sha512-YNcWv3R3n3U6iQYBsFOiWSuRGE5su1tJSiX6pAPRVk7dP0L7lqCteXGzuVRQ0gMZqUl8v1P0+fAKxF6PLo9B5A==}
- engines: {node: '>=8.3.0'}
-
- '@stoplight/json@3.21.7':
- resolution: {integrity: sha512-xcJXgKFqv/uCEgtGlPxy3tPA+4I+ZI4vAuMJ885+ThkTHFVkC+0Fm58lA9NlsyjnkpxFh4YiQWpH+KefHdbA0A==}
- engines: {node: '>=8.3.0'}
-
- '@stoplight/ordered-object-literal@1.0.5':
- resolution: {integrity: sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg==}
- engines: {node: '>=8'}
-
- '@stoplight/path@1.3.2':
- resolution: {integrity: sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==}
- engines: {node: '>=8'}
-
- '@stoplight/spectral-core@1.20.0':
- resolution: {integrity: sha512-5hBP81nCC1zn1hJXL/uxPNRKNcB+/pEIHgCjPRpl/w/qy9yC9ver04tw1W0l/PMiv0UeB5dYgozXVQ4j5a6QQQ==}
- engines: {node: ^16.20 || ^18.18 || >= 20.17}
-
- '@stoplight/spectral-formats@1.8.2':
- resolution: {integrity: sha512-c06HB+rOKfe7tuxg0IdKDEA5XnjL2vrn/m/OVIIxtINtBzphZrOgtRn7epQ5bQF5SWp84Ue7UJWaGgDwVngMFw==}
- engines: {node: ^16.20 || ^18.18 || >= 20.17}
-
- '@stoplight/spectral-functions@1.10.1':
- resolution: {integrity: sha512-obu8ZfoHxELOapfGsCJixKZXZcffjg+lSoNuttpmUFuDzVLT3VmH8QkPXfOGOL5Pz80BR35ClNAToDkdnYIURg==}
- engines: {node: ^16.20 || ^18.18 || >= 20.17}
-
- '@stoplight/spectral-parsers@1.0.5':
- resolution: {integrity: sha512-ANDTp2IHWGvsQDAY85/jQi9ZrF4mRrA5bciNHX+PUxPr4DwS6iv4h+FVWJMVwcEYdpyoIdyL+SRmHdJfQEPmwQ==}
- engines: {node: ^16.20 || ^18.18 || >= 20.17}
-
- '@stoplight/spectral-ref-resolver@1.0.5':
- resolution: {integrity: sha512-gj3TieX5a9zMW29z3mBlAtDOCgN3GEc1VgZnCVlr5irmR4Qi5LuECuFItAq4pTn5Zu+sW5bqutsCH7D4PkpyAA==}
- engines: {node: ^16.20 || ^18.18 || >= 20.17}
-
- '@stoplight/spectral-rulesets@1.22.0':
- resolution: {integrity: sha512-l2EY2jiKKLsvnPfGy+pXC0LeGsbJzcQP5G/AojHgf+cwN//VYxW1Wvv4WKFx/CLmLxc42mJYF2juwWofjWYNIQ==}
- engines: {node: ^16.20 || ^18.18 || >= 20.17}
-
- '@stoplight/spectral-runtime@1.1.4':
- resolution: {integrity: sha512-YHbhX3dqW0do6DhiPSgSGQzr6yQLlWybhKwWx0cqxjMwxej3TqLv3BXMfIUYFKKUqIwH4Q2mV8rrMM8qD2N0rQ==}
- engines: {node: ^16.20 || ^18.18 || >= 20.17}
-
- '@stoplight/types@13.20.0':
- resolution: {integrity: sha512-2FNTv05If7ib79VPDA/r9eUet76jewXFH2y2K5vuge6SXbRHtWBhcaRmu+6QpF4/WRNoJj5XYRSwLGXDxysBGA==}
- engines: {node: ^12.20 || >=14.13}
-
- '@stoplight/types@13.6.0':
- resolution: {integrity: sha512-dzyuzvUjv3m1wmhPfq82lCVYGcXG0xUYgqnWfCq3PCVR4BKFhjdkHrnJ+jIDoMKvXb05AZP/ObQF6+NpDo29IQ==}
- engines: {node: ^12.20 || >=14.13}
-
- '@stoplight/types@14.1.1':
- resolution: {integrity: sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g==}
- engines: {node: ^12.20 || >=14.13}
-
- '@stoplight/yaml-ast-parser@0.0.50':
- resolution: {integrity: sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ==}
-
- '@stoplight/yaml@4.3.0':
- resolution: {integrity: sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w==}
- engines: {node: '>=10.8'}
-
'@swc/core-darwin-arm64@1.13.3':
resolution: {integrity: sha512-ux0Ws4pSpBTqbDS9GlVP354MekB1DwYlbxXU3VhnDr4GBcCOimpocx62x7cFJkSpEBF8bmX8+/TTCGKh4PbyXw==}
engines: {node: '>=10'}
@@ -1992,10 +1855,34 @@ packages:
peerDependencies:
vite: ^5.2.0 || ^6 || ^7
+ '@tanstack/devtools-event-client@0.3.3':
+ resolution: {integrity: sha512-RfV+OPV/M3CGryYqTue684u10jUt55PEqeBOnOtCe6tAmHI9Iqyc8nHeDhWPEV9715gShuauFVaMc9RiUVNdwg==}
+ engines: {node: '>=18'}
+
+ '@tanstack/form-core@1.24.3':
+ resolution: {integrity: sha512-e+HzSD49NWr4aIqJWtPPzmi+/phBJAP3nSPN8dvxwmJWqAxuB/cH138EcmCFf3+oA7j3BXvwvTY0I+8UweGPjQ==}
+
'@tanstack/history@1.131.2':
resolution: {integrity: sha512-cs1WKawpXIe+vSTeiZUuSBy8JFjEuDgdMKZFRLKwQysKo8y2q6Q1HvS74Yw+m5IhOW1nTZooa6rlgdfXcgFAaw==}
engines: {node: '>=12'}
+ '@tanstack/query-core@5.90.2':
+ resolution: {integrity: sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ==}
+
+ '@tanstack/react-form@1.23.7':
+ resolution: {integrity: sha512-p/j9Gi2+s135sOjj48RjM+6xZQr1FVpliQlETLYBEGmmmxWHgYYs2b62mTDSnuv7AqtuZhpQ+t0CRFVfbQLsFA==}
+ peerDependencies:
+ '@tanstack/react-start': ^1.130.10
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@tanstack/react-start':
+ optional: true
+
+ '@tanstack/react-query@5.90.2':
+ resolution: {integrity: sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw==}
+ peerDependencies:
+ react: ^18 || ^19
+
'@tanstack/react-router-devtools@1.131.36':
resolution: {integrity: sha512-2huBmW+mqPoJs6ZHfjuunEkVRfgWZh67IUjgdSyqdaYGLa3qsG3zcG4bpTIq6HwJuzcK00JRM3AQ4NLPdttaJQ==}
engines: {node: '>=12'}
@@ -2017,6 +1904,12 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ '@tanstack/react-store@0.7.7':
+ resolution: {integrity: sha512-qqT0ufegFRDGSof9D/VqaZgjNgp4tRPHZIJq2+QIHkMUtHjaJ0lYrrXjeIUJvjnTbgPfSD1XgOMEt0lmANn6Zg==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
'@tanstack/react-table@8.21.3':
resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==}
engines: {node: '>=12'}
@@ -2072,6 +1965,9 @@ packages:
'@tanstack/store@0.7.2':
resolution: {integrity: sha512-RP80Z30BYiPX2Pyo0Nyw4s1SJFH2jyM6f9i3HfX4pA+gm5jsnYryscdq2aIQLnL4TaGuQMO+zXmN9nh1Qck+Pg==}
+ '@tanstack/store@0.7.7':
+ resolution: {integrity: sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==}
+
'@tanstack/table-core@8.21.3':
resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==}
engines: {node: '>=12'}
@@ -2110,9 +2006,6 @@ packages:
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
- '@types/es-aggregate-error@1.0.6':
- resolution: {integrity: sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg==}
-
'@types/estree-jsx@1.0.5':
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
@@ -2137,6 +2030,9 @@ packages:
'@types/node@24.3.1':
resolution: {integrity: sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==}
+ '@types/prismjs@1.26.5':
+ resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
+
'@types/react-dom@19.1.9':
resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==}
peerDependencies:
@@ -2151,9 +2047,6 @@ packages:
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
- '@types/urijs@1.19.25':
- resolution: {integrity: sha512-XOfUup9r3Y06nFAZh3WvO0rBU4OtlfPB/vgxpjg+NRdGU6CN6djdc6OEiH+PcqHCY6eFLo9Ista73uarf4gnBg==}
-
'@typescript-eslint/eslint-plugin@8.43.0':
resolution: {integrity: sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2222,10 +2115,6 @@ packages:
peerDependencies:
vite: ^4 || ^5 || ^6 || ^7
- abort-controller@3.0.0:
- resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
- engines: {node: '>=6.5'}
-
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -2236,41 +2125,9 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
- ajv-draft-04@1.0.0:
- resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==}
- peerDependencies:
- ajv: ^8.5.0
- peerDependenciesMeta:
- ajv:
- optional: true
-
- ajv-errors@3.0.0:
- resolution: {integrity: sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==}
- peerDependencies:
- ajv: ^8.0.1
-
- ajv-formats@2.1.1:
- resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
- peerDependencies:
- ajv: ^8.0.0
- peerDependenciesMeta:
- ajv:
- optional: true
-
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
- ajv@8.17.1:
- resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
-
- ansi-colors@4.1.3:
- resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
- engines: {node: '>=6'}
-
- ansi-regex@5.0.1:
- resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
- engines: {node: '>=8'}
-
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@@ -2290,44 +2147,14 @@ packages:
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
engines: {node: '>=10'}
- array-buffer-byte-length@1.0.2:
- resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
- engines: {node: '>= 0.4'}
-
array-move@3.0.1:
resolution: {integrity: sha512-H3Of6NIn2nNU1gsVDqDnYKY/LCdWvCMMOWifNGhKcVQgiZ6nOek39aESOvro6zmueP07exSl93YLvkN4fZOkSg==}
engines: {node: '>=10'}
- array-union@2.1.0:
- resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
- engines: {node: '>=8'}
-
- arraybuffer.prototype.slice@1.0.4:
- resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
- engines: {node: '>= 0.4'}
-
ast-types@0.16.1:
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
engines: {node: '>=4'}
- astring@1.9.0:
- resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==}
- hasBin: true
-
- async-function@1.0.0:
- resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
- engines: {node: '>= 0.4'}
-
- asynckit@0.4.0:
- resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
-
- available-typed-arrays@1.0.7:
- resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
- engines: {node: '>= 0.4'}
-
- axios@1.11.0:
- resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==}
-
babel-dead-code-elimination@1.0.10:
resolution: {integrity: sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==}
@@ -2356,25 +2183,6 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
- cac@6.7.14:
- resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
- engines: {node: '>=8'}
-
- call-bind-apply-helpers@1.0.2:
- resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
- engines: {node: '>= 0.4'}
-
- call-bind@1.0.8:
- resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
- engines: {node: '>= 0.4'}
-
- call-bound@1.0.4:
- resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
- engines: {node: '>= 0.4'}
-
- call-me-maybe@1.0.2:
- resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==}
-
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@@ -2405,10 +2213,6 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
- chokidar@4.0.3:
- resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
- engines: {node: '>= 14.16.0'}
-
chownr@3.0.0:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
@@ -2416,10 +2220,6 @@ packages:
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
- cliui@8.0.1:
- resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
- engines: {node: '>=12'}
-
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
@@ -2443,16 +2243,9 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
- combined-stream@1.0.8:
- resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
- engines: {node: '>= 0.8'}
-
comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
- compare-versions@6.1.1:
- resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
-
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -2513,18 +2306,6 @@ packages:
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
engines: {node: '>=12'}
- data-view-buffer@1.0.2:
- resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
- engines: {node: '>= 0.4'}
-
- data-view-byte-length@1.0.2:
- resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
- engines: {node: '>= 0.4'}
-
- data-view-byte-offset@1.0.1:
- resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
- engines: {node: '>= 0.4'}
-
debug@4.4.1:
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'}
@@ -2537,6 +2318,9 @@ packages:
decimal.js-light@2.5.1:
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
+ decode-formdata@0.9.0:
+ resolution: {integrity: sha512-q5uwOjR3Um5YD+ZWPOF/1sGHVW9A5rCrRwITQChRXlmPkxDFBqCm4jNTIVdGHNH9OnR+V9MoZVgRhsFb+ARbUw==}
+
decode-named-character-reference@1.2.0:
resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
@@ -2547,22 +2331,6 @@ packages:
resolution: {integrity: sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==}
engines: {node: '>=0.10.0'}
- define-data-property@1.1.4:
- resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
- engines: {node: '>= 0.4'}
-
- define-properties@1.2.1:
- resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
- engines: {node: '>= 0.4'}
-
- delayed-stream@1.0.0:
- resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
- engines: {node: '>=0.4.0'}
-
- dependency-graph@0.11.0:
- resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==}
- engines: {node: '>= 0.6.0'}
-
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -2574,6 +2342,9 @@ packages:
detect-node-es@1.1.0:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+ devalue@5.4.1:
+ resolution: {integrity: sha512-YtoaOfsqjbZQKGIMRYDWKjUmSB4VJ/RElB+bXZawQAQYAo4xu08GKTMVlsZDTF6R2MbAgjcAQRPI5eIyRAT2OQ==}
+
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
@@ -2581,17 +2352,9 @@ packages:
resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==}
engines: {node: '>=0.3.1'}
- dir-glob@3.0.1:
- resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
- engines: {node: '>=8'}
-
dom-helpers@5.2.1:
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
- dunder-proto@1.0.1:
- resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
- engines: {node: '>= 0.4'}
-
electron-to-chromium@1.5.203:
resolution: {integrity: sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g==}
@@ -2601,52 +2364,10 @@ packages:
react: ^18.0.0
react-dom: ^18.0.0
- emoji-regex@8.0.0:
- resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
-
enhanced-resolve@5.18.3:
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
engines: {node: '>=10.13.0'}
- enquirer@2.4.1:
- resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==}
- engines: {node: '>=8.6'}
-
- entities@4.5.0:
- resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
- engines: {node: '>=0.12'}
-
- es-abstract@1.24.0:
- resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==}
- engines: {node: '>= 0.4'}
-
- es-aggregate-error@1.0.14:
- resolution: {integrity: sha512-3YxX6rVb07B5TV11AV5wsL7nQCHXNwoHPsQC8S4AmBiqYhyNCJ5BRKXkXyDJvs8QzXN20NgRtxe3dEEQD9NLHA==}
- engines: {node: '>= 0.4'}
-
- es-define-property@1.0.1:
- resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
- engines: {node: '>= 0.4'}
-
- es-errors@1.3.0:
- resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
- engines: {node: '>= 0.4'}
-
- es-object-atoms@1.1.1:
- resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
- engines: {node: '>= 0.4'}
-
- es-set-tostringtag@2.1.0:
- resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
- engines: {node: '>= 0.4'}
-
- es-to-primitive@1.3.0:
- resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
- engines: {node: '>= 0.4'}
-
- es6-promise@3.3.1:
- resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==}
-
esbuild@0.25.9:
resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==}
engines: {node: '>=18'}
@@ -2725,17 +2446,9 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
- event-target-shim@5.0.1:
- resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
- engines: {node: '>=6'}
-
eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
- execa@5.1.1:
- resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
- engines: {node: '>=10'}
-
extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
@@ -2756,15 +2469,6 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
- fast-memoize@2.5.2:
- resolution: {integrity: sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==}
-
- fast-safe-stringify@2.1.1:
- resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
-
- fast-uri@3.0.6:
- resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==}
-
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
@@ -2796,75 +2500,24 @@ packages:
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
- follow-redirects@1.15.9:
- resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
- engines: {node: '>=4.0'}
- peerDependencies:
- debug: '*'
- peerDependenciesMeta:
- debug:
- optional: true
-
- for-each@0.3.5:
- resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
- engines: {node: '>= 0.4'}
-
- form-data@4.0.4:
- resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
- engines: {node: '>= 6'}
-
formik@2.4.6:
resolution: {integrity: sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==}
peerDependencies:
react: '>=16.8.0'
- fs-extra@11.3.0:
- resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==}
- engines: {node: '>=14.14'}
-
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
- function-bind@1.1.2:
- resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
-
- function.prototype.name@1.1.8:
- resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
- engines: {node: '>= 0.4'}
-
- functions-have-names@1.2.3:
- resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
-
gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
- get-caller-file@2.0.5:
- resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
- engines: {node: 6.* || 8.* || >= 10.*}
-
- get-intrinsic@1.3.0:
- resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
- engines: {node: '>= 0.4'}
-
get-nonce@1.0.1:
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
engines: {node: '>=6'}
- get-proto@1.0.1:
- resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
- engines: {node: '>= 0.4'}
-
- get-stream@6.0.1:
- resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
- engines: {node: '>=10'}
-
- get-symbol-description@1.1.0:
- resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
- engines: {node: '>= 0.4'}
-
get-tsconfig@4.10.1:
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
@@ -2884,56 +2537,21 @@ packages:
resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==}
engines: {node: '>=18'}
- globalthis@1.0.4:
- resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
- engines: {node: '>= 0.4'}
-
- globby@11.1.0:
- resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
- engines: {node: '>=10'}
-
goober@2.1.16:
resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==}
peerDependencies:
csstype: ^3.0.10
- gopd@1.2.0:
- resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
- engines: {node: '>= 0.4'}
-
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
- has-bigints@1.1.0:
- resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
- engines: {node: '>= 0.4'}
-
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
- has-property-descriptors@1.0.2:
- resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
-
- has-proto@1.2.0:
- resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
- engines: {node: '>= 0.4'}
-
- has-symbols@1.1.0:
- resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
- engines: {node: '>= 0.4'}
-
- has-tostringtag@1.0.2:
- resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
- engines: {node: '>= 0.4'}
-
- hasown@2.0.2:
- resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
- engines: {node: '>= 0.4'}
-
hast-util-to-jsx-runtime@2.3.6:
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
@@ -2946,13 +2564,6 @@ packages:
html-url-attributes@3.0.1:
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
- http2-client@1.3.5:
- resolution: {integrity: sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==}
-
- human-signals@2.1.0:
- resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
- engines: {node: '>=10.17.0'}
-
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -2964,9 +2575,6 @@ packages:
immer@10.1.1:
resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==}
- immer@9.0.21:
- resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==}
-
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
@@ -2978,10 +2586,6 @@ packages:
inline-style-parser@0.2.4:
resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
- internal-slot@1.1.0:
- resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
- engines: {node: '>= 0.4'}
-
internmap@2.0.3:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
@@ -2992,38 +2596,10 @@ packages:
is-alphanumerical@2.0.1:
resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
- is-array-buffer@3.0.5:
- resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
- engines: {node: '>= 0.4'}
-
- is-async-function@2.1.1:
- resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
- engines: {node: '>= 0.4'}
-
- is-bigint@1.1.0:
- resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
- engines: {node: '>= 0.4'}
-
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
- is-boolean-object@1.2.2:
- resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
- engines: {node: '>= 0.4'}
-
- is-callable@1.2.7:
- resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
- engines: {node: '>= 0.4'}
-
- is-data-view@1.0.2:
- resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
- engines: {node: '>= 0.4'}
-
- is-date-object@1.1.0:
- resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
- engines: {node: '>= 0.4'}
-
is-decimal@2.0.1:
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
@@ -3031,18 +2607,6 @@ packages:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
- is-finalizationregistry@1.1.1:
- resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
- engines: {node: '>= 0.4'}
-
- is-fullwidth-code-point@3.0.0:
- resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
- engines: {node: '>=8'}
-
- is-generator-function@1.1.0:
- resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==}
- engines: {node: '>= 0.4'}
-
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@@ -3050,18 +2614,6 @@ packages:
is-hexadecimal@2.0.1:
resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
- is-map@2.0.3:
- resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
- engines: {node: '>= 0.4'}
-
- is-negative-zero@2.0.3:
- resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
- engines: {node: '>= 0.4'}
-
- is-number-object@1.1.1:
- resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
- engines: {node: '>= 0.4'}
-
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
@@ -3070,49 +2622,6 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
- is-regex@1.2.1:
- resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
- engines: {node: '>= 0.4'}
-
- is-set@2.0.3:
- resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
- engines: {node: '>= 0.4'}
-
- is-shared-array-buffer@1.0.4:
- resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
- engines: {node: '>= 0.4'}
-
- is-stream@2.0.1:
- resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
- engines: {node: '>=8'}
-
- is-string@1.1.1:
- resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
- engines: {node: '>= 0.4'}
-
- is-symbol@1.1.1:
- resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
- engines: {node: '>= 0.4'}
-
- is-typed-array@1.1.15:
- resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
- engines: {node: '>= 0.4'}
-
- is-weakmap@2.0.2:
- resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
- engines: {node: '>= 0.4'}
-
- is-weakref@1.1.1:
- resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
- engines: {node: '>= 0.4'}
-
- is-weakset@2.0.4:
- resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
- engines: {node: '>= 0.4'}
-
- isarray@2.0.5:
- resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
-
isbot@5.1.29:
resolution: {integrity: sha512-DelDWWoa3mBoyWTq3wjp+GIWx/yZdN7zLUE7NFhKjAiJ+uJVRkbLlwykdduCE4sPUUy8mlTYTmdhBUYu91F+sw==}
engines: {node: '>=18'}
@@ -3131,10 +2640,6 @@ packages:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
- jsep@1.4.0:
- resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==}
- engines: {node: '>= 10.16.0'}
-
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
@@ -3146,9 +2651,6 @@ packages:
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
- json-schema-traverse@1.0.0:
- resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
-
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
@@ -3157,31 +2659,9 @@ packages:
engines: {node: '>=6'}
hasBin: true
- jsonc-parser@2.2.1:
- resolution: {integrity: sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==}
-
- jsonfile@6.1.0:
- resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
-
- jsonpath-plus@10.3.0:
- resolution: {integrity: sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==}
- engines: {node: '>=18.0.0'}
- hasBin: true
-
- jsonpointer@5.0.1:
- resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
- engines: {node: '>=0.10.0'}
-
- jsonschema@1.5.0:
- resolution: {integrity: sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==}
-
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
- leven@3.1.0:
- resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
- engines: {node: '>=6'}
-
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
@@ -3250,9 +2730,6 @@ packages:
resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
engines: {node: '>= 12.0.0'}
- linkify-it@5.0.0:
- resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
-
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@@ -3260,37 +2737,12 @@ packages:
lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
- lodash.isempty@4.4.0:
- resolution: {integrity: sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==}
-
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
- lodash.omitby@4.6.0:
- resolution: {integrity: sha512-5OrRcIVR75M288p4nbI2WLAf3ndw2GD9fyNv3Bc15+WCxJDdZ4lYndSxGd7hnG6PVjiJTeJE2dHEGhIuKGicIQ==}
-
- lodash.topath@4.5.2:
- resolution: {integrity: sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg==}
-
- lodash.uniq@4.5.0:
- resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
-
- lodash.uniqby@4.7.0:
- resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==}
-
- lodash.uniqwith@4.5.0:
- resolution: {integrity: sha512-7lYL8bLopMoy4CTICbxygAUq6CdRJ36vFc80DucPueUee+d5NBRxz3FdT9Pes/HEx5mPoT9jwnsEJWz1N7uq7Q==}
-
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
- loglevel-plugin-prefix@0.8.4:
- resolution: {integrity: sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==}
-
- loglevel@1.9.2:
- resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==}
- engines: {node: '>= 0.6.0'}
-
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@@ -3306,23 +2758,12 @@ packages:
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
- lunr@2.3.9:
- resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
-
magic-string@0.30.19:
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
- markdown-it@14.1.0:
- resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
- hasBin: true
-
markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
- math-intrinsics@1.1.0:
- resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
- engines: {node: '>= 0.4'}
-
mdast-util-find-and-replace@3.0.2:
resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
@@ -3368,12 +2809,6 @@ packages:
mdast-util-to-string@4.0.0:
resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
- mdurl@2.0.0:
- resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
-
- merge-stream@2.0.0:
- resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
-
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@@ -3466,25 +2901,9 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
- mime-db@1.52.0:
- resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
- engines: {node: '>= 0.6'}
-
- mime-types@2.1.35:
- resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
- engines: {node: '>= 0.6'}
-
- mimic-fn@2.1.0:
- resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
- engines: {node: '>=6'}
-
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
- minimatch@6.2.0:
- resolution: {integrity: sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==}
- engines: {node: '>=10'}
-
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -3513,94 +2932,21 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
- nimma@0.2.3:
- resolution: {integrity: sha512-1ZOI8J+1PKKGceo/5CT5GfQOG6H8I2BencSK06YarZ2wXwH37BSSUWldqJmMJYA5JfqDqffxDXynt6f11AyKcA==}
- engines: {node: ^12.20 || >=14.13}
-
- node-fetch-h2@2.3.0:
- resolution: {integrity: sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==}
- engines: {node: 4.x || >=6.0.0}
-
- node-fetch@2.7.0:
- resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
- engines: {node: 4.x || >=6.0.0}
- peerDependencies:
- encoding: ^0.1.0
- peerDependenciesMeta:
- encoding:
- optional: true
-
- node-readfiles@0.2.0:
- resolution: {integrity: sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==}
-
- node-releases@2.0.19:
- resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+ node-releases@2.0.19:
+ resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
- npm-run-path@4.0.1:
- resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
- engines: {node: '>=8'}
-
- oas-kit-common@1.0.8:
- resolution: {integrity: sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==}
-
- oas-linter@3.2.2:
- resolution: {integrity: sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==}
-
- oas-resolver@2.5.6:
- resolution: {integrity: sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==}
- hasBin: true
-
- oas-schema-walker@1.1.5:
- resolution: {integrity: sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==}
-
- oas-validator@5.0.8:
- resolution: {integrity: sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==}
-
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
- object-inspect@1.13.4:
- resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
- engines: {node: '>= 0.4'}
-
- object-keys@1.1.1:
- resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
- engines: {node: '>= 0.4'}
-
- object.assign@4.1.7:
- resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
- engines: {node: '>= 0.4'}
-
- onetime@5.1.2:
- resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
- engines: {node: '>=6'}
-
- openapi-types@12.1.3:
- resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
-
- openapi3-ts@4.2.2:
- resolution: {integrity: sha512-+9g4actZKeb3czfi9gVQ4Br2Ju3KwhCAQJBNaKgye5KggqcBLIhFHH+nIkcm0BUX00TrAJl6dH4JWgM4G4JWrw==}
-
- openapi3-ts@4.4.0:
- resolution: {integrity: sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==}
-
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
- orval@7.11.2:
- resolution: {integrity: sha512-Cjc/dgnQwAOkvymzvPpFqFc2nQwZ29E+ZFWUI8yKejleHaoFKIdwvkM/b1njtLEjePDcF0hyqXXCTz2wWaXLig==}
- hasBin: true
-
- own-keys@1.0.1:
- resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
- engines: {node: '>= 0.4'}
-
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@@ -3624,10 +2970,6 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
- path-type@4.0.0:
- resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
- engines: {node: '>=8'}
-
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -3639,14 +2981,6 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
- pony-cause@1.1.1:
- resolution: {integrity: sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g==}
- engines: {node: '>=12.0.0'}
-
- possible-typed-array-names@1.1.0:
- resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
- engines: {node: '>= 0.4'}
-
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
@@ -3721,6 +3055,11 @@ packages:
engines: {node: '>=14'}
hasBin: true
+ prism-react-renderer@2.4.1:
+ resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==}
+ peerDependencies:
+ react: '>=16.0.0'
+
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
@@ -3730,13 +3069,6 @@ packages:
property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
- proxy-from-env@1.1.0:
- resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
-
- punycode.js@2.3.1:
- resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
- engines: {node: '>=6'}
-
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -3857,10 +3189,6 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
- readdirp@4.1.2:
- resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
- engines: {node: '>= 14.18.0'}
-
recast@0.23.11:
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
engines: {node: '>= 4'}
@@ -3875,17 +3203,6 @@ packages:
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- reflect.getprototypeof@1.0.10:
- resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
- engines: {node: '>= 0.4'}
-
- reftools@1.1.9:
- resolution: {integrity: sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==}
-
- regexp.prototype.flags@1.5.4:
- resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
- engines: {node: '>= 0.4'}
-
remark-gfm@4.0.1:
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
@@ -3898,14 +3215,6 @@ packages:
remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
- require-directory@2.1.1:
- resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
- engines: {node: '>=0.10.0'}
-
- require-from-string@2.0.2:
- resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
- engines: {node: '>=0.10.0'}
-
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -3925,21 +3234,6 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
- safe-array-concat@1.1.3:
- resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
- engines: {node: '>=0.4'}
-
- safe-push-apply@1.0.0:
- resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
- engines: {node: '>= 0.4'}
-
- safe-regex-test@1.1.0:
- resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
- engines: {node: '>= 0.4'}
-
- safe-stable-stringify@1.1.1:
- resolution: {integrity: sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==}
-
scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
@@ -3962,18 +3256,6 @@ packages:
resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==}
engines: {node: '>=10'}
- set-function-length@1.2.2:
- resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
- engines: {node: '>= 0.4'}
-
- set-function-name@2.0.2:
- resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
- engines: {node: '>= 0.4'}
-
- set-proto@1.0.0:
- resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
- engines: {node: '>= 0.4'}
-
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -3982,51 +3264,6 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
- should-equal@2.0.0:
- resolution: {integrity: sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==}
-
- should-format@3.0.3:
- resolution: {integrity: sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==}
-
- should-type-adaptors@1.1.0:
- resolution: {integrity: sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==}
-
- should-type@1.4.0:
- resolution: {integrity: sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==}
-
- should-util@1.0.1:
- resolution: {integrity: sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==}
-
- should@13.2.3:
- resolution: {integrity: sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==}
-
- side-channel-list@1.0.0:
- resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
- engines: {node: '>= 0.4'}
-
- side-channel-map@1.0.1:
- resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
- engines: {node: '>= 0.4'}
-
- side-channel-weakmap@1.0.2:
- resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
- engines: {node: '>= 0.4'}
-
- side-channel@1.1.0:
- resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
- engines: {node: '>= 0.4'}
-
- signal-exit@3.0.7:
- resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
-
- simple-eval@1.0.1:
- resolution: {integrity: sha512-LH7FpTAkeD+y5xQC4fzS+tFtaNlvt3Ib1zKzvhjv/Y+cioV4zIuw4IZr2yhRLu67CWL7FR9/6KXKnjRoZTvGGQ==}
- engines: {node: '>=12'}
-
- slash@3.0.0:
- resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
- engines: {node: '>=8'}
-
solid-js@1.9.9:
resolution: {integrity: sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==}
@@ -4051,41 +3288,9 @@ packages:
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
- stop-iteration-iterator@1.1.0:
- resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
- engines: {node: '>= 0.4'}
-
- string-argv@0.3.2:
- resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
- engines: {node: '>=0.6.19'}
-
- string-width@4.2.3:
- resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
- engines: {node: '>=8'}
-
- string.prototype.trim@1.2.10:
- resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
- engines: {node: '>= 0.4'}
-
- string.prototype.trimend@1.0.9:
- resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
- engines: {node: '>= 0.4'}
-
- string.prototype.trimstart@1.0.8:
- resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
- engines: {node: '>= 0.4'}
-
stringify-entities@4.0.4:
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
- strip-ansi@6.0.1:
- resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
- engines: {node: '>=8'}
-
- strip-final-newline@2.0.0:
- resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
- engines: {node: '>=6'}
-
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
@@ -4100,10 +3305,6 @@ packages:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
- swagger2openapi@7.0.8:
- resolution: {integrity: sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==}
- hasBin: true
-
tailwind-merge@3.3.1:
resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
@@ -4138,9 +3339,6 @@ packages:
toposort@2.0.2:
resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==}
- tr46@0.0.3:
- resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
-
trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
@@ -4153,19 +3351,6 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
- tsconfck@2.1.2:
- resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==}
- engines: {node: ^14.13.1 || ^16 || >=18}
- hasBin: true
- peerDependencies:
- typescript: ^4.3.5 || ^5.0.0
- peerDependenciesMeta:
- typescript:
- optional: true
-
- tslib@1.14.1:
- resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
-
tslib@2.0.1:
resolution: {integrity: sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==}
@@ -4188,35 +3373,6 @@ packages:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
- typed-array-buffer@1.0.3:
- resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
- engines: {node: '>= 0.4'}
-
- typed-array-byte-length@1.0.3:
- resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
- engines: {node: '>= 0.4'}
-
- typed-array-byte-offset@1.0.4:
- resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
- engines: {node: '>= 0.4'}
-
- typed-array-length@1.0.7:
- resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
- engines: {node: '>= 0.4'}
-
- typedoc-plugin-markdown@4.8.1:
- resolution: {integrity: sha512-ug7fc4j0SiJxSwBGLncpSo8tLvrT9VONvPUQqQDTKPxCoFQBADLli832RGPtj6sfSVJebNSrHZQRUdEryYH/7g==}
- engines: {node: '>= 18'}
- peerDependencies:
- typedoc: 0.28.x
-
- typedoc@0.28.7:
- resolution: {integrity: sha512-lpz0Oxl6aidFkmS90VQDQjk/Qf2iw0IUvFqirdONBdj7jPSN9mGXhy66BcGNDxx5ZMyKKiBVAREvPEzT6Uxipw==}
- engines: {node: '>= 18', pnpm: '>= 10'}
- hasBin: true
- peerDependencies:
- typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x
-
typescript-eslint@8.43.0:
resolution: {integrity: sha512-FyRGJKUGvcFekRRcBKFBlAhnp4Ng8rhe8tuvvkR9OiU0gfd4vyvTRQHEckO6VDlH57jbeUQem2IpqPq9kLJH+w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -4229,13 +3385,6 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
- uc.micro@2.1.0:
- resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
-
- unbox-primitive@1.1.0:
- resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
- engines: {node: '>= 0.4'}
-
undici-types@7.10.0:
resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
@@ -4257,10 +3406,6 @@ packages:
unist-util-visit@5.0.0:
resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
- universalify@2.0.1:
- resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
- engines: {node: '>= 10.0.0'}
-
unplugin@2.3.6:
resolution: {integrity: sha512-+/MdXl8bLTXI2lJF22gUBeCFqZruEpL/oM9f8wxCuKh9+Mw9qeul3gTqgbKpMeOFlusCzc0s7x2Kax2xKW+FQg==}
engines: {node: '>=18.12.0'}
@@ -4274,9 +3419,6 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
- urijs@1.19.11:
- resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==}
-
use-callback-ref@1.3.3:
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
engines: {node: '>=10'}
@@ -4302,14 +3444,6 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- utility-types@3.11.0:
- resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
- engines: {node: '>= 4'}
-
- validator@13.15.15:
- resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==}
- engines: {node: '>= 0.10'}
-
vfile-message@4.0.3:
resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
@@ -4359,31 +3493,9 @@ packages:
yaml:
optional: true
- webidl-conversions@3.0.1:
- resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
-
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
- whatwg-url@5.0.0:
- resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
-
- which-boxed-primitive@1.1.1:
- resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
- engines: {node: '>= 0.4'}
-
- which-builtin-type@1.2.1:
- resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
- engines: {node: '>= 0.4'}
-
- which-collection@1.0.2:
- resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
- engines: {node: '>= 0.4'}
-
- which-typed-array@1.1.19:
- resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
- engines: {node: '>= 0.4'}
-
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -4393,14 +3505,6 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
- wrap-ansi@7.0.0:
- resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
- engines: {node: '>=10'}
-
- y18n@5.0.8:
- resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
- engines: {node: '>=10'}
-
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -4408,23 +3512,11 @@ packages:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'}
- yaml@1.10.2:
- resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
- engines: {node: '>= 6'}
-
yaml@2.8.0:
resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
engines: {node: '>= 14.6'}
hasBin: true
- yargs-parser@21.1.1:
- resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
- engines: {node: '>=12'}
-
- yargs@17.7.2:
- resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
- engines: {node: '>=12'}
-
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -4435,6 +3527,9 @@ packages:
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+ zod@4.1.11:
+ resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==}
+
zustand@5.0.8:
resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==}
engines: {node: '>=12.20.0'}
@@ -4463,31 +3558,6 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.12
'@jridgewell/trace-mapping': 0.3.29
- '@apidevtools/json-schema-ref-parser@11.7.2':
- dependencies:
- '@jsdevtools/ono': 7.1.3
- '@types/json-schema': 7.0.15
- js-yaml: 4.1.0
-
- '@apidevtools/openapi-schemas@2.1.0': {}
-
- '@apidevtools/swagger-methods@3.0.2': {}
-
- '@apidevtools/swagger-parser@10.1.1(openapi-types@12.1.3)':
- dependencies:
- '@apidevtools/json-schema-ref-parser': 11.7.2
- '@apidevtools/openapi-schemas': 2.1.0
- '@apidevtools/swagger-methods': 3.0.2
- '@jsdevtools/ono': 7.1.3
- ajv: 8.17.1
- ajv-draft-04: 1.0.0(ajv@8.17.1)
- call-me-maybe: 1.0.2
- openapi-types: 12.1.3
-
- '@asyncapi/specs@6.8.1':
- dependencies:
- '@types/json-schema': 7.0.15
-
'@babel/code-frame@7.27.1':
dependencies:
'@babel/helper-validator-identifier': 7.27.1
@@ -4676,6 +3746,34 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
+ '@bufbuild/protobuf@2.9.0': {}
+
+ '@connectrpc/connect-query-core@2.2.0(@bufbuild/protobuf@2.9.0)(@connectrpc/connect@2.1.0(@bufbuild/protobuf@2.9.0))(@tanstack/query-core@5.90.2)':
+ dependencies:
+ '@bufbuild/protobuf': 2.9.0
+ '@connectrpc/connect': 2.1.0(@bufbuild/protobuf@2.9.0)
+ '@tanstack/query-core': 5.90.2
+
+ '@connectrpc/connect-query@2.2.0(@bufbuild/protobuf@2.9.0)(@connectrpc/connect@2.1.0(@bufbuild/protobuf@2.9.0))(@tanstack/query-core@5.90.2)(@tanstack/react-query@5.90.2(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ dependencies:
+ '@bufbuild/protobuf': 2.9.0
+ '@connectrpc/connect': 2.1.0(@bufbuild/protobuf@2.9.0)
+ '@connectrpc/connect-query-core': 2.2.0(@bufbuild/protobuf@2.9.0)(@connectrpc/connect@2.1.0(@bufbuild/protobuf@2.9.0))(@tanstack/query-core@5.90.2)
+ '@tanstack/react-query': 5.90.2(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ transitivePeerDependencies:
+ - '@tanstack/query-core'
+
+ '@connectrpc/connect-web@2.1.0(@bufbuild/protobuf@2.9.0)(@connectrpc/connect@2.1.0(@bufbuild/protobuf@2.9.0))':
+ dependencies:
+ '@bufbuild/protobuf': 2.9.0
+ '@connectrpc/connect': 2.1.0(@bufbuild/protobuf@2.9.0)
+
+ '@connectrpc/connect@2.1.0(@bufbuild/protobuf@2.9.0)':
+ dependencies:
+ '@bufbuild/protobuf': 2.9.0
+
'@esbuild/aix-ppc64@0.25.9':
optional: true
@@ -4803,8 +3901,6 @@ snapshots:
'@eslint/core': 0.15.2
levn: 0.4.1
- '@exodus/schemasafe@1.3.0': {}
-
'@floating-ui/core@1.7.2':
dependencies:
'@floating-ui/utils': 0.2.10
@@ -4822,14 +3918,6 @@ snapshots:
'@floating-ui/utils@0.2.10': {}
- '@gerrit0/mini-shiki@3.8.1':
- dependencies:
- '@shikijs/engine-oniguruma': 3.8.1
- '@shikijs/langs': 3.8.1
- '@shikijs/themes': 3.8.1
- '@shikijs/types': 3.8.1
- '@shikijs/vscode-textmate': 10.0.2
-
'@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.6':
@@ -4843,24 +3931,6 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
- '@ibm-cloud/openapi-ruleset-utilities@1.9.0': {}
-
- '@ibm-cloud/openapi-ruleset@1.31.1':
- dependencies:
- '@ibm-cloud/openapi-ruleset-utilities': 1.9.0
- '@stoplight/spectral-formats': 1.8.2
- '@stoplight/spectral-functions': 1.10.1
- '@stoplight/spectral-rulesets': 1.22.0
- chalk: 4.1.2
- jsonschema: 1.5.0
- lodash: 4.17.21
- loglevel: 1.9.2
- loglevel-plugin-prefix: 0.8.4
- minimatch: 6.2.0
- validator: 13.15.15
- transitivePeerDependencies:
- - encoding
-
'@isaacs/fs-minipass@4.0.1':
dependencies:
minipass: 7.1.2
@@ -4886,20 +3956,6 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.4
- '@jsdevtools/ono@7.1.3': {}
-
- '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)':
- dependencies:
- jsep: 1.4.0
-
- '@jsep-plugin/regex@1.0.4(jsep@1.4.0)':
- dependencies:
- jsep: 1.4.0
-
- '@jsep-plugin/ternary@1.1.4(jsep@1.4.0)':
- dependencies:
- jsep: 1.4.0
-
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -4912,112 +3968,6 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1
- '@orval/angular@7.11.2(openapi-types@12.1.3)':
- dependencies:
- '@orval/core': 7.11.2(openapi-types@12.1.3)
- transitivePeerDependencies:
- - encoding
- - openapi-types
- - supports-color
-
- '@orval/axios@7.11.2(openapi-types@12.1.3)':
- dependencies:
- '@orval/core': 7.11.2(openapi-types@12.1.3)
- transitivePeerDependencies:
- - encoding
- - openapi-types
- - supports-color
-
- '@orval/core@7.11.2(openapi-types@12.1.3)':
- dependencies:
- '@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3)
- '@ibm-cloud/openapi-ruleset': 1.31.1
- acorn: 8.15.0
- ajv: 8.17.1
- chalk: 4.1.2
- compare-versions: 6.1.1
- debug: 4.4.1
- esbuild: 0.25.9
- esutils: 2.0.3
- fs-extra: 11.3.0
- globby: 11.1.0
- lodash.isempty: 4.4.0
- lodash.uniq: 4.5.0
- lodash.uniqby: 4.7.0
- lodash.uniqwith: 4.5.0
- micromatch: 4.0.8
- openapi3-ts: 4.4.0
- swagger2openapi: 7.0.8
- transitivePeerDependencies:
- - encoding
- - openapi-types
- - supports-color
-
- '@orval/fetch@7.11.2(openapi-types@12.1.3)':
- dependencies:
- '@orval/core': 7.11.2(openapi-types@12.1.3)
- transitivePeerDependencies:
- - encoding
- - openapi-types
- - supports-color
-
- '@orval/hono@7.11.2(openapi-types@12.1.3)':
- dependencies:
- '@orval/core': 7.11.2(openapi-types@12.1.3)
- '@orval/zod': 7.11.2(openapi-types@12.1.3)
- lodash.uniq: 4.5.0
- transitivePeerDependencies:
- - encoding
- - openapi-types
- - supports-color
-
- '@orval/mcp@7.11.2(openapi-types@12.1.3)':
- dependencies:
- '@orval/core': 7.11.2(openapi-types@12.1.3)
- '@orval/fetch': 7.11.2(openapi-types@12.1.3)
- '@orval/zod': 7.11.2(openapi-types@12.1.3)
- transitivePeerDependencies:
- - encoding
- - openapi-types
- - supports-color
-
- '@orval/mock@7.11.2(openapi-types@12.1.3)':
- dependencies:
- '@orval/core': 7.11.2(openapi-types@12.1.3)
- openapi3-ts: 4.4.0
- transitivePeerDependencies:
- - encoding
- - openapi-types
- - supports-color
-
- '@orval/query@7.11.2(openapi-types@12.1.3)':
- dependencies:
- '@orval/core': 7.11.2(openapi-types@12.1.3)
- '@orval/fetch': 7.11.2(openapi-types@12.1.3)
- lodash.omitby: 4.6.0
- transitivePeerDependencies:
- - encoding
- - openapi-types
- - supports-color
-
- '@orval/swr@7.11.2(openapi-types@12.1.3)':
- dependencies:
- '@orval/core': 7.11.2(openapi-types@12.1.3)
- '@orval/fetch': 7.11.2(openapi-types@12.1.3)
- transitivePeerDependencies:
- - encoding
- - openapi-types
- - supports-color
-
- '@orval/zod@7.11.2(openapi-types@12.1.3)':
- dependencies:
- '@orval/core': 7.11.2(openapi-types@12.1.3)
- lodash.uniq: 4.5.0
- transitivePeerDependencies:
- - encoding
- - openapi-types
- - supports-color
-
'@radix-ui/number@1.1.1': {}
'@radix-ui/primitive@1.0.0':
@@ -5242,28 +4192,6 @@ snapshots:
'@types/react': 19.1.12
'@types/react-dom': 19.1.9(@types/react@19.1.12)
- '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.2
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1)
- '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1)
- '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.12)(react@19.1.1)
- '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@19.1.1)
- '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@19.1.1)
- aria-hidden: 1.2.6
- react: 19.1.1
- react-dom: 19.1.1(react@19.1.1)
- react-remove-scroll: 2.7.1(@types/react@19.1.12)(react@19.1.1)
- optionalDependencies:
- '@types/react': 19.1.12
- '@types/react-dom': 19.1.9(@types/react@19.1.12)
-
'@radix-ui/react-dialog@1.1.15(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@radix-ui/primitive': 1.1.3
@@ -6189,212 +5117,29 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.44.2':
optional: true
- '@shikijs/engine-oniguruma@3.8.1':
- dependencies:
- '@shikijs/types': 3.8.1
- '@shikijs/vscode-textmate': 10.0.2
+ '@swc/core-darwin-arm64@1.13.3':
+ optional: true
- '@shikijs/langs@3.8.1':
- dependencies:
- '@shikijs/types': 3.8.1
+ '@swc/core-darwin-x64@1.13.3':
+ optional: true
- '@shikijs/themes@3.8.1':
- dependencies:
- '@shikijs/types': 3.8.1
+ '@swc/core-linux-arm-gnueabihf@1.13.3':
+ optional: true
- '@shikijs/types@3.8.1':
- dependencies:
- '@shikijs/vscode-textmate': 10.0.2
- '@types/hast': 3.0.4
+ '@swc/core-linux-arm64-gnu@1.13.3':
+ optional: true
- '@shikijs/vscode-textmate@10.0.2': {}
+ '@swc/core-linux-arm64-musl@1.13.3':
+ optional: true
- '@stoplight/better-ajv-errors@1.0.3(ajv@8.17.1)':
- dependencies:
- ajv: 8.17.1
- jsonpointer: 5.0.1
- leven: 3.1.0
+ '@swc/core-linux-x64-gnu@1.13.3':
+ optional: true
- '@stoplight/json-ref-readers@1.2.2':
- dependencies:
- node-fetch: 2.7.0
- tslib: 1.14.1
- transitivePeerDependencies:
- - encoding
+ '@swc/core-linux-x64-musl@1.13.3':
+ optional: true
- '@stoplight/json-ref-resolver@3.1.6':
- dependencies:
- '@stoplight/json': 3.21.7
- '@stoplight/path': 1.3.2
- '@stoplight/types': 13.20.0
- '@types/urijs': 1.19.25
- dependency-graph: 0.11.0
- fast-memoize: 2.5.2
- immer: 9.0.21
- lodash: 4.17.21
- tslib: 2.8.1
- urijs: 1.19.11
-
- '@stoplight/json@3.21.7':
- dependencies:
- '@stoplight/ordered-object-literal': 1.0.5
- '@stoplight/path': 1.3.2
- '@stoplight/types': 13.20.0
- jsonc-parser: 2.2.1
- lodash: 4.17.21
- safe-stable-stringify: 1.1.1
-
- '@stoplight/ordered-object-literal@1.0.5': {}
-
- '@stoplight/path@1.3.2': {}
-
- '@stoplight/spectral-core@1.20.0':
- dependencies:
- '@stoplight/better-ajv-errors': 1.0.3(ajv@8.17.1)
- '@stoplight/json': 3.21.7
- '@stoplight/path': 1.3.2
- '@stoplight/spectral-parsers': 1.0.5
- '@stoplight/spectral-ref-resolver': 1.0.5
- '@stoplight/spectral-runtime': 1.1.4
- '@stoplight/types': 13.6.0
- '@types/es-aggregate-error': 1.0.6
- '@types/json-schema': 7.0.15
- ajv: 8.17.1
- ajv-errors: 3.0.0(ajv@8.17.1)
- ajv-formats: 2.1.1(ajv@8.17.1)
- es-aggregate-error: 1.0.14
- jsonpath-plus: 10.3.0
- lodash: 4.17.21
- lodash.topath: 4.5.2
- minimatch: 3.1.2
- nimma: 0.2.3
- pony-cause: 1.1.1
- simple-eval: 1.0.1
- tslib: 2.8.1
- transitivePeerDependencies:
- - encoding
-
- '@stoplight/spectral-formats@1.8.2':
- dependencies:
- '@stoplight/json': 3.21.7
- '@stoplight/spectral-core': 1.20.0
- '@types/json-schema': 7.0.15
- tslib: 2.8.1
- transitivePeerDependencies:
- - encoding
-
- '@stoplight/spectral-functions@1.10.1':
- dependencies:
- '@stoplight/better-ajv-errors': 1.0.3(ajv@8.17.1)
- '@stoplight/json': 3.21.7
- '@stoplight/spectral-core': 1.20.0
- '@stoplight/spectral-formats': 1.8.2
- '@stoplight/spectral-runtime': 1.1.4
- ajv: 8.17.1
- ajv-draft-04: 1.0.0(ajv@8.17.1)
- ajv-errors: 3.0.0(ajv@8.17.1)
- ajv-formats: 2.1.1(ajv@8.17.1)
- lodash: 4.17.21
- tslib: 2.8.1
- transitivePeerDependencies:
- - encoding
-
- '@stoplight/spectral-parsers@1.0.5':
- dependencies:
- '@stoplight/json': 3.21.7
- '@stoplight/types': 14.1.1
- '@stoplight/yaml': 4.3.0
- tslib: 2.8.1
-
- '@stoplight/spectral-ref-resolver@1.0.5':
- dependencies:
- '@stoplight/json-ref-readers': 1.2.2
- '@stoplight/json-ref-resolver': 3.1.6
- '@stoplight/spectral-runtime': 1.1.4
- dependency-graph: 0.11.0
- tslib: 2.8.1
- transitivePeerDependencies:
- - encoding
-
- '@stoplight/spectral-rulesets@1.22.0':
- dependencies:
- '@asyncapi/specs': 6.8.1
- '@stoplight/better-ajv-errors': 1.0.3(ajv@8.17.1)
- '@stoplight/json': 3.21.7
- '@stoplight/spectral-core': 1.20.0
- '@stoplight/spectral-formats': 1.8.2
- '@stoplight/spectral-functions': 1.10.1
- '@stoplight/spectral-runtime': 1.1.4
- '@stoplight/types': 13.20.0
- '@types/json-schema': 7.0.15
- ajv: 8.17.1
- ajv-formats: 2.1.1(ajv@8.17.1)
- json-schema-traverse: 1.0.0
- leven: 3.1.0
- lodash: 4.17.21
- tslib: 2.8.1
- transitivePeerDependencies:
- - encoding
-
- '@stoplight/spectral-runtime@1.1.4':
- dependencies:
- '@stoplight/json': 3.21.7
- '@stoplight/path': 1.3.2
- '@stoplight/types': 13.20.0
- abort-controller: 3.0.0
- lodash: 4.17.21
- node-fetch: 2.7.0
- tslib: 2.8.1
- transitivePeerDependencies:
- - encoding
-
- '@stoplight/types@13.20.0':
- dependencies:
- '@types/json-schema': 7.0.15
- utility-types: 3.11.0
-
- '@stoplight/types@13.6.0':
- dependencies:
- '@types/json-schema': 7.0.15
- utility-types: 3.11.0
-
- '@stoplight/types@14.1.1':
- dependencies:
- '@types/json-schema': 7.0.15
- utility-types: 3.11.0
-
- '@stoplight/yaml-ast-parser@0.0.50': {}
-
- '@stoplight/yaml@4.3.0':
- dependencies:
- '@stoplight/ordered-object-literal': 1.0.5
- '@stoplight/types': 14.1.1
- '@stoplight/yaml-ast-parser': 0.0.50
- tslib: 2.8.1
-
- '@swc/core-darwin-arm64@1.13.3':
- optional: true
-
- '@swc/core-darwin-x64@1.13.3':
- optional: true
-
- '@swc/core-linux-arm-gnueabihf@1.13.3':
- optional: true
-
- '@swc/core-linux-arm64-gnu@1.13.3':
- optional: true
-
- '@swc/core-linux-arm64-musl@1.13.3':
- optional: true
-
- '@swc/core-linux-x64-gnu@1.13.3':
- optional: true
-
- '@swc/core-linux-x64-musl@1.13.3':
- optional: true
-
- '@swc/core-win32-arm64-msvc@1.13.3':
- optional: true
+ '@swc/core-win32-arm64-msvc@1.13.3':
+ optional: true
'@swc/core-win32-ia32-msvc@1.13.3':
optional: true
@@ -6495,8 +5240,32 @@ snapshots:
tailwindcss: 4.1.13
vite: 7.1.5(@types/node@24.3.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.4)(yaml@2.8.0)
+ '@tanstack/devtools-event-client@0.3.3': {}
+
+ '@tanstack/form-core@1.24.3':
+ dependencies:
+ '@tanstack/devtools-event-client': 0.3.3
+ '@tanstack/store': 0.7.7
+
'@tanstack/history@1.131.2': {}
+ '@tanstack/query-core@5.90.2': {}
+
+ '@tanstack/react-form@1.23.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ dependencies:
+ '@tanstack/form-core': 1.24.3
+ '@tanstack/react-store': 0.7.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ decode-formdata: 0.9.0
+ devalue: 5.4.1
+ react: 19.1.1
+ transitivePeerDependencies:
+ - react-dom
+
+ '@tanstack/react-query@5.90.2(react@19.1.1)':
+ dependencies:
+ '@tanstack/query-core': 5.90.2
+ react: 19.1.1
+
'@tanstack/react-router-devtools@1.131.36(@tanstack/react-router@1.131.36(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.36)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9)(tiny-invariant@1.3.3)':
dependencies:
'@tanstack/react-router': 1.131.36(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -6527,6 +5296,13 @@ snapshots:
react-dom: 19.1.1(react@19.1.1)
use-sync-external-store: 1.5.0(react@19.1.1)
+ '@tanstack/react-store@0.7.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ dependencies:
+ '@tanstack/store': 0.7.7
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ use-sync-external-store: 1.5.0(react@19.1.1)
+
'@tanstack/react-table@8.21.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@tanstack/table-core': 8.21.3
@@ -6601,6 +5377,8 @@ snapshots:
'@tanstack/store@0.7.2': {}
+ '@tanstack/store@0.7.7': {}
+
'@tanstack/table-core@8.21.3': {}
'@tanstack/virtual-file-routes@1.131.2': {}
@@ -6633,10 +5411,6 @@ snapshots:
dependencies:
'@types/ms': 2.1.0
- '@types/es-aggregate-error@1.0.6':
- dependencies:
- '@types/node': 24.3.1
-
'@types/estree-jsx@1.0.5':
dependencies:
'@types/estree': 1.0.8
@@ -6664,6 +5438,8 @@ snapshots:
dependencies:
undici-types: 7.10.0
+ '@types/prismjs@1.26.5': {}
+
'@types/react-dom@19.1.9(@types/react@19.1.12)':
dependencies:
'@types/react': 19.1.12
@@ -6676,8 +5452,6 @@ snapshots:
'@types/unist@3.0.3': {}
- '@types/urijs@1.19.25': {}
-
'@typescript-eslint/eslint-plugin@8.43.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)':
dependencies:
'@eslint-community/regexpp': 4.12.1
@@ -6781,28 +5555,12 @@ snapshots:
transitivePeerDependencies:
- '@swc/helpers'
- abort-controller@3.0.0:
- dependencies:
- event-target-shim: 5.0.1
-
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
acorn: 8.15.0
acorn@8.15.0: {}
- ajv-draft-04@1.0.0(ajv@8.17.1):
- optionalDependencies:
- ajv: 8.17.1
-
- ajv-errors@3.0.0(ajv@8.17.1):
- dependencies:
- ajv: 8.17.1
-
- ajv-formats@2.1.1(ajv@8.17.1):
- optionalDependencies:
- ajv: 8.17.1
-
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -6810,17 +5568,6 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
- ajv@8.17.1:
- dependencies:
- fast-deep-equal: 3.1.3
- fast-uri: 3.0.6
- json-schema-traverse: 1.0.0
- require-from-string: 2.0.2
-
- ansi-colors@4.1.3: {}
-
- ansi-regex@5.0.1: {}
-
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
@@ -6838,47 +5585,12 @@ snapshots:
dependencies:
tslib: 2.8.1
- array-buffer-byte-length@1.0.2:
- dependencies:
- call-bound: 1.0.4
- is-array-buffer: 3.0.5
-
array-move@3.0.1: {}
- array-union@2.1.0: {}
-
- arraybuffer.prototype.slice@1.0.4:
- dependencies:
- array-buffer-byte-length: 1.0.2
- call-bind: 1.0.8
- define-properties: 1.2.1
- es-abstract: 1.24.0
- es-errors: 1.3.0
- get-intrinsic: 1.3.0
- is-array-buffer: 3.0.5
-
ast-types@0.16.1:
dependencies:
tslib: 2.8.1
- astring@1.9.0: {}
-
- async-function@1.0.0: {}
-
- asynckit@0.4.0: {}
-
- available-typed-arrays@1.0.7:
- dependencies:
- possible-typed-array-names: 1.1.0
-
- axios@1.11.0:
- dependencies:
- follow-redirects: 1.15.9
- form-data: 4.0.4
- proxy-from-env: 1.1.0
- transitivePeerDependencies:
- - debug
-
babel-dead-code-elimination@1.0.10:
dependencies:
'@babel/core': 7.28.3
@@ -6914,27 +5626,6 @@ snapshots:
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.25.2)
- cac@6.7.14: {}
-
- call-bind-apply-helpers@1.0.2:
- dependencies:
- es-errors: 1.3.0
- function-bind: 1.1.2
-
- call-bind@1.0.8:
- dependencies:
- call-bind-apply-helpers: 1.0.2
- es-define-property: 1.0.1
- get-intrinsic: 1.3.0
- set-function-length: 1.2.2
-
- call-bound@1.0.4:
- dependencies:
- call-bind-apply-helpers: 1.0.2
- get-intrinsic: 1.3.0
-
- call-me-maybe@1.0.2: {}
-
callsites@3.1.0: {}
caniuse-lite@1.0.30001735: {}
@@ -6966,22 +5657,12 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
- chokidar@4.0.3:
- dependencies:
- readdirp: 4.1.2
-
chownr@3.0.0: {}
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1
- cliui@8.0.1:
- dependencies:
- string-width: 4.2.3
- strip-ansi: 6.0.1
- wrap-ansi: 7.0.0
-
clsx@2.1.1: {}
cmdk@0.2.1(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
@@ -6995,7 +5676,7 @@ snapshots:
cmdk@1.1.1(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1)
- '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@19.1.1)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
@@ -7010,14 +5691,8 @@ snapshots:
color-name@1.1.4: {}
- combined-stream@1.0.8:
- dependencies:
- delayed-stream: 1.0.0
-
comma-separated-tokens@2.0.3: {}
- compare-versions@6.1.1: {}
-
concat-map@0.0.1: {}
convert-source-map@2.0.0: {}
@@ -7070,30 +5745,14 @@ snapshots:
d3-timer@3.0.1: {}
- data-view-buffer@1.0.2:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- is-data-view: 1.0.2
-
- data-view-byte-length@1.0.2:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- is-data-view: 1.0.2
-
- data-view-byte-offset@1.0.1:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- is-data-view: 1.0.2
-
debug@4.4.1:
dependencies:
ms: 2.1.3
decimal.js-light@2.5.1: {}
+ decode-formdata@0.9.0: {}
+
decode-named-character-reference@1.2.0:
dependencies:
character-entities: 2.0.2
@@ -7102,49 +5761,25 @@ snapshots:
deepmerge@2.2.1: {}
- define-data-property@1.1.4:
- dependencies:
- es-define-property: 1.0.1
- es-errors: 1.3.0
- gopd: 1.2.0
-
- define-properties@1.2.1:
- dependencies:
- define-data-property: 1.1.4
- has-property-descriptors: 1.0.2
- object-keys: 1.1.1
-
- delayed-stream@1.0.0: {}
-
- dependency-graph@0.11.0: {}
-
dequal@2.0.3: {}
detect-libc@2.0.4: {}
detect-node-es@1.1.0: {}
+ devalue@5.4.1: {}
+
devlop@1.1.0:
dependencies:
dequal: 2.0.3
diff@8.0.2: {}
- dir-glob@3.0.1:
- dependencies:
- path-type: 4.0.0
-
dom-helpers@5.2.1:
dependencies:
'@babel/runtime': 7.27.6
csstype: 3.1.3
- dunder-proto@1.0.1:
- dependencies:
- call-bind-apply-helpers: 1.0.2
- es-errors: 1.3.0
- gopd: 1.2.0
-
electron-to-chromium@1.5.203: {}
emblor@1.4.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
@@ -7163,111 +5798,11 @@ snapshots:
- '@types/react'
- '@types/react-dom'
- emoji-regex@8.0.0: {}
-
enhanced-resolve@5.18.3:
dependencies:
graceful-fs: 4.2.11
tapable: 2.2.2
- enquirer@2.4.1:
- dependencies:
- ansi-colors: 4.1.3
- strip-ansi: 6.0.1
-
- entities@4.5.0: {}
-
- es-abstract@1.24.0:
- dependencies:
- array-buffer-byte-length: 1.0.2
- arraybuffer.prototype.slice: 1.0.4
- available-typed-arrays: 1.0.7
- call-bind: 1.0.8
- call-bound: 1.0.4
- data-view-buffer: 1.0.2
- data-view-byte-length: 1.0.2
- data-view-byte-offset: 1.0.1
- es-define-property: 1.0.1
- es-errors: 1.3.0
- es-object-atoms: 1.1.1
- es-set-tostringtag: 2.1.0
- es-to-primitive: 1.3.0
- function.prototype.name: 1.1.8
- get-intrinsic: 1.3.0
- get-proto: 1.0.1
- get-symbol-description: 1.1.0
- globalthis: 1.0.4
- gopd: 1.2.0
- has-property-descriptors: 1.0.2
- has-proto: 1.2.0
- has-symbols: 1.1.0
- hasown: 2.0.2
- internal-slot: 1.1.0
- is-array-buffer: 3.0.5
- is-callable: 1.2.7
- is-data-view: 1.0.2
- is-negative-zero: 2.0.3
- is-regex: 1.2.1
- is-set: 2.0.3
- is-shared-array-buffer: 1.0.4
- is-string: 1.1.1
- is-typed-array: 1.1.15
- is-weakref: 1.1.1
- math-intrinsics: 1.1.0
- object-inspect: 1.13.4
- object-keys: 1.1.1
- object.assign: 4.1.7
- own-keys: 1.0.1
- regexp.prototype.flags: 1.5.4
- safe-array-concat: 1.1.3
- safe-push-apply: 1.0.0
- safe-regex-test: 1.1.0
- set-proto: 1.0.0
- stop-iteration-iterator: 1.1.0
- string.prototype.trim: 1.2.10
- string.prototype.trimend: 1.0.9
- string.prototype.trimstart: 1.0.8
- typed-array-buffer: 1.0.3
- typed-array-byte-length: 1.0.3
- typed-array-byte-offset: 1.0.4
- typed-array-length: 1.0.7
- unbox-primitive: 1.1.0
- which-typed-array: 1.1.19
-
- es-aggregate-error@1.0.14:
- dependencies:
- define-data-property: 1.1.4
- define-properties: 1.2.1
- es-abstract: 1.24.0
- es-errors: 1.3.0
- function-bind: 1.1.2
- globalthis: 1.0.4
- has-property-descriptors: 1.0.2
- set-function-name: 2.0.2
-
- es-define-property@1.0.1: {}
-
- es-errors@1.3.0: {}
-
- es-object-atoms@1.1.1:
- dependencies:
- es-errors: 1.3.0
-
- es-set-tostringtag@2.1.0:
- dependencies:
- es-errors: 1.3.0
- get-intrinsic: 1.3.0
- has-tostringtag: 1.0.2
- hasown: 2.0.2
-
- es-to-primitive@1.3.0:
- dependencies:
- is-callable: 1.2.7
- is-date-object: 1.1.0
- is-symbol: 1.1.1
-
- es6-promise@3.3.1: {}
-
esbuild@0.25.9:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.9
@@ -7384,22 +5919,8 @@ snapshots:
esutils@2.0.3: {}
- event-target-shim@5.0.1: {}
-
eventemitter3@4.0.7: {}
- execa@5.1.1:
- dependencies:
- cross-spawn: 7.0.6
- get-stream: 6.0.1
- human-signals: 2.1.0
- is-stream: 2.0.1
- merge-stream: 2.0.0
- npm-run-path: 4.0.1
- onetime: 5.1.2
- signal-exit: 3.0.7
- strip-final-newline: 2.0.0
-
extend@3.0.2: {}
fast-deep-equal@3.1.3: {}
@@ -7418,12 +5939,6 @@ snapshots:
fast-levenshtein@2.0.6: {}
- fast-memoize@2.5.2: {}
-
- fast-safe-stringify@2.1.1: {}
-
- fast-uri@3.0.6: {}
-
fastq@1.19.1:
dependencies:
reusify: 1.1.0
@@ -7452,20 +5967,6 @@ snapshots:
flatted@3.3.3: {}
- follow-redirects@1.15.9: {}
-
- for-each@0.3.5:
- dependencies:
- is-callable: 1.2.7
-
- form-data@4.0.4:
- dependencies:
- asynckit: 0.4.0
- combined-stream: 1.0.8
- es-set-tostringtag: 2.1.0
- hasown: 2.0.2
- mime-types: 2.1.35
-
formik@2.4.6(react@19.1.1):
dependencies:
'@types/hoist-non-react-statics': 3.3.6
@@ -7478,60 +5979,13 @@ snapshots:
tiny-warning: 1.0.3
tslib: 2.8.1
- fs-extra@11.3.0:
- dependencies:
- graceful-fs: 4.2.11
- jsonfile: 6.1.0
- universalify: 2.0.1
-
fsevents@2.3.3:
optional: true
- function-bind@1.1.2: {}
-
- function.prototype.name@1.1.8:
- dependencies:
- call-bind: 1.0.8
- call-bound: 1.0.4
- define-properties: 1.2.1
- functions-have-names: 1.2.3
- hasown: 2.0.2
- is-callable: 1.2.7
-
- functions-have-names@1.2.3: {}
-
gensync@1.0.0-beta.2: {}
- get-caller-file@2.0.5: {}
-
- get-intrinsic@1.3.0:
- dependencies:
- call-bind-apply-helpers: 1.0.2
- es-define-property: 1.0.1
- es-errors: 1.3.0
- es-object-atoms: 1.1.1
- function-bind: 1.1.2
- get-proto: 1.0.1
- gopd: 1.2.0
- has-symbols: 1.1.0
- hasown: 2.0.2
- math-intrinsics: 1.1.0
-
get-nonce@1.0.1: {}
- get-proto@1.0.1:
- dependencies:
- dunder-proto: 1.0.1
- es-object-atoms: 1.1.1
-
- get-stream@6.0.1: {}
-
- get-symbol-description@1.1.0:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- get-intrinsic: 1.3.0
-
get-tsconfig@4.10.1:
dependencies:
resolve-pkg-maps: 1.0.0
@@ -7548,52 +6002,16 @@ snapshots:
globals@16.3.0: {}
- globalthis@1.0.4:
- dependencies:
- define-properties: 1.2.1
- gopd: 1.2.0
-
- globby@11.1.0:
- dependencies:
- array-union: 2.1.0
- dir-glob: 3.0.1
- fast-glob: 3.3.3
- ignore: 5.3.2
- merge2: 1.4.1
- slash: 3.0.0
-
goober@2.1.16(csstype@3.1.3):
dependencies:
csstype: 3.1.3
- gopd@1.2.0: {}
-
graceful-fs@4.2.11: {}
graphemer@1.4.0: {}
- has-bigints@1.1.0: {}
-
has-flag@4.0.0: {}
- has-property-descriptors@1.0.2:
- dependencies:
- es-define-property: 1.0.1
-
- has-proto@1.2.0:
- dependencies:
- dunder-proto: 1.0.1
-
- has-symbols@1.1.0: {}
-
- has-tostringtag@1.0.2:
- dependencies:
- has-symbols: 1.1.0
-
- hasown@2.0.2:
- dependencies:
- function-bind: 1.1.2
-
hast-util-to-jsx-runtime@2.3.6:
dependencies:
'@types/estree': 1.0.8
@@ -7624,10 +6042,6 @@ snapshots:
html-url-attributes@3.0.1: {}
- http2-client@1.3.5: {}
-
- human-signals@2.1.0: {}
-
ignore@5.3.2: {}
ignore@7.0.5: {}
@@ -7635,8 +6049,6 @@ snapshots:
immer@10.1.1:
optional: true
- immer@9.0.21: {}
-
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
@@ -7646,12 +6058,6 @@ snapshots:
inline-style-parser@0.2.4: {}
- internal-slot@1.1.0:
- dependencies:
- es-errors: 1.3.0
- hasown: 2.0.2
- side-channel: 1.1.0
-
internmap@2.0.3: {}
is-alphabetical@2.0.1: {}
@@ -7661,125 +6067,24 @@ snapshots:
is-alphabetical: 2.0.1
is-decimal: 2.0.1
- is-array-buffer@3.0.5:
- dependencies:
- call-bind: 1.0.8
- call-bound: 1.0.4
- get-intrinsic: 1.3.0
-
- is-async-function@2.1.1:
- dependencies:
- async-function: 1.0.0
- call-bound: 1.0.4
- get-proto: 1.0.1
- has-tostringtag: 1.0.2
- safe-regex-test: 1.1.0
-
- is-bigint@1.1.0:
- dependencies:
- has-bigints: 1.1.0
-
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
- is-boolean-object@1.2.2:
- dependencies:
- call-bound: 1.0.4
- has-tostringtag: 1.0.2
-
- is-callable@1.2.7: {}
-
- is-data-view@1.0.2:
- dependencies:
- call-bound: 1.0.4
- get-intrinsic: 1.3.0
- is-typed-array: 1.1.15
-
- is-date-object@1.1.0:
- dependencies:
- call-bound: 1.0.4
- has-tostringtag: 1.0.2
-
is-decimal@2.0.1: {}
is-extglob@2.1.1: {}
- is-finalizationregistry@1.1.1:
- dependencies:
- call-bound: 1.0.4
-
- is-fullwidth-code-point@3.0.0: {}
-
- is-generator-function@1.1.0:
- dependencies:
- call-bound: 1.0.4
- get-proto: 1.0.1
- has-tostringtag: 1.0.2
- safe-regex-test: 1.1.0
-
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-hexadecimal@2.0.1: {}
- is-map@2.0.3: {}
-
- is-negative-zero@2.0.3: {}
-
- is-number-object@1.1.1:
- dependencies:
- call-bound: 1.0.4
- has-tostringtag: 1.0.2
-
is-number@7.0.0: {}
is-plain-obj@4.1.0: {}
- is-regex@1.2.1:
- dependencies:
- call-bound: 1.0.4
- gopd: 1.2.0
- has-tostringtag: 1.0.2
- hasown: 2.0.2
-
- is-set@2.0.3: {}
-
- is-shared-array-buffer@1.0.4:
- dependencies:
- call-bound: 1.0.4
-
- is-stream@2.0.1: {}
-
- is-string@1.1.1:
- dependencies:
- call-bound: 1.0.4
- has-tostringtag: 1.0.2
-
- is-symbol@1.1.1:
- dependencies:
- call-bound: 1.0.4
- has-symbols: 1.1.0
- safe-regex-test: 1.1.0
-
- is-typed-array@1.1.15:
- dependencies:
- which-typed-array: 1.1.19
-
- is-weakmap@2.0.2: {}
-
- is-weakref@1.1.1:
- dependencies:
- call-bound: 1.0.4
-
- is-weakset@2.0.4:
- dependencies:
- call-bound: 1.0.4
- get-intrinsic: 1.3.0
-
- isarray@2.0.5: {}
-
isbot@5.1.29: {}
isexe@2.0.0: {}
@@ -7792,44 +6097,20 @@ snapshots:
dependencies:
argparse: 2.0.1
- jsep@1.4.0: {}
-
jsesc@3.1.0: {}
json-buffer@3.0.1: {}
json-schema-traverse@0.4.1: {}
- json-schema-traverse@1.0.0: {}
-
json-stable-stringify-without-jsonify@1.0.1: {}
json5@2.2.3: {}
- jsonc-parser@2.2.1: {}
-
- jsonfile@6.1.0:
- dependencies:
- universalify: 2.0.1
- optionalDependencies:
- graceful-fs: 4.2.11
-
- jsonpath-plus@10.3.0:
- dependencies:
- '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0)
- '@jsep-plugin/regex': 1.0.4(jsep@1.4.0)
- jsep: 1.4.0
-
- jsonpointer@5.0.1: {}
-
- jsonschema@1.5.0: {}
-
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
- leven@3.1.0: {}
-
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
@@ -7880,36 +6161,16 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.30.1
lightningcss-win32-x64-msvc: 1.30.1
- linkify-it@5.0.0:
- dependencies:
- uc.micro: 2.1.0
-
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
lodash-es@4.17.21: {}
- lodash.isempty@4.4.0: {}
-
lodash.merge@4.6.2: {}
- lodash.omitby@4.6.0: {}
-
- lodash.topath@4.5.2: {}
-
- lodash.uniq@4.5.0: {}
-
- lodash.uniqby@4.7.0: {}
-
- lodash.uniqwith@4.5.0: {}
-
lodash@4.17.21: {}
- loglevel-plugin-prefix@0.8.4: {}
-
- loglevel@1.9.2: {}
-
longest-streak@3.1.0: {}
loose-envify@1.4.0:
@@ -7924,25 +6185,12 @@ snapshots:
dependencies:
react: 19.1.1
- lunr@2.3.9: {}
-
magic-string@0.30.19:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
- markdown-it@14.1.0:
- dependencies:
- argparse: 2.0.1
- entities: 4.5.0
- linkify-it: 5.0.0
- mdurl: 2.0.0
- punycode.js: 2.3.1
- uc.micro: 2.1.0
-
markdown-table@3.0.4: {}
- math-intrinsics@1.1.0: {}
-
mdast-util-find-and-replace@3.0.2:
dependencies:
'@types/mdast': 4.0.4
@@ -8096,10 +6344,6 @@ snapshots:
dependencies:
'@types/mdast': 4.0.4
- mdurl@2.0.0: {}
-
- merge-stream@2.0.0: {}
-
merge2@1.4.1: {}
micromark-core-commonmark@2.0.3:
@@ -8298,22 +6542,10 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
- mime-db@1.52.0: {}
-
- mime-types@2.1.35:
- dependencies:
- mime-db: 1.52.0
-
- mimic-fn@2.1.0: {}
-
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.12
- minimatch@6.2.0:
- dependencies:
- brace-expansion: 2.0.2
-
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.2
@@ -8332,96 +6564,12 @@ snapshots:
natural-compare@1.4.0: {}
- nimma@0.2.3:
- dependencies:
- '@jsep-plugin/regex': 1.0.4(jsep@1.4.0)
- '@jsep-plugin/ternary': 1.1.4(jsep@1.4.0)
- astring: 1.9.0
- jsep: 1.4.0
- optionalDependencies:
- jsonpath-plus: 10.3.0
- lodash.topath: 4.5.2
-
- node-fetch-h2@2.3.0:
- dependencies:
- http2-client: 1.3.5
-
- node-fetch@2.7.0:
- dependencies:
- whatwg-url: 5.0.0
-
- node-readfiles@0.2.0:
- dependencies:
- es6-promise: 3.3.1
-
node-releases@2.0.19: {}
normalize-path@3.0.0: {}
- npm-run-path@4.0.1:
- dependencies:
- path-key: 3.1.1
-
- oas-kit-common@1.0.8:
- dependencies:
- fast-safe-stringify: 2.1.1
-
- oas-linter@3.2.2:
- dependencies:
- '@exodus/schemasafe': 1.3.0
- should: 13.2.3
- yaml: 1.10.2
-
- oas-resolver@2.5.6:
- dependencies:
- node-fetch-h2: 2.3.0
- oas-kit-common: 1.0.8
- reftools: 1.1.9
- yaml: 1.10.2
- yargs: 17.7.2
-
- oas-schema-walker@1.1.5: {}
-
- oas-validator@5.0.8:
- dependencies:
- call-me-maybe: 1.0.2
- oas-kit-common: 1.0.8
- oas-linter: 3.2.2
- oas-resolver: 2.5.6
- oas-schema-walker: 1.1.5
- reftools: 1.1.9
- should: 13.2.3
- yaml: 1.10.2
-
object-assign@4.1.1: {}
- object-inspect@1.13.4: {}
-
- object-keys@1.1.1: {}
-
- object.assign@4.1.7:
- dependencies:
- call-bind: 1.0.8
- call-bound: 1.0.4
- define-properties: 1.2.1
- es-object-atoms: 1.1.1
- has-symbols: 1.1.0
- object-keys: 1.1.1
-
- onetime@5.1.2:
- dependencies:
- mimic-fn: 2.1.0
-
- openapi-types@12.1.3: {}
-
- openapi3-ts@4.2.2:
- dependencies:
- yaml: 2.8.0
-
- openapi3-ts@4.4.0:
- dependencies:
- yaml: 2.8.0
-
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -8431,45 +6579,6 @@ snapshots:
type-check: 0.4.0
word-wrap: 1.2.5
- orval@7.11.2(openapi-types@12.1.3):
- dependencies:
- '@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3)
- '@orval/angular': 7.11.2(openapi-types@12.1.3)
- '@orval/axios': 7.11.2(openapi-types@12.1.3)
- '@orval/core': 7.11.2(openapi-types@12.1.3)
- '@orval/fetch': 7.11.2(openapi-types@12.1.3)
- '@orval/hono': 7.11.2(openapi-types@12.1.3)
- '@orval/mcp': 7.11.2(openapi-types@12.1.3)
- '@orval/mock': 7.11.2(openapi-types@12.1.3)
- '@orval/query': 7.11.2(openapi-types@12.1.3)
- '@orval/swr': 7.11.2(openapi-types@12.1.3)
- '@orval/zod': 7.11.2(openapi-types@12.1.3)
- ajv: 8.17.1
- cac: 6.7.14
- chalk: 4.1.2
- chokidar: 4.0.3
- enquirer: 2.4.1
- execa: 5.1.1
- find-up: 5.0.0
- fs-extra: 11.3.0
- lodash.uniq: 4.5.0
- openapi3-ts: 4.2.2
- string-argv: 0.3.2
- tsconfck: 2.1.2(typescript@5.9.2)
- typedoc: 0.28.7(typescript@5.9.2)
- typedoc-plugin-markdown: 4.8.1(typedoc@0.28.7(typescript@5.9.2))
- typescript: 5.9.2
- transitivePeerDependencies:
- - encoding
- - openapi-types
- - supports-color
-
- own-keys@1.0.1:
- dependencies:
- get-intrinsic: 1.3.0
- object-keys: 1.1.1
- safe-push-apply: 1.0.0
-
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
@@ -8496,18 +6605,12 @@ snapshots:
path-key@3.1.1: {}
- path-type@4.0.0: {}
-
picocolors@1.1.1: {}
picomatch@2.3.1: {}
picomatch@4.0.3: {}
- pony-cause@1.1.1: {}
-
- possible-typed-array-names@1.1.0: {}
-
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
@@ -8522,6 +6625,12 @@ snapshots:
prettier@3.6.2: {}
+ prism-react-renderer@2.4.1(react@19.1.1):
+ dependencies:
+ '@types/prismjs': 1.26.5
+ clsx: 2.1.1
+ react: 19.1.1
+
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
@@ -8532,10 +6641,6 @@ snapshots:
property-information@7.1.0: {}
- proxy-from-env@1.1.0: {}
-
- punycode.js@2.3.1: {}
-
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
@@ -8713,8 +6818,6 @@ snapshots:
dependencies:
picomatch: 2.3.1
- readdirp@4.1.2: {}
-
recast@0.23.11:
dependencies:
ast-types: 0.16.1
@@ -8740,28 +6843,6 @@ snapshots:
tiny-invariant: 1.3.3
victory-vendor: 36.9.2
- reflect.getprototypeof@1.0.10:
- dependencies:
- call-bind: 1.0.8
- define-properties: 1.2.1
- es-abstract: 1.24.0
- es-errors: 1.3.0
- es-object-atoms: 1.1.1
- get-intrinsic: 1.3.0
- get-proto: 1.0.1
- which-builtin-type: 1.2.1
-
- reftools@1.1.9: {}
-
- regexp.prototype.flags@1.5.4:
- dependencies:
- call-bind: 1.0.8
- define-properties: 1.2.1
- es-errors: 1.3.0
- get-proto: 1.0.1
- gopd: 1.2.0
- set-function-name: 2.0.2
-
remark-gfm@4.0.1:
dependencies:
'@types/mdast': 4.0.4
@@ -8796,10 +6877,6 @@ snapshots:
mdast-util-to-markdown: 2.1.2
unified: 11.0.5
- require-directory@2.1.1: {}
-
- require-from-string@2.0.2: {}
-
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
@@ -8836,27 +6913,6 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
- safe-array-concat@1.1.3:
- dependencies:
- call-bind: 1.0.8
- call-bound: 1.0.4
- get-intrinsic: 1.3.0
- has-symbols: 1.1.0
- isarray: 2.0.5
-
- safe-push-apply@1.0.0:
- dependencies:
- es-errors: 1.3.0
- isarray: 2.0.5
-
- safe-regex-test@1.1.0:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- is-regex: 1.2.1
-
- safe-stable-stringify@1.1.1: {}
-
scheduler@0.26.0: {}
semver@6.3.1: {}
@@ -8869,96 +6925,12 @@ snapshots:
seroval@1.3.2: {}
- set-function-length@1.2.2:
- dependencies:
- define-data-property: 1.1.4
- es-errors: 1.3.0
- function-bind: 1.1.2
- get-intrinsic: 1.3.0
- gopd: 1.2.0
- has-property-descriptors: 1.0.2
-
- set-function-name@2.0.2:
- dependencies:
- define-data-property: 1.1.4
- es-errors: 1.3.0
- functions-have-names: 1.2.3
- has-property-descriptors: 1.0.2
-
- set-proto@1.0.0:
- dependencies:
- dunder-proto: 1.0.1
- es-errors: 1.3.0
- es-object-atoms: 1.1.1
-
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
- should-equal@2.0.0:
- dependencies:
- should-type: 1.4.0
-
- should-format@3.0.3:
- dependencies:
- should-type: 1.4.0
- should-type-adaptors: 1.1.0
-
- should-type-adaptors@1.1.0:
- dependencies:
- should-type: 1.4.0
- should-util: 1.0.1
-
- should-type@1.4.0: {}
-
- should-util@1.0.1: {}
-
- should@13.2.3:
- dependencies:
- should-equal: 2.0.0
- should-format: 3.0.3
- should-type: 1.4.0
- should-type-adaptors: 1.1.0
- should-util: 1.0.1
-
- side-channel-list@1.0.0:
- dependencies:
- es-errors: 1.3.0
- object-inspect: 1.13.4
-
- side-channel-map@1.0.1:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- get-intrinsic: 1.3.0
- object-inspect: 1.13.4
-
- side-channel-weakmap@1.0.2:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- get-intrinsic: 1.3.0
- object-inspect: 1.13.4
- side-channel-map: 1.0.1
-
- side-channel@1.1.0:
- dependencies:
- es-errors: 1.3.0
- object-inspect: 1.13.4
- side-channel-list: 1.0.0
- side-channel-map: 1.0.1
- side-channel-weakmap: 1.0.2
-
- signal-exit@3.0.7: {}
-
- simple-eval@1.0.1:
- dependencies:
- jsep: 1.4.0
-
- slash@3.0.0: {}
-
solid-js@1.9.9:
dependencies:
csstype: 3.1.3
@@ -8978,53 +6950,11 @@ snapshots:
space-separated-tokens@2.0.2: {}
- stop-iteration-iterator@1.1.0:
- dependencies:
- es-errors: 1.3.0
- internal-slot: 1.1.0
-
- string-argv@0.3.2: {}
-
- string-width@4.2.3:
- dependencies:
- emoji-regex: 8.0.0
- is-fullwidth-code-point: 3.0.0
- strip-ansi: 6.0.1
-
- string.prototype.trim@1.2.10:
- dependencies:
- call-bind: 1.0.8
- call-bound: 1.0.4
- define-data-property: 1.1.4
- define-properties: 1.2.1
- es-abstract: 1.24.0
- es-object-atoms: 1.1.1
- has-property-descriptors: 1.0.2
-
- string.prototype.trimend@1.0.9:
- dependencies:
- call-bind: 1.0.8
- call-bound: 1.0.4
- define-properties: 1.2.1
- es-object-atoms: 1.1.1
-
- string.prototype.trimstart@1.0.8:
- dependencies:
- call-bind: 1.0.8
- define-properties: 1.2.1
- es-object-atoms: 1.1.1
-
stringify-entities@4.0.4:
dependencies:
character-entities-html4: 2.1.0
character-entities-legacy: 3.0.0
- strip-ansi@6.0.1:
- dependencies:
- ansi-regex: 5.0.1
-
- strip-final-newline@2.0.0: {}
-
strip-json-comments@3.1.1: {}
style-to-js@1.1.17:
@@ -9039,22 +6969,6 @@ snapshots:
dependencies:
has-flag: 4.0.0
- swagger2openapi@7.0.8:
- dependencies:
- call-me-maybe: 1.0.2
- node-fetch: 2.7.0
- node-fetch-h2: 2.3.0
- node-readfiles: 0.2.0
- oas-kit-common: 1.0.8
- oas-resolver: 2.5.6
- oas-schema-walker: 1.1.5
- oas-validator: 5.0.8
- reftools: 1.1.9
- yaml: 1.10.2
- yargs: 17.7.2
- transitivePeerDependencies:
- - encoding
-
tailwind-merge@3.3.1: {}
tailwindcss@4.1.13: {}
@@ -9087,8 +7001,6 @@ snapshots:
toposort@2.0.2: {}
- tr46@0.0.3: {}
-
trim-lines@3.0.1: {}
trough@2.2.0: {}
@@ -9097,12 +7009,6 @@ snapshots:
dependencies:
typescript: 5.9.2
- tsconfck@2.1.2(typescript@5.9.2):
- optionalDependencies:
- typescript: 5.9.2
-
- tslib@1.14.1: {}
-
tslib@2.0.1: {}
tslib@2.8.1: {}
@@ -9122,52 +7028,6 @@ snapshots:
type-fest@2.19.0: {}
- typed-array-buffer@1.0.3:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- is-typed-array: 1.1.15
-
- typed-array-byte-length@1.0.3:
- dependencies:
- call-bind: 1.0.8
- for-each: 0.3.5
- gopd: 1.2.0
- has-proto: 1.2.0
- is-typed-array: 1.1.15
-
- typed-array-byte-offset@1.0.4:
- dependencies:
- available-typed-arrays: 1.0.7
- call-bind: 1.0.8
- for-each: 0.3.5
- gopd: 1.2.0
- has-proto: 1.2.0
- is-typed-array: 1.1.15
- reflect.getprototypeof: 1.0.10
-
- typed-array-length@1.0.7:
- dependencies:
- call-bind: 1.0.8
- for-each: 0.3.5
- gopd: 1.2.0
- is-typed-array: 1.1.15
- possible-typed-array-names: 1.1.0
- reflect.getprototypeof: 1.0.10
-
- typedoc-plugin-markdown@4.8.1(typedoc@0.28.7(typescript@5.9.2)):
- dependencies:
- typedoc: 0.28.7(typescript@5.9.2)
-
- typedoc@0.28.7(typescript@5.9.2):
- dependencies:
- '@gerrit0/mini-shiki': 3.8.1
- lunr: 2.3.9
- markdown-it: 14.1.0
- minimatch: 9.0.5
- typescript: 5.9.2
- yaml: 2.8.0
-
typescript-eslint@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2):
dependencies:
'@typescript-eslint/eslint-plugin': 8.43.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)
@@ -9181,15 +7041,6 @@ snapshots:
typescript@5.9.2: {}
- uc.micro@2.1.0: {}
-
- unbox-primitive@1.1.0:
- dependencies:
- call-bound: 1.0.4
- has-bigints: 1.1.0
- has-symbols: 1.1.0
- which-boxed-primitive: 1.1.1
-
undici-types@7.10.0: {}
unified@11.0.5:
@@ -9225,8 +7076,6 @@ snapshots:
unist-util-is: 6.0.0
unist-util-visit-parents: 6.0.1
- universalify@2.0.1: {}
-
unplugin@2.3.6:
dependencies:
'@jridgewell/remapping': 2.3.5
@@ -9244,8 +7093,6 @@ snapshots:
dependencies:
punycode: 2.3.1
- urijs@1.19.11: {}
-
use-callback-ref@1.3.3(@types/react@19.1.12)(react@19.1.1):
dependencies:
react: 19.1.1
@@ -9265,10 +7112,6 @@ snapshots:
dependencies:
react: 19.1.1
- utility-types@3.11.0: {}
-
- validator@13.15.15: {}
-
vfile-message@4.0.3:
dependencies:
'@types/unist': 3.0.3
@@ -9312,89 +7155,20 @@ snapshots:
tsx: 4.20.4
yaml: 2.8.0
- webidl-conversions@3.0.1: {}
-
webpack-virtual-modules@0.6.2: {}
- whatwg-url@5.0.0:
- dependencies:
- tr46: 0.0.3
- webidl-conversions: 3.0.1
-
- which-boxed-primitive@1.1.1:
- dependencies:
- is-bigint: 1.1.0
- is-boolean-object: 1.2.2
- is-number-object: 1.1.1
- is-string: 1.1.1
- is-symbol: 1.1.1
-
- which-builtin-type@1.2.1:
- dependencies:
- call-bound: 1.0.4
- function.prototype.name: 1.1.8
- has-tostringtag: 1.0.2
- is-async-function: 2.1.1
- is-date-object: 1.1.0
- is-finalizationregistry: 1.1.1
- is-generator-function: 1.1.0
- is-regex: 1.2.1
- is-weakref: 1.1.1
- isarray: 2.0.5
- which-boxed-primitive: 1.1.1
- which-collection: 1.0.2
- which-typed-array: 1.1.19
-
- which-collection@1.0.2:
- dependencies:
- is-map: 2.0.3
- is-set: 2.0.3
- is-weakmap: 2.0.2
- is-weakset: 2.0.4
-
- which-typed-array@1.1.19:
- dependencies:
- available-typed-arrays: 1.0.7
- call-bind: 1.0.8
- call-bound: 1.0.4
- for-each: 0.3.5
- get-proto: 1.0.1
- gopd: 1.2.0
- has-tostringtag: 1.0.2
-
which@2.0.2:
dependencies:
isexe: 2.0.0
word-wrap@1.2.5: {}
- wrap-ansi@7.0.0:
- dependencies:
- ansi-styles: 4.3.0
- string-width: 4.2.3
- strip-ansi: 6.0.1
-
- y18n@5.0.8: {}
-
yallist@3.1.1: {}
yallist@5.0.0: {}
- yaml@1.10.2: {}
-
- yaml@2.8.0: {}
-
- yargs-parser@21.1.1: {}
-
- yargs@17.7.2:
- dependencies:
- cliui: 8.0.1
- escalade: 3.2.0
- get-caller-file: 2.0.5
- require-directory: 2.1.1
- string-width: 4.2.3
- y18n: 5.0.8
- yargs-parser: 21.1.1
+ yaml@2.8.0:
+ optional: true
yocto-queue@0.1.0: {}
@@ -9407,6 +7181,8 @@ snapshots:
zod@3.25.76: {}
+ zod@4.1.11: {}
+
zustand@5.0.8(@types/react@19.1.12)(immer@10.1.1)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)):
optionalDependencies:
'@types/react': 19.1.12
diff --git a/frontend/public/logo.svg b/frontend/public/logo.svg
new file mode 100644
index 0000000..5d8f168
--- /dev/null
+++ b/frontend/public/logo.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/frontend/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/src/api/api.ts b/frontend/src/api/api.ts
new file mode 100644
index 0000000..bdf0d3e
--- /dev/null
+++ b/frontend/src/api/api.ts
@@ -0,0 +1,88 @@
+import {
+ Code,
+ ConnectError,
+ createClient,
+ type Interceptor,
+} from "@connectrpc/connect";
+import { createConnectTransport } from "@connectrpc/connect-web";
+import { QueryClient } from "@tanstack/react-query";
+
+import { NotificationService } from "./gen/sentinel/notifications/v1/notifications_pb";
+import { AgentsService } from "./gen/sentinel/agents/v1/agents_pb";
+import { AuthService } from "./gen/sentinel/auth/v1/auth_pb";
+import { ProjectsService } from "./gen/sentinel/projects/v1/projects_pb";
+import { SystemService } from "./gen/sentinel/system/v1/service_pb";
+import { useAuthStore } from "@/app/stores/auth";
+import { useProjectStore } from "@/app/stores/projects";
+
+export const VITE_SERVER_API_BASE_URL =
+ import.meta.env.VITE_SERVER_API_BASE_URL || window.location.origin + "/api";
+
+const skipAuthMethods = ["RefreshToken", "Authorization"];
+
+const authInterceptor: Interceptor = (next) => async (req) => {
+ const { session, refreshToken } = useAuthStore.getState();
+ const { selectedProjectId } = useProjectStore.getState();
+
+ if (selectedProjectId) {
+ req.header.set("X-Project-ID", selectedProjectId);
+ }
+
+ if (session?.accessToken) {
+ req.header.set("Authorization", `Bearer ${session.accessToken}`);
+ }
+
+ try {
+ return await next(req);
+ } catch (error) {
+ if (
+ error instanceof ConnectError &&
+ error.code === Code.Unauthenticated &&
+ !skipAuthMethods.includes(req.method.name)
+ ) {
+ try {
+ await refreshToken();
+ if (!session?.accessToken) {
+ throw new Error("No access token");
+ }
+
+ req.header.set("Authorization", `Bearer ${session?.accessToken}`);
+ return await next(req);
+ } catch (refreshError) {
+ console.error("Failed to refresh token:", refreshError);
+ throw refreshError;
+ }
+ }
+ throw error;
+ }
+
+ // try {
+ // return await next(req);
+ // } catch (error) {
+ // if (error instanceof ConnectError && error.code === Code.Unauthenticated) {
+ // clear();
+ // window.location.href = "/login";
+ // }
+ // throw error;
+ // }
+};
+
+export const connectTransport = createConnectTransport({
+ baseUrl: VITE_SERVER_API_BASE_URL,
+ interceptors: [authInterceptor],
+});
+
+export const connectQueryClient = new QueryClient();
+
+export const systemClient = createClient(SystemService, connectTransport);
+
+export const authClient = createClient(AuthService, connectTransport);
+
+export const projectsClient = createClient(ProjectsService, connectTransport);
+
+export const agentsClient = createClient(AgentsService, connectTransport);
+
+export const notificationsClient = createClient(
+ NotificationService,
+ connectTransport,
+);
diff --git a/frontend/src/api/gen/sentinel/agents/v1/agents-AgentsService_connectquery.ts b/frontend/src/api/gen/sentinel/agents/v1/agents-AgentsService_connectquery.ts
new file mode 100644
index 0000000..b0c763d
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/agents/v1/agents-AgentsService_connectquery.ts
@@ -0,0 +1,30 @@
+// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts"
+// @generated from file sentinel/agents/v1/agents.proto (package sentinel.agents.v1, syntax proto3)
+/* eslint-disable */
+
+import { AgentsService } from "./agents_pb";
+
+/**
+ * @generated from rpc sentinel.agents.v1.AgentsService.AgentsList
+ */
+export const agentsList = AgentsService.method.agentsList;
+
+/**
+ * @generated from rpc sentinel.agents.v1.AgentsService.AgentsGet
+ */
+export const agentsGet = AgentsService.method.agentsGet;
+
+/**
+ * @generated from rpc sentinel.agents.v1.AgentsService.AgentsCreate
+ */
+export const agentsCreate = AgentsService.method.agentsCreate;
+
+/**
+ * @generated from rpc sentinel.agents.v1.AgentsService.AgentsUpdate
+ */
+export const agentsUpdate = AgentsService.method.agentsUpdate;
+
+/**
+ * @generated from rpc sentinel.agents.v1.AgentsService.AgentsDelete
+ */
+export const agentsDelete = AgentsService.method.agentsDelete;
diff --git a/frontend/src/api/gen/sentinel/agents/v1/agents_pb.ts b/frontend/src/api/gen/sentinel/agents/v1/agents_pb.ts
new file mode 100644
index 0000000..490d840
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/agents/v1/agents_pb.ts
@@ -0,0 +1,537 @@
+// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
+// @generated from file sentinel/agents/v1/agents.proto (package sentinel.agents.v1, syntax proto3)
+/* eslint-disable */
+
+import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
+import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
+import type { Timestamp } from "@bufbuild/protobuf/wkt";
+import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
+import type { SystemInfo } from "../../system/v1/service_pb";
+import { file_sentinel_system_v1_service } from "../../system/v1/service_pb";
+import type { Message } from "@bufbuild/protobuf";
+
+/**
+ * Describes the file sentinel/agents/v1/agents.proto.
+ */
+export const file_sentinel_agents_v1_agents: GenFile = /*@__PURE__*/
+ fileDesc("Ch9zZW50aW5lbC9hZ2VudHMvdjEvYWdlbnRzLnByb3RvEhJzZW50aW5lbC5hZ2VudHMudjEiDQoLQWdlbnRDb25maWcikAQKBUFnZW50EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSEgoKdG9rZW5faGludBgEIAEoCRIYCgtmaW5nZXJwcmludBgFIAEoCUgAiAEBEisKBGtpbmQYBiABKA4yHS5zZW50aW5lbC5hZ2VudHMudjEuQWdlbnRLaW5kEi8KBnN0YXR1cxgHIAEoDjIfLnNlbnRpbmVsLmFnZW50cy52MS5BZ2VudFN0YXR1cxISCgppc19lbmFibGVkGAggASgIEhUKCGxvY2F0aW9uGAkgASgJSAGIAQESDAoEdGFncxgKIAMoCRIvCgZjb25maWcYCyABKAsyHy5zZW50aW5lbC5hZ2VudHMudjEuQWdlbnRDb25maWcSMwoLc3lzdGVtX2luZm8YDCABKAsyHi5zZW50aW5lbC5zeXN0ZW0udjEuU3lzdGVtSW5mbxIwCgxsYXN0X3NlZW5fYXQYDSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCmNyZWF0ZWRfYXQYDiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYDyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQg4KDF9maW5nZXJwcmludEILCglfbG9jYXRpb24iEwoRQWdlbnRzTGlzdFJlcXVlc3QiTQoSQWdlbnRzTGlzdFJlc3BvbnNlEigKBWl0ZW1zGAEgAygLMhkuc2VudGluZWwuYWdlbnRzLnYxLkFnZW50Eg0KBWNvdW50GAIgASgNIh4KEEFnZW50c0dldFJlcXVlc3QSCgoCaWQYASABKAkiPAoRQWdlbnRzR2V0UmVzcG9uc2USJwoEaXRlbRgBIAEoCzIZLnNlbnRpbmVsLmFnZW50cy52MS5BZ2VudCKLAQoTQWdlbnRzQ3JlYXRlUmVxdWVzdBIMCgRuYW1lGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEhIKCmlzX2VuYWJsZWQYAyABKAgSDAoEdGFncxgEIAMoCRIvCgZjb25maWcYBSABKAsyHy5zZW50aW5lbC5hZ2VudHMudjEuQWdlbnRDb25maWci7QIKFEFnZW50c0NyZWF0ZVJlc3BvbnNlEgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSDQoFdG9rZW4YBCABKAkSEgoKdG9rZW5faGludBgFIAEoCRIvCgZzdGF0dXMYBiABKA4yHy5zZW50aW5lbC5hZ2VudHMudjEuQWdlbnRTdGF0dXMSKwoEa2luZBgHIAEoDjIdLnNlbnRpbmVsLmFnZW50cy52MS5BZ2VudEtpbmQSEgoKaXNfZW5hYmxlZBgIIAEoCBIVCghsb2NhdGlvbhgJIAEoCUgAiAEBEgwKBHRhZ3MYCiADKAkSLwoGY29uZmlnGAsgASgLMh8uc2VudGluZWwuYWdlbnRzLnYxLkFnZW50Q29uZmlnEi4KCmNyZWF0ZWRfYXQYDCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgsKCV9sb2NhdGlvbiK7AQoTQWdlbnRzVXBkYXRlUmVxdWVzdBIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEhIKCmlzX2VuYWJsZWQYBCABKAgSFQoIbG9jYXRpb24YBSABKAlIAIgBARIMCgR0YWdzGAYgAygJEi8KBmNvbmZpZxgHIAEoCzIfLnNlbnRpbmVsLmFnZW50cy52MS5BZ2VudENvbmZpZ0ILCglfbG9jYXRpb24iPwoUQWdlbnRzVXBkYXRlUmVzcG9uc2USJwoEaXRlbRgBIAEoCzIZLnNlbnRpbmVsLmFnZW50cy52MS5BZ2VudCIhChNBZ2VudHNEZWxldGVSZXF1ZXN0EgoKAmlkGAEgASgJIhYKFEFnZW50c0RlbGV0ZVJlc3BvbnNlIhgKFkFnZW50c1N1YnNjcmliZVJlcXVlc3QiGQoXQWdlbnRzU3Vic2NyaWJlUmVzcG9uc2UqXwoLQWdlbnRTdGF0dXMSHAoYQUdFTlRfU1RBVFVTX1VOU1BFQ0lGSUVEEAASFwoTQUdFTlRfU1RBVFVTX0FDVElWRRABEhkKFUFHRU5UX1NUQVRVU19JTkFDVElWRRACKlQKCUFnZW50S2luZBIaChZBR0VOVF9LSU5EX1VOU1BFQ0lGSUVEEAASEgoOQUdFTlRfS0lORF9IVUIQARIXChNBR0VOVF9LSU5EX0VYVEVSTkFMEAIy3QQKDUFnZW50c1NlcnZpY2USWwoKQWdlbnRzTGlzdBIlLnNlbnRpbmVsLmFnZW50cy52MS5BZ2VudHNMaXN0UmVxdWVzdBomLnNlbnRpbmVsLmFnZW50cy52MS5BZ2VudHNMaXN0UmVzcG9uc2USWAoJQWdlbnRzR2V0EiQuc2VudGluZWwuYWdlbnRzLnYxLkFnZW50c0dldFJlcXVlc3QaJS5zZW50aW5lbC5hZ2VudHMudjEuQWdlbnRzR2V0UmVzcG9uc2USYQoMQWdlbnRzQ3JlYXRlEicuc2VudGluZWwuYWdlbnRzLnYxLkFnZW50c0NyZWF0ZVJlcXVlc3QaKC5zZW50aW5lbC5hZ2VudHMudjEuQWdlbnRzQ3JlYXRlUmVzcG9uc2USYQoMQWdlbnRzVXBkYXRlEicuc2VudGluZWwuYWdlbnRzLnYxLkFnZW50c1VwZGF0ZVJlcXVlc3QaKC5zZW50aW5lbC5hZ2VudHMudjEuQWdlbnRzVXBkYXRlUmVzcG9uc2USYQoMQWdlbnRzRGVsZXRlEicuc2VudGluZWwuYWdlbnRzLnYxLkFnZW50c0RlbGV0ZVJlcXVlc3QaKC5zZW50aW5lbC5hZ2VudHMudjEuQWdlbnRzRGVsZXRlUmVzcG9uc2USbAoPQWdlbnRzU3Vic2NyaWJlEiouc2VudGluZWwuYWdlbnRzLnYxLkFnZW50c1N1YnNjcmliZVJlcXVlc3QaKy5zZW50aW5lbC5hZ2VudHMudjEuQWdlbnRzU3Vic2NyaWJlUmVzcG9uc2UwAULkAQoWY29tLnNlbnRpbmVsLmFnZW50cy52MUILQWdlbnRzUHJvdG9QAVpTZ2l0aHViLmNvbS9zeHdlYmRldi9zZW50aW5lbC9pbnRlcm5hbC9odWIvaHVic2VydmVyL2FwaS9zZW50aW5lbC9hZ2VudHMvdjE7YWdlbnRzdjGiAgNTQViqAhJTZW50aW5lbC5BZ2VudHMuVjHKAhJTZW50aW5lbFxBZ2VudHNcVjHiAh5TZW50aW5lbFxBZ2VudHNcVjFcR1BCTWV0YWRhdGHqAhRTZW50aW5lbDo6QWdlbnRzOjpWMWIGcHJvdG8z", [file_google_protobuf_timestamp, file_sentinel_system_v1_service]);
+
+/**
+ * @generated from message sentinel.agents.v1.AgentConfig
+ */
+export type AgentConfig = Message<"sentinel.agents.v1.AgentConfig"> & {
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentConfig.
+ * Use `create(AgentConfigSchema)` to create a new message.
+ */
+export const AgentConfigSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 0);
+
+/**
+ * @generated from message sentinel.agents.v1.Agent
+ */
+export type Agent = Message<"sentinel.agents.v1.Agent"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: string name = 2;
+ */
+ name: string;
+
+ /**
+ * @generated from field: string description = 3;
+ */
+ description: string;
+
+ /**
+ * @generated from field: string token_hint = 4;
+ */
+ tokenHint: string;
+
+ /**
+ * @generated from field: optional string fingerprint = 5;
+ */
+ fingerprint?: string;
+
+ /**
+ * @generated from field: sentinel.agents.v1.AgentKind kind = 6;
+ */
+ kind: AgentKind;
+
+ /**
+ * @generated from field: sentinel.agents.v1.AgentStatus status = 7;
+ */
+ status: AgentStatus;
+
+ /**
+ * @generated from field: bool is_enabled = 8;
+ */
+ isEnabled: boolean;
+
+ /**
+ * @generated from field: optional string location = 9;
+ */
+ location?: string;
+
+ /**
+ * @generated from field: repeated string tags = 10;
+ */
+ tags: string[];
+
+ /**
+ * @generated from field: sentinel.agents.v1.AgentConfig config = 11;
+ */
+ config?: AgentConfig;
+
+ /**
+ * @generated from field: sentinel.system.v1.SystemInfo system_info = 12;
+ */
+ systemInfo?: SystemInfo;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp last_seen_at = 13;
+ */
+ lastSeenAt?: Timestamp;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp created_at = 14;
+ */
+ createdAt?: Timestamp;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp updated_at = 15;
+ */
+ updatedAt?: Timestamp;
+};
+
+/**
+ * Describes the message sentinel.agents.v1.Agent.
+ * Use `create(AgentSchema)` to create a new message.
+ */
+export const AgentSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 1);
+
+/**
+ * AgentsList agents lists all agents.
+ *
+ * @generated from message sentinel.agents.v1.AgentsListRequest
+ */
+export type AgentsListRequest = Message<"sentinel.agents.v1.AgentsListRequest"> & {
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentsListRequest.
+ * Use `create(AgentsListRequestSchema)` to create a new message.
+ */
+export const AgentsListRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 2);
+
+/**
+ * @generated from message sentinel.agents.v1.AgentsListResponse
+ */
+export type AgentsListResponse = Message<"sentinel.agents.v1.AgentsListResponse"> & {
+ /**
+ * @generated from field: repeated sentinel.agents.v1.Agent items = 1;
+ */
+ items: Agent[];
+
+ /**
+ * @generated from field: uint32 count = 2;
+ */
+ count: number;
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentsListResponse.
+ * Use `create(AgentsListResponseSchema)` to create a new message.
+ */
+export const AgentsListResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 3);
+
+/**
+ * AgentsGet agent gets an agent by ID.
+ *
+ * @generated from message sentinel.agents.v1.AgentsGetRequest
+ */
+export type AgentsGetRequest = Message<"sentinel.agents.v1.AgentsGetRequest"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentsGetRequest.
+ * Use `create(AgentsGetRequestSchema)` to create a new message.
+ */
+export const AgentsGetRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 4);
+
+/**
+ * @generated from message sentinel.agents.v1.AgentsGetResponse
+ */
+export type AgentsGetResponse = Message<"sentinel.agents.v1.AgentsGetResponse"> & {
+ /**
+ * @generated from field: sentinel.agents.v1.Agent item = 1;
+ */
+ item?: Agent;
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentsGetResponse.
+ * Use `create(AgentsGetResponseSchema)` to create a new message.
+ */
+export const AgentsGetResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 5);
+
+/**
+ * AgentsCreate agent creates a new agent.
+ *
+ * @generated from message sentinel.agents.v1.AgentsCreateRequest
+ */
+export type AgentsCreateRequest = Message<"sentinel.agents.v1.AgentsCreateRequest"> & {
+ /**
+ * @generated from field: string name = 1;
+ */
+ name: string;
+
+ /**
+ * @generated from field: string description = 2;
+ */
+ description: string;
+
+ /**
+ * @generated from field: bool is_enabled = 3;
+ */
+ isEnabled: boolean;
+
+ /**
+ * @generated from field: repeated string tags = 4;
+ */
+ tags: string[];
+
+ /**
+ * @generated from field: sentinel.agents.v1.AgentConfig config = 5;
+ */
+ config?: AgentConfig;
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentsCreateRequest.
+ * Use `create(AgentsCreateRequestSchema)` to create a new message.
+ */
+export const AgentsCreateRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 6);
+
+/**
+ * @generated from message sentinel.agents.v1.AgentsCreateResponse
+ */
+export type AgentsCreateResponse = Message<"sentinel.agents.v1.AgentsCreateResponse"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: string name = 2;
+ */
+ name: string;
+
+ /**
+ * @generated from field: string description = 3;
+ */
+ description: string;
+
+ /**
+ * @generated from field: string token = 4;
+ */
+ token: string;
+
+ /**
+ * @generated from field: string token_hint = 5;
+ */
+ tokenHint: string;
+
+ /**
+ * @generated from field: sentinel.agents.v1.AgentStatus status = 6;
+ */
+ status: AgentStatus;
+
+ /**
+ * @generated from field: sentinel.agents.v1.AgentKind kind = 7;
+ */
+ kind: AgentKind;
+
+ /**
+ * @generated from field: bool is_enabled = 8;
+ */
+ isEnabled: boolean;
+
+ /**
+ * @generated from field: optional string location = 9;
+ */
+ location?: string;
+
+ /**
+ * @generated from field: repeated string tags = 10;
+ */
+ tags: string[];
+
+ /**
+ * @generated from field: sentinel.agents.v1.AgentConfig config = 11;
+ */
+ config?: AgentConfig;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp created_at = 12;
+ */
+ createdAt?: Timestamp;
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentsCreateResponse.
+ * Use `create(AgentsCreateResponseSchema)` to create a new message.
+ */
+export const AgentsCreateResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 7);
+
+/**
+ * AgentsUpdate agent updates an existing agent.
+ *
+ * @generated from message sentinel.agents.v1.AgentsUpdateRequest
+ */
+export type AgentsUpdateRequest = Message<"sentinel.agents.v1.AgentsUpdateRequest"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: string name = 2;
+ */
+ name: string;
+
+ /**
+ * @generated from field: string description = 3;
+ */
+ description: string;
+
+ /**
+ * @generated from field: bool is_enabled = 4;
+ */
+ isEnabled: boolean;
+
+ /**
+ * @generated from field: optional string location = 5;
+ */
+ location?: string;
+
+ /**
+ * @generated from field: repeated string tags = 6;
+ */
+ tags: string[];
+
+ /**
+ * @generated from field: sentinel.agents.v1.AgentConfig config = 7;
+ */
+ config?: AgentConfig;
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentsUpdateRequest.
+ * Use `create(AgentsUpdateRequestSchema)` to create a new message.
+ */
+export const AgentsUpdateRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 8);
+
+/**
+ * @generated from message sentinel.agents.v1.AgentsUpdateResponse
+ */
+export type AgentsUpdateResponse = Message<"sentinel.agents.v1.AgentsUpdateResponse"> & {
+ /**
+ * @generated from field: sentinel.agents.v1.Agent item = 1;
+ */
+ item?: Agent;
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentsUpdateResponse.
+ * Use `create(AgentsUpdateResponseSchema)` to create a new message.
+ */
+export const AgentsUpdateResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 9);
+
+/**
+ * Delete agent deletes an agent by ID.
+ *
+ * @generated from message sentinel.agents.v1.AgentsDeleteRequest
+ */
+export type AgentsDeleteRequest = Message<"sentinel.agents.v1.AgentsDeleteRequest"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentsDeleteRequest.
+ * Use `create(AgentsDeleteRequestSchema)` to create a new message.
+ */
+export const AgentsDeleteRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 10);
+
+/**
+ * @generated from message sentinel.agents.v1.AgentsDeleteResponse
+ */
+export type AgentsDeleteResponse = Message<"sentinel.agents.v1.AgentsDeleteResponse"> & {
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentsDeleteResponse.
+ * Use `create(AgentsDeleteResponseSchema)` to create a new message.
+ */
+export const AgentsDeleteResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 11);
+
+/**
+ * AgentsSubscribe subscribes to agent events.
+ *
+ * @generated from message sentinel.agents.v1.AgentsSubscribeRequest
+ */
+export type AgentsSubscribeRequest = Message<"sentinel.agents.v1.AgentsSubscribeRequest"> & {
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentsSubscribeRequest.
+ * Use `create(AgentsSubscribeRequestSchema)` to create a new message.
+ */
+export const AgentsSubscribeRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 12);
+
+/**
+ * @generated from message sentinel.agents.v1.AgentsSubscribeResponse
+ */
+export type AgentsSubscribeResponse = Message<"sentinel.agents.v1.AgentsSubscribeResponse"> & {
+};
+
+/**
+ * Describes the message sentinel.agents.v1.AgentsSubscribeResponse.
+ * Use `create(AgentsSubscribeResponseSchema)` to create a new message.
+ */
+export const AgentsSubscribeResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_agents_v1_agents, 13);
+
+/**
+ * @generated from enum sentinel.agents.v1.AgentStatus
+ */
+export enum AgentStatus {
+ /**
+ * @generated from enum value: AGENT_STATUS_UNSPECIFIED = 0;
+ */
+ UNSPECIFIED = 0,
+
+ /**
+ * @generated from enum value: AGENT_STATUS_ACTIVE = 1;
+ */
+ ACTIVE = 1,
+
+ /**
+ * @generated from enum value: AGENT_STATUS_INACTIVE = 2;
+ */
+ INACTIVE = 2,
+}
+
+/**
+ * Describes the enum sentinel.agents.v1.AgentStatus.
+ */
+export const AgentStatusSchema: GenEnum = /*@__PURE__*/
+ enumDesc(file_sentinel_agents_v1_agents, 0);
+
+/**
+ * @generated from enum sentinel.agents.v1.AgentKind
+ */
+export enum AgentKind {
+ /**
+ * @generated from enum value: AGENT_KIND_UNSPECIFIED = 0;
+ */
+ UNSPECIFIED = 0,
+
+ /**
+ * @generated from enum value: AGENT_KIND_HUB = 1;
+ */
+ HUB = 1,
+
+ /**
+ * @generated from enum value: AGENT_KIND_EXTERNAL = 2;
+ */
+ EXTERNAL = 2,
+}
+
+/**
+ * Describes the enum sentinel.agents.v1.AgentKind.
+ */
+export const AgentKindSchema: GenEnum = /*@__PURE__*/
+ enumDesc(file_sentinel_agents_v1_agents, 1);
+
+/**
+ * AgentsService is the service for managing agents.
+ *
+ * @generated from service sentinel.agents.v1.AgentsService
+ */
+export const AgentsService: GenService<{
+ /**
+ * @generated from rpc sentinel.agents.v1.AgentsService.AgentsList
+ */
+ agentsList: {
+ methodKind: "unary";
+ input: typeof AgentsListRequestSchema;
+ output: typeof AgentsListResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.agents.v1.AgentsService.AgentsGet
+ */
+ agentsGet: {
+ methodKind: "unary";
+ input: typeof AgentsGetRequestSchema;
+ output: typeof AgentsGetResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.agents.v1.AgentsService.AgentsCreate
+ */
+ agentsCreate: {
+ methodKind: "unary";
+ input: typeof AgentsCreateRequestSchema;
+ output: typeof AgentsCreateResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.agents.v1.AgentsService.AgentsUpdate
+ */
+ agentsUpdate: {
+ methodKind: "unary";
+ input: typeof AgentsUpdateRequestSchema;
+ output: typeof AgentsUpdateResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.agents.v1.AgentsService.AgentsDelete
+ */
+ agentsDelete: {
+ methodKind: "unary";
+ input: typeof AgentsDeleteRequestSchema;
+ output: typeof AgentsDeleteResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.agents.v1.AgentsService.AgentsSubscribe
+ */
+ agentsSubscribe: {
+ methodKind: "server_streaming";
+ input: typeof AgentsSubscribeRequestSchema;
+ output: typeof AgentsSubscribeResponseSchema;
+ },
+}> = /*@__PURE__*/
+ serviceDesc(file_sentinel_agents_v1_agents, 0);
+
diff --git a/frontend/src/api/gen/sentinel/auth/v1/auth-AuthService_connectquery.ts b/frontend/src/api/gen/sentinel/auth/v1/auth-AuthService_connectquery.ts
new file mode 100644
index 0000000..c28cb3d
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/auth/v1/auth-AuthService_connectquery.ts
@@ -0,0 +1,44 @@
+// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts"
+// @generated from file sentinel/auth/v1/auth.proto (package sentinel.auth.v1, syntax proto3)
+/* eslint-disable */
+
+import { AuthService } from "./auth_pb";
+
+/**
+ * Auth
+ *
+ * @generated from rpc sentinel.auth.v1.AuthService.Authorization
+ */
+export const authorization = AuthService.method.authorization;
+
+/**
+ * @generated from rpc sentinel.auth.v1.AuthService.Authenticate
+ */
+export const authenticate = AuthService.method.authenticate;
+
+/**
+ * @generated from rpc sentinel.auth.v1.AuthService.RefreshToken
+ */
+export const refreshToken = AuthService.method.refreshToken;
+
+/**
+ * @generated from rpc sentinel.auth.v1.AuthService.Logout
+ */
+export const logout = AuthService.method.logout;
+
+/**
+ * Sessions
+ *
+ * @generated from rpc sentinel.auth.v1.AuthService.ActiveSessions
+ */
+export const activeSessions = AuthService.method.activeSessions;
+
+/**
+ * @generated from rpc sentinel.auth.v1.AuthService.DeleteSession
+ */
+export const deleteSession = AuthService.method.deleteSession;
+
+/**
+ * @generated from rpc sentinel.auth.v1.AuthService.TerminateAllSessions
+ */
+export const terminateAllSessions = AuthService.method.terminateAllSessions;
diff --git a/frontend/src/api/gen/sentinel/auth/v1/auth_pb.ts b/frontend/src/api/gen/sentinel/auth/v1/auth_pb.ts
new file mode 100644
index 0000000..b29cbf1
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/auth/v1/auth_pb.ts
@@ -0,0 +1,489 @@
+// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
+// @generated from file sentinel/auth/v1/auth.proto (package sentinel.auth.v1, syntax proto3)
+/* eslint-disable */
+
+import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
+import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
+import type { Timestamp } from "@bufbuild/protobuf/wkt";
+import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
+import type { User } from "../../users/v1/users_pb";
+import { file_sentinel_users_v1_users } from "../../users/v1/users_pb";
+import type { Message } from "@bufbuild/protobuf";
+
+/**
+ * Describes the file sentinel/auth/v1/auth.proto.
+ */
+export const file_sentinel_auth_v1_auth: GenFile = /*@__PURE__*/
+ fileDesc("ChtzZW50aW5lbC9hdXRoL3YxL2F1dGgucHJvdG8SEHNlbnRpbmVsLmF1dGgudjEipQEKCkRldmljZUluZm8SEQoJZGV2aWNlX2lkGAEgASgJEjEKC2RldmljZV90eXBlGAIgASgOMhwuc2VudGluZWwuYXV0aC52MS5EZXZpY2VUeXBlEhMKC2RldmljZV9uYW1lGAMgASgJEhMKC2ZpbmdlcnByaW50GAQgASgJEhIKCm9zX3ZlcnNpb24YBSABKAkSEwoLYXBwX3ZlcnNpb24YBiABKAkiiQEKB1Nlc3Npb24SMQoLZGV2aWNlX2luZm8YASABKAsyHC5zZW50aW5lbC5hdXRoLnYxLkRldmljZUluZm8SDwoHY291bnRyeRgCIAEoCRIKCgJpcBgDIAEoCRIuCgpjcmVhdGVkX2F0GAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCJqChRBdXRob3JpemF0aW9uUmVxdWVzdBINCgVlbWFpbBgBIAEoCRIQCghwYXNzd29yZBgCIAEoCRIxCgtkZXZpY2VfaW5mbxgEIAEoCzIcLnNlbnRpbmVsLmF1dGgudjEuRGV2aWNlSW5mbyL6AQoLQXV0aFBheWxvYWQSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhUKDXJlZnJlc2hfdG9rZW4YAiABKAkSOwoXYWNjZXNzX3Rva2VuX2V4cGlyZWRfYXQYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjwKGHJlZnJlc2hfdG9rZW5fZXhwaXJlZF9hdBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASEQoJZGV2aWNlX2lkGAUgASgJEhwKD29yZ2FuaXphdGlvbl9pZBgGIAEoCUgAiAEBQhIKEF9vcmdhbml6YXRpb25faWQicwoVQXV0aG9yaXphdGlvblJlc3BvbnNlEjMKDGF1dGhfcGF5bG9hZBgBIAEoCzIdLnNlbnRpbmVsLmF1dGgudjEuQXV0aFBheWxvYWQSJQoEdXNlchgCIAEoCzIXLnNlbnRpbmVsLnVzZXJzLnYxLlVzZXIiKwoTQXV0aGVudGljYXRlUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkiPQoUQXV0aGVudGljYXRlUmVzcG9uc2USJQoEdXNlchgCIAEoCzIXLnNlbnRpbmVsLnVzZXJzLnYxLlVzZXIiLAoTUmVmcmVzaFRva2VuUmVxdWVzdBIVCg1yZWZyZXNoX3Rva2VuGAEgASgJIr4BChRSZWZyZXNoVG9rZW5SZXNwb25zZRIUCgxhY2Nlc3NfdG9rZW4YASABKAkSFQoNcmVmcmVzaF90b2tlbhgCIAEoCRI7ChdhY2Nlc3NfdG9rZW5fZXhwaXJlZF9hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASPAoYcmVmcmVzaF90b2tlbl9leHBpcmVkX2F0GAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCIXChVBY3RpdmVTZXNzaW9uc1JlcXVlc3QiRQoWQWN0aXZlU2Vzc2lvbnNSZXNwb25zZRIrCghzZXNzaW9ucxgBIAMoCzIZLnNlbnRpbmVsLmF1dGgudjEuU2Vzc2lvbiIpChREZWxldGVTZXNzaW9uUmVxdWVzdBIRCglkZXZpY2VfaWQYASABKAkiFwoVRGVsZXRlU2Vzc2lvblJlc3BvbnNlIkMKG1Rlcm1pbmF0ZUFsbFNlc3Npb25zUmVxdWVzdBIWCglkZXZpY2VfaWQYASABKAlIAIgBAUIMCgpfZGV2aWNlX2lkIh4KHFRlcm1pbmF0ZUFsbFNlc3Npb25zUmVzcG9uc2UiDwoNTG9nb3V0UmVxdWVzdCIQCg5Mb2dvdXRSZXNwb25zZSo+CgpEZXZpY2VUeXBlEhsKF0RFVklDRV9UWVBFX1VOU1BFQ0lGSUVEEAASEwoPREVWSUNFX1RZUEVfV0VCEAEyxgUKC0F1dGhTZXJ2aWNlEmIKDUF1dGhvcml6YXRpb24SJi5zZW50aW5lbC5hdXRoLnYxLkF1dGhvcml6YXRpb25SZXF1ZXN0Gicuc2VudGluZWwuYXV0aC52MS5BdXRob3JpemF0aW9uUmVzcG9uc2UiABJfCgxBdXRoZW50aWNhdGUSJS5zZW50aW5lbC5hdXRoLnYxLkF1dGhlbnRpY2F0ZVJlcXVlc3QaJi5zZW50aW5lbC5hdXRoLnYxLkF1dGhlbnRpY2F0ZVJlc3BvbnNlIgASXwoMUmVmcmVzaFRva2VuEiUuc2VudGluZWwuYXV0aC52MS5SZWZyZXNoVG9rZW5SZXF1ZXN0GiYuc2VudGluZWwuYXV0aC52MS5SZWZyZXNoVG9rZW5SZXNwb25zZSIAEk0KBkxvZ291dBIfLnNlbnRpbmVsLmF1dGgudjEuTG9nb3V0UmVxdWVzdBogLnNlbnRpbmVsLmF1dGgudjEuTG9nb3V0UmVzcG9uc2UiABJlCg5BY3RpdmVTZXNzaW9ucxInLnNlbnRpbmVsLmF1dGgudjEuQWN0aXZlU2Vzc2lvbnNSZXF1ZXN0Giguc2VudGluZWwuYXV0aC52MS5BY3RpdmVTZXNzaW9uc1Jlc3BvbnNlIgASYgoNRGVsZXRlU2Vzc2lvbhImLnNlbnRpbmVsLmF1dGgudjEuRGVsZXRlU2Vzc2lvblJlcXVlc3QaJy5zZW50aW5lbC5hdXRoLnYxLkRlbGV0ZVNlc3Npb25SZXNwb25zZSIAEncKFFRlcm1pbmF0ZUFsbFNlc3Npb25zEi0uc2VudGluZWwuYXV0aC52MS5UZXJtaW5hdGVBbGxTZXNzaW9uc1JlcXVlc3QaLi5zZW50aW5lbC5hdXRoLnYxLlRlcm1pbmF0ZUFsbFNlc3Npb25zUmVzcG9uc2UiAELUAQoUY29tLnNlbnRpbmVsLmF1dGgudjFCCUF1dGhQcm90b1ABWk9naXRodWIuY29tL3N4d2ViZGV2L3NlbnRpbmVsL2ludGVybmFsL2h1Yi9odWJzZXJ2ZXIvYXBpL3NlbnRpbmVsL2F1dGgvdjE7YXV0aHYxogIDU0FYqgIQU2VudGluZWwuQXV0aC5WMcoCEFNlbnRpbmVsXEF1dGhcVjHiAhxTZW50aW5lbFxBdXRoXFYxXEdQQk1ldGFkYXRh6gISU2VudGluZWw6OkF1dGg6OlYxYgZwcm90bzM", [file_google_protobuf_timestamp, file_sentinel_users_v1_users]);
+
+/**
+ * @generated from message sentinel.auth.v1.DeviceInfo
+ */
+export type DeviceInfo = Message<"sentinel.auth.v1.DeviceInfo"> & {
+ /**
+ * @generated from field: string device_id = 1;
+ */
+ deviceId: string;
+
+ /**
+ * @generated from field: sentinel.auth.v1.DeviceType device_type = 2;
+ */
+ deviceType: DeviceType;
+
+ /**
+ * @generated from field: string device_name = 3;
+ */
+ deviceName: string;
+
+ /**
+ * @generated from field: string fingerprint = 4;
+ */
+ fingerprint: string;
+
+ /**
+ * @generated from field: string os_version = 5;
+ */
+ osVersion: string;
+
+ /**
+ * @generated from field: string app_version = 6;
+ */
+ appVersion: string;
+};
+
+/**
+ * Describes the message sentinel.auth.v1.DeviceInfo.
+ * Use `create(DeviceInfoSchema)` to create a new message.
+ */
+export const DeviceInfoSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 0);
+
+/**
+ * @generated from message sentinel.auth.v1.Session
+ */
+export type Session = Message<"sentinel.auth.v1.Session"> & {
+ /**
+ * @generated from field: sentinel.auth.v1.DeviceInfo device_info = 1;
+ */
+ deviceInfo?: DeviceInfo;
+
+ /**
+ * @generated from field: string country = 2;
+ */
+ country: string;
+
+ /**
+ * @generated from field: string ip = 3;
+ */
+ ip: string;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp created_at = 4;
+ */
+ createdAt?: Timestamp;
+};
+
+/**
+ * Describes the message sentinel.auth.v1.Session.
+ * Use `create(SessionSchema)` to create a new message.
+ */
+export const SessionSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 1);
+
+/**
+ * Authorization
+ *
+ * @generated from message sentinel.auth.v1.AuthorizationRequest
+ */
+export type AuthorizationRequest = Message<"sentinel.auth.v1.AuthorizationRequest"> & {
+ /**
+ * @generated from field: string email = 1;
+ */
+ email: string;
+
+ /**
+ * @generated from field: string password = 2;
+ */
+ password: string;
+
+ /**
+ * @generated from field: sentinel.auth.v1.DeviceInfo device_info = 4;
+ */
+ deviceInfo?: DeviceInfo;
+};
+
+/**
+ * Describes the message sentinel.auth.v1.AuthorizationRequest.
+ * Use `create(AuthorizationRequestSchema)` to create a new message.
+ */
+export const AuthorizationRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 2);
+
+/**
+ * @generated from message sentinel.auth.v1.AuthPayload
+ */
+export type AuthPayload = Message<"sentinel.auth.v1.AuthPayload"> & {
+ /**
+ * @generated from field: string access_token = 1;
+ */
+ accessToken: string;
+
+ /**
+ * @generated from field: string refresh_token = 2;
+ */
+ refreshToken: string;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp access_token_expired_at = 3;
+ */
+ accessTokenExpiredAt?: Timestamp;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp refresh_token_expired_at = 4;
+ */
+ refreshTokenExpiredAt?: Timestamp;
+
+ /**
+ * @generated from field: string device_id = 5;
+ */
+ deviceId: string;
+
+ /**
+ * @generated from field: optional string organization_id = 6;
+ */
+ organizationId?: string;
+};
+
+/**
+ * Describes the message sentinel.auth.v1.AuthPayload.
+ * Use `create(AuthPayloadSchema)` to create a new message.
+ */
+export const AuthPayloadSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 3);
+
+/**
+ * @generated from message sentinel.auth.v1.AuthorizationResponse
+ */
+export type AuthorizationResponse = Message<"sentinel.auth.v1.AuthorizationResponse"> & {
+ /**
+ * @generated from field: sentinel.auth.v1.AuthPayload auth_payload = 1;
+ */
+ authPayload?: AuthPayload;
+
+ /**
+ * @generated from field: sentinel.users.v1.User user = 2;
+ */
+ user?: User;
+};
+
+/**
+ * Describes the message sentinel.auth.v1.AuthorizationResponse.
+ * Use `create(AuthorizationResponseSchema)` to create a new message.
+ */
+export const AuthorizationResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 4);
+
+/**
+ * Authenticate
+ *
+ * @generated from message sentinel.auth.v1.AuthenticateRequest
+ */
+export type AuthenticateRequest = Message<"sentinel.auth.v1.AuthenticateRequest"> & {
+ /**
+ * @generated from field: string access_token = 1;
+ */
+ accessToken: string;
+};
+
+/**
+ * Describes the message sentinel.auth.v1.AuthenticateRequest.
+ * Use `create(AuthenticateRequestSchema)` to create a new message.
+ */
+export const AuthenticateRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 5);
+
+/**
+ * @generated from message sentinel.auth.v1.AuthenticateResponse
+ */
+export type AuthenticateResponse = Message<"sentinel.auth.v1.AuthenticateResponse"> & {
+ /**
+ * @generated from field: sentinel.users.v1.User user = 2;
+ */
+ user?: User;
+};
+
+/**
+ * Describes the message sentinel.auth.v1.AuthenticateResponse.
+ * Use `create(AuthenticateResponseSchema)` to create a new message.
+ */
+export const AuthenticateResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 6);
+
+/**
+ * Refresh token
+ *
+ * @generated from message sentinel.auth.v1.RefreshTokenRequest
+ */
+export type RefreshTokenRequest = Message<"sentinel.auth.v1.RefreshTokenRequest"> & {
+ /**
+ * @generated from field: string refresh_token = 1;
+ */
+ refreshToken: string;
+};
+
+/**
+ * Describes the message sentinel.auth.v1.RefreshTokenRequest.
+ * Use `create(RefreshTokenRequestSchema)` to create a new message.
+ */
+export const RefreshTokenRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 7);
+
+/**
+ * @generated from message sentinel.auth.v1.RefreshTokenResponse
+ */
+export type RefreshTokenResponse = Message<"sentinel.auth.v1.RefreshTokenResponse"> & {
+ /**
+ * @generated from field: string access_token = 1;
+ */
+ accessToken: string;
+
+ /**
+ * @generated from field: string refresh_token = 2;
+ */
+ refreshToken: string;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp access_token_expired_at = 3;
+ */
+ accessTokenExpiredAt?: Timestamp;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp refresh_token_expired_at = 4;
+ */
+ refreshTokenExpiredAt?: Timestamp;
+};
+
+/**
+ * Describes the message sentinel.auth.v1.RefreshTokenResponse.
+ * Use `create(RefreshTokenResponseSchema)` to create a new message.
+ */
+export const RefreshTokenResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 8);
+
+/**
+ * Active sessions
+ *
+ * @generated from message sentinel.auth.v1.ActiveSessionsRequest
+ */
+export type ActiveSessionsRequest = Message<"sentinel.auth.v1.ActiveSessionsRequest"> & {
+};
+
+/**
+ * Describes the message sentinel.auth.v1.ActiveSessionsRequest.
+ * Use `create(ActiveSessionsRequestSchema)` to create a new message.
+ */
+export const ActiveSessionsRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 9);
+
+/**
+ * @generated from message sentinel.auth.v1.ActiveSessionsResponse
+ */
+export type ActiveSessionsResponse = Message<"sentinel.auth.v1.ActiveSessionsResponse"> & {
+ /**
+ * @generated from field: repeated sentinel.auth.v1.Session sessions = 1;
+ */
+ sessions: Session[];
+};
+
+/**
+ * Describes the message sentinel.auth.v1.ActiveSessionsResponse.
+ * Use `create(ActiveSessionsResponseSchema)` to create a new message.
+ */
+export const ActiveSessionsResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 10);
+
+/**
+ * Delete session
+ *
+ * @generated from message sentinel.auth.v1.DeleteSessionRequest
+ */
+export type DeleteSessionRequest = Message<"sentinel.auth.v1.DeleteSessionRequest"> & {
+ /**
+ * @generated from field: string device_id = 1;
+ */
+ deviceId: string;
+};
+
+/**
+ * Describes the message sentinel.auth.v1.DeleteSessionRequest.
+ * Use `create(DeleteSessionRequestSchema)` to create a new message.
+ */
+export const DeleteSessionRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 11);
+
+/**
+ * @generated from message sentinel.auth.v1.DeleteSessionResponse
+ */
+export type DeleteSessionResponse = Message<"sentinel.auth.v1.DeleteSessionResponse"> & {
+};
+
+/**
+ * Describes the message sentinel.auth.v1.DeleteSessionResponse.
+ * Use `create(DeleteSessionResponseSchema)` to create a new message.
+ */
+export const DeleteSessionResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 12);
+
+/**
+ * Terminate all sessions
+ *
+ * @generated from message sentinel.auth.v1.TerminateAllSessionsRequest
+ */
+export type TerminateAllSessionsRequest = Message<"sentinel.auth.v1.TerminateAllSessionsRequest"> & {
+ /**
+ * Current device id. This session will not be terminated.
+ * If not set, all sessions will be terminated.
+ *
+ * @generated from field: optional string device_id = 1;
+ */
+ deviceId?: string;
+};
+
+/**
+ * Describes the message sentinel.auth.v1.TerminateAllSessionsRequest.
+ * Use `create(TerminateAllSessionsRequestSchema)` to create a new message.
+ */
+export const TerminateAllSessionsRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 13);
+
+/**
+ * @generated from message sentinel.auth.v1.TerminateAllSessionsResponse
+ */
+export type TerminateAllSessionsResponse = Message<"sentinel.auth.v1.TerminateAllSessionsResponse"> & {
+};
+
+/**
+ * Describes the message sentinel.auth.v1.TerminateAllSessionsResponse.
+ * Use `create(TerminateAllSessionsResponseSchema)` to create a new message.
+ */
+export const TerminateAllSessionsResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 14);
+
+/**
+ * Logout
+ *
+ * @generated from message sentinel.auth.v1.LogoutRequest
+ */
+export type LogoutRequest = Message<"sentinel.auth.v1.LogoutRequest"> & {
+};
+
+/**
+ * Describes the message sentinel.auth.v1.LogoutRequest.
+ * Use `create(LogoutRequestSchema)` to create a new message.
+ */
+export const LogoutRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 15);
+
+/**
+ * @generated from message sentinel.auth.v1.LogoutResponse
+ */
+export type LogoutResponse = Message<"sentinel.auth.v1.LogoutResponse"> & {
+};
+
+/**
+ * Describes the message sentinel.auth.v1.LogoutResponse.
+ * Use `create(LogoutResponseSchema)` to create a new message.
+ */
+export const LogoutResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_auth_v1_auth, 16);
+
+/**
+ * @generated from enum sentinel.auth.v1.DeviceType
+ */
+export enum DeviceType {
+ /**
+ * @generated from enum value: DEVICE_TYPE_UNSPECIFIED = 0;
+ */
+ UNSPECIFIED = 0,
+
+ /**
+ * @generated from enum value: DEVICE_TYPE_WEB = 1;
+ */
+ WEB = 1,
+}
+
+/**
+ * Describes the enum sentinel.auth.v1.DeviceType.
+ */
+export const DeviceTypeSchema: GenEnum = /*@__PURE__*/
+ enumDesc(file_sentinel_auth_v1_auth, 0);
+
+/**
+ * AuthService is the service for authentication and authorization.
+ *
+ * @generated from service sentinel.auth.v1.AuthService
+ */
+export const AuthService: GenService<{
+ /**
+ * Auth
+ *
+ * @generated from rpc sentinel.auth.v1.AuthService.Authorization
+ */
+ authorization: {
+ methodKind: "unary";
+ input: typeof AuthorizationRequestSchema;
+ output: typeof AuthorizationResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.auth.v1.AuthService.Authenticate
+ */
+ authenticate: {
+ methodKind: "unary";
+ input: typeof AuthenticateRequestSchema;
+ output: typeof AuthenticateResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.auth.v1.AuthService.RefreshToken
+ */
+ refreshToken: {
+ methodKind: "unary";
+ input: typeof RefreshTokenRequestSchema;
+ output: typeof RefreshTokenResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.auth.v1.AuthService.Logout
+ */
+ logout: {
+ methodKind: "unary";
+ input: typeof LogoutRequestSchema;
+ output: typeof LogoutResponseSchema;
+ },
+ /**
+ * Sessions
+ *
+ * @generated from rpc sentinel.auth.v1.AuthService.ActiveSessions
+ */
+ activeSessions: {
+ methodKind: "unary";
+ input: typeof ActiveSessionsRequestSchema;
+ output: typeof ActiveSessionsResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.auth.v1.AuthService.DeleteSession
+ */
+ deleteSession: {
+ methodKind: "unary";
+ input: typeof DeleteSessionRequestSchema;
+ output: typeof DeleteSessionResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.auth.v1.AuthService.TerminateAllSessions
+ */
+ terminateAllSessions: {
+ methodKind: "unary";
+ input: typeof TerminateAllSessionsRequestSchema;
+ output: typeof TerminateAllSessionsResponseSchema;
+ },
+}> = /*@__PURE__*/
+ serviceDesc(file_sentinel_auth_v1_auth, 0);
+
diff --git a/frontend/src/api/gen/sentinel/common/v1/common_pb.ts b/frontend/src/api/gen/sentinel/common/v1/common_pb.ts
new file mode 100644
index 0000000..5d346f1
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/common/v1/common_pb.ts
@@ -0,0 +1,46 @@
+// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
+// @generated from file sentinel/common/v1/common.proto (package sentinel.common.v1, syntax proto3)
+/* eslint-disable */
+
+import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2";
+import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
+import type { Message } from "@bufbuild/protobuf";
+
+/**
+ * Describes the file sentinel/common/v1/common.proto.
+ */
+export const file_sentinel_common_v1_common: GenFile = /*@__PURE__*/
+ fileDesc("Ch9zZW50aW5lbC9jb21tb24vdjEvY29tbW9uLnByb3RvEhJzZW50aW5lbC5jb21tb24udjEijwEKEUZpbmRSZXF1ZXN0Q29tbW9uEhEKBHBhZ2UYASABKA1IAIgBARIWCglwYWdlX3NpemUYAiABKA1IAYgBARIVCghvcmRlcl9ieRgDIAEoCUgCiAEBEhQKDGlzX2FzY19vcmRlchgEIAEoCEIHCgVfcGFnZUIMCgpfcGFnZV9zaXplQgsKCV9vcmRlcl9ieULkAQoWY29tLnNlbnRpbmVsLmNvbW1vbi52MUILQ29tbW9uUHJvdG9QAVpTZ2l0aHViLmNvbS9zeHdlYmRldi9zZW50aW5lbC9pbnRlcm5hbC9odWIvaHVic2VydmVyL2FwaS9zZW50aW5lbC9jb21tb24vdjE7Y29tbW9udjGiAgNTQ1iqAhJTZW50aW5lbC5Db21tb24uVjHKAhJTZW50aW5lbFxDb21tb25cVjHiAh5TZW50aW5lbFxDb21tb25cVjFcR1BCTWV0YWRhdGHqAhRTZW50aW5lbDo6Q29tbW9uOjpWMWIGcHJvdG8z");
+
+/**
+ * @generated from message sentinel.common.v1.FindRequestCommon
+ */
+export type FindRequestCommon = Message<"sentinel.common.v1.FindRequestCommon"> & {
+ /**
+ * @generated from field: optional uint32 page = 1;
+ */
+ page?: number;
+
+ /**
+ * @generated from field: optional uint32 page_size = 2;
+ */
+ pageSize?: number;
+
+ /**
+ * @generated from field: optional string order_by = 3;
+ */
+ orderBy?: string;
+
+ /**
+ * @generated from field: bool is_asc_order = 4;
+ */
+ isAscOrder: boolean;
+};
+
+/**
+ * Describes the message sentinel.common.v1.FindRequestCommon.
+ * Use `create(FindRequestCommonSchema)` to create a new message.
+ */
+export const FindRequestCommonSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_common_v1_common, 0);
+
diff --git a/frontend/src/api/gen/sentinel/notifications/v1/notifications-NotificationService_connectquery.ts b/frontend/src/api/gen/sentinel/notifications/v1/notifications-NotificationService_connectquery.ts
new file mode 100644
index 0000000..4287229
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/notifications/v1/notifications-NotificationService_connectquery.ts
@@ -0,0 +1,40 @@
+// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts"
+// @generated from file sentinel/notifications/v1/notifications.proto (package sentinel.notifications.v1, syntax proto3)
+/* eslint-disable */
+
+import { NotificationService } from "./notifications_pb";
+
+/**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.ProvidersList
+ */
+export const providersList = NotificationService.method.providersList;
+
+/**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.ProviderCreate
+ */
+export const providerCreate = NotificationService.method.providerCreate;
+
+/**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.ProviderUpdate
+ */
+export const providerUpdate = NotificationService.method.providerUpdate;
+
+/**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.ProviderDelete
+ */
+export const providerDelete = NotificationService.method.providerDelete;
+
+/**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.ProviderTest
+ */
+export const providerTest = NotificationService.method.providerTest;
+
+/**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.HistoryList
+ */
+export const historyList = NotificationService.method.historyList;
+
+/**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.HistoryDeleteAll
+ */
+export const historyDeleteAll = NotificationService.method.historyDeleteAll;
diff --git a/frontend/src/api/gen/sentinel/notifications/v1/notifications_pb.ts b/frontend/src/api/gen/sentinel/notifications/v1/notifications_pb.ts
new file mode 100644
index 0000000..800fade
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/notifications/v1/notifications_pb.ts
@@ -0,0 +1,552 @@
+// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
+// @generated from file sentinel/notifications/v1/notifications.proto (package sentinel.notifications.v1, syntax proto3)
+/* eslint-disable */
+
+import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
+import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
+import type { Timestamp } from "@bufbuild/protobuf/wkt";
+import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
+import type { FindRequestCommon } from "../../common/v1/common_pb";
+import { file_sentinel_common_v1_common } from "../../common/v1/common_pb";
+import type { Message } from "@bufbuild/protobuf";
+
+/**
+ * Describes the file sentinel/notifications/v1/notifications.proto.
+ */
+export const file_sentinel_notifications_v1_notifications: GenFile = /*@__PURE__*/
+ fileDesc("Ci1zZW50aW5lbC9ub3RpZmljYXRpb25zL3YxL25vdGlmaWNhdGlvbnMucHJvdG8SGXNlbnRpbmVsLm5vdGlmaWNhdGlvbnMudjEiHQoOU2hvdXRycnJDb25maWcSCwoDdXJsGAEgASgJIlkKDlByb3ZpZGVyQ29uZmlnEj0KCHNob3V0cnJyGAEgASgLMikuc2VudGluZWwubm90aWZpY2F0aW9ucy52MS5TaG91dHJyckNvbmZpZ0gAQggKBmNvbmZpZyL8AQoIUHJvdmlkZXISCgoCaWQYASABKAkSNQoEdHlwZRgCIAEoDjInLnNlbnRpbmVsLm5vdGlmaWNhdGlvbnMudjEuUHJvdmlkZXJUeXBlEjkKBmNvbmZpZxgDIAEoCzIpLnNlbnRpbmVsLm5vdGlmaWNhdGlvbnMudjEuUHJvdmlkZXJDb25maWcSEgoKaXNfZW5hYmxlZBgEIAEoCBIuCgpjcmVhdGVkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCLgAgoLSGlzdG9yeUl0ZW0SCgoCaWQYASABKAkSDwoHbWVzc2FnZRgCIAEoCRIOCgZzdGF0dXMYAyABKAkSFQoIcmVzcG9uc2UYBCABKAlIAIgBARIQCghhdHRlbXB0cxgFIAEoAxIaCg1lcnJvcl9tZXNzYWdlGAYgASgJSAGIAQESMwoPbGFzdF9hdHRlbXB0X2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIrCgdzZW50X2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgpjcmVhdGVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEILCglfcmVzcG9uc2VCEAoOX2Vycm9yX21lc3NhZ2UiFgoUUHJvdmlkZXJzTGlzdFJlcXVlc3QiSwoVUHJvdmlkZXJzTGlzdFJlc3BvbnNlEjIKBWl0ZW1zGAEgAygLMiMuc2VudGluZWwubm90aWZpY2F0aW9ucy52MS5Qcm92aWRlciKdAQoVUHJvdmlkZXJDcmVhdGVSZXF1ZXN0EjUKBHR5cGUYASABKA4yJy5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxLlByb3ZpZGVyVHlwZRI5CgZjb25maWcYAiABKAsyKS5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxLlByb3ZpZGVyQ29uZmlnEhIKCmlzX2VuYWJsZWQYAyABKAgiSwoWUHJvdmlkZXJDcmVhdGVSZXNwb25zZRIxCgRpdGVtGAEgASgLMiMuc2VudGluZWwubm90aWZpY2F0aW9ucy52MS5Qcm92aWRlciKpAQoVUHJvdmlkZXJVcGRhdGVSZXF1ZXN0EgoKAmlkGAEgASgJEjUKBHR5cGUYAiABKA4yJy5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxLlByb3ZpZGVyVHlwZRI5CgZjb25maWcYAyABKAsyKS5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxLlByb3ZpZGVyQ29uZmlnEhIKCmlzX2VuYWJsZWQYBCABKAgiSwoWUHJvdmlkZXJVcGRhdGVSZXNwb25zZRIxCgRpdGVtGAEgASgLMiMuc2VudGluZWwubm90aWZpY2F0aW9ucy52MS5Qcm92aWRlciIjChVQcm92aWRlckRlbGV0ZVJlcXVlc3QSCgoCaWQYASABKAkiGAoWUHJvdmlkZXJEZWxldGVSZXNwb25zZSIhChNQcm92aWRlclRlc3RSZXF1ZXN0EgoKAmlkGAEgASgJIhYKFFByb3ZpZGVyVGVzdFJlc3BvbnNlIlsKEkhpc3RvcnlMaXN0UmVxdWVzdBI1CgZjb21tb24YASABKAsyJS5zZW50aW5lbC5jb21tb24udjEuRmluZFJlcXVlc3RDb21tb24SDgoGc3RhdHVzGAIgASgJIlsKE0hpc3RvcnlMaXN0UmVzcG9uc2USNQoFaXRlbXMYASADKAsyJi5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxLkhpc3RvcnlJdGVtEg0KBWNvdW50GAIgASgNIhkKF0hpc3RvcnlEZWxldGVBbGxSZXF1ZXN0IhoKGEhpc3RvcnlEZWxldGVBbGxSZXNwb25zZSIZChdIaXN0b3J5U3Vic2NyaWJlUmVxdWVzdCIaChhIaXN0b3J5U3Vic2NyaWJlUmVzcG9uc2UqSQoMUHJvdmlkZXJUeXBlEh0KGVBST1ZJREVSX1RZUEVfVU5TUEVDSUZJRUQQABIaChZQUk9WSURFUl9UWVBFX1NIT1VUUlJSEAEyyQcKE05vdGlmaWNhdGlvblNlcnZpY2UScgoNUHJvdmlkZXJzTGlzdBIvLnNlbnRpbmVsLm5vdGlmaWNhdGlvbnMudjEuUHJvdmlkZXJzTGlzdFJlcXVlc3QaMC5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxLlByb3ZpZGVyc0xpc3RSZXNwb25zZRJ1Cg5Qcm92aWRlckNyZWF0ZRIwLnNlbnRpbmVsLm5vdGlmaWNhdGlvbnMudjEuUHJvdmlkZXJDcmVhdGVSZXF1ZXN0GjEuc2VudGluZWwubm90aWZpY2F0aW9ucy52MS5Qcm92aWRlckNyZWF0ZVJlc3BvbnNlEnUKDlByb3ZpZGVyVXBkYXRlEjAuc2VudGluZWwubm90aWZpY2F0aW9ucy52MS5Qcm92aWRlclVwZGF0ZVJlcXVlc3QaMS5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxLlByb3ZpZGVyVXBkYXRlUmVzcG9uc2USdQoOUHJvdmlkZXJEZWxldGUSMC5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxLlByb3ZpZGVyRGVsZXRlUmVxdWVzdBoxLnNlbnRpbmVsLm5vdGlmaWNhdGlvbnMudjEuUHJvdmlkZXJEZWxldGVSZXNwb25zZRJvCgxQcm92aWRlclRlc3QSLi5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxLlByb3ZpZGVyVGVzdFJlcXVlc3QaLy5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxLlByb3ZpZGVyVGVzdFJlc3BvbnNlEmwKC0hpc3RvcnlMaXN0Ei0uc2VudGluZWwubm90aWZpY2F0aW9ucy52MS5IaXN0b3J5TGlzdFJlcXVlc3QaLi5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxLkhpc3RvcnlMaXN0UmVzcG9uc2USewoQSGlzdG9yeURlbGV0ZUFsbBIyLnNlbnRpbmVsLm5vdGlmaWNhdGlvbnMudjEuSGlzdG9yeURlbGV0ZUFsbFJlcXVlc3QaMy5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxLkhpc3RvcnlEZWxldGVBbGxSZXNwb25zZRJ9ChBIaXN0b3J5U3Vic2NyaWJlEjIuc2VudGluZWwubm90aWZpY2F0aW9ucy52MS5IaXN0b3J5U3Vic2NyaWJlUmVxdWVzdBozLnNlbnRpbmVsLm5vdGlmaWNhdGlvbnMudjEuSGlzdG9yeVN1YnNjcmliZVJlc3BvbnNlMAFCnAIKHWNvbS5zZW50aW5lbC5ub3RpZmljYXRpb25zLnYxQhJOb3RpZmljYXRpb25zUHJvdG9QAVphZ2l0aHViLmNvbS9zeHdlYmRldi9zZW50aW5lbC9pbnRlcm5hbC9odWIvaHVic2VydmVyL2FwaS9zZW50aW5lbC9ub3RpZmljYXRpb25zL3YxO25vdGlmaWNhdGlvbnN2MaICA1NOWKoCGVNlbnRpbmVsLk5vdGlmaWNhdGlvbnMuVjHKAhlTZW50aW5lbFxOb3RpZmljYXRpb25zXFYx4gIlU2VudGluZWxcTm90aWZpY2F0aW9uc1xWMVxHUEJNZXRhZGF0YeoCG1NlbnRpbmVsOjpOb3RpZmljYXRpb25zOjpWMWIGcHJvdG8z", [file_google_protobuf_timestamp, file_sentinel_common_v1_common]);
+
+/**
+ * @generated from message sentinel.notifications.v1.ShoutrrrConfig
+ */
+export type ShoutrrrConfig = Message<"sentinel.notifications.v1.ShoutrrrConfig"> & {
+ /**
+ * @generated from field: string url = 1;
+ */
+ url: string;
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.ShoutrrrConfig.
+ * Use `create(ShoutrrrConfigSchema)` to create a new message.
+ */
+export const ShoutrrrConfigSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 0);
+
+/**
+ * @generated from message sentinel.notifications.v1.ProviderConfig
+ */
+export type ProviderConfig = Message<"sentinel.notifications.v1.ProviderConfig"> & {
+ /**
+ * @generated from oneof sentinel.notifications.v1.ProviderConfig.config
+ */
+ config: {
+ /**
+ * @generated from field: sentinel.notifications.v1.ShoutrrrConfig shoutrrr = 1;
+ */
+ value: ShoutrrrConfig;
+ case: "shoutrrr";
+ } | { case: undefined; value?: undefined };
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.ProviderConfig.
+ * Use `create(ProviderConfigSchema)` to create a new message.
+ */
+export const ProviderConfigSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 1);
+
+/**
+ * @generated from message sentinel.notifications.v1.Provider
+ */
+export type Provider = Message<"sentinel.notifications.v1.Provider"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: sentinel.notifications.v1.ProviderType type = 2;
+ */
+ type: ProviderType;
+
+ /**
+ * @generated from field: sentinel.notifications.v1.ProviderConfig config = 3;
+ */
+ config?: ProviderConfig;
+
+ /**
+ * @generated from field: bool is_enabled = 4;
+ */
+ isEnabled: boolean;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp created_at = 5;
+ */
+ createdAt?: Timestamp;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp updated_at = 6;
+ */
+ updatedAt?: Timestamp;
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.Provider.
+ * Use `create(ProviderSchema)` to create a new message.
+ */
+export const ProviderSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 2);
+
+/**
+ * @generated from message sentinel.notifications.v1.HistoryItem
+ */
+export type HistoryItem = Message<"sentinel.notifications.v1.HistoryItem"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: string message = 2;
+ */
+ message: string;
+
+ /**
+ * @generated from field: string status = 3;
+ */
+ status: string;
+
+ /**
+ * @generated from field: optional string response = 4;
+ */
+ response?: string;
+
+ /**
+ * @generated from field: int64 attempts = 5;
+ */
+ attempts: bigint;
+
+ /**
+ * @generated from field: optional string error_message = 6;
+ */
+ errorMessage?: string;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp last_attempt_at = 7;
+ */
+ lastAttemptAt?: Timestamp;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp sent_at = 8;
+ */
+ sentAt?: Timestamp;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp created_at = 9;
+ */
+ createdAt?: Timestamp;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp updated_at = 10;
+ */
+ updatedAt?: Timestamp;
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.HistoryItem.
+ * Use `create(HistoryItemSchema)` to create a new message.
+ */
+export const HistoryItemSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 3);
+
+/**
+ * ProvidersList lists all notification providers.
+ *
+ * @generated from message sentinel.notifications.v1.ProvidersListRequest
+ */
+export type ProvidersListRequest = Message<"sentinel.notifications.v1.ProvidersListRequest"> & {
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.ProvidersListRequest.
+ * Use `create(ProvidersListRequestSchema)` to create a new message.
+ */
+export const ProvidersListRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 4);
+
+/**
+ * @generated from message sentinel.notifications.v1.ProvidersListResponse
+ */
+export type ProvidersListResponse = Message<"sentinel.notifications.v1.ProvidersListResponse"> & {
+ /**
+ * @generated from field: repeated sentinel.notifications.v1.Provider items = 1;
+ */
+ items: Provider[];
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.ProvidersListResponse.
+ * Use `create(ProvidersListResponseSchema)` to create a new message.
+ */
+export const ProvidersListResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 5);
+
+/**
+ * ProviderCreate creates a new notification provider.
+ *
+ * @generated from message sentinel.notifications.v1.ProviderCreateRequest
+ */
+export type ProviderCreateRequest = Message<"sentinel.notifications.v1.ProviderCreateRequest"> & {
+ /**
+ * @generated from field: sentinel.notifications.v1.ProviderType type = 1;
+ */
+ type: ProviderType;
+
+ /**
+ * @generated from field: sentinel.notifications.v1.ProviderConfig config = 2;
+ */
+ config?: ProviderConfig;
+
+ /**
+ * @generated from field: bool is_enabled = 3;
+ */
+ isEnabled: boolean;
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.ProviderCreateRequest.
+ * Use `create(ProviderCreateRequestSchema)` to create a new message.
+ */
+export const ProviderCreateRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 6);
+
+/**
+ * @generated from message sentinel.notifications.v1.ProviderCreateResponse
+ */
+export type ProviderCreateResponse = Message<"sentinel.notifications.v1.ProviderCreateResponse"> & {
+ /**
+ * @generated from field: sentinel.notifications.v1.Provider item = 1;
+ */
+ item?: Provider;
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.ProviderCreateResponse.
+ * Use `create(ProviderCreateResponseSchema)` to create a new message.
+ */
+export const ProviderCreateResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 7);
+
+/**
+ * ProviderUpdate updates an existing notification provider.
+ *
+ * @generated from message sentinel.notifications.v1.ProviderUpdateRequest
+ */
+export type ProviderUpdateRequest = Message<"sentinel.notifications.v1.ProviderUpdateRequest"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: sentinel.notifications.v1.ProviderType type = 2;
+ */
+ type: ProviderType;
+
+ /**
+ * @generated from field: sentinel.notifications.v1.ProviderConfig config = 3;
+ */
+ config?: ProviderConfig;
+
+ /**
+ * @generated from field: bool is_enabled = 4;
+ */
+ isEnabled: boolean;
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.ProviderUpdateRequest.
+ * Use `create(ProviderUpdateRequestSchema)` to create a new message.
+ */
+export const ProviderUpdateRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 8);
+
+/**
+ * @generated from message sentinel.notifications.v1.ProviderUpdateResponse
+ */
+export type ProviderUpdateResponse = Message<"sentinel.notifications.v1.ProviderUpdateResponse"> & {
+ /**
+ * @generated from field: sentinel.notifications.v1.Provider item = 1;
+ */
+ item?: Provider;
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.ProviderUpdateResponse.
+ * Use `create(ProviderUpdateResponseSchema)` to create a new message.
+ */
+export const ProviderUpdateResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 9);
+
+/**
+ * ProviderDelete deletes a notification provider.
+ *
+ * @generated from message sentinel.notifications.v1.ProviderDeleteRequest
+ */
+export type ProviderDeleteRequest = Message<"sentinel.notifications.v1.ProviderDeleteRequest"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.ProviderDeleteRequest.
+ * Use `create(ProviderDeleteRequestSchema)` to create a new message.
+ */
+export const ProviderDeleteRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 10);
+
+/**
+ * @generated from message sentinel.notifications.v1.ProviderDeleteResponse
+ */
+export type ProviderDeleteResponse = Message<"sentinel.notifications.v1.ProviderDeleteResponse"> & {
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.ProviderDeleteResponse.
+ * Use `create(ProviderDeleteResponseSchema)` to create a new message.
+ */
+export const ProviderDeleteResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 11);
+
+/**
+ * ProviderTest sends a test notification using the specified provider.
+ *
+ * @generated from message sentinel.notifications.v1.ProviderTestRequest
+ */
+export type ProviderTestRequest = Message<"sentinel.notifications.v1.ProviderTestRequest"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.ProviderTestRequest.
+ * Use `create(ProviderTestRequestSchema)` to create a new message.
+ */
+export const ProviderTestRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 12);
+
+/**
+ * @generated from message sentinel.notifications.v1.ProviderTestResponse
+ */
+export type ProviderTestResponse = Message<"sentinel.notifications.v1.ProviderTestResponse"> & {
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.ProviderTestResponse.
+ * Use `create(ProviderTestResponseSchema)` to create a new message.
+ */
+export const ProviderTestResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 13);
+
+/**
+ * History retrieves the notification history with optional filters.
+ *
+ * @generated from message sentinel.notifications.v1.HistoryListRequest
+ */
+export type HistoryListRequest = Message<"sentinel.notifications.v1.HistoryListRequest"> & {
+ /**
+ * @generated from field: sentinel.common.v1.FindRequestCommon common = 1;
+ */
+ common?: FindRequestCommon;
+
+ /**
+ * @generated from field: string status = 2;
+ */
+ status: string;
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.HistoryListRequest.
+ * Use `create(HistoryListRequestSchema)` to create a new message.
+ */
+export const HistoryListRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 14);
+
+/**
+ * @generated from message sentinel.notifications.v1.HistoryListResponse
+ */
+export type HistoryListResponse = Message<"sentinel.notifications.v1.HistoryListResponse"> & {
+ /**
+ * @generated from field: repeated sentinel.notifications.v1.HistoryItem items = 1;
+ */
+ items: HistoryItem[];
+
+ /**
+ * @generated from field: uint32 count = 2;
+ */
+ count: number;
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.HistoryListResponse.
+ * Use `create(HistoryListResponseSchema)` to create a new message.
+ */
+export const HistoryListResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 15);
+
+/**
+ * HistoryDeleteAll deletes all notification history items.
+ *
+ * @generated from message sentinel.notifications.v1.HistoryDeleteAllRequest
+ */
+export type HistoryDeleteAllRequest = Message<"sentinel.notifications.v1.HistoryDeleteAllRequest"> & {
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.HistoryDeleteAllRequest.
+ * Use `create(HistoryDeleteAllRequestSchema)` to create a new message.
+ */
+export const HistoryDeleteAllRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 16);
+
+/**
+ * @generated from message sentinel.notifications.v1.HistoryDeleteAllResponse
+ */
+export type HistoryDeleteAllResponse = Message<"sentinel.notifications.v1.HistoryDeleteAllResponse"> & {
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.HistoryDeleteAllResponse.
+ * Use `create(HistoryDeleteAllResponseSchema)` to create a new message.
+ */
+export const HistoryDeleteAllResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 17);
+
+/**
+ * HistorySubscribe subscribes to real-time notification history updates.
+ *
+ * @generated from message sentinel.notifications.v1.HistorySubscribeRequest
+ */
+export type HistorySubscribeRequest = Message<"sentinel.notifications.v1.HistorySubscribeRequest"> & {
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.HistorySubscribeRequest.
+ * Use `create(HistorySubscribeRequestSchema)` to create a new message.
+ */
+export const HistorySubscribeRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 18);
+
+/**
+ * @generated from message sentinel.notifications.v1.HistorySubscribeResponse
+ */
+export type HistorySubscribeResponse = Message<"sentinel.notifications.v1.HistorySubscribeResponse"> & {
+};
+
+/**
+ * Describes the message sentinel.notifications.v1.HistorySubscribeResponse.
+ * Use `create(HistorySubscribeResponseSchema)` to create a new message.
+ */
+export const HistorySubscribeResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_notifications_v1_notifications, 19);
+
+/**
+ * @generated from enum sentinel.notifications.v1.ProviderType
+ */
+export enum ProviderType {
+ /**
+ * @generated from enum value: PROVIDER_TYPE_UNSPECIFIED = 0;
+ */
+ UNSPECIFIED = 0,
+
+ /**
+ * @generated from enum value: PROVIDER_TYPE_SHOUTRRR = 1;
+ */
+ SHOUTRRR = 1,
+}
+
+/**
+ * Describes the enum sentinel.notifications.v1.ProviderType.
+ */
+export const ProviderTypeSchema: GenEnum = /*@__PURE__*/
+ enumDesc(file_sentinel_notifications_v1_notifications, 0);
+
+/**
+ * NotificationService is the service for managing notification providers and
+ * history.
+ *
+ * @generated from service sentinel.notifications.v1.NotificationService
+ */
+export const NotificationService: GenService<{
+ /**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.ProvidersList
+ */
+ providersList: {
+ methodKind: "unary";
+ input: typeof ProvidersListRequestSchema;
+ output: typeof ProvidersListResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.ProviderCreate
+ */
+ providerCreate: {
+ methodKind: "unary";
+ input: typeof ProviderCreateRequestSchema;
+ output: typeof ProviderCreateResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.ProviderUpdate
+ */
+ providerUpdate: {
+ methodKind: "unary";
+ input: typeof ProviderUpdateRequestSchema;
+ output: typeof ProviderUpdateResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.ProviderDelete
+ */
+ providerDelete: {
+ methodKind: "unary";
+ input: typeof ProviderDeleteRequestSchema;
+ output: typeof ProviderDeleteResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.ProviderTest
+ */
+ providerTest: {
+ methodKind: "unary";
+ input: typeof ProviderTestRequestSchema;
+ output: typeof ProviderTestResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.HistoryList
+ */
+ historyList: {
+ methodKind: "unary";
+ input: typeof HistoryListRequestSchema;
+ output: typeof HistoryListResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.HistoryDeleteAll
+ */
+ historyDeleteAll: {
+ methodKind: "unary";
+ input: typeof HistoryDeleteAllRequestSchema;
+ output: typeof HistoryDeleteAllResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.notifications.v1.NotificationService.HistorySubscribe
+ */
+ historySubscribe: {
+ methodKind: "server_streaming";
+ input: typeof HistorySubscribeRequestSchema;
+ output: typeof HistorySubscribeResponseSchema;
+ },
+}> = /*@__PURE__*/
+ serviceDesc(file_sentinel_notifications_v1_notifications, 0);
+
diff --git a/frontend/src/api/gen/sentinel/projects/v1/projects-ProjectsService_connectquery.ts b/frontend/src/api/gen/sentinel/projects/v1/projects-ProjectsService_connectquery.ts
new file mode 100644
index 0000000..8de874c
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/projects/v1/projects-ProjectsService_connectquery.ts
@@ -0,0 +1,30 @@
+// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts"
+// @generated from file sentinel/projects/v1/projects.proto (package sentinel.projects.v1, syntax proto3)
+/* eslint-disable */
+
+import { ProjectsService } from "./projects_pb";
+
+/**
+ * @generated from rpc sentinel.projects.v1.ProjectsService.ProjectsList
+ */
+export const projectsList = ProjectsService.method.projectsList;
+
+/**
+ * @generated from rpc sentinel.projects.v1.ProjectsService.ProjectCreate
+ */
+export const projectCreate = ProjectsService.method.projectCreate;
+
+/**
+ * @generated from rpc sentinel.projects.v1.ProjectsService.ProjectGet
+ */
+export const projectGet = ProjectsService.method.projectGet;
+
+/**
+ * @generated from rpc sentinel.projects.v1.ProjectsService.ProjectUpdate
+ */
+export const projectUpdate = ProjectsService.method.projectUpdate;
+
+/**
+ * @generated from rpc sentinel.projects.v1.ProjectsService.ProjectDelete
+ */
+export const projectDelete = ProjectsService.method.projectDelete;
diff --git a/frontend/src/api/gen/sentinel/projects/v1/projects_pb.ts b/frontend/src/api/gen/sentinel/projects/v1/projects_pb.ts
new file mode 100644
index 0000000..04c6dc1
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/projects/v1/projects_pb.ts
@@ -0,0 +1,352 @@
+// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
+// @generated from file sentinel/projects/v1/projects.proto (package sentinel.projects.v1, syntax proto3)
+/* eslint-disable */
+
+import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
+import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
+import type { Timestamp } from "@bufbuild/protobuf/wkt";
+import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
+import type { Message } from "@bufbuild/protobuf";
+
+/**
+ * Describes the file sentinel/projects/v1/projects.proto.
+ */
+export const file_sentinel_projects_v1_projects: GenFile = /*@__PURE__*/
+ fileDesc("CiNzZW50aW5lbC9wcm9qZWN0cy92MS9wcm9qZWN0cy5wcm90bxIUc2VudGluZWwucHJvamVjdHMudjEiTAoWUHJvamVjdE1vbml0b3JEZWZhdWx0cxIQCghpbnRlcnZhbBgBIAEoAxIPCgd0aW1lb3V0GAIgASgDEg8KB3JldHJpZXMYAyABKAMiWQoPUHJvamVjdFNldHRpbmdzEkYKEG1vbml0b3JfZGVmYXVsdHMYASABKAsyLC5zZW50aW5lbC5wcm9qZWN0cy52MS5Qcm9qZWN0TW9uaXRvckRlZmF1bHRzItEBCgdQcm9qZWN0EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSNwoIc2V0dGluZ3MYBiABKAsyJS5zZW50aW5lbC5wcm9qZWN0cy52MS5Qcm9qZWN0U2V0dGluZ3MSLgoKY3JlYXRlZF9hdBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiFQoTUHJvamVjdHNMaXN0UmVxdWVzdCJEChRQcm9qZWN0c0xpc3RSZXNwb25zZRIsCgVpdGVtcxgBIAMoCzIdLnNlbnRpbmVsLnByb2plY3RzLnYxLlByb2plY3QicgoUUHJvamVjdENyZWF0ZVJlcXVlc3QSDAoEbmFtZRgBIAEoCRITCgtkZXNjcmlwdGlvbhgCIAEoCRI3CghzZXR0aW5ncxgDIAEoCzIlLnNlbnRpbmVsLnByb2plY3RzLnYxLlByb2plY3RTZXR0aW5ncyJEChVQcm9qZWN0Q3JlYXRlUmVzcG9uc2USKwoEaXRlbRgBIAEoCzIdLnNlbnRpbmVsLnByb2plY3RzLnYxLlByb2plY3QiHwoRUHJvamVjdEdldFJlcXVlc3QSCgoCaWQYASABKAkiQQoSUHJvamVjdEdldFJlc3BvbnNlEisKBGl0ZW0YASABKAsyHS5zZW50aW5lbC5wcm9qZWN0cy52MS5Qcm9qZWN0In4KFFByb2plY3RVcGRhdGVSZXF1ZXN0EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSNwoIc2V0dGluZ3MYBCABKAsyJS5zZW50aW5lbC5wcm9qZWN0cy52MS5Qcm9qZWN0U2V0dGluZ3MiRAoVUHJvamVjdFVwZGF0ZVJlc3BvbnNlEisKBGl0ZW0YASABKAsyHS5zZW50aW5lbC5wcm9qZWN0cy52MS5Qcm9qZWN0IiIKFFByb2plY3REZWxldGVSZXF1ZXN0EgoKAmlkGAEgASgJIhcKFVByb2plY3REZWxldGVSZXNwb25zZTKXBAoPUHJvamVjdHNTZXJ2aWNlEmUKDFByb2plY3RzTGlzdBIpLnNlbnRpbmVsLnByb2plY3RzLnYxLlByb2plY3RzTGlzdFJlcXVlc3QaKi5zZW50aW5lbC5wcm9qZWN0cy52MS5Qcm9qZWN0c0xpc3RSZXNwb25zZRJoCg1Qcm9qZWN0Q3JlYXRlEiouc2VudGluZWwucHJvamVjdHMudjEuUHJvamVjdENyZWF0ZVJlcXVlc3QaKy5zZW50aW5lbC5wcm9qZWN0cy52MS5Qcm9qZWN0Q3JlYXRlUmVzcG9uc2USXwoKUHJvamVjdEdldBInLnNlbnRpbmVsLnByb2plY3RzLnYxLlByb2plY3RHZXRSZXF1ZXN0Giguc2VudGluZWwucHJvamVjdHMudjEuUHJvamVjdEdldFJlc3BvbnNlEmgKDVByb2plY3RVcGRhdGUSKi5zZW50aW5lbC5wcm9qZWN0cy52MS5Qcm9qZWN0VXBkYXRlUmVxdWVzdBorLnNlbnRpbmVsLnByb2plY3RzLnYxLlByb2plY3RVcGRhdGVSZXNwb25zZRJoCg1Qcm9qZWN0RGVsZXRlEiouc2VudGluZWwucHJvamVjdHMudjEuUHJvamVjdERlbGV0ZVJlcXVlc3QaKy5zZW50aW5lbC5wcm9qZWN0cy52MS5Qcm9qZWN0RGVsZXRlUmVzcG9uc2VC9AEKGGNvbS5zZW50aW5lbC5wcm9qZWN0cy52MUINUHJvamVjdHNQcm90b1ABWldnaXRodWIuY29tL3N4d2ViZGV2L3NlbnRpbmVsL2ludGVybmFsL2h1Yi9odWJzZXJ2ZXIvYXBpL3NlbnRpbmVsL3Byb2plY3RzL3YxO3Byb2plY3RzdjGiAgNTUFiqAhRTZW50aW5lbC5Qcm9qZWN0cy5WMcoCFFNlbnRpbmVsXFByb2plY3RzXFYx4gIgU2VudGluZWxcUHJvamVjdHNcVjFcR1BCTWV0YWRhdGHqAhZTZW50aW5lbDo6UHJvamVjdHM6OlYxYgZwcm90bzM", [file_google_protobuf_timestamp]);
+
+/**
+ * @generated from message sentinel.projects.v1.ProjectMonitorDefaults
+ */
+export type ProjectMonitorDefaults = Message<"sentinel.projects.v1.ProjectMonitorDefaults"> & {
+ /**
+ * in milliseconds
+ *
+ * @generated from field: int64 interval = 1;
+ */
+ interval: bigint;
+
+ /**
+ * in milliseconds
+ *
+ * @generated from field: int64 timeout = 2;
+ */
+ timeout: bigint;
+
+ /**
+ * @generated from field: int64 retries = 3;
+ */
+ retries: bigint;
+};
+
+/**
+ * Describes the message sentinel.projects.v1.ProjectMonitorDefaults.
+ * Use `create(ProjectMonitorDefaultsSchema)` to create a new message.
+ */
+export const ProjectMonitorDefaultsSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 0);
+
+/**
+ * @generated from message sentinel.projects.v1.ProjectSettings
+ */
+export type ProjectSettings = Message<"sentinel.projects.v1.ProjectSettings"> & {
+ /**
+ * @generated from field: sentinel.projects.v1.ProjectMonitorDefaults monitor_defaults = 1;
+ */
+ monitorDefaults?: ProjectMonitorDefaults;
+};
+
+/**
+ * Describes the message sentinel.projects.v1.ProjectSettings.
+ * Use `create(ProjectSettingsSchema)` to create a new message.
+ */
+export const ProjectSettingsSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 1);
+
+/**
+ * @generated from message sentinel.projects.v1.Project
+ */
+export type Project = Message<"sentinel.projects.v1.Project"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: string name = 2;
+ */
+ name: string;
+
+ /**
+ * @generated from field: string description = 3;
+ */
+ description: string;
+
+ /**
+ * @generated from field: sentinel.projects.v1.ProjectSettings settings = 6;
+ */
+ settings?: ProjectSettings;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp created_at = 4;
+ */
+ createdAt?: Timestamp;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp updated_at = 5;
+ */
+ updatedAt?: Timestamp;
+};
+
+/**
+ * Describes the message sentinel.projects.v1.Project.
+ * Use `create(ProjectSchema)` to create a new message.
+ */
+export const ProjectSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 2);
+
+/**
+ * ProjectsListRequest is the request message for listing projects.
+ *
+ * @generated from message sentinel.projects.v1.ProjectsListRequest
+ */
+export type ProjectsListRequest = Message<"sentinel.projects.v1.ProjectsListRequest"> & {
+};
+
+/**
+ * Describes the message sentinel.projects.v1.ProjectsListRequest.
+ * Use `create(ProjectsListRequestSchema)` to create a new message.
+ */
+export const ProjectsListRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 3);
+
+/**
+ * @generated from message sentinel.projects.v1.ProjectsListResponse
+ */
+export type ProjectsListResponse = Message<"sentinel.projects.v1.ProjectsListResponse"> & {
+ /**
+ * @generated from field: repeated sentinel.projects.v1.Project items = 1;
+ */
+ items: Project[];
+};
+
+/**
+ * Describes the message sentinel.projects.v1.ProjectsListResponse.
+ * Use `create(ProjectsListResponseSchema)` to create a new message.
+ */
+export const ProjectsListResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 4);
+
+/**
+ * ProjectCreateRequest is the request message for creating a project.
+ *
+ * @generated from message sentinel.projects.v1.ProjectCreateRequest
+ */
+export type ProjectCreateRequest = Message<"sentinel.projects.v1.ProjectCreateRequest"> & {
+ /**
+ * @generated from field: string name = 1;
+ */
+ name: string;
+
+ /**
+ * @generated from field: string description = 2;
+ */
+ description: string;
+
+ /**
+ * @generated from field: sentinel.projects.v1.ProjectSettings settings = 3;
+ */
+ settings?: ProjectSettings;
+};
+
+/**
+ * Describes the message sentinel.projects.v1.ProjectCreateRequest.
+ * Use `create(ProjectCreateRequestSchema)` to create a new message.
+ */
+export const ProjectCreateRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 5);
+
+/**
+ * @generated from message sentinel.projects.v1.ProjectCreateResponse
+ */
+export type ProjectCreateResponse = Message<"sentinel.projects.v1.ProjectCreateResponse"> & {
+ /**
+ * @generated from field: sentinel.projects.v1.Project item = 1;
+ */
+ item?: Project;
+};
+
+/**
+ * Describes the message sentinel.projects.v1.ProjectCreateResponse.
+ * Use `create(ProjectCreateResponseSchema)` to create a new message.
+ */
+export const ProjectCreateResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 6);
+
+/**
+ * ProjectGetRequest is the request message for getting a project.
+ *
+ * @generated from message sentinel.projects.v1.ProjectGetRequest
+ */
+export type ProjectGetRequest = Message<"sentinel.projects.v1.ProjectGetRequest"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+};
+
+/**
+ * Describes the message sentinel.projects.v1.ProjectGetRequest.
+ * Use `create(ProjectGetRequestSchema)` to create a new message.
+ */
+export const ProjectGetRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 7);
+
+/**
+ * @generated from message sentinel.projects.v1.ProjectGetResponse
+ */
+export type ProjectGetResponse = Message<"sentinel.projects.v1.ProjectGetResponse"> & {
+ /**
+ * @generated from field: sentinel.projects.v1.Project item = 1;
+ */
+ item?: Project;
+};
+
+/**
+ * Describes the message sentinel.projects.v1.ProjectGetResponse.
+ * Use `create(ProjectGetResponseSchema)` to create a new message.
+ */
+export const ProjectGetResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 8);
+
+/**
+ * ProjectUpdateRequest is the request message for updating a project.
+ *
+ * @generated from message sentinel.projects.v1.ProjectUpdateRequest
+ */
+export type ProjectUpdateRequest = Message<"sentinel.projects.v1.ProjectUpdateRequest"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: string name = 2;
+ */
+ name: string;
+
+ /**
+ * @generated from field: string description = 3;
+ */
+ description: string;
+
+ /**
+ * @generated from field: sentinel.projects.v1.ProjectSettings settings = 4;
+ */
+ settings?: ProjectSettings;
+};
+
+/**
+ * Describes the message sentinel.projects.v1.ProjectUpdateRequest.
+ * Use `create(ProjectUpdateRequestSchema)` to create a new message.
+ */
+export const ProjectUpdateRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 9);
+
+/**
+ * @generated from message sentinel.projects.v1.ProjectUpdateResponse
+ */
+export type ProjectUpdateResponse = Message<"sentinel.projects.v1.ProjectUpdateResponse"> & {
+ /**
+ * @generated from field: sentinel.projects.v1.Project item = 1;
+ */
+ item?: Project;
+};
+
+/**
+ * Describes the message sentinel.projects.v1.ProjectUpdateResponse.
+ * Use `create(ProjectUpdateResponseSchema)` to create a new message.
+ */
+export const ProjectUpdateResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 10);
+
+/**
+ * ProjectDeleteRequest is the request message for deleting a project.
+ *
+ * @generated from message sentinel.projects.v1.ProjectDeleteRequest
+ */
+export type ProjectDeleteRequest = Message<"sentinel.projects.v1.ProjectDeleteRequest"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+};
+
+/**
+ * Describes the message sentinel.projects.v1.ProjectDeleteRequest.
+ * Use `create(ProjectDeleteRequestSchema)` to create a new message.
+ */
+export const ProjectDeleteRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 11);
+
+/**
+ * @generated from message sentinel.projects.v1.ProjectDeleteResponse
+ */
+export type ProjectDeleteResponse = Message<"sentinel.projects.v1.ProjectDeleteResponse"> & {
+};
+
+/**
+ * Describes the message sentinel.projects.v1.ProjectDeleteResponse.
+ * Use `create(ProjectDeleteResponseSchema)` to create a new message.
+ */
+export const ProjectDeleteResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_projects_v1_projects, 12);
+
+/**
+ * ProjectsService is the service for managing projects.
+ *
+ * @generated from service sentinel.projects.v1.ProjectsService
+ */
+export const ProjectsService: GenService<{
+ /**
+ * @generated from rpc sentinel.projects.v1.ProjectsService.ProjectsList
+ */
+ projectsList: {
+ methodKind: "unary";
+ input: typeof ProjectsListRequestSchema;
+ output: typeof ProjectsListResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.projects.v1.ProjectsService.ProjectCreate
+ */
+ projectCreate: {
+ methodKind: "unary";
+ input: typeof ProjectCreateRequestSchema;
+ output: typeof ProjectCreateResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.projects.v1.ProjectsService.ProjectGet
+ */
+ projectGet: {
+ methodKind: "unary";
+ input: typeof ProjectGetRequestSchema;
+ output: typeof ProjectGetResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.projects.v1.ProjectsService.ProjectUpdate
+ */
+ projectUpdate: {
+ methodKind: "unary";
+ input: typeof ProjectUpdateRequestSchema;
+ output: typeof ProjectUpdateResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.projects.v1.ProjectsService.ProjectDelete
+ */
+ projectDelete: {
+ methodKind: "unary";
+ input: typeof ProjectDeleteRequestSchema;
+ output: typeof ProjectDeleteResponseSchema;
+ },
+}> = /*@__PURE__*/
+ serviceDesc(file_sentinel_projects_v1_projects, 0);
+
diff --git a/frontend/src/api/gen/sentinel/resources/v1/service-ResourcesService_connectquery.ts b/frontend/src/api/gen/sentinel/resources/v1/service-ResourcesService_connectquery.ts
new file mode 100644
index 0000000..f626856
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/resources/v1/service-ResourcesService_connectquery.ts
@@ -0,0 +1,30 @@
+// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts"
+// @generated from file sentinel/resources/v1/service.proto (package sentinel.resources.v1, syntax proto3)
+/* eslint-disable */
+
+import { ResourcesService } from "./service_pb";
+
+/**
+ * @generated from rpc sentinel.resources.v1.ResourcesService.ResourcesList
+ */
+export const resourcesList = ResourcesService.method.resourcesList;
+
+/**
+ * @generated from rpc sentinel.resources.v1.ResourcesService.ResourceGet
+ */
+export const resourceGet = ResourcesService.method.resourceGet;
+
+/**
+ * @generated from rpc sentinel.resources.v1.ResourcesService.ResourceCreate
+ */
+export const resourceCreate = ResourcesService.method.resourceCreate;
+
+/**
+ * @generated from rpc sentinel.resources.v1.ResourcesService.ResourceUpdate
+ */
+export const resourceUpdate = ResourcesService.method.resourceUpdate;
+
+/**
+ * @generated from rpc sentinel.resources.v1.ResourcesService.ResourceDelete
+ */
+export const resourceDelete = ResourcesService.method.resourceDelete;
diff --git a/frontend/src/api/gen/sentinel/resources/v1/service_pb.ts b/frontend/src/api/gen/sentinel/resources/v1/service_pb.ts
new file mode 100644
index 0000000..434a6e2
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/resources/v1/service_pb.ts
@@ -0,0 +1,399 @@
+// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
+// @generated from file sentinel/resources/v1/service.proto (package sentinel.resources.v1, syntax proto3)
+/* eslint-disable */
+
+import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
+import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
+import type { Timestamp } from "@bufbuild/protobuf/wkt";
+import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
+import type { FindRequestCommon } from "../../common/v1/common_pb";
+import { file_sentinel_common_v1_common } from "../../common/v1/common_pb";
+import type { Message } from "@bufbuild/protobuf";
+
+/**
+ * Describes the file sentinel/resources/v1/service.proto.
+ */
+export const file_sentinel_resources_v1_service: GenFile = /*@__PURE__*/
+ fileDesc("CiNzZW50aW5lbC9yZXNvdXJjZXMvdjEvc2VydmljZS5wcm90bxIVc2VudGluZWwucmVzb3VyY2VzLnYxIgkKB1BheWxvYWQinwIKCFJlc291cmNlEgoKAmlkGAEgASgJEhIKCnByb2plY3RfaWQYAiABKAkSDAoEbmFtZRgDIAEoCRITCgtkZXNjcmlwdGlvbhgEIAEoCRIxCgRraW5kGAUgASgOMiMuc2VudGluZWwucmVzb3VyY2VzLnYxLlJlc291cmNlS2luZBIMCgR0YWdzGAYgAygJEi8KB3BheWxvYWQYByABKAsyHi5zZW50aW5lbC5yZXNvdXJjZXMudjEuUGF5bG9hZBIuCgpjcmVhdGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCJNChRSZXNvdXJjZXNMaXN0UmVxdWVzdBI1CgZjb21tb24YASABKAsyJS5zZW50aW5lbC5jb21tb24udjEuRmluZFJlcXVlc3RDb21tb24iVgoVUmVzb3VyY2VzTGlzdFJlc3BvbnNlEi4KBWl0ZW1zGAEgAygLMh8uc2VudGluZWwucmVzb3VyY2VzLnYxLlJlc291cmNlEg0KBWNvdW50GAIgASgNIiAKElJlc291cmNlR2V0UmVxdWVzdBIKCgJpZBgBIAEoCSJEChNSZXNvdXJjZUdldFJlc3BvbnNlEi0KBGl0ZW0YASABKAsyHy5zZW50aW5lbC5yZXNvdXJjZXMudjEuUmVzb3VyY2Ui0wEKFVJlc291cmNlQ3JlYXRlUmVxdWVzdBISCgpwcm9qZWN0X2lkGAEgASgJEhEKCWFnZW50X2lkcxgCIAMoCRIMCgRuYW1lGAMgASgJEhMKC2Rlc2NyaXB0aW9uGAQgASgJEjEKBGtpbmQYBSABKA4yIy5zZW50aW5lbC5yZXNvdXJjZXMudjEuUmVzb3VyY2VLaW5kEgwKBHRhZ3MYBiADKAkSLwoHcGF5bG9hZBgHIAEoCzIeLnNlbnRpbmVsLnJlc291cmNlcy52MS5QYXlsb2FkIkcKFlJlc291cmNlQ3JlYXRlUmVzcG9uc2USLQoEaXRlbRgBIAEoCzIfLnNlbnRpbmVsLnJlc291cmNlcy52MS5SZXNvdXJjZSKYAQoVUmVzb3VyY2VVcGRhdGVSZXF1ZXN0EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSDAoEdGFncxgEIAMoCRIvCgdwYXlsb2FkGAUgASgLMh4uc2VudGluZWwucmVzb3VyY2VzLnYxLlBheWxvYWQSEQoJYWdlbnRfaWRzGAYgAygJIkcKFlJlc291cmNlVXBkYXRlUmVzcG9uc2USLQoEaXRlbRgBIAEoCzIfLnNlbnRpbmVsLnJlc291cmNlcy52MS5SZXNvdXJjZSIjChVSZXNvdXJjZURlbGV0ZVJlcXVlc3QSCgoCaWQYASABKAkiGAoWUmVzb3VyY2VEZWxldGVSZXNwb25zZSpiCgxSZXNvdXJjZUtpbmQSHQoZUkVTT1VSQ0VfS0lORF9VTlNQRUNJRklFRBAAEhkKFVJFU09VUkNFX0tJTkRfU0VSVklDRRABEhgKFFJFU09VUkNFX0tJTkRfU0VSVkVSEAIysQQKEFJlc291cmNlc1NlcnZpY2USagoNUmVzb3VyY2VzTGlzdBIrLnNlbnRpbmVsLnJlc291cmNlcy52MS5SZXNvdXJjZXNMaXN0UmVxdWVzdBosLnNlbnRpbmVsLnJlc291cmNlcy52MS5SZXNvdXJjZXNMaXN0UmVzcG9uc2USZAoLUmVzb3VyY2VHZXQSKS5zZW50aW5lbC5yZXNvdXJjZXMudjEuUmVzb3VyY2VHZXRSZXF1ZXN0Giouc2VudGluZWwucmVzb3VyY2VzLnYxLlJlc291cmNlR2V0UmVzcG9uc2USbQoOUmVzb3VyY2VDcmVhdGUSLC5zZW50aW5lbC5yZXNvdXJjZXMudjEuUmVzb3VyY2VDcmVhdGVSZXF1ZXN0Gi0uc2VudGluZWwucmVzb3VyY2VzLnYxLlJlc291cmNlQ3JlYXRlUmVzcG9uc2USbQoOUmVzb3VyY2VVcGRhdGUSLC5zZW50aW5lbC5yZXNvdXJjZXMudjEuUmVzb3VyY2VVcGRhdGVSZXF1ZXN0Gi0uc2VudGluZWwucmVzb3VyY2VzLnYxLlJlc291cmNlVXBkYXRlUmVzcG9uc2USbQoOUmVzb3VyY2VEZWxldGUSLC5zZW50aW5lbC5yZXNvdXJjZXMudjEuUmVzb3VyY2VEZWxldGVSZXF1ZXN0Gi0uc2VudGluZWwucmVzb3VyY2VzLnYxLlJlc291cmNlRGVsZXRlUmVzcG9uc2VC+gEKGWNvbS5zZW50aW5lbC5yZXNvdXJjZXMudjFCDFNlcnZpY2VQcm90b1ABWllnaXRodWIuY29tL3N4d2ViZGV2L3NlbnRpbmVsL2ludGVybmFsL2h1Yi9odWJzZXJ2ZXIvYXBpL3NlbnRpbmVsL3Jlc291cmNlcy92MTtyZXNvdXJjZXN2MaICA1NSWKoCFVNlbnRpbmVsLlJlc291cmNlcy5WMcoCFVNlbnRpbmVsXFJlc291cmNlc1xWMeICIVNlbnRpbmVsXFJlc291cmNlc1xWMVxHUEJNZXRhZGF0YeoCF1NlbnRpbmVsOjpSZXNvdXJjZXM6OlYxYgZwcm90bzM", [file_google_protobuf_timestamp, file_sentinel_common_v1_common]);
+
+/**
+ * @generated from message sentinel.resources.v1.Payload
+ */
+export type Payload = Message<"sentinel.resources.v1.Payload"> & {
+};
+
+/**
+ * Describes the message sentinel.resources.v1.Payload.
+ * Use `create(PayloadSchema)` to create a new message.
+ */
+export const PayloadSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_resources_v1_service, 0);
+
+/**
+ * @generated from message sentinel.resources.v1.Resource
+ */
+export type Resource = Message<"sentinel.resources.v1.Resource"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: string project_id = 2;
+ */
+ projectId: string;
+
+ /**
+ * @generated from field: string name = 3;
+ */
+ name: string;
+
+ /**
+ * @generated from field: string description = 4;
+ */
+ description: string;
+
+ /**
+ * @generated from field: sentinel.resources.v1.ResourceKind kind = 5;
+ */
+ kind: ResourceKind;
+
+ /**
+ * @generated from field: repeated string tags = 6;
+ */
+ tags: string[];
+
+ /**
+ * @generated from field: sentinel.resources.v1.Payload payload = 7;
+ */
+ payload?: Payload;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp created_at = 8;
+ */
+ createdAt?: Timestamp;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp updated_at = 9;
+ */
+ updatedAt?: Timestamp;
+};
+
+/**
+ * Describes the message sentinel.resources.v1.Resource.
+ * Use `create(ResourceSchema)` to create a new message.
+ */
+export const ResourceSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_resources_v1_service, 1);
+
+/**
+ * ResourcesList requests a list of resources.
+ *
+ * @generated from message sentinel.resources.v1.ResourcesListRequest
+ */
+export type ResourcesListRequest = Message<"sentinel.resources.v1.ResourcesListRequest"> & {
+ /**
+ * @generated from field: sentinel.common.v1.FindRequestCommon common = 1;
+ */
+ common?: FindRequestCommon;
+};
+
+/**
+ * Describes the message sentinel.resources.v1.ResourcesListRequest.
+ * Use `create(ResourcesListRequestSchema)` to create a new message.
+ */
+export const ResourcesListRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_resources_v1_service, 2);
+
+/**
+ * @generated from message sentinel.resources.v1.ResourcesListResponse
+ */
+export type ResourcesListResponse = Message<"sentinel.resources.v1.ResourcesListResponse"> & {
+ /**
+ * @generated from field: repeated sentinel.resources.v1.Resource items = 1;
+ */
+ items: Resource[];
+
+ /**
+ * @generated from field: uint32 count = 2;
+ */
+ count: number;
+};
+
+/**
+ * Describes the message sentinel.resources.v1.ResourcesListResponse.
+ * Use `create(ResourcesListResponseSchema)` to create a new message.
+ */
+export const ResourcesListResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_resources_v1_service, 3);
+
+/**
+ * ResourceGet requests a resource by ID.
+ *
+ * @generated from message sentinel.resources.v1.ResourceGetRequest
+ */
+export type ResourceGetRequest = Message<"sentinel.resources.v1.ResourceGetRequest"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+};
+
+/**
+ * Describes the message sentinel.resources.v1.ResourceGetRequest.
+ * Use `create(ResourceGetRequestSchema)` to create a new message.
+ */
+export const ResourceGetRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_resources_v1_service, 4);
+
+/**
+ * @generated from message sentinel.resources.v1.ResourceGetResponse
+ */
+export type ResourceGetResponse = Message<"sentinel.resources.v1.ResourceGetResponse"> & {
+ /**
+ * @generated from field: sentinel.resources.v1.Resource item = 1;
+ */
+ item?: Resource;
+};
+
+/**
+ * Describes the message sentinel.resources.v1.ResourceGetResponse.
+ * Use `create(ResourceGetResponseSchema)` to create a new message.
+ */
+export const ResourceGetResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_resources_v1_service, 5);
+
+/**
+ * ResourceCreate creates a new resource.
+ *
+ * @generated from message sentinel.resources.v1.ResourceCreateRequest
+ */
+export type ResourceCreateRequest = Message<"sentinel.resources.v1.ResourceCreateRequest"> & {
+ /**
+ * @generated from field: string project_id = 1;
+ */
+ projectId: string;
+
+ /**
+ * @generated from field: repeated string agent_ids = 2;
+ */
+ agentIds: string[];
+
+ /**
+ * @generated from field: string name = 3;
+ */
+ name: string;
+
+ /**
+ * @generated from field: string description = 4;
+ */
+ description: string;
+
+ /**
+ * @generated from field: sentinel.resources.v1.ResourceKind kind = 5;
+ */
+ kind: ResourceKind;
+
+ /**
+ * @generated from field: repeated string tags = 6;
+ */
+ tags: string[];
+
+ /**
+ * @generated from field: sentinel.resources.v1.Payload payload = 7;
+ */
+ payload?: Payload;
+};
+
+/**
+ * Describes the message sentinel.resources.v1.ResourceCreateRequest.
+ * Use `create(ResourceCreateRequestSchema)` to create a new message.
+ */
+export const ResourceCreateRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_resources_v1_service, 6);
+
+/**
+ * @generated from message sentinel.resources.v1.ResourceCreateResponse
+ */
+export type ResourceCreateResponse = Message<"sentinel.resources.v1.ResourceCreateResponse"> & {
+ /**
+ * @generated from field: sentinel.resources.v1.Resource item = 1;
+ */
+ item?: Resource;
+};
+
+/**
+ * Describes the message sentinel.resources.v1.ResourceCreateResponse.
+ * Use `create(ResourceCreateResponseSchema)` to create a new message.
+ */
+export const ResourceCreateResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_resources_v1_service, 7);
+
+/**
+ * ResourceUpdate updates an existing resource.
+ *
+ * @generated from message sentinel.resources.v1.ResourceUpdateRequest
+ */
+export type ResourceUpdateRequest = Message<"sentinel.resources.v1.ResourceUpdateRequest"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: string name = 2;
+ */
+ name: string;
+
+ /**
+ * @generated from field: string description = 3;
+ */
+ description: string;
+
+ /**
+ * @generated from field: repeated string tags = 4;
+ */
+ tags: string[];
+
+ /**
+ * @generated from field: sentinel.resources.v1.Payload payload = 5;
+ */
+ payload?: Payload;
+
+ /**
+ * @generated from field: repeated string agent_ids = 6;
+ */
+ agentIds: string[];
+};
+
+/**
+ * Describes the message sentinel.resources.v1.ResourceUpdateRequest.
+ * Use `create(ResourceUpdateRequestSchema)` to create a new message.
+ */
+export const ResourceUpdateRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_resources_v1_service, 8);
+
+/**
+ * @generated from message sentinel.resources.v1.ResourceUpdateResponse
+ */
+export type ResourceUpdateResponse = Message<"sentinel.resources.v1.ResourceUpdateResponse"> & {
+ /**
+ * @generated from field: sentinel.resources.v1.Resource item = 1;
+ */
+ item?: Resource;
+};
+
+/**
+ * Describes the message sentinel.resources.v1.ResourceUpdateResponse.
+ * Use `create(ResourceUpdateResponseSchema)` to create a new message.
+ */
+export const ResourceUpdateResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_resources_v1_service, 9);
+
+/**
+ * ResourceDelete deletes a resource by ID.
+ *
+ * @generated from message sentinel.resources.v1.ResourceDeleteRequest
+ */
+export type ResourceDeleteRequest = Message<"sentinel.resources.v1.ResourceDeleteRequest"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+};
+
+/**
+ * Describes the message sentinel.resources.v1.ResourceDeleteRequest.
+ * Use `create(ResourceDeleteRequestSchema)` to create a new message.
+ */
+export const ResourceDeleteRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_resources_v1_service, 10);
+
+/**
+ * @generated from message sentinel.resources.v1.ResourceDeleteResponse
+ */
+export type ResourceDeleteResponse = Message<"sentinel.resources.v1.ResourceDeleteResponse"> & {
+};
+
+/**
+ * Describes the message sentinel.resources.v1.ResourceDeleteResponse.
+ * Use `create(ResourceDeleteResponseSchema)` to create a new message.
+ */
+export const ResourceDeleteResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_resources_v1_service, 11);
+
+/**
+ * @generated from enum sentinel.resources.v1.ResourceKind
+ */
+export enum ResourceKind {
+ /**
+ * @generated from enum value: RESOURCE_KIND_UNSPECIFIED = 0;
+ */
+ UNSPECIFIED = 0,
+
+ /**
+ * @generated from enum value: RESOURCE_KIND_SERVICE = 1;
+ */
+ SERVICE = 1,
+
+ /**
+ * @generated from enum value: RESOURCE_KIND_SERVER = 2;
+ */
+ SERVER = 2,
+}
+
+/**
+ * Describes the enum sentinel.resources.v1.ResourceKind.
+ */
+export const ResourceKindSchema: GenEnum = /*@__PURE__*/
+ enumDesc(file_sentinel_resources_v1_service, 0);
+
+/**
+ * ResourcesService is the service for managing resources.
+ *
+ * @generated from service sentinel.resources.v1.ResourcesService
+ */
+export const ResourcesService: GenService<{
+ /**
+ * @generated from rpc sentinel.resources.v1.ResourcesService.ResourcesList
+ */
+ resourcesList: {
+ methodKind: "unary";
+ input: typeof ResourcesListRequestSchema;
+ output: typeof ResourcesListResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.resources.v1.ResourcesService.ResourceGet
+ */
+ resourceGet: {
+ methodKind: "unary";
+ input: typeof ResourceGetRequestSchema;
+ output: typeof ResourceGetResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.resources.v1.ResourcesService.ResourceCreate
+ */
+ resourceCreate: {
+ methodKind: "unary";
+ input: typeof ResourceCreateRequestSchema;
+ output: typeof ResourceCreateResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.resources.v1.ResourcesService.ResourceUpdate
+ */
+ resourceUpdate: {
+ methodKind: "unary";
+ input: typeof ResourceUpdateRequestSchema;
+ output: typeof ResourceUpdateResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.resources.v1.ResourcesService.ResourceDelete
+ */
+ resourceDelete: {
+ methodKind: "unary";
+ input: typeof ResourceDeleteRequestSchema;
+ output: typeof ResourceDeleteResponseSchema;
+ },
+}> = /*@__PURE__*/
+ serviceDesc(file_sentinel_resources_v1_service, 0);
+
diff --git a/frontend/src/api/gen/sentinel/service/v1/service_pb.ts b/frontend/src/api/gen/sentinel/service/v1/service_pb.ts
new file mode 100644
index 0000000..7690176
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/service/v1/service_pb.ts
@@ -0,0 +1,320 @@
+// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
+// @generated from file sentinel/service/v1/service.proto (package sentinel.service.v1, syntax proto3)
+/* eslint-disable */
+
+import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2";
+import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
+import type { Timestamp } from "@bufbuild/protobuf/wkt";
+import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
+import type { Message } from "@bufbuild/protobuf";
+
+/**
+ * Describes the file sentinel/service/v1/service.proto.
+ */
+export const file_sentinel_service_v1_service: GenFile = /*@__PURE__*/
+ fileDesc("CiFzZW50aW5lbC9zZXJ2aWNlL3YxL3NlcnZpY2UucHJvdG8SE3NlbnRpbmVsLnNlcnZpY2UudjEi/AIKCkhUVFBDb25maWcSQQoJZW5kcG9pbnRzGAEgAygLMi4uc2VudGluZWwuc2VydmljZS52MS5IVFRQQ29uZmlnLkVuZHBvaW50Q29uZmlnEhEKCWNvbmRpdGlvbhgCIAEoCRqXAgoORW5kcG9pbnRDb25maWcSDAoEbmFtZRgBIAEoCRILCgN1cmwYAiABKAkSDgoGbWV0aG9kGAMgASgJEkwKB2hlYWRlcnMYBCADKAsyOy5zZW50aW5lbC5zZXJ2aWNlLnYxLkhUVFBDb25maWcuRW5kcG9pbnRDb25maWcuSGVhZGVyc0VudHJ5EgwKBGJvZHkYBSABKAkSFwoPZXhwZWN0ZWRfc3RhdHVzGAYgASgFEhEKCWpzb25fcGF0aBgHIAEoCRIQCgh1c2VybmFtZRgIIAEoCRIQCghwYXNzd29yZBgJIAEoCRouCgxIZWFkZXJzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJFCglUQ1BDb25maWcSEAoIZW5kcG9pbnQYASABKAkSEQoJc2VuZF9kYXRhGAIgASgJEhMKC2V4cGVjdF9kYXRhGAMgASgJImsKCkdSUENDb25maWcSEAoIZW5kcG9pbnQYASABKAkSEgoKY2hlY2tfdHlwZRgCIAEoCRIUCgxzZXJ2aWNlX25hbWUYAyABKAkSCwoDdGxzGAQgASgIEhQKDGluc2VjdXJlX3RscxgFIAEoCCLtAQoNU2VydmljZUNvbmZpZxI5CgtodHRwX2NvbmZpZxgBIAEoCzIfLnNlbnRpbmVsLnNlcnZpY2UudjEuSFRUUENvbmZpZ0gAiAEBEjcKCnRjcF9jb25maWcYAiABKAsyHi5zZW50aW5lbC5zZXJ2aWNlLnYxLlRDUENvbmZpZ0gBiAEBEjkKC2dycGNfY29uZmlnGAMgASgLMh8uc2VudGluZWwuc2VydmljZS52MS5HUlBDQ29uZmlnSAKIAQFCDgoMX2h0dHBfY29uZmlnQg0KC190Y3BfY29uZmlnQg4KDF9ncnBjX2NvbmZpZyLnAgoHU2VydmljZRIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEjYKCHByb3RvY29sGAMgASgOMiQuc2VudGluZWwuc2VydmljZS52MS5TZXJ2aWNlUHJvdG9jb2wSEAoIaW50ZXJ2YWwYBCABKAMSDwoHdGltZW91dBgFIAEoAxIPCgdyZXRyaWVzGAYgASgDEgwKBHRhZ3MYByADKAkSMgoGY29uZmlnGAggASgLMiIuc2VudGluZWwuc2VydmljZS52MS5TZXJ2aWNlQ29uZmlnEiAKGGlzX25vdGlmaWNhdGlvbnNfZW5hYmxlZBgJIAEoCBISCgppc19lbmFibGVkGAogASgIEi4KCmNyZWF0ZWRfYXQYCyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYDCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wKoMBCg9TZXJ2aWNlUHJvdG9jb2wSIAocU0VSVklDRV9QUk9UT0NPTF9VTlNQRUNJRklFRBAAEhkKFVNFUlZJQ0VfUFJPVE9DT0xfSFRUUBABEhgKFFNFUlZJQ0VfUFJPVE9DT0xfVENQEAISGQoVU0VSVklDRV9QUk9UT0NPTF9HUlBDEAMqewoNU2VydmljZVN0YXR1cxIeChpTRVJWSUNFX1NUQVRVU19VTlNQRUNJRklFRBAAEhoKFlNFUlZJQ0VfU1RBVFVTX1VOS05PV04QARIVChFTRVJWSUNFX1NUQVRVU19VUBACEhcKE1NFUlZJQ0VfU1RBVFVTX0RPV04QA0LsAQoXY29tLnNlbnRpbmVsLnNlcnZpY2UudjFCDFNlcnZpY2VQcm90b1ABWlVnaXRodWIuY29tL3N4d2ViZGV2L3NlbnRpbmVsL2ludGVybmFsL2h1Yi9odWJzZXJ2ZXIvYXBpL3NlbnRpbmVsL3NlcnZpY2UvdjE7c2VydmljZXYxogIDU1NYqgITU2VudGluZWwuU2VydmljZS5WMcoCE1NlbnRpbmVsXFNlcnZpY2VcVjHiAh9TZW50aW5lbFxTZXJ2aWNlXFYxXEdQQk1ldGFkYXRh6gIVU2VudGluZWw6OlNlcnZpY2U6OlYxYgZwcm90bzM", [file_google_protobuf_timestamp]);
+
+/**
+ * @generated from message sentinel.service.v1.HTTPConfig
+ */
+export type HTTPConfig = Message<"sentinel.service.v1.HTTPConfig"> & {
+ /**
+ * @generated from field: repeated sentinel.service.v1.HTTPConfig.EndpointConfig endpoints = 1;
+ */
+ endpoints: HTTPConfig_EndpointConfig[];
+
+ /**
+ * @generated from field: string condition = 2;
+ */
+ condition: string;
+};
+
+/**
+ * Describes the message sentinel.service.v1.HTTPConfig.
+ * Use `create(HTTPConfigSchema)` to create a new message.
+ */
+export const HTTPConfigSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_service_v1_service, 0);
+
+/**
+ * @generated from message sentinel.service.v1.HTTPConfig.EndpointConfig
+ */
+export type HTTPConfig_EndpointConfig = Message<"sentinel.service.v1.HTTPConfig.EndpointConfig"> & {
+ /**
+ * @generated from field: string name = 1;
+ */
+ name: string;
+
+ /**
+ * @generated from field: string url = 2;
+ */
+ url: string;
+
+ /**
+ * @generated from field: string method = 3;
+ */
+ method: string;
+
+ /**
+ * @generated from field: map headers = 4;
+ */
+ headers: { [key: string]: string };
+
+ /**
+ * @generated from field: string body = 5;
+ */
+ body: string;
+
+ /**
+ * @generated from field: int32 expected_status = 6;
+ */
+ expectedStatus: number;
+
+ /**
+ * @generated from field: string json_path = 7;
+ */
+ jsonPath: string;
+
+ /**
+ * @generated from field: string username = 8;
+ */
+ username: string;
+
+ /**
+ * @generated from field: string password = 9;
+ */
+ password: string;
+};
+
+/**
+ * Describes the message sentinel.service.v1.HTTPConfig.EndpointConfig.
+ * Use `create(HTTPConfig_EndpointConfigSchema)` to create a new message.
+ */
+export const HTTPConfig_EndpointConfigSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_service_v1_service, 0, 0);
+
+/**
+ * @generated from message sentinel.service.v1.TCPConfig
+ */
+export type TCPConfig = Message<"sentinel.service.v1.TCPConfig"> & {
+ /**
+ * @generated from field: string endpoint = 1;
+ */
+ endpoint: string;
+
+ /**
+ * @generated from field: string send_data = 2;
+ */
+ sendData: string;
+
+ /**
+ * @generated from field: string expect_data = 3;
+ */
+ expectData: string;
+};
+
+/**
+ * Describes the message sentinel.service.v1.TCPConfig.
+ * Use `create(TCPConfigSchema)` to create a new message.
+ */
+export const TCPConfigSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_service_v1_service, 1);
+
+/**
+ * @generated from message sentinel.service.v1.GRPCConfig
+ */
+export type GRPCConfig = Message<"sentinel.service.v1.GRPCConfig"> & {
+ /**
+ * @generated from field: string endpoint = 1;
+ */
+ endpoint: string;
+
+ /**
+ * @generated from field: string check_type = 2;
+ */
+ checkType: string;
+
+ /**
+ * @generated from field: string service_name = 3;
+ */
+ serviceName: string;
+
+ /**
+ * @generated from field: bool tls = 4;
+ */
+ tls: boolean;
+
+ /**
+ * @generated from field: bool insecure_tls = 5;
+ */
+ insecureTls: boolean;
+};
+
+/**
+ * Describes the message sentinel.service.v1.GRPCConfig.
+ * Use `create(GRPCConfigSchema)` to create a new message.
+ */
+export const GRPCConfigSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_service_v1_service, 2);
+
+/**
+ * @generated from message sentinel.service.v1.ServiceConfig
+ */
+export type ServiceConfig = Message<"sentinel.service.v1.ServiceConfig"> & {
+ /**
+ * @generated from field: optional sentinel.service.v1.HTTPConfig http_config = 1;
+ */
+ httpConfig?: HTTPConfig;
+
+ /**
+ * @generated from field: optional sentinel.service.v1.TCPConfig tcp_config = 2;
+ */
+ tcpConfig?: TCPConfig;
+
+ /**
+ * @generated from field: optional sentinel.service.v1.GRPCConfig grpc_config = 3;
+ */
+ grpcConfig?: GRPCConfig;
+};
+
+/**
+ * Describes the message sentinel.service.v1.ServiceConfig.
+ * Use `create(ServiceConfigSchema)` to create a new message.
+ */
+export const ServiceConfigSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_service_v1_service, 3);
+
+/**
+ * @generated from message sentinel.service.v1.Service
+ */
+export type Service = Message<"sentinel.service.v1.Service"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: string name = 2;
+ */
+ name: string;
+
+ /**
+ * @generated from field: sentinel.service.v1.ServiceProtocol protocol = 3;
+ */
+ protocol: ServiceProtocol;
+
+ /**
+ * @generated from field: int64 interval = 4;
+ */
+ interval: bigint;
+
+ /**
+ * @generated from field: int64 timeout = 5;
+ */
+ timeout: bigint;
+
+ /**
+ * @generated from field: int64 retries = 6;
+ */
+ retries: bigint;
+
+ /**
+ * @generated from field: repeated string tags = 7;
+ */
+ tags: string[];
+
+ /**
+ * @generated from field: sentinel.service.v1.ServiceConfig config = 8;
+ */
+ config?: ServiceConfig;
+
+ /**
+ * @generated from field: bool is_notifications_enabled = 9;
+ */
+ isNotificationsEnabled: boolean;
+
+ /**
+ * @generated from field: bool is_enabled = 10;
+ */
+ isEnabled: boolean;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp created_at = 11;
+ */
+ createdAt?: Timestamp;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp updated_at = 12;
+ */
+ updatedAt?: Timestamp;
+};
+
+/**
+ * Describes the message sentinel.service.v1.Service.
+ * Use `create(ServiceSchema)` to create a new message.
+ */
+export const ServiceSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_service_v1_service, 4);
+
+/**
+ * @generated from enum sentinel.service.v1.ServiceProtocol
+ */
+export enum ServiceProtocol {
+ /**
+ * @generated from enum value: SERVICE_PROTOCOL_UNSPECIFIED = 0;
+ */
+ UNSPECIFIED = 0,
+
+ /**
+ * @generated from enum value: SERVICE_PROTOCOL_HTTP = 1;
+ */
+ HTTP = 1,
+
+ /**
+ * @generated from enum value: SERVICE_PROTOCOL_TCP = 2;
+ */
+ TCP = 2,
+
+ /**
+ * @generated from enum value: SERVICE_PROTOCOL_GRPC = 3;
+ */
+ GRPC = 3,
+}
+
+/**
+ * Describes the enum sentinel.service.v1.ServiceProtocol.
+ */
+export const ServiceProtocolSchema: GenEnum = /*@__PURE__*/
+ enumDesc(file_sentinel_service_v1_service, 0);
+
+/**
+ * @generated from enum sentinel.service.v1.ServiceStatus
+ */
+export enum ServiceStatus {
+ /**
+ * @generated from enum value: SERVICE_STATUS_UNSPECIFIED = 0;
+ */
+ UNSPECIFIED = 0,
+
+ /**
+ * @generated from enum value: SERVICE_STATUS_UNKNOWN = 1;
+ */
+ UNKNOWN = 1,
+
+ /**
+ * @generated from enum value: SERVICE_STATUS_UP = 2;
+ */
+ UP = 2,
+
+ /**
+ * @generated from enum value: SERVICE_STATUS_DOWN = 3;
+ */
+ DOWN = 3,
+}
+
+/**
+ * Describes the enum sentinel.service.v1.ServiceStatus.
+ */
+export const ServiceStatusSchema: GenEnum = /*@__PURE__*/
+ enumDesc(file_sentinel_service_v1_service, 1);
+
diff --git a/frontend/src/api/gen/sentinel/system/v1/service-SystemService_connectquery.ts b/frontend/src/api/gen/sentinel/system/v1/service-SystemService_connectquery.ts
new file mode 100644
index 0000000..e71e2de
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/system/v1/service-SystemService_connectquery.ts
@@ -0,0 +1,25 @@
+// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts"
+// @generated from file sentinel/system/v1/service.proto (package sentinel.system.v1, syntax proto3)
+/* eslint-disable */
+
+import { SystemService } from "./service_pb";
+
+/**
+ * @generated from rpc sentinel.system.v1.SystemService.CheckIsInitialized
+ */
+export const checkIsInitialized = SystemService.method.checkIsInitialized;
+
+/**
+ * @generated from rpc sentinel.system.v1.SystemService.Initialize
+ */
+export const initialize = SystemService.method.initialize;
+
+/**
+ * @generated from rpc sentinel.system.v1.SystemService.GetSystemInfo
+ */
+export const getSystemInfo = SystemService.method.getSystemInfo;
+
+/**
+ * @generated from rpc sentinel.system.v1.SystemService.CheckForUpdates
+ */
+export const checkForUpdates = SystemService.method.checkForUpdates;
diff --git a/frontend/src/api/gen/sentinel/system/v1/service_pb.ts b/frontend/src/api/gen/sentinel/system/v1/service_pb.ts
new file mode 100644
index 0000000..b2fa285
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/system/v1/service_pb.ts
@@ -0,0 +1,325 @@
+// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
+// @generated from file sentinel/system/v1/service.proto (package sentinel.system.v1, syntax proto3)
+/* eslint-disable */
+
+import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
+import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
+import type { Timestamp } from "@bufbuild/protobuf/wkt";
+import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
+import type { Message } from "@bufbuild/protobuf";
+
+/**
+ * Describes the file sentinel/system/v1/service.proto.
+ */
+export const file_sentinel_system_v1_service: GenFile = /*@__PURE__*/
+ fileDesc("CiBzZW50aW5lbC9zeXN0ZW0vdjEvc2VydmljZS5wcm90bxISc2VudGluZWwuc3lzdGVtLnYxItoBCg9BdmFpbGFibGVVcGRhdGUSFwoPY3VycmVudF92ZXJzaW9uGAEgASgJEhQKDGlzX2F2YWlsYWJsZRgCIAEoCBI8CgdkZXRhaWxzGAMgASgLMisuc2VudGluZWwuc3lzdGVtLnYxLkF2YWlsYWJsZVVwZGF0ZS5EZXRhaWxzGloKB0RldGFpbHMSGwoTaXNfYXZhaWxhYmxlX21hbnVhbBgBIAEoCBIQCgh0YWdfbmFtZRgCIAEoCRILCgN1cmwYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkijQIKClN5c3RlbUluZm8SDwoHdmVyc2lvbhgBIAEoCRITCgtjb21taXRfaGFzaBgCIAEoCRISCgpidWlsZF9kYXRlGAMgASgJEhIKCmdvX3ZlcnNpb24YBCABKAkSFgoOc3FsaXRlX3ZlcnNpb24YBSABKAkSCgoCb3MYBiABKAkSDAoEYXJjaBgHIAEoCRIQCghob3N0bmFtZRgIIAEoCRIWCg5rZXJuZWxfdmVyc2lvbhgJIAEoCRIRCgljcHVfbW9kZWwYCiABKAkSEgoKaXBfYWRkcmVzcxgLIAEoCRIuCgpzdGFydGVkX2F0GAwgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCIbChlDaGVja0lzSW5pdGlhbGl6ZWRSZXF1ZXN0IjQKGkNoZWNrSXNJbml0aWFsaXplZFJlc3BvbnNlEhYKDmlzX2luaXRpYWxpemVkGAEgASgIIjQKEUluaXRpYWxpemVSZXF1ZXN0Eg0KBWVtYWlsGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIhQKEkluaXRpYWxpemVSZXNwb25zZSIWChRHZXRTeXN0ZW1JbmZvUmVxdWVzdCJFChVHZXRTeXN0ZW1JbmZvUmVzcG9uc2USLAoEaW5mbxgBIAEoCzIeLnNlbnRpbmVsLnN5c3RlbS52MS5TeXN0ZW1JbmZvIhgKFkNoZWNrRm9yVXBkYXRlc1JlcXVlc3QiTAoXQ2hlY2tGb3JVcGRhdGVzUmVzcG9uc2USMQoEaW5mbxgBIAEoCzIjLnNlbnRpbmVsLnN5c3RlbS52MS5BdmFpbGFibGVVcGRhdGUyswMKDVN5c3RlbVNlcnZpY2UScwoSQ2hlY2tJc0luaXRpYWxpemVkEi0uc2VudGluZWwuc3lzdGVtLnYxLkNoZWNrSXNJbml0aWFsaXplZFJlcXVlc3QaLi5zZW50aW5lbC5zeXN0ZW0udjEuQ2hlY2tJc0luaXRpYWxpemVkUmVzcG9uc2USWwoKSW5pdGlhbGl6ZRIlLnNlbnRpbmVsLnN5c3RlbS52MS5Jbml0aWFsaXplUmVxdWVzdBomLnNlbnRpbmVsLnN5c3RlbS52MS5Jbml0aWFsaXplUmVzcG9uc2USZAoNR2V0U3lzdGVtSW5mbxIoLnNlbnRpbmVsLnN5c3RlbS52MS5HZXRTeXN0ZW1JbmZvUmVxdWVzdBopLnNlbnRpbmVsLnN5c3RlbS52MS5HZXRTeXN0ZW1JbmZvUmVzcG9uc2USagoPQ2hlY2tGb3JVcGRhdGVzEiouc2VudGluZWwuc3lzdGVtLnYxLkNoZWNrRm9yVXBkYXRlc1JlcXVlc3QaKy5zZW50aW5lbC5zeXN0ZW0udjEuQ2hlY2tGb3JVcGRhdGVzUmVzcG9uc2VC5QEKFmNvbS5zZW50aW5lbC5zeXN0ZW0udjFCDFNlcnZpY2VQcm90b1ABWlNnaXRodWIuY29tL3N4d2ViZGV2L3NlbnRpbmVsL2ludGVybmFsL2h1Yi9odWJzZXJ2ZXIvYXBpL3NlbnRpbmVsL3N5c3RlbS92MTtzeXN0ZW12MaICA1NTWKoCElNlbnRpbmVsLlN5c3RlbS5WMcoCElNlbnRpbmVsXFN5c3RlbVxWMeICHlNlbnRpbmVsXFN5c3RlbVxWMVxHUEJNZXRhZGF0YeoCFFNlbnRpbmVsOjpTeXN0ZW06OlYxYgZwcm90bzM", [file_google_protobuf_timestamp]);
+
+/**
+ * AvailableUpdate represents information about an available update
+ *
+ * @generated from message sentinel.system.v1.AvailableUpdate
+ */
+export type AvailableUpdate = Message<"sentinel.system.v1.AvailableUpdate"> & {
+ /**
+ * @generated from field: string current_version = 1;
+ */
+ currentVersion: string;
+
+ /**
+ * @generated from field: bool is_available = 2;
+ */
+ isAvailable: boolean;
+
+ /**
+ * @generated from field: sentinel.system.v1.AvailableUpdate.Details details = 3;
+ */
+ details?: AvailableUpdate_Details;
+};
+
+/**
+ * Describes the message sentinel.system.v1.AvailableUpdate.
+ * Use `create(AvailableUpdateSchema)` to create a new message.
+ */
+export const AvailableUpdateSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_system_v1_service, 0);
+
+/**
+ * @generated from message sentinel.system.v1.AvailableUpdate.Details
+ */
+export type AvailableUpdate_Details = Message<"sentinel.system.v1.AvailableUpdate.Details"> & {
+ /**
+ * @generated from field: bool is_available_manual = 1;
+ */
+ isAvailableManual: boolean;
+
+ /**
+ * @generated from field: string tag_name = 2;
+ */
+ tagName: string;
+
+ /**
+ * @generated from field: string url = 3;
+ */
+ url: string;
+
+ /**
+ * @generated from field: string description = 4;
+ */
+ description: string;
+};
+
+/**
+ * Describes the message sentinel.system.v1.AvailableUpdate.Details.
+ * Use `create(AvailableUpdate_DetailsSchema)` to create a new message.
+ */
+export const AvailableUpdate_DetailsSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_system_v1_service, 0, 0);
+
+/**
+ * SystemInfo represents information about the server
+ *
+ * @generated from message sentinel.system.v1.SystemInfo
+ */
+export type SystemInfo = Message<"sentinel.system.v1.SystemInfo"> & {
+ /**
+ * @generated from field: string version = 1;
+ */
+ version: string;
+
+ /**
+ * @generated from field: string commit_hash = 2;
+ */
+ commitHash: string;
+
+ /**
+ * @generated from field: string build_date = 3;
+ */
+ buildDate: string;
+
+ /**
+ * @generated from field: string go_version = 4;
+ */
+ goVersion: string;
+
+ /**
+ * @generated from field: string sqlite_version = 5;
+ */
+ sqliteVersion: string;
+
+ /**
+ * @generated from field: string os = 6;
+ */
+ os: string;
+
+ /**
+ * @generated from field: string arch = 7;
+ */
+ arch: string;
+
+ /**
+ * @generated from field: string hostname = 8;
+ */
+ hostname: string;
+
+ /**
+ * @generated from field: string kernel_version = 9;
+ */
+ kernelVersion: string;
+
+ /**
+ * @generated from field: string cpu_model = 10;
+ */
+ cpuModel: string;
+
+ /**
+ * @generated from field: string ip_address = 11;
+ */
+ ipAddress: string;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp started_at = 12;
+ */
+ startedAt?: Timestamp;
+};
+
+/**
+ * Describes the message sentinel.system.v1.SystemInfo.
+ * Use `create(SystemInfoSchema)` to create a new message.
+ */
+export const SystemInfoSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_system_v1_service, 1);
+
+/**
+ * CheckIsInitializedRequest
+ *
+ * @generated from message sentinel.system.v1.CheckIsInitializedRequest
+ */
+export type CheckIsInitializedRequest = Message<"sentinel.system.v1.CheckIsInitializedRequest"> & {
+};
+
+/**
+ * Describes the message sentinel.system.v1.CheckIsInitializedRequest.
+ * Use `create(CheckIsInitializedRequestSchema)` to create a new message.
+ */
+export const CheckIsInitializedRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_system_v1_service, 2);
+
+/**
+ * @generated from message sentinel.system.v1.CheckIsInitializedResponse
+ */
+export type CheckIsInitializedResponse = Message<"sentinel.system.v1.CheckIsInitializedResponse"> & {
+ /**
+ * @generated from field: bool is_initialized = 1;
+ */
+ isInitialized: boolean;
+};
+
+/**
+ * Describes the message sentinel.system.v1.CheckIsInitializedResponse.
+ * Use `create(CheckIsInitializedResponseSchema)` to create a new message.
+ */
+export const CheckIsInitializedResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_system_v1_service, 3);
+
+/**
+ * InitializeRequest
+ *
+ * @generated from message sentinel.system.v1.InitializeRequest
+ */
+export type InitializeRequest = Message<"sentinel.system.v1.InitializeRequest"> & {
+ /**
+ * @generated from field: string email = 1;
+ */
+ email: string;
+
+ /**
+ * @generated from field: string password = 2;
+ */
+ password: string;
+};
+
+/**
+ * Describes the message sentinel.system.v1.InitializeRequest.
+ * Use `create(InitializeRequestSchema)` to create a new message.
+ */
+export const InitializeRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_system_v1_service, 4);
+
+/**
+ * @generated from message sentinel.system.v1.InitializeResponse
+ */
+export type InitializeResponse = Message<"sentinel.system.v1.InitializeResponse"> & {
+};
+
+/**
+ * Describes the message sentinel.system.v1.InitializeResponse.
+ * Use `create(InitializeResponseSchema)` to create a new message.
+ */
+export const InitializeResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_system_v1_service, 5);
+
+/**
+ * GetSystemInfoRequest
+ *
+ * @generated from message sentinel.system.v1.GetSystemInfoRequest
+ */
+export type GetSystemInfoRequest = Message<"sentinel.system.v1.GetSystemInfoRequest"> & {
+};
+
+/**
+ * Describes the message sentinel.system.v1.GetSystemInfoRequest.
+ * Use `create(GetSystemInfoRequestSchema)` to create a new message.
+ */
+export const GetSystemInfoRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_system_v1_service, 6);
+
+/**
+ * @generated from message sentinel.system.v1.GetSystemInfoResponse
+ */
+export type GetSystemInfoResponse = Message<"sentinel.system.v1.GetSystemInfoResponse"> & {
+ /**
+ * @generated from field: sentinel.system.v1.SystemInfo info = 1;
+ */
+ info?: SystemInfo;
+};
+
+/**
+ * Describes the message sentinel.system.v1.GetSystemInfoResponse.
+ * Use `create(GetSystemInfoResponseSchema)` to create a new message.
+ */
+export const GetSystemInfoResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_system_v1_service, 7);
+
+/**
+ * CheckForUpdatesRequest
+ *
+ * @generated from message sentinel.system.v1.CheckForUpdatesRequest
+ */
+export type CheckForUpdatesRequest = Message<"sentinel.system.v1.CheckForUpdatesRequest"> & {
+};
+
+/**
+ * Describes the message sentinel.system.v1.CheckForUpdatesRequest.
+ * Use `create(CheckForUpdatesRequestSchema)` to create a new message.
+ */
+export const CheckForUpdatesRequestSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_system_v1_service, 8);
+
+/**
+ * @generated from message sentinel.system.v1.CheckForUpdatesResponse
+ */
+export type CheckForUpdatesResponse = Message<"sentinel.system.v1.CheckForUpdatesResponse"> & {
+ /**
+ * @generated from field: sentinel.system.v1.AvailableUpdate info = 1;
+ */
+ info?: AvailableUpdate;
+};
+
+/**
+ * Describes the message sentinel.system.v1.CheckForUpdatesResponse.
+ * Use `create(CheckForUpdatesResponseSchema)` to create a new message.
+ */
+export const CheckForUpdatesResponseSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_system_v1_service, 9);
+
+/**
+ * SystemService is the service for system information
+ *
+ * @generated from service sentinel.system.v1.SystemService
+ */
+export const SystemService: GenService<{
+ /**
+ * @generated from rpc sentinel.system.v1.SystemService.CheckIsInitialized
+ */
+ checkIsInitialized: {
+ methodKind: "unary";
+ input: typeof CheckIsInitializedRequestSchema;
+ output: typeof CheckIsInitializedResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.system.v1.SystemService.Initialize
+ */
+ initialize: {
+ methodKind: "unary";
+ input: typeof InitializeRequestSchema;
+ output: typeof InitializeResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.system.v1.SystemService.GetSystemInfo
+ */
+ getSystemInfo: {
+ methodKind: "unary";
+ input: typeof GetSystemInfoRequestSchema;
+ output: typeof GetSystemInfoResponseSchema;
+ },
+ /**
+ * @generated from rpc sentinel.system.v1.SystemService.CheckForUpdates
+ */
+ checkForUpdates: {
+ methodKind: "unary";
+ input: typeof CheckForUpdatesRequestSchema;
+ output: typeof CheckForUpdatesResponseSchema;
+ },
+}> = /*@__PURE__*/
+ serviceDesc(file_sentinel_system_v1_service, 0);
+
diff --git a/frontend/src/api/gen/sentinel/users/v1/users_pb.ts b/frontend/src/api/gen/sentinel/users/v1/users_pb.ts
new file mode 100644
index 0000000..7f9f8fa
--- /dev/null
+++ b/frontend/src/api/gen/sentinel/users/v1/users_pb.ts
@@ -0,0 +1,63 @@
+// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
+// @generated from file sentinel/users/v1/users.proto (package sentinel.users.v1, syntax proto3)
+/* eslint-disable */
+
+import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2";
+import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
+import type { Timestamp } from "@bufbuild/protobuf/wkt";
+import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
+import type { Message } from "@bufbuild/protobuf";
+
+/**
+ * Describes the file sentinel/users/v1/users.proto.
+ */
+export const file_sentinel_users_v1_users: GenFile = /*@__PURE__*/
+ fileDesc("Ch1zZW50aW5lbC91c2Vycy92MS91c2Vycy5wcm90bxIRc2VudGluZWwudXNlcnMudjEitgEKBFVzZXISCgoCaWQYASABKAkSDQoFZW1haWwYAiABKAkSEQoJZnVsbF9uYW1lGAMgASgJEgwKBHJvbGUYBCABKAkSEgoKYXZhdGFyX3VybBgFIAEoCRIuCgpjcmVhdGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcELcAQoVY29tLnNlbnRpbmVsLnVzZXJzLnYxQgpVc2Vyc1Byb3RvUAFaUWdpdGh1Yi5jb20vc3h3ZWJkZXYvc2VudGluZWwvaW50ZXJuYWwvaHViL2h1YnNlcnZlci9hcGkvc2VudGluZWwvdXNlcnMvdjE7dXNlcnN2MaICA1NVWKoCEVNlbnRpbmVsLlVzZXJzLlYxygIRU2VudGluZWxcVXNlcnNcVjHiAh1TZW50aW5lbFxVc2Vyc1xWMVxHUEJNZXRhZGF0YeoCE1NlbnRpbmVsOjpVc2Vyczo6VjFiBnByb3RvMw", [file_google_protobuf_timestamp]);
+
+/**
+ * @generated from message sentinel.users.v1.User
+ */
+export type User = Message<"sentinel.users.v1.User"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: string email = 2;
+ */
+ email: string;
+
+ /**
+ * @generated from field: string full_name = 3;
+ */
+ fullName: string;
+
+ /**
+ * @generated from field: string role = 4;
+ */
+ role: string;
+
+ /**
+ * @generated from field: string avatar_url = 5;
+ */
+ avatarUrl: string;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp created_at = 6;
+ */
+ createdAt?: Timestamp;
+
+ /**
+ * @generated from field: google.protobuf.Timestamp updated_at = 7;
+ */
+ updatedAt?: Timestamp;
+};
+
+/**
+ * Describes the message sentinel.users.v1.User.
+ * Use `create(UserSchema)` to create a new message.
+ */
+export const UserSchema: GenMessage = /*@__PURE__*/
+ messageDesc(file_sentinel_users_v1_users, 0);
+
diff --git a/frontend/src/app/app.tsx b/frontend/src/app/app.tsx
new file mode 100644
index 0000000..6f149b9
--- /dev/null
+++ b/frontend/src/app/app.tsx
@@ -0,0 +1,73 @@
+import { RouterProvider } from "@tanstack/react-router";
+
+import { useAuthStore } from "./stores/auth";
+import { router } from "../router";
+import { useSystemStore } from "./stores/system";
+import PageLoader from "@/shared/components/pageLoader";
+import { useUserPreferenceStore } from "./stores/userPreferience";
+import { useEffect } from "react";
+import { useShallow } from "zustand/react/shallow";
+
+const App = () => {
+ const { initTheme } = useUserPreferenceStore(
+ useShallow((s) => ({ initTheme: s.initTheme })),
+ );
+
+ const {
+ isLoading: isSystemLoading,
+ isSystemInitialized,
+ checkIsInitialized,
+ error,
+ } = useSystemStore(
+ useShallow((s) => ({
+ isLoading: s.isLoading,
+ error: s.error,
+ isSystemInitialized: s.isSystemInitialized,
+ checkIsInitialized: s.checkIsInitialized,
+ })),
+ );
+
+ const { isLoading, isAuthenticated, init } = useAuthStore(
+ useShallow((s) => ({
+ isLoading: s.isLoading,
+ isAuthenticated: s.isAuthenticated,
+ init: s.init,
+ })),
+ );
+
+ // Initialize theme on app load
+ useEffect(() => {
+ initTheme();
+ }, [initTheme]);
+
+ // Initialize system state on app load
+ useEffect(() => {
+ checkIsInitialized();
+ }, [checkIsInitialized]);
+
+ // Initialize auth on app load
+ useEffect(() => {
+ if (!isSystemLoading) return;
+ init();
+ }, [isSystemLoading, init]);
+
+ if (error) {
+ throw error;
+ }
+
+ if (isSystemLoading || isLoading) {
+ return ;
+ }
+
+ return (
+
+ );
+};
+
+export default App;
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
new file mode 100644
index 0000000..89dfbb5
--- /dev/null
+++ b/frontend/src/app/layout.tsx
@@ -0,0 +1,34 @@
+import {
+ SidebarInset,
+ SidebarProvider,
+ SidebarTrigger,
+} from "@/shared/components/ui/sidebar";
+import { AppSidebar } from "@/shared/components/sidebar/sidebar";
+import { ThemeToggle } from "@/shared/components/theme-toggle";
+import { SystemInfo } from "@/features/apiInfo/systemInfo";
+
+type LayoutProps = {
+ children: React.ReactNode;
+};
+
+export default function Layout({ children }: LayoutProps) {
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/app/layouts/parts/Header.tsx b/frontend/src/app/layouts/parts/Header.tsx
deleted file mode 100644
index bb9b647..0000000
--- a/frontend/src/app/layouts/parts/Header.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-import { Link } from "@tanstack/react-router";
-
-import { HouseIcon, BookXIcon } from "lucide-react";
-
-import { Button } from "@shared/components/ui/button";
-import {
- NavigationMenu,
- NavigationMenuItem,
- NavigationMenuList,
-} from "@shared/components/ui/navigation-menu";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@shared/components/ui/popover";
-import { cn } from "@/shared/lib/utils";
-import ServiceCreate from "@/pages/service/serviceCreate";
-import { UpdateBanner } from "@/features/apiInfo/update-banner";
-import { ServerInfo } from "@/features/apiInfo/server-info";
-
-// Navigation links array
-const navigationLinks = [
- { href: "/", label: "Dashboard", icon: HouseIcon, active: true },
- { href: "/incidents", label: "Incidents", icon: BookXIcon, active: true },
- // { href: "/certificates", label: "Certificates", icon: ShieldCheck },
-];
-
-function NavigationMenuLink({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-export default function Component() {
- return (
-
- );
-}
diff --git a/frontend/src/app/protected.tsx b/frontend/src/app/protected.tsx
new file mode 100644
index 0000000..71057d5
--- /dev/null
+++ b/frontend/src/app/protected.tsx
@@ -0,0 +1,36 @@
+import PageLoader from "@/shared/components/pageLoader";
+import Layout from "./layout";
+import { useProjectStore } from "./stores/projects";
+import ProjectCreate from "@/pages/projects/create";
+import { useShallow } from "zustand/react/shallow";
+import { useEffect } from "react";
+
+type ProtectedWrapperProps = {
+ children: React.ReactNode;
+};
+
+const ProtectedWrapper = ({ children }: ProtectedWrapperProps) => {
+ const { isLoading, projects, loadProjects } = useProjectStore(
+ useShallow((s) => ({
+ isLoading: s.isLoading,
+ projects: s.projects,
+ loadProjects: s.loadProjects,
+ })),
+ );
+
+ useEffect(() => {
+ loadProjects();
+ }, [loadProjects]);
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (projects.length === 0) {
+ return ;
+ }
+
+ return {children};
+};
+
+export default ProtectedWrapper;
diff --git a/frontend/src/app/stores/auth.ts b/frontend/src/app/stores/auth.ts
new file mode 100644
index 0000000..06ad6d5
--- /dev/null
+++ b/frontend/src/app/stores/auth.ts
@@ -0,0 +1,285 @@
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+import type { User } from "@/api/gen/sentinel/users/v1/users_pb";
+import { type AuthorizationResponse } from "@/api/gen/sentinel/auth/v1/auth_pb";
+import {
+ AuthorizationRequestSchema,
+ AuthenticateRequestSchema,
+ RefreshTokenRequestSchema,
+ LogoutRequestSchema,
+ DeviceInfoSchema,
+ DeviceType,
+} from "@/api/gen/sentinel/auth/v1/auth_pb";
+import { Code, ConnectError } from "@connectrpc/connect";
+import { create as createMsg } from "@bufbuild/protobuf";
+import { timestampDate } from "@bufbuild/protobuf/wkt";
+
+import { authClient } from "@/api/api";
+import { toast } from "sonner";
+
+export type AuthSession = {
+ accessToken: string;
+ refreshToken: string;
+ accessTokenExpiredAt: string;
+ refreshTokenExpiredAt: string;
+ deviceId: string;
+};
+
+export type AuthError = {
+ name: string;
+ message: string;
+};
+
+export interface AuthStoreState {
+ // state
+ isLoading: boolean;
+ isAuthenticated: boolean;
+ user?: User;
+ session?: AuthSession | null;
+
+ // actions
+ init: () => Promise;
+ authorization: (email: string, password: string) => Promise;
+ refreshToken: () => Promise;
+ logout: () => Promise;
+ clear: () => void;
+}
+
+export const useAuthStore = create()(
+ persist(
+ (set, get) => {
+ // Interval for token refresh: checks every 30 seconds
+ let refreshInterval: ReturnType | null = null;
+
+ const startRefreshInterval = () => {
+ if (refreshInterval) return;
+
+ refreshInterval = setInterval(() => {
+ const session = get().session;
+ if (!session || !session.accessTokenExpiredAt) return;
+
+ const expiresAt = new Date(session.accessTokenExpiredAt).getTime();
+ const now = Date.now();
+ const timeLeft = expiresAt - now;
+
+ // If less than 5 minutes left, refresh token
+ if (timeLeft < 5 * 60 * 1000) {
+ get().refreshToken();
+ }
+ }, 30 * 1000);
+ };
+
+ // Start interval immediately
+ startRefreshInterval();
+
+ return {
+ isLoading: false,
+ isAuthenticated: false,
+ user: undefined,
+ session: null,
+
+ clear: () => {
+ // Stop refresh interval
+ if (refreshInterval) {
+ clearInterval(refreshInterval);
+ refreshInterval = null;
+ }
+
+ set({
+ isLoading: false,
+ isAuthenticated: false,
+ user: undefined,
+ session: null,
+ });
+ },
+
+ init: async () => {
+ set({ isLoading: true });
+
+ const session = get().session;
+ if (!session) {
+ get().clear();
+ return;
+ }
+
+ // Refresh token expired -> clear
+ if (Date.now() >= new Date(session.refreshTokenExpiredAt).getTime()) {
+ get().clear();
+ return;
+ }
+
+ let currentSession = session;
+ // Access token expired -> try refresh
+ if (
+ Date.now() >=
+ new Date(currentSession.accessTokenExpiredAt).getTime()
+ ) {
+ try {
+ const res = await authClient.refreshToken(
+ createMsg(RefreshTokenRequestSchema, {
+ refreshToken: currentSession.refreshToken,
+ }),
+ );
+ if (!res.accessTokenExpiredAt || !res.refreshTokenExpiredAt) {
+ throw new Error("missing token expiration in response");
+ }
+ currentSession = {
+ accessToken: res.accessToken,
+ refreshToken: res.refreshToken,
+ accessTokenExpiredAt: timestampDate(
+ res.accessTokenExpiredAt,
+ ).toISOString(),
+ refreshTokenExpiredAt: timestampDate(
+ res.refreshTokenExpiredAt,
+ ).toISOString(),
+ deviceId: currentSession.deviceId,
+ };
+ set({ session: currentSession, isAuthenticated: true });
+ } catch (e) {
+ if (e instanceof ConnectError) {
+ if (e.code === Code.Unauthenticated) {
+ get().clear();
+ }
+ toast.error(e.rawMessage);
+ }
+ return;
+ }
+ }
+
+ // Authenticate to fetch user
+ try {
+ const res = await authClient.authenticate(
+ createMsg(AuthenticateRequestSchema, {
+ accessToken: currentSession.accessToken,
+ }),
+ );
+ set({ user: res.user, isAuthenticated: true, isLoading: false });
+ } catch (e) {
+ if (e instanceof ConnectError) {
+ if (e.code === Code.Unauthenticated) {
+ get().clear();
+ return;
+ }
+ toast.error(e.rawMessage);
+ } else {
+ toast.error(
+ `Failed to authenticate user ${(e as Error)?.message}`,
+ );
+ }
+ set({ isLoading: false });
+ }
+ },
+
+ authorization: async (email: string, password: string) => {
+ try {
+ const res: AuthorizationResponse = await authClient.authorization(
+ createMsg(AuthorizationRequestSchema, {
+ email,
+ password,
+ deviceInfo: createMsg(DeviceInfoSchema, {
+ deviceId: get().session?.deviceId || "",
+ deviceType: DeviceType.WEB,
+ deviceName: navigator.userAgent,
+ }),
+ }),
+ );
+ if (
+ !res.authPayload ||
+ !res.authPayload.accessTokenExpiredAt ||
+ !res.authPayload.refreshTokenExpiredAt
+ ) {
+ throw new Error("missing token expiration in response");
+ }
+ const session: AuthSession = {
+ accessToken: res.authPayload.accessToken,
+ refreshToken: res.authPayload.refreshToken,
+ accessTokenExpiredAt: timestampDate(
+ res.authPayload.accessTokenExpiredAt,
+ ).toISOString(),
+ refreshTokenExpiredAt: timestampDate(
+ res.authPayload.refreshTokenExpiredAt,
+ ).toISOString(),
+ deviceId: res.authPayload.deviceId,
+ };
+ set({ session, user: res.user, isAuthenticated: true });
+
+ // Restart refresh interval after successful authorization
+ startRefreshInterval();
+ } catch (e) {
+ if (e instanceof ConnectError) {
+ toast.error(e.rawMessage);
+ } else if (e instanceof Error) {
+ toast.error(e.message);
+ } else {
+ toast.error("An unknown error occurred during authorization");
+ }
+ throw e;
+ }
+ },
+
+ refreshToken: async (): Promise => {
+ const session = get().session;
+ if (
+ !session ||
+ !session.refreshToken ||
+ !session.refreshTokenExpiredAt
+ ) {
+ get().clear();
+ return;
+ }
+
+ // If refresh token expired, signal Unauthenticated-like condition, but don't clear here
+ if (Date.now() >= new Date(session.refreshTokenExpiredAt).getTime()) {
+ get().clear();
+ return;
+ }
+
+ try {
+ const res = await authClient.refreshToken(
+ createMsg(RefreshTokenRequestSchema, {
+ refreshToken: session.refreshToken,
+ }),
+ );
+
+ if (!res.accessTokenExpiredAt || !res.refreshTokenExpiredAt) {
+ throw new Error("missing token expiration in response");
+ }
+
+ const newSession: AuthSession = {
+ accessToken: res.accessToken,
+ refreshToken: res.refreshToken,
+ accessTokenExpiredAt: timestampDate(
+ res.accessTokenExpiredAt,
+ ).toISOString(),
+ refreshTokenExpiredAt: timestampDate(
+ res.refreshTokenExpiredAt,
+ ).toISOString(),
+ deviceId: session.deviceId,
+ };
+ set({ session: newSession, isAuthenticated: true });
+ return;
+ } catch (e) {
+ console.log("refresh token error", e);
+ get().clear();
+ window.location.href = "/login";
+ }
+ },
+
+ logout: async () => {
+ try {
+ await authClient.logout(createMsg(LogoutRequestSchema));
+ } catch {
+ // ignore network/logging errors
+ } finally {
+ get().clear();
+ }
+ },
+ };
+ },
+ {
+ name: "authStore",
+ version: 1,
+ partialize: (state: AuthStoreState) => ({ session: state.session }),
+ },
+ ),
+);
diff --git a/frontend/src/app/stores/projects.ts b/frontend/src/app/stores/projects.ts
new file mode 100644
index 0000000..0021fed
--- /dev/null
+++ b/frontend/src/app/stores/projects.ts
@@ -0,0 +1,96 @@
+import { projectsClient } from "@/api/api";
+import {
+ ProjectCreateRequestSchema,
+ type Project,
+} from "@/api/gen/sentinel/projects/v1/projects_pb";
+import { ConnectError } from "@connectrpc/connect";
+import { toast } from "sonner";
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+import { create as createProto } from "@bufbuild/protobuf";
+
+type ProjectStore = {
+ isLoading: boolean;
+ selectedProjectId?: string;
+ projects: Project[];
+ selectProject: (id: string | undefined) => void;
+ selectedProject: () => Project | undefined;
+ loadProjects: () => Promise;
+ createProject: (name: string, description: string) => Promise;
+};
+
+export const useProjectStore = create()(
+ persist(
+ (set, get) => ({
+ isLoading: true,
+ projects: [],
+ selectProject: (id: string | undefined) => {
+ set({ selectedProjectId: id });
+ window.location.reload();
+ },
+ selectedProject: () => {
+ const { selectedProjectId, projects } = get();
+ const selectedProject = projects.find(
+ (p) => p.id === selectedProjectId,
+ );
+
+ if (selectedProject) {
+ return selectedProject;
+ }
+
+ if (projects.length > 0) {
+ return projects[0];
+ }
+
+ return undefined;
+ },
+ loadProjects: async () => {
+ try {
+ const data = await projectsClient.projectsList({});
+ set({ projects: data.items });
+
+ const selectedProjectId = get().selectedProjectId;
+ if (!selectedProjectId && data.items.length > 0) {
+ set({ selectedProjectId: data.items[0].id });
+ }
+ } catch (error) {
+ if (error instanceof ConnectError) {
+ toast.error(error.rawMessage);
+ } else {
+ console.error("Failed to load projects:", error);
+ }
+ } finally {
+ set({ isLoading: false });
+ }
+ },
+ createProject: async (name: string, description: string) => {
+ try {
+ const res = await projectsClient.projectCreate(
+ createProto(ProjectCreateRequestSchema, {
+ name,
+ description,
+ }),
+ );
+
+ await get().loadProjects();
+
+ set({ selectedProjectId: res.item?.id });
+
+ window.location.reload();
+ } catch (error) {
+ if (error instanceof ConnectError) {
+ toast.error(error.rawMessage);
+ } else {
+ console.error("Failed to create project:", error);
+ }
+ }
+ },
+ }),
+ {
+ name: "projectStore",
+ partialize: (state) => ({
+ selectedProjectId: state.selectedProjectId,
+ }),
+ },
+ ),
+);
diff --git a/frontend/src/app/stores/system.ts b/frontend/src/app/stores/system.ts
new file mode 100644
index 0000000..2505d91
--- /dev/null
+++ b/frontend/src/app/stores/system.ts
@@ -0,0 +1,52 @@
+import { systemClient } from "@/api/api";
+import { ConnectError } from "@connectrpc/connect";
+import { toast } from "sonner";
+import { create } from "zustand";
+import { create as createProto } from "@bufbuild/protobuf";
+import {
+ CheckIsInitializedRequestSchema,
+ InitializeRequestSchema,
+} from "@/api/gen/sentinel/system/v1/service_pb";
+
+type SystemStore = {
+ isLoading: boolean;
+ error?: unknown;
+ isSystemInitialized: boolean;
+ initializeSystem: (email: string, password: string) => Promise;
+ checkIsInitialized: () => Promise;
+};
+
+export const useSystemStore = create()((set) => ({
+ isLoading: true,
+ isSystemInitialized: false,
+ checkIsInitialized: async () => {
+ try {
+ const data = await systemClient.checkIsInitialized(
+ createProto(CheckIsInitializedRequestSchema),
+ );
+ set({ isSystemInitialized: data.isInitialized });
+ } catch (error) {
+ set({ error: error });
+ } finally {
+ set({ isLoading: false });
+ }
+ },
+ initializeSystem: async (email: string, password: string) => {
+ try {
+ await systemClient.initialize(
+ createProto(InitializeRequestSchema, {
+ email,
+ password,
+ }),
+ );
+ set({ isSystemInitialized: true });
+ toast.success("System initialized successfully");
+ } catch (error) {
+ if (error instanceof ConnectError) {
+ toast.error(error.rawMessage);
+ } else {
+ console.error("Failed to initialize system:", error);
+ }
+ }
+ },
+}));
diff --git a/frontend/src/app/stores/userPreferience.ts b/frontend/src/app/stores/userPreferience.ts
new file mode 100644
index 0000000..d5449f7
--- /dev/null
+++ b/frontend/src/app/stores/userPreferience.ts
@@ -0,0 +1,45 @@
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+
+type Theme = "none" | "dark" | "light";
+
+type UserPreferenceStore = {
+ theme: Theme;
+ toggleTheme: () => void;
+ initTheme: () => void;
+};
+
+export const useUserPreferenceStore = create()(
+ persist(
+ (set, get) => ({
+ theme: "none",
+ toggleTheme: () => {
+ const currentTheme = get().theme;
+ const newTheme = currentTheme === "dark" ? "light" : "dark";
+ set({ theme: newTheme });
+ get().initTheme();
+ },
+ initTheme: () => {
+ const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
+ .matches
+ ? "dark"
+ : "light";
+
+ if (get().theme === "none") {
+ set({ theme: systemTheme });
+ }
+
+ const root = window.document.documentElement;
+
+ root.classList.remove("light", "dark");
+ root.classList.add(get().theme);
+ },
+ }),
+ {
+ name: "userPreferenceStore",
+ partialize: (state) => ({
+ theme: state.theme,
+ }),
+ },
+ ),
+);
diff --git a/frontend/src/entities/ActivityIndicatorSVG/ActivityIndicatorSVG.tsx b/frontend/src/entities/ActivityIndicatorSVG/ActivityIndicatorSVG.tsx
index bc2c645..afe2561 100644
--- a/frontend/src/entities/ActivityIndicatorSVG/ActivityIndicatorSVG.tsx
+++ b/frontend/src/entities/ActivityIndicatorSVG/ActivityIndicatorSVG.tsx
@@ -1,6 +1,5 @@
export const ActivityIndicatorSVG = ({ active = true, size = 16 }) => {
const color = active ? "#3b82f6" : "#ef4444";
- const pulseColor = active ? "#3b82f6" : "#ef4444";
return (