fixed middlewares, better types

This commit is contained in:
d34dscene
2025-06-28 00:39:03 +02:00
parent 45bd241515
commit ddc358d09b
65 changed files with 1782 additions and 2777 deletions

View File

@@ -1,3 +1,4 @@
// Package main is the entrypoint for the mantrae agent.
package main
import (

View File

@@ -1,3 +1,4 @@
// Package client provides the agent's main gRPC client logic.
package client
import (

View File

@@ -1,3 +1,4 @@
// Package collector provides functions for collecting data from the host system.
package collector
import (

12
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/mizuchilabs/mantrae
go 1.24.4
require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250613105001-9f2d3c737feb.1
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250625184727-c923a0c2a132.1
connectrpc.com/connect v1.18.1
connectrpc.com/cors v0.1.0
connectrpc.com/grpchealth v1.4.0
@@ -12,7 +12,7 @@ require (
github.com/aws/aws-sdk-go-v2 v1.36.5
github.com/aws/aws-sdk-go-v2/config v1.29.17
github.com/aws/aws-sdk-go-v2/credentials v1.17.70
github.com/aws/aws-sdk-go-v2/service/s3 v1.81.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0
github.com/caarlos0/env/v11 v11.3.1
github.com/cloudflare/cloudflare-go v0.115.0
github.com/coreos/go-oidc/v3 v3.14.1
@@ -20,18 +20,19 @@ require (
github.com/domodwyer/mailyak/v3 v3.6.2
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/uuid v1.6.0
github.com/hypersequent/zen v0.0.0-20250317110808-f521ea1d4fc3
github.com/joeig/go-powerdns/v3 v3.16.0
github.com/pressly/goose/v3 v3.24.3
github.com/rs/cors v1.11.1
github.com/stretchr/testify v1.10.0
github.com/traefik/paerser v0.2.2
github.com/traefik/traefik/v3 v3.4.1
github.com/traefik/traefik/v3 v3.4.3
golang.org/x/crypto v0.39.0
golang.org/x/net v0.41.0
golang.org/x/oauth2 v0.30.0
google.golang.org/protobuf v1.36.6
modernc.org/sqlite v1.38.0
sigs.k8s.io/yaml v1.4.0
sigs.k8s.io/yaml v1.5.0
)
require (
@@ -63,7 +64,7 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-acme/lego/v4 v4.23.1 // indirect
github.com/go-jose/go-jose/v4 v4.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
@@ -108,6 +109,7 @@ require (
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/sync v0.15.0 // indirect

27
go.sum
View File

@@ -1,5 +1,5 @@
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250613105001-9f2d3c737feb.1 h1:AUL6VF5YWL01j/1H/DQbPUSDkEwYqwVCNw7yhbpOxSQ=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250613105001-9f2d3c737feb.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250625184727-c923a0c2a132.1 h1:6tCo3lsKNLqUjRPhyc8JuYWYUiQkulufxSDOfG1zgWQ=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250625184727-c923a0c2a132.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U=
buf.build/go/protovalidate v0.13.1 h1:6loHDTWdY/1qmqmt1MijBIKeN4T9Eajrqb9isT1W1s8=
buf.build/go/protovalidate v0.13.1/go.mod h1:C/QcOn/CjXRn5udUwYBiLs8y1TGy7RS+GOSKqjS77aU=
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
@@ -48,8 +48,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzRE
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
github.com/aws/aws-sdk-go-v2/service/s3 v1.81.0 h1:1GmCadhKR3J2sMVKs2bAYq9VnwYeCqfRyZzD4RASGlA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.81.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0 h1:JubM8CGDDFaAOmBrd8CRYNr49ZNgEAiLwGwgNMdS0nw=
github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=
@@ -93,8 +93,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-acme/lego/v4 v4.23.1 h1:lZ5fGtGESA2L9FB8dNTvrQUq3/X4QOb8ExkKyY7LSV4=
github.com/go-acme/lego/v4 v4.23.1/go.mod h1:7UMVR7oQbIYw6V7mTgGwi4Er7B6Ww0c+c8feiBM0EgI=
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
@@ -117,7 +117,6 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY=
github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
@@ -137,6 +136,8 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/http-wasm/http-wasm-host-go v0.7.0 h1:+1KrRyOO6tWiDB24QrtSYyDmzFLBBs3jioKaUT0mq1c=
github.com/http-wasm/http-wasm-host-go v0.7.0/go.mod h1:adXKcLmL7yuavH/e0kBAp7b3TgAHTo/enCduyN5bXGM=
github.com/hypersequent/zen v0.0.0-20250317110808-f521ea1d4fc3 h1:hql4suSs7RG3+t5UyjdPXIzBmXg6AyFoqRRF+TPR3yY=
github.com/hypersequent/zen v0.0.0-20250317110808-f521ea1d4fc3/go.mod h1:uJ9mqUok1RHIAoVlkWxPHJqXNLwhLzh7jCUbp2V9Rws=
github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k=
github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
github.com/joeig/go-powerdns/v3 v3.16.0 h1:d6k0dVlBYr+B9P5U+74rVY1VmQxUG6Qdtlb3F33cBLQ=
@@ -210,8 +211,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ=
github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
github.com/traefik/traefik/v3 v3.4.1 h1:QBO/C9ILViPVBhsmY8nEnoobTULxg6oW1jUTX8FFh8w=
github.com/traefik/traefik/v3 v3.4.1/go.mod h1:8FHoFbX5P+zMQ3UUjjfrDH87BDSbHllcUQyiI2wCP3o=
github.com/traefik/traefik/v3 v3.4.3 h1:4bFwOd+kd+c+XQevhHbZ4V4Ui5jMXI5aHh6YdHj0mqM=
github.com/traefik/traefik/v3 v3.4.3/go.mod h1:uXB3uTO0wFWHFdliHvsHXvG52n3anUYHed89yRDcZmc=
github.com/unrolled/render v1.7.0 h1:1yke01/tZiZpiXfUG+zqB+6fq3G4I+KDmnh0EhPq7So=
github.com/unrolled/render v1.7.0/go.mod h1:LwQSeDhjml8NLjIO9GJO1/1qpFJxtfVIpzxXKjfVkoI=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -254,6 +255,10 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -348,5 +353,5 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=

View File

@@ -1,3 +1,4 @@
// Package handler provides HTTP handlers for the API.
package handler
import (
@@ -172,7 +173,7 @@ func OIDCCallback(a *config.App) http.HandlerFunc {
// Extract user info from verified token
var userInfo OIDCUserInfo
if err := verifiedToken.Claims(&userInfo); err != nil {
if err = verifiedToken.Claims(&userInfo); err != nil {
http.Error(
w,
fmt.Sprintf("Failed to parse claims: %v", err),
@@ -180,7 +181,7 @@ func OIDCCallback(a *config.App) http.HandlerFunc {
)
return
}
if err := userInfo.Validate(); err != nil {
if err = userInfo.Validate(); err != nil {
http.Error(
w,
fmt.Sprintf("Invalid user info: %v", err),

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"path/filepath"
"slices"
@@ -27,7 +28,11 @@ func UploadAvatar(a *config.App) http.HandlerFunc {
http.Error(w, "File too large or invalid form data", http.StatusBadRequest)
return
}
defer r.MultipartForm.RemoveAll()
defer func() {
if err := r.MultipartForm.RemoveAll(); err != nil {
slog.Error("failed to close request body", "error", err)
}
}()
userID := r.URL.Query().Get("user_id")
if userID == "" {
@@ -40,7 +45,11 @@ func UploadAvatar(a *config.App) http.HandlerFunc {
http.Error(w, "Failed to get uploaded file", http.StatusBadRequest)
return
}
defer file.Close()
defer func() {
if err = file.Close(); err != nil {
slog.Error("failed to close uploaded file", "error", err)
}
}()
extension := filepath.Ext(header.Filename)
allowedExtensions := []string{".png", ".jpg", ".jpeg"}
@@ -79,14 +88,22 @@ func UploadBackup(a *config.App) http.HandlerFunc {
http.Error(w, "File too large or invalid form data", http.StatusBadRequest)
return
}
defer r.MultipartForm.RemoveAll()
defer func() {
if err := r.MultipartForm.RemoveAll(); err != nil {
slog.Error("failed to close request body", "error", err)
}
}()
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "Failed to get uploaded file", http.StatusBadRequest)
return
}
defer file.Close()
defer func() {
if err = file.Close(); err != nil {
slog.Error("failed to close uploaded file", "error", err)
}
}()
extension := filepath.Ext(header.Filename)
allowedExtensions := []string{".db", ".json", ".yaml", ".yml"}
@@ -136,25 +153,22 @@ func UploadBackup(a *config.App) http.HandlerFunc {
return
}
if extension == ".yaml" || extension == ".yml" {
if err = yaml.Unmarshal(content, &dynamic); err != nil {
http.Error(
w,
fmt.Sprintf("Failed to decode YAML file: %v", err),
http.StatusInternalServerError,
)
switch extension {
case ".yaml", ".yml":
if err = yaml.Unmarshal(content, dynamic); err != nil {
http.Error(w, fmt.Sprintf("Failed to decode YAML file: %v", err), http.StatusInternalServerError)
return
}
} else if extension == ".json" {
if err = json.Unmarshal(content, &dynamic); err != nil {
http.Error(
w,
fmt.Sprintf("Failed to decode JSON file: %v", err),
http.StatusInternalServerError,
)
case ".json":
if err = json.Unmarshal(content, dynamic); err != nil {
http.Error(w, fmt.Sprintf("Failed to decode JSON file: %v", err), http.StatusInternalServerError)
return
}
default:
http.Error(w, "Invalid file type", http.StatusBadRequest)
return
}
if err = convert.DynamicToDB(r.Context(), *a.Conn.GetQuery(), profileIDValue, dynamic); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View File

@@ -1,3 +1,4 @@
// Package middlewares provides authentication and logging middleware.
package middlewares
import (

View File

@@ -67,7 +67,11 @@ func NewServer(app *config.App) *Server {
func (s *Server) Start(ctx context.Context) error {
s.registerServices()
defer s.app.Conn.Close()
defer func() {
if err := s.app.Conn.Close(); err != nil {
slog.Error("failed to close database connection", "error", err)
}
}()
server := &http.Server{
Addr: s.Host + ":" + s.Port,
@@ -83,12 +87,12 @@ func (s *Server) Start(ctx context.Context) error {
// Start server in a goroutine
go func() {
serverUrl, _ := s.app.SM.Get("server_url")
if serverUrl == "" {
serverUrl = s.Host + ":" + s.Port
serverURL, ok := s.app.SM.Get("server_url")
if ok && serverURL == "" {
serverURL = s.Host + ":" + s.Port
}
slog.Info("Server listening on", "address", "127.0.0.1:"+s.Port)
slog.Info("Agents can connect to", "address", serverUrl)
slog.Info("Agents can connect to", "address", serverURL)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
serverErr <- err
}

View File

@@ -48,11 +48,11 @@ func (s *AgentService) CreateAgent(
return nil, connect.NewError(connect.CodeInternal, err)
}
serverUrl, err := s.app.Conn.GetQuery().GetSetting(ctx, settings.KeyServerURL)
serverURL, err := s.app.Conn.GetQuery().GetSetting(ctx, settings.KeyServerURL)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
if serverUrl.Value == "" {
if serverURL.Value == "" {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("server url is required, check your settings"),
@@ -157,7 +157,7 @@ func (s *AgentService) HealthCheck(
}
// Rotate Token if it's close to expiring
if _, err := s.updateToken(ctx, &agent); err != nil {
if _, err = s.updateToken(ctx, &agent); err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
@@ -235,7 +235,7 @@ func (s *AgentService) updateToken(ctx context.Context, agent *db.Agent) (*strin
}
func (s *AgentService) createToken(agentID string, profileID int64) (*string, error) {
serverUrl, ok := s.app.SM.Get(settings.KeyServerURL)
serverURL, ok := s.app.SM.Get(settings.KeyServerURL)
if !ok {
return nil, errors.New("failed to get server url setting")
}
@@ -248,7 +248,7 @@ func (s *AgentService) createToken(agentID string, profileID int64) (*string, er
token, err := meta.EncodeAgentToken(
profileID,
agentID,
serverUrl,
serverURL,
s.app.Secret,
time.Now().Add(settings.AsDuration(agentInterval)),
)

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"io"
"log/slog"
"connectrpc.com/connect"
"github.com/mizuchilabs/mantrae/internal/config"
@@ -95,7 +96,11 @@ func (s *BackupService) DownloadBackup(
if err != nil {
return connect.NewError(connect.CodeInternal, err)
}
defer reader.Close()
defer func() {
if err = reader.Close(); err != nil {
slog.Error("failed to close backup reader", "error", err)
}
}()
buf := make([]byte, 32*1024)
for {

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"log/slog"
"net/http"
"slices"
@@ -320,7 +321,11 @@ func (s *MiddlewareService) GetMiddlewarePlugins(
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
defer resp.Body.Close()
defer func() {
if err := resp.Body.Close(); err != nil {
slog.Error("failed to close response body", "error", err)
}
}()
var allPlugins []schema.Plugin
if err := json.NewDecoder(resp.Body).Decode(&allPlugins); err != nil {

View File

@@ -1,3 +1,4 @@
// Package service provides the gRPC service implementations.
package service
import (

View File

@@ -1,3 +1,4 @@
// Package backup provides functionality for creating and restoring backups.
package backup
import (

View File

@@ -15,6 +15,7 @@ type Flags struct {
Version bool
Update bool
Squash bool
Zod bool
}
func ParseFlags() {
@@ -22,6 +23,7 @@ func ParseFlags() {
flag.BoolVar(&f.Version, "version", false, "Print version and exit")
flag.BoolVar(&f.Update, "update", false, "Update the application")
flag.BoolVar(&f.Squash, "squash", false, "Squash the database")
flag.BoolVar(&f.Zod, "zod", false, "Generate zod schemas (only for dev)")
flag.Parse()
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
@@ -35,5 +37,10 @@ func ParseFlags() {
store.Squash()
os.Exit(1)
}
if f.Zod {
StructToZodSchema()
os.Exit(1)
}
build.Update(f.Update)
}

View File

@@ -1,3 +1,4 @@
// Package config provides application configuration and setup.
package config
import (

55
internal/config/zod.go Normal file
View File

@@ -0,0 +1,55 @@
package config
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/hypersequent/zen"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
)
// StructToZodSchema converts a struct to a zod schema (for use in the frontend)
func StructToZodSchema() {
types := map[string]any{
// Routers
"httpRouter": dynamic.Router{},
"tcpRouter": dynamic.TCPRouter{},
"udpRouter": dynamic.UDPRouter{},
// Services
"httpService": dynamic.Service{},
"tcpService": dynamic.TCPService{},
"udpService": dynamic.UDPService{},
// HTTP Middlewares
"httpMiddleware": dynamic.Middleware{},
// TCP Middlewares
"tcpMiddleware": dynamic.TCPMiddleware{},
}
var builder strings.Builder
// Add a header
builder.WriteString("// This file is auto-generated via `zen.StructToZodSchema`.\n")
builder.WriteString("// Do not edit manually.\n\n")
builder.WriteString("import { z } from 'zod';\n\n")
for _, strct := range types {
schema := zen.StructToZodSchema(strct)
builder.WriteString(fmt.Sprintf("%s\n", schema))
}
out := "./web/src/lib/gen/zen/traefik-schemas.ts"
if err := os.MkdirAll(filepath.Dir(out), 0755); err != nil {
panic(err)
}
if err := os.WriteFile(out, []byte(builder.String()), 0644); err != nil {
panic(err)
}
fmt.Printf("✅ Zod schemas written to %s\n", out)
}

View File

@@ -18,6 +18,7 @@ func HTTPMiddlewareToProto(m *db.HttpMiddleware) *mantraev1.Middleware {
Name: m.Name,
Config: config,
Type: mantraev1.MiddlewareType_MIDDLEWARE_TYPE_HTTP,
Enabled: m.Enabled,
CreatedAt: SafeTimestamp(m.CreatedAt),
UpdatedAt: SafeTimestamp(m.UpdatedAt),
}
@@ -36,6 +37,7 @@ func TCPMiddlewareToProto(m *db.TcpMiddleware) *mantraev1.Middleware {
Name: m.Name,
Config: config,
Type: mantraev1.MiddlewareType_MIDDLEWARE_TYPE_TCP,
Enabled: m.Enabled,
CreatedAt: SafeTimestamp(m.CreatedAt),
UpdatedAt: SafeTimestamp(m.UpdatedAt),
}
@@ -58,6 +60,7 @@ func TCPMiddlewaresToProto(middlewares []db.TcpMiddleware) []*mantraev1.Middlewa
}
// Specialized batch conversion functions
func MiddlewaresByProfileToProto(
middlewares []db.ListMiddlewaresByProfileRow,
) []*mantraev1.Middleware {
@@ -76,6 +79,7 @@ func MiddlewaresByProfileToProto(
Name: m.Name,
Config: config,
Type: mantraev1.MiddlewareType_MIDDLEWARE_TYPE_HTTP,
Enabled: m.Enabled,
CreatedAt: SafeTimestamp(m.CreatedAt),
UpdatedAt: SafeTimestamp(m.UpdatedAt),
})
@@ -91,6 +95,7 @@ func MiddlewaresByProfileToProto(
Name: m.Name,
Config: config,
Type: mantraev1.MiddlewareType_MIDDLEWARE_TYPE_TCP,
Enabled: m.Enabled,
CreatedAt: SafeTimestamp(m.CreatedAt),
UpdatedAt: SafeTimestamp(m.UpdatedAt),
})
@@ -117,6 +122,7 @@ func MiddlewaresByAgentToProto(middlewares []db.ListMiddlewaresByAgentRow) []*ma
Name: m.Name,
Config: config,
Type: mantraev1.MiddlewareType_MIDDLEWARE_TYPE_HTTP,
Enabled: m.Enabled,
CreatedAt: SafeTimestamp(m.CreatedAt),
UpdatedAt: SafeTimestamp(m.UpdatedAt),
})
@@ -132,6 +138,7 @@ func MiddlewaresByAgentToProto(middlewares []db.ListMiddlewaresByAgentRow) []*ma
Name: m.Name,
Config: config,
Type: mantraev1.MiddlewareType_MIDDLEWARE_TYPE_TCP,
Enabled: m.Enabled,
CreatedAt: SafeTimestamp(m.CreatedAt),
UpdatedAt: SafeTimestamp(m.UpdatedAt),
})

View File

@@ -1,3 +1,4 @@
// Package dns provides functionality for managing DNS records.
package dns
import (

View File

@@ -147,7 +147,11 @@ func (t *TechnitiumProvider) DeleteRecord(subdomain string) error {
if err != nil {
return err
}
defer resp.Body.Close()
defer func() {
if err := resp.Body.Close(); err != nil {
slog.Error("failed to close response body", "error", err)
}
}()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
@@ -202,7 +206,11 @@ func (t *TechnitiumProvider) createRecord(subdomain, recordType string) error {
if err != nil {
return err
}
defer resp.Body.Close()
defer func() {
if err = resp.Body.Close(); err != nil {
slog.Error("failed to close response body", "error", err)
}
}()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
@@ -224,7 +232,11 @@ func (t *TechnitiumProvider) createRecord(subdomain, recordType string) error {
if err != nil {
return err
}
defer resp.Body.Close()
defer func() {
if err := resp.Body.Close(); err != nil {
slog.Error("failed to close response body", "error", err)
}
}()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
@@ -259,7 +271,11 @@ func (t *TechnitiumProvider) updateRecord(subdomain, recordType string) error {
if err != nil {
return err
}
defer resp.Body.Close()
defer func() {
if err := resp.Body.Close(); err != nil {
slog.Error("failed to close response body", "error", err)
}
}()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
@@ -290,7 +306,11 @@ func (t *TechnitiumProvider) ListRecords(subdomain string) ([]DNSRecord, error)
if err != nil {
return nil, err
}
defer resp.Body.Close()
defer func() {
if err = resp.Body.Close(); err != nil {
slog.Error("failed to close response body", "error", err)
}
}()
var tRecords struct {
Status string `json:"status"`

View File

@@ -1,3 +1,4 @@
// Package mail provides functionality for sending emails.
package mail
import (

View File

@@ -1,3 +1,4 @@
// Package settings provides functionality for managing application settings.
package settings
import (

View File

@@ -0,0 +1,149 @@
package settings
import (
"context"
"os"
"testing"
"time"
"github.com/mizuchilabs/mantrae/internal/store"
"github.com/mizuchilabs/mantrae/internal/store/db"
"github.com/stretchr/testify/assert"
)
func setupTest() (*SettingsManager, func()) {
conn := store.NewConnection(":memory:")
sm := NewManager(conn)
return sm, func() { conn.Close() }
}
func TestNewManager(t *testing.T) {
conn := store.NewConnection(":memory:")
defer conn.Close()
sm := NewManager(conn)
assert.NotNil(t, sm)
assert.NotNil(t, sm.conn)
assert.NotNil(t, sm.cache)
}
func TestGetAndSet(t *testing.T) {
sm, teardown := setupTest()
defer teardown()
ctx := context.Background()
// Test setting and getting a value
err := sm.Set(ctx, KeyServerURL, "http://localhost:8080")
assert.NoError(t, err)
val, ok := sm.Get(KeyServerURL)
assert.True(t, ok)
assert.Equal(t, "http://localhost:8080", val)
// Test setting an invalid key
err = sm.Set(ctx, "invalid_key", "some_value")
assert.Error(t, err)
}
func TestGetAll(t *testing.T) {
sm, teardown := setupTest()
defer teardown()
ctx := context.Background()
sm.Start(ctx)
// Test getting all values
allSettings := sm.GetAll()
assert.NotEmpty(t, allSettings)
assert.Equal(t, "local", allSettings[KeyStorage])
}
func TestGetMany(t *testing.T) {
sm, teardown := setupTest()
defer teardown()
ctx := context.Background()
sm.Start(ctx)
// Test getting many values
keys := []string{KeyServerURL, KeyStorage}
manySettings := sm.GetMany(keys)
assert.Len(t, manySettings, 2)
assert.Equal(t, "", manySettings[KeyServerURL])
assert.Equal(t, "local", manySettings[KeyStorage])
}
func TestStart(t *testing.T) {
sm, teardown := setupTest()
defer teardown()
ctx := context.Background()
// Set an environment variable
os.Setenv("SERVER_URL", "http://env.test")
defer os.Unsetenv("SERVER_URL")
// Add a value to the database
err := sm.conn.GetQuery().UpsertSetting(ctx, db.UpsertSettingParams{
Key: KeyStorage,
Value: "db_value",
})
assert.NoError(t, err)
sm.Start(ctx)
// Check that the environment variable is used
val, ok := sm.Get(KeyServerURL)
assert.True(t, ok)
assert.Equal(t, "http://env.test", val)
// Check that the database value is used
val, ok = sm.Get(KeyStorage)
assert.True(t, ok)
assert.Equal(t, "db_value", val)
// Check that the default value is used
val, ok = sm.Get(KeyBackupEnabled)
assert.True(t, ok)
assert.Equal(t, "true", val)
}
func TestValidation(t *testing.T) {
sm, teardown := setupTest()
defer teardown()
ctx := context.Background()
// Test validation for server url
err := sm.Set(ctx, KeyServerURL, " ")
assert.Error(t, err)
// Test validation for email port
err = sm.Set(ctx, KeyEmailPort, "abc")
assert.Error(t, err)
err = sm.Set(ctx, KeyEmailPort, "70000")
assert.Error(t, err)
// Test validation for backup keep
err = sm.Set(ctx, KeyBackupKeep, "0")
assert.Error(t, err)
}
func TestAsHelpers(t *testing.T) {
testString := "test"
testStringEmpty := ""
assert.Equal(t, "test", AsString(&testString))
assert.Equal(t, "", AsString(&testStringEmpty))
assert.True(t, AsBool("true"))
assert.False(t, AsBool("false"))
assert.False(t, AsBool("invalid"))
assert.Equal(t, 123, AsInt("123"))
assert.Equal(t, 0, AsInt("invalid"))
assert.Equal(t, 123.45, AsFloat64("123.45"))
assert.Equal(t, 0.0, AsFloat64("invalid"))
assert.Equal(t, time.Hour, AsDuration("1h"))
assert.Equal(t, time.Duration(0), AsDuration("invalid"))
}

View File

@@ -1,3 +1,4 @@
// Package storage provides a generic interface for storage backends.
package storage
import (

View File

@@ -1,3 +1,4 @@
// Package store provides functionality for interacting with the database.
package store
import (

View File

@@ -64,20 +64,11 @@ INSERT INTO
agent_id,
name,
config,
enabled,
created_at,
updated_at
)
VALUES
(
?,
?,
?,
?,
?,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
) RETURNING id, profile_id, agent_id, name, config, enabled, created_at, updated_at
(?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) RETURNING id, profile_id, agent_id, name, config, enabled, created_at, updated_at
`
type CreateHttpMiddlewareParams struct {
@@ -85,7 +76,6 @@ type CreateHttpMiddlewareParams struct {
AgentID *string `json:"agentId"`
Name string `json:"name"`
Config *schema.Middleware `json:"config"`
Enabled bool `json:"enabled"`
}
func (q *Queries) CreateHttpMiddleware(ctx context.Context, arg CreateHttpMiddlewareParams) (HttpMiddleware, error) {
@@ -94,7 +84,6 @@ func (q *Queries) CreateHttpMiddleware(ctx context.Context, arg CreateHttpMiddle
arg.AgentID,
arg.Name,
arg.Config,
arg.Enabled,
)
var i HttpMiddleware
err := row.Scan(

View File

@@ -64,20 +64,11 @@ INSERT INTO
agent_id,
name,
config,
enabled,
created_at,
updated_at
)
VALUES
(
?,
?,
?,
?,
?,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
) RETURNING id, profile_id, agent_id, name, config, enabled, created_at, updated_at
(?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) RETURNING id, profile_id, agent_id, name, config, enabled, created_at, updated_at
`
type CreateTcpMiddlewareParams struct {
@@ -85,7 +76,6 @@ type CreateTcpMiddlewareParams struct {
AgentID *string `json:"agentId"`
Name string `json:"name"`
Config *schema.TCPMiddleware `json:"config"`
Enabled bool `json:"enabled"`
}
func (q *Queries) CreateTcpMiddleware(ctx context.Context, arg CreateTcpMiddlewareParams) (TcpMiddleware, error) {
@@ -94,7 +84,6 @@ func (q *Queries) CreateTcpMiddleware(ctx context.Context, arg CreateTcpMiddlewa
arg.AgentID,
arg.Name,
arg.Config,
arg.Enabled,
)
var i TcpMiddleware
err := row.Scan(

View File

@@ -5,20 +5,11 @@ INSERT INTO
agent_id,
name,
config,
enabled,
created_at,
updated_at
)
VALUES
(
?,
?,
?,
?,
?,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
) RETURNING *;
(?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) RETURNING *;
-- name: GetHttpMiddleware :one
SELECT

View File

@@ -5,20 +5,11 @@ INSERT INTO
agent_id,
name,
config,
enabled,
created_at,
updated_at
)
VALUES
(
?,
?,
?,
?,
?,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
) RETURNING *;
(?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) RETURNING *;
-- name: GetTcpMiddleware :one
SELECT

View File

@@ -11,7 +11,11 @@ func Squash() {
conn.Migrate()
db := conn.db
defer db.Close()
defer func() {
if err := db.Close(); err != nil {
slog.Error("failed to close database", "error", err)
}
}()
var currentVersion int64
err := db.QueryRow("SELECT version_id FROM goose_db_version ORDER BY id DESC LIMIT 1").
@@ -31,7 +35,11 @@ func Squash() {
if err != nil {
panic(err)
}
defer rows.Close()
defer func() {
if err = rows.Close(); err != nil {
slog.Error("failed to close rows", "error", err)
}
}()
// Create new base migration
baseFile := "-- +goose Up\n"

View File

@@ -1,3 +1,4 @@
// Package traefik provides functionality for interacting with the Traefik API.
package traefik
import (
@@ -7,6 +8,7 @@ import (
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"time"
@@ -32,7 +34,11 @@ func UpdateTraefikAPI(DB *sql.DB, instanceID int64) error {
if err != nil {
return fmt.Errorf("failed to fetch %s: %w", instance.Url+RawAPI, err)
}
defer rawResp.Close()
defer func() {
if err = rawResp.Close(); err != nil {
slog.Error("failed to close raw response", "error", err)
}
}()
var config schema.Configuration
if err = json.NewDecoder(rawResp).Decode(&config); err != nil {
@@ -43,7 +49,11 @@ func UpdateTraefikAPI(DB *sql.DB, instanceID int64) error {
if err != nil {
return fmt.Errorf("failed to fetch %s: %w", instance.Url+EntrypointsAPI, err)
}
defer entrypointsResp.Close()
defer func() {
if err = entrypointsResp.Close(); err != nil {
slog.Error("failed to close entrypoints response", "error", err)
}
}()
var entrypoints schema.EntryPoints
if err = json.NewDecoder(entrypointsResp).Decode(&entrypoints); err != nil {
@@ -54,7 +64,11 @@ func UpdateTraefikAPI(DB *sql.DB, instanceID int64) error {
if err != nil {
return fmt.Errorf("failed to fetch %s: %w", instance.Url+OverviewAPI, err)
}
defer overviewResp.Close()
defer func() {
if err = overviewResp.Close(); err != nil {
slog.Error("failed to close overview response", "error", err)
}
}()
var overview schema.Overview
if err = json.NewDecoder(overviewResp).Decode(&overview); err != nil {
@@ -65,7 +79,11 @@ func UpdateTraefikAPI(DB *sql.DB, instanceID int64) error {
if err != nil {
return fmt.Errorf("failed to fetch %s: %w", instance.Url+VersionAPI, err)
}
defer versionResp.Close()
defer func() {
if err = versionResp.Close(); err != nil {
slog.Error("failed to close version response", "error", err)
}
}()
var version schema.Version
if err := json.NewDecoder(versionResp).Decode(&version); err != nil {

View File

@@ -1,3 +1,4 @@
// Package build provides build information.
package build
var (

View File

@@ -107,7 +107,11 @@ func fetchLatestRelease() (*release, error) {
if err != nil {
return nil, err
}
defer res.Body.Close()
defer func() {
if err = res.Body.Close(); err != nil {
slog.Error("failed to close response body", "error", err)
}
}()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("(%d) failed to send latest release request", res.StatusCode)
@@ -139,7 +143,11 @@ func downloadFile(url string, dest string) error {
if err != nil {
return err
}
defer res.Body.Close()
defer func() {
if err = res.Body.Close(); err != nil {
slog.Error("failed to close response body", "error", err)
}
}()
if res.StatusCode != http.StatusOK {
return fmt.Errorf("(%d) failed to send download file request", res.StatusCode)
@@ -149,7 +157,11 @@ func downloadFile(url string, dest string) error {
if err != nil {
return err
}
defer out.Close()
defer func() {
if err = out.Close(); err != nil {
slog.Error("failed to close output file", "error", err)
}
}()
if _, err := io.Copy(out, res.Body); err != nil {
return err
@@ -207,12 +219,9 @@ func compareVersions(a, b string) int {
bSplit := strings.Split(b, ".")
bTotal := len(bSplit)
limit := aTotal
if bTotal > aTotal {
limit = bTotal
}
limit := max(aTotal, bTotal)
for i := 0; i < limit; i++ {
for i := range limit {
var x, y int
if i < aTotal {

View File

@@ -1,3 +1,4 @@
// Package logger provides logging setup and configuration for the application.
package logger
import (

View File

@@ -1,3 +1,4 @@
// Package meta provides functionality for handling JWT claims.
package meta
import (

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"strings"
@@ -123,7 +124,11 @@ func getIP(services []string, validationFunc func(string) bool) (string, error)
if err != nil {
continue
}
defer resp.Body.Close()
defer func() {
if err := resp.Body.Close(); err != nil {
slog.Error("failed to close response body", "error", err)
}
}()
if resp.StatusCode != http.StatusOK {
continue

View File

@@ -1,3 +1,4 @@
// Package util provides utility functions.
package util
import (

View File

@@ -80,9 +80,10 @@ type Middleware struct {
AgentId string `protobuf:"bytes,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"`
Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"`
Config *structpb.Struct `protobuf:"bytes,5,opt,name=config,proto3" json:"config,omitempty"`
Type MiddlewareType `protobuf:"varint,6,opt,name=type,proto3,enum=mantrae.v1.MiddlewareType" json:"type,omitempty"`
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
Enabled bool `protobuf:"varint,6,opt,name=enabled,proto3" json:"enabled,omitempty"`
Type MiddlewareType `protobuf:"varint,7,opt,name=type,proto3,enum=mantrae.v1.MiddlewareType" json:"type,omitempty"`
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -152,6 +153,13 @@ func (x *Middleware) GetConfig() *structpb.Struct {
return nil
}
func (x *Middleware) GetEnabled() bool {
if x != nil {
return x.Enabled
}
return false
}
func (x *Middleware) GetType() MiddlewareType {
if x != nil {
return x.Type
@@ -1032,7 +1040,7 @@ var file_mantrae_v1_middleware_proto_rawDesc = string([]byte{
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc1, 0x02, 0x0a, 0x0a, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdb, 0x02, 0x0a, 0x0a, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65,
0x77, 0x61, 0x72, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f,
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c,
@@ -1042,202 +1050,204 @@ var file_mantrae_v1_middleware_proto_rawDesc = string([]byte{
0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x05, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28,
0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d,
0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,
0x79, 0x70, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61,
0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39,
0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09,
0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0xac, 0x03, 0x0a, 0x06, 0x50, 0x6c,
0x75, 0x67, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70,
0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61,
0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74,
0x68, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6d, 0x70, 0x6f, 0x72,
0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12,
0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x63, 0x6f,
0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x63, 0x6f,
0x6e, 0x55, 0x72, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x5f, 0x75,
0x72, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72,
0x55, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x0a, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6c,
0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0d, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0c,
0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14,
0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73,
0x74, 0x61, 0x72, 0x73, 0x12, 0x33, 0x0a, 0x07, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x18,
0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74,
0x52, 0x07, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65,
0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63,
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x49, 0x0a, 0x0d, 0x50, 0x6c, 0x75, 0x67,
0x69, 0x6e, 0x53, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x38, 0x73,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x38, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x79,
0x61, 0x6d, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x79, 0x61, 0x6d, 0x6c, 0x12,
0x12, 0x0a, 0x04, 0x74, 0x6f, 0x6d, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74,
0x6f, 0x6d, 0x6c, 0x22, 0x6f, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65,
0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x22,
0x02, 0x20, 0x00, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x54, 0x79, 0x70,
0x65, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04,
0x74, 0x79, 0x70, 0x65, 0x22, 0x4f, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c,
0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a,
0x0a, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d,
0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x0a, 0x6d, 0x69, 0x64, 0x64, 0x6c,
0x65, 0x77, 0x61, 0x72, 0x65, 0x22, 0xed, 0x01, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x29, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x22, 0x02, 0x20,
0x00, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08,
0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10,
0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x54, 0x79, 0x70,
0x65, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04,
0x74, 0x79, 0x70, 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x05,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x52, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d,
0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x36, 0x0a, 0x0a, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x0a, 0x6d,
0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x22, 0xdd, 0x01, 0x0a, 0x17, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65,
0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06,
0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2e, 0x0a,
0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x61,
0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77,
0x61, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x39, 0x0a,
0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63,
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61,
0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
0x64, 0x41, 0x74, 0x22, 0xac, 0x03, 0x0a, 0x06, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x0e,
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12,
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61,
0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61,
0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18,
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x12, 0x0a,
0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70,
0x65, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d,
0x6d, 0x61, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d,
0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18,
0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x1d,
0x0a, 0x0a, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x16, 0x0a,
0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72,
0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6c,
0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72,
0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x73, 0x12, 0x33,
0x0a, 0x07, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x19, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x75,
0x67, 0x69, 0x6e, 0x53, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x52, 0x07, 0x73, 0x6e, 0x69, 0x70,
0x70, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61,
0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
0x41, 0x74, 0x22, 0x49, 0x0a, 0x0d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x6e, 0x69, 0x70,
0x70, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x38, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x03, 0x6b, 0x38, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x79, 0x61, 0x6d, 0x6c, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x79, 0x61, 0x6d, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x6f, 0x6d,
0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x6f, 0x6d, 0x6c, 0x22, 0x6f, 0x0a,
0x14, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x03, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x22, 0x02, 0x20, 0x00, 0x52, 0x02, 0x69,
0x64, 0x12, 0x1e, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42,
0x64, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64,
0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x42, 0x0b, 0xba, 0x48, 0x08,
0xc8, 0x01, 0x01, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x4f,
0x0a, 0x15, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0a, 0x6d, 0x69, 0x64, 0x64, 0x6c,
0x65, 0x77, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61,
0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77,
0x61, 0x72, 0x65, 0x52, 0x0a, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x22,
0xed, 0x01, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65,
0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x0a, 0x70,
0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42,
0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x22, 0x02, 0x20, 0x00, 0x52, 0x09, 0x70, 0x72, 0x6f,
0x66, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f,
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49,
0x64, 0x12, 0x1e, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42,
0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x65, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64,
0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x42, 0x0b, 0xba, 0x48, 0x08,
0xc8, 0x01, 0x01, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2f,
0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17,
0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x52, 0x0a, 0x18, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0a, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77,
0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x74,
0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72,
0x65, 0x52, 0x0a, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x22, 0x72, 0x0a,
0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x03, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x22, 0x02, 0x20, 0x00,
0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x42, 0x0b,
0xba, 0x48, 0x08, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70,
0x65, 0x22, 0x1a, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c,
0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xef, 0x02,
0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x66,
0x69, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0a, 0xba, 0x48,
0x07, 0xc8, 0x01, 0x01, 0x22, 0x02, 0x20, 0x00, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c,
0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x48, 0x00,
0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x3d, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e,
0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61,
0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x42, 0x08, 0xba, 0x48, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01,
0x48, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x71, 0x0a, 0x05, 0x6c,
0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x42, 0x56, 0xba, 0x48, 0x53, 0xba,
0x01, 0x50, 0x0a, 0x0b, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12,
0x29, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x65,
0x69, 0x74, 0x68, 0x65, 0x72, 0x20, 0x2d, 0x31, 0x20, 0x6f, 0x72, 0x20, 0x67, 0x72, 0x65, 0x61,
0x74, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x30, 0x1a, 0x16, 0x74, 0x68, 0x69, 0x73,
0x20, 0x3d, 0x3d, 0x20, 0x2d, 0x31, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x3e,
0x20, 0x30, 0x48, 0x02, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, 0x01, 0x01, 0x12, 0x24,
0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07,
0xba, 0x48, 0x04, 0x22, 0x02, 0x28, 0x00, 0x48, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65,
0x74, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69,
0x64, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c,
0x69, 0x6d, 0x69, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22,
0x74, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72,
0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x0b, 0x6d, 0x69,
0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22,
0x52, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77,
0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0a, 0x6d,
0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64,
0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x0b, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77,
0x61, 0x72, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c,
0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x1d, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64,
0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x22, 0x4c, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c,
0x65, 0x77, 0x61, 0x72, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69,
0x6e, 0x73, 0x2a, 0x64, 0x0a, 0x0e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65,
0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x4d, 0x49, 0x44, 0x44, 0x4c, 0x45, 0x57, 0x41,
0x52, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46,
0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x4d, 0x49, 0x44, 0x44, 0x4c, 0x45, 0x57,
0x41, 0x52, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, 0x12,
0x17, 0x0a, 0x13, 0x4d, 0x49, 0x44, 0x44, 0x4c, 0x45, 0x57, 0x41, 0x52, 0x45, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x54, 0x43, 0x50, 0x10, 0x02, 0x32, 0xdc, 0x04, 0x0a, 0x11, 0x4d, 0x69, 0x64,
0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x59,
0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x12,
0x20, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74,
0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47,
0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x5d, 0x0a, 0x10, 0x43, 0x72, 0x65,
0x61, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x23, 0x2e,
0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,
0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x0a, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77,
0x61, 0x72, 0x65, 0x22, 0xdd, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x69,
0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x1a, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0a, 0xba, 0x48, 0x07,
0xc8, 0x01, 0x01, 0x22, 0x02, 0x20, 0x00, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01,
0x01, 0x72, 0x02, 0x10, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x74,
0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x74,
0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72,
0x65, 0x54, 0x79, 0x70, 0x65, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x02,
0x10, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63,
0x74, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61,
0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62,
0x6c, 0x65, 0x64, 0x22, 0x52, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x64,
0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x36, 0x0a, 0x0a, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31,
0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x0a, 0x6d, 0x69, 0x64,
0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x22, 0x72, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74,
0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x23, 0x2e, 0x6d,
0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74,
0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x23, 0x2e, 0x6d, 0x61,
0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d,
0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x24, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x69,
0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x74,
0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c,
0x65, 0x77, 0x61, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e,
0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d,
0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x6e, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4d, 0x69,
0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x12,
0x27, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74,
0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72,
0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77,
0x61, 0x72, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x42, 0xa9, 0x01, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x2e,
0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0f, 0x4d, 0x69, 0x64, 0x64,
0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x69, 0x7a, 0x75, 0x63, 0x68,
0x69, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2f,
0x76, 0x31, 0x3b, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x4d,
0x58, 0x58, 0xaa, 0x02, 0x0a, 0x4d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x56, 0x31, 0xca,
0x02, 0x0a, 0x4d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x16, 0x4d,
0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74,
0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0b, 0x4d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x3a,
0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0a,
0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x22, 0x02, 0x20, 0x00, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b,
0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d,
0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65,
0x77, 0x61, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xc8, 0x01, 0x01,
0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x1a, 0x0a, 0x18, 0x44,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xef, 0x02, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74,
0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x29, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x22, 0x02,
0x20, 0x00, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a,
0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42,
0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x48, 0x00, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e,
0x74, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x3d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76,
0x31, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65,
0x42, 0x08, 0xba, 0x48, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, 0x48, 0x01, 0x52, 0x04, 0x74, 0x79,
0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x71, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04,
0x20, 0x01, 0x28, 0x03, 0x42, 0x56, 0xba, 0x48, 0x53, 0xba, 0x01, 0x50, 0x0a, 0x0b, 0x6c, 0x69,
0x6d, 0x69, 0x74, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x29, 0x6c, 0x69, 0x6d, 0x69, 0x74,
0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x65, 0x69, 0x74, 0x68, 0x65, 0x72, 0x20,
0x2d, 0x31, 0x20, 0x6f, 0x72, 0x20, 0x67, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68,
0x61, 0x6e, 0x20, 0x30, 0x1a, 0x16, 0x74, 0x68, 0x69, 0x73, 0x20, 0x3d, 0x3d, 0x20, 0x2d, 0x31,
0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x3e, 0x20, 0x30, 0x48, 0x02, 0x52, 0x05,
0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73,
0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x28,
0x00, 0x48, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x88, 0x01, 0x01, 0x42, 0x0b,
0x0a, 0x09, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x5f,
0x74, 0x79, 0x70, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x09,
0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x74, 0x0a, 0x17, 0x4c, 0x69, 0x73,
0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x0b, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61,
0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x74,
0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72,
0x65, 0x52, 0x0b, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x73, 0x12, 0x1f,
0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20,
0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22,
0x1d, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65,
0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4c,
0x0a, 0x1c, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x50,
0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c,
0x0a, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x12, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x75,
0x67, 0x69, 0x6e, 0x52, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2a, 0x64, 0x0a, 0x0e,
0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f,
0x0a, 0x1b, 0x4d, 0x49, 0x44, 0x44, 0x4c, 0x45, 0x57, 0x41, 0x52, 0x45, 0x5f, 0x54, 0x59, 0x50,
0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12,
0x18, 0x0a, 0x14, 0x4d, 0x49, 0x44, 0x44, 0x4c, 0x45, 0x57, 0x41, 0x52, 0x45, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x49, 0x44,
0x44, 0x4c, 0x45, 0x57, 0x41, 0x52, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x43, 0x50,
0x10, 0x02, 0x32, 0xdc, 0x04, 0x0a, 0x11, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72,
0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x59, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4d,
0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x6e, 0x74,
0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65,
0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61,
0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64,
0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03,
0x90, 0x02, 0x01, 0x12, 0x5d, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x64,
0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c,
0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d,
0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x5d, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64,
0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65,
0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65,
0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61,
0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d,
0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x5d, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c,
0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77,
0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61, 0x6e,
0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x69,
0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x5f, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61,
0x72, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31,
0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77,
0x61, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02,
0x01, 0x12, 0x6e, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61,
0x72, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x6d, 0x61, 0x6e, 0x74,
0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65,
0x77, 0x61, 0x72, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x47, 0x65, 0x74, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x50, 0x6c, 0x75,
0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02,
0x01, 0x42, 0xa9, 0x01, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61,
0x65, 0x2e, 0x76, 0x31, 0x42, 0x0f, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65,
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x69, 0x7a, 0x75, 0x63, 0x68, 0x69, 0x6c, 0x61, 0x62, 0x73, 0x2f,
0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65,
0x6e, 0x2f, 0x6d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x6d, 0x61, 0x6e,
0x74, 0x72, 0x61, 0x65, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x4d, 0x58, 0x58, 0xaa, 0x02, 0x0a, 0x4d,
0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0a, 0x4d, 0x61, 0x6e, 0x74,
0x72, 0x61, 0x65, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x16, 0x4d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65,
0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea,
0x02, 0x0b, 0x4d, 0x61, 0x6e, 0x74, 0x72, 0x61, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (

View File

@@ -1219,6 +1219,9 @@ components:
config:
title: config
$ref: '#/components/schemas/google.protobuf.Struct'
enabled:
type: boolean
title: enabled
type:
title: type
$ref: '#/components/schemas/mantrae.v1.MiddlewareType'

View File

@@ -33,9 +33,10 @@ message Middleware {
string agent_id = 3;
string name = 4;
google.protobuf.Struct config = 5;
MiddlewareType type = 6;
google.protobuf.Timestamp created_at = 7;
google.protobuf.Timestamp updated_at = 8;
bool enabled = 6;
MiddlewareType type = 7;
google.protobuf.Timestamp created_at = 8;
google.protobuf.Timestamp updated_at = 9;
}
message Plugin {

View File

@@ -1,11 +0,0 @@
packages:
- path: "github.com/traefik/traefik/v3/pkg/config/dynamic"
output_path: "web/src/lib/gen/tygo/dynamic"
type_mappings:
ptypes.Duration: string
- path: "github.com/traefik/traefik/v3/pkg/tls"
output_path: "web/src/lib/gen/tygo/tls"
- path: "github.com/traefik/traefik/v3/pkg/types"
output_path: "web/src/lib/gen/tygo/types"

View File

@@ -2,12 +2,12 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="apple-touch-icon" sizes="76x76" href="%sveltekit.assets%/apple-touch-icon.png" />
<link rel="icon" type="image/ico" href="%sveltekit.assets%/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="%sveltekit.assets%/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="%sveltekit.assets%/favicon-16x16.png" />
<link rel="manifest" href="%sveltekit.assets%/site.webmanifest" />
<link rel="mask-icon" href="%sveltekit.assets%/safari-pinned-tab.svg" color="#5bbad5" />
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon.png" />
<link rel="icon" type="image/ico" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="theme-color" content="#ffffff" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%

View File

@@ -1,69 +1,244 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index';
import { cleanupFormData, safeClone } from '$lib/utils';
import FormField from './FormField.svelte';
import type { FieldMetadata } from '$lib/types/middlewares';
import Separator from '../ui/separator/separator.svelte';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Checkbox } from '$lib/components/ui/checkbox';
import { Button } from '$lib/components/ui/button';
import { Textarea } from '$lib/components/ui/textarea';
import { extractSchemaFields, type FormField } from '$lib/formGenerator';
import type { ZodSchema } from 'zod';
import DynamicForm from './DynamicForm.svelte';
import YAML from 'yaml';
interface Props {
schema: ZodSchema;
data: Record<string, unknown>;
metadata?: Record<string, FieldMetadata>;
onSubmit: (data: Record<string, unknown>) => void;
disabled?: boolean;
onUpdate: (data: Record<string, unknown>) => void;
}
let { data = $bindable(), metadata = {}, onSubmit, disabled }: Props = $props();
let { schema, data, onUpdate }: Props = $props();
// Form state
let formData = $derived(safeClone(data));
let fields = $derived(extractSchemaFields(schema));
// Handle form submission
function handleSubmit(e: Event) {
e.preventDefault();
const clonedData = safeClone(formData);
const cleanedData = (cleanupFormData(clonedData) as Record<string, unknown>) || {};
onSubmit(cleanedData);
// Make data reactive
let formData = $derived(data);
function updateField(key: string, value: unknown) {
formData[key] = value;
onUpdate({ ...formData });
}
type FormFieldType = {
key: string;
path: string;
value: unknown;
metadata: FieldMetadata;
type: string;
};
function addArrayItem(key: string) {
if (!formData[key]) formData[key] = [];
(formData[key] as unknown[]).push(getDefaultValue(fields[key].arrayItemType!));
onUpdate({ ...formData });
}
// Process object fields
const fields = $derived(processFields(formData));
function processFields(obj: Record<string, unknown>, parentKey = ''): FormFieldType[] {
return Object.entries(obj).flatMap(([key, value]) => {
const currentPath = parentKey ? `${parentKey}.${key}` : key;
const fieldMetadata = metadata[currentPath] || {};
function removeArrayItem(key: string, index: number) {
(formData[key] as unknown[]).splice(index, 1);
onUpdate({ ...formData });
}
if (value && typeof value === 'object' && !Array.isArray(value)) {
return processFields(value as Record<string, unknown>, currentPath);
function addRecordItem(key: string) {
if (!formData[key]) formData[key] = {};
const newKey = `key${Object.keys(formData[key] as object).length + 1}`;
(formData[key] as Record<string, unknown>)[newKey] = getDefaultValue(
fields[key].recordValueType!
);
onUpdate({ ...formData });
}
function removeRecordItem(key: string, recordKey: string) {
delete (formData[key] as Record<string, unknown>)[recordKey];
onUpdate({ ...formData });
}
function updateRecordKey(key: string, oldKey: string, newKey: string) {
const record = formData[key] as Record<string, unknown>;
if (oldKey !== newKey && !record[newKey]) {
record[newKey] = record[oldKey];
delete record[oldKey];
onUpdate({ ...formData });
}
}
function updateRecordValue(key: string, recordKey: string, value: unknown) {
(formData[key] as Record<string, unknown>)[recordKey] = value;
onUpdate({ ...formData });
}
let yamlError = $state<string | null>(null);
function handlePluginChange(value: string) {
try {
const parsed = YAML.parse(value);
if (parsed) {
yamlError = null;
formData = parsed;
onUpdate({ ...formData });
}
} catch (e) {
yamlError = e instanceof Error ? e.message : 'Invalid YAML';
}
}
return [
{
key,
path: currentPath,
value,
metadata: fieldMetadata,
type: Array.isArray(value) ? 'array' : typeof value
}
];
});
function getPluginValue(): string {
const value = formData;
if (typeof value === 'string') return value;
if (typeof value === 'object') return YAML.stringify(value, { indent: 2 });
return '';
}
function getDefaultValue(field: FormField): unknown {
switch (field.type) {
case 'string':
return '';
case 'number':
return 0;
case 'boolean':
return false;
case 'array':
return [];
case 'object':
return {};
case 'record':
return {};
case 'plugin':
return {};
default:
return '';
}
}
</script>
<form onsubmit={handleSubmit}>
<div class="grid gap-4">
{#each fields as field (field.key)}
<FormField {...field} {disabled} bind:data={formData} />
{/each}
</div>
<div class="flex flex-col gap-4">
{#each Object.entries(fields) as [key, field] (key)}
<div class="flex flex-col gap-2">
<Label class="text-sm font-medium">
{field.label}
{#if !field.optional}
<span class="text-red-500">*</span>
{/if}
</Label>
<Separator class="my-4" />
<Button type="submit" class="w-full">Save</Button>
</form>
{#if field.type === 'string'}
<Input
bind:value={data[key]}
placeholder={field.description}
oninput={() => updateField(key, data[key])}
/>
{:else if field.type === 'number'}
<Input
type="number"
bind:value={data[key]}
placeholder={field.description}
oninput={() => updateField(key, data[key])}
/>
{:else if field.type === 'boolean'}
<Checkbox
checked={data[key] as boolean}
onCheckedChange={(checked) => updateField(key, checked)}
/>
{:else if field.type === 'plugin'}
<div class="flex flex-col gap-2">
<Textarea
value={getPluginValue()}
placeholder="Edit plugin configuration as YAML"
class="min-h-[100px] font-mono text-sm"
oninput={(e) => handlePluginChange(e.currentTarget.value)}
/>
{#if yamlError}
<p class="text-xs text-red-400 dark:text-red-700">{yamlError}</p>
{/if}
</div>
{:else if field.type === 'array'}
<div class="flex flex-col gap-2 rounded-md border p-3">
{#if data[key] && Array.isArray(data[key])}
{#each data[key] as item, index (index)}
<div class="flex items-center gap-2">
{#if field.arrayItemType?.type === 'string'}
<Input
bind:value={data[key][index]}
oninput={() => updateField(key, data[key])}
/>
{:else if field.arrayItemType?.type === 'number'}
<Input
type="number"
bind:value={data[key][index]}
oninput={() => updateField(key, data[key])}
/>
{:else if field.arrayItemType?.type === 'object' && field.arrayItemType.nestedSchema}
<div class="flex-1 rounded border p-2">
<DynamicForm
schema={field.arrayItemType.nestedSchema}
data={(data[key][index] as Record<string, unknown>) || {}}
onUpdate={(nestedData) => {
(data[key] as unknown[])[index] = nestedData;
updateField(key, data[key]);
}}
/>
</div>
{/if}
<Button variant="outline" size="sm" onclick={() => removeArrayItem(key, index)}>
Remove
</Button>
</div>
{/each}
{/if}
<Button variant="outline" size="sm" onclick={() => addArrayItem(key)}>
Add {field.label}
</Button>
</div>
{:else if field.type === 'record'}
<div class="flex flex-col gap-2 rounded-md border p-3">
{#if formData[key] && typeof formData[key] === 'object'}
{#each Object.entries(formData[key] as Record<string, unknown>) as [recordKey, recordValue], index (recordKey)}
<div class="flex items-center gap-2">
<Input
value={recordKey}
placeholder="Key"
oninput={(e) => updateRecordKey(key, recordKey, e.currentTarget.value)}
/>
{#if field.recordValueType?.type === 'string'}
<Input
value={recordValue as string}
placeholder="Value"
oninput={(e) => updateRecordValue(key, recordKey, e.currentTarget.value)}
/>
{:else if field.recordValueType?.type === 'number'}
<Input
type="number"
value={recordValue as number}
placeholder="Value"
oninput={(e) =>
updateRecordValue(key, recordKey, parseFloat(e.currentTarget.value) || 0)}
/>
{/if}
<Button
variant="outline"
size="sm"
onclick={() => removeRecordItem(key, recordKey)}
>
Remove
</Button>
</div>
{/each}
{/if}
<Button variant="outline" size="sm" onclick={() => addRecordItem(key)}>
Add {field.label} Entry
</Button>
</div>
{:else if field.type === 'object' && field.nestedSchema}
<div class="rounded-md border p-3">
<DynamicForm
schema={field.nestedSchema}
data={(formData[key] as Record<string, unknown>) || {}}
onUpdate={(nestedData) => updateField(key, nestedData)}
/>
</div>
{/if}
{#if field.description}
<p class="text-muted-foreground text-xs">{field.description}</p>
{/if}
</div>
{/each}
</div>

View File

@@ -1,289 +0,0 @@
<script lang="ts">
import * as Select from '$lib/components/ui/select';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Switch } from '$lib/components/ui/switch';
import { Plus, Trash } from '@lucide/svelte';
import { mwNames } from '$lib/api';
import type { FieldMetadata } from '$lib/types/middlewares';
import Separator from '../ui/separator/separator.svelte';
interface Props {
key: string;
path: string;
type: string;
data: Record<string, unknown>;
metadata?: FieldMetadata;
disabled?: boolean;
}
let { key, path, type, data = $bindable(), metadata = {}, disabled }: Props = $props();
type FormValue =
| string
| number
| boolean
| string[]
| Record<string, unknown>
| Record<string, unknown>[];
function getNestedValue(obj: Record<string, unknown>, path: string): FormValue {
return path.split('.').reduce<unknown>((acc, part) => {
if (acc && typeof acc === 'object') {
return (acc as Record<string, unknown>)[part];
}
return undefined;
}, obj) as FormValue;
}
function setNestedValue(obj: Record<string, unknown>, path: string, value: FormValue) {
const parts = path.split('.');
const last = parts.pop()!;
const target = parts.reduce((acc, part) => {
if (!acc[part] || typeof acc[part] !== 'object') {
acc[part] = {};
}
return acc[part] as Record<string, unknown>;
}, obj);
target[last] = value;
}
function handleChange(e: Event) {
const target = e.target as HTMLInputElement;
const newValue = type === 'number' ? Number(target.value) : target.value;
setNestedValue(data, path, newValue);
}
function handleSwitchChange(checked: boolean) {
setNestedValue(data, path, checked);
}
function handleArrayChange(index: number, value: string) {
const array = (getNestedValue(data, path) as string[]) || [];
array[index] = value;
setNestedValue(data, path, array);
}
function addArrayItem() {
const array = (getNestedValue(data, path) as string[]) || [];
array.push('');
setNestedValue(data, path, array);
}
function removeArrayItem(index: number) {
const array = (getNestedValue(data, path) as string[]) || [];
array.splice(index, 1);
setNestedValue(data, path, array);
}
const fieldValue = $derived(getNestedValue(data, path));
function formatLabel(str: string): string {
return str
.split(/(?=[A-Z])/)
.join(' ')
.replace(/^\w/, (c) => c.toUpperCase());
}
// Extra special cases
const isChainMiddleware = $derived(path === 'middlewares');
function handleMiddlewareChange(values: string[]) {
setNestedValue(data, path, values);
}
function handleObjectArrayChange(index: number, field: string, value: string) {
const array = (getNestedValue(data, path) as Record<string, unknown>[]) || [];
if (!isObjectArray(array)) return;
if (!array[index]) {
array[index] = {};
}
array[index] = { ...array[index], [field]: value };
setNestedValue(data, path, array);
}
function removeObjectArrayItem(index: number) {
if (index < 1) return;
const array = (getNestedValue(data, path) as string[]) || [];
array.splice(index, 1);
setNestedValue(data, path, array);
}
function addObjectArrayItem() {
const array = (getNestedValue(data, path) as Record<string, unknown>[]) || [];
// Use the first item as a template, creating an object with the same keys but empty values
const template = array[0] || {};
const newItem = Object.keys(template).reduce(
(obj, key) => {
obj[key] = '';
return obj;
},
{} as Record<string, unknown>
);
array.push(newItem);
setNestedValue(data, path, array);
}
// Helper to detect if array contains objects
function isObjectArray(value: unknown[]): value is Record<string, unknown>[] {
return (
Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' && value[0] !== null
);
}
</script>
<div class="grid gap-2">
<Label for={path} class="flex flex-row items-center justify-between">
{formatLabel(key)}
{#if metadata.description}
<span class="text-muted-foreground ml-1 text-sm">
{metadata.description}
</span>
{/if}
</Label>
{#if isChainMiddleware}
<div class="flex flex-col gap-2">
{#if Array.isArray(fieldValue) && fieldValue.length > 0 && disabled}
{#each fieldValue as middleware (middleware)}
<div class="flex items-center gap-2">
<Input type="text" value={middleware} readonly {disabled} />
{#if !disabled}
<Button variant="destructive" size="icon" type="button">
<Trash class="h-4 w-4" />
</Button>
{/if}
</div>
{/each}
{/if}
{#if !disabled}
<Select.Root
type="multiple"
value={fieldValue as string[]}
onValueChange={handleMiddlewareChange}
{disabled}
>
<Select.Trigger>
{Array.isArray(fieldValue) && fieldValue.length > 0
? fieldValue.join(', ')
: 'Select Middlewares'}
</Select.Trigger>
<Select.Content>
{#each $mwNames as name (name)}
<Select.Item value={name}>{name}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
{/if}
</div>
{:else if type === 'boolean'}
<Switch
id={path}
checked={fieldValue as boolean}
onCheckedChange={handleSwitchChange}
{disabled}
/>
{:else if type === 'array'}
<div class="flex flex-col gap-2">
{#if Array.isArray(fieldValue) && isObjectArray(fieldValue)}
<div class="ml-4 flex flex-col gap-2 rounded border-l p-4">
{#each fieldValue as item, i (i)}
<div class="flex flex-col gap-2 rounded">
{#each Object.entries(item) as [field, value] (field)}
<div class="grid grid-cols-4 items-center gap-2">
<Label class="col-span-1">{formatLabel(field)}</Label>
<Input
type="text"
value={value as string}
class="col-span-3"
onchange={(e) =>
handleObjectArrayChange(i, field, (e.target as HTMLInputElement).value)}
placeholder={metadata.placeholder}
{disabled}
/>
</div>
{/each}
<Separator class="my-2" />
{#if !disabled}
<Button
variant="secondary"
size="icon"
type="button"
class="w-full text-red-500"
onclick={() => removeObjectArrayItem(i)}
>
<Trash />
</Button>
{/if}
</div>
{/each}
</div>
{:else}
{#each (fieldValue as string[]) || [] as value, i (i)}
<div class="flex gap-2">
<Input
type="text"
{value}
onchange={(e) => handleArrayChange(i, (e.target as HTMLInputElement).value)}
placeholder={metadata.placeholder}
{disabled}
/>
{#if !disabled}
<Button
variant="ghost"
size="icon"
type="button"
class="text-red-500"
onclick={() => removeArrayItem(i)}
>
<Trash />
</Button>
{/if}
</div>
{/each}
{/if}
{#if !disabled}
<Button
type="button"
variant="outline"
onclick={Array.isArray(fieldValue) && isObjectArray(fieldValue)
? addObjectArrayItem
: addArrayItem}
class="w-full"
>
<Plus />
Add {key.charAt(0).toUpperCase() + key.slice(1)}
</Button>
{/if}
</div>
{:else if type === 'number'}
<Input
type="number"
id={path}
value={fieldValue !== undefined ? (fieldValue as number) : ''}
onchange={handleChange}
placeholder={metadata.placeholder}
{disabled}
/>
{#if metadata.examples?.length}
<div class="text-muted-foreground text-sm">
Examples: {metadata.examples.join(', ')}
</div>
{/if}
{:else}
<Input
type="text"
id={path}
value={fieldValue as string}
placeholder={metadata.placeholder}
onchange={handleChange}
{disabled}
/>
{#if metadata.examples?.length}
<div class="text-muted-foreground text-sm">
Examples: {metadata.examples.join(', ')}
</div>
{/if}
{/if}
</div>

View File

@@ -1,127 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index';
import { Textarea } from '$lib/components/ui/textarea/index.js';
import Separator from '../ui/separator/separator.svelte';
import { loading } from '$lib/api';
import YAML from 'yaml';
interface Props {
data: Record<string, unknown>;
onSubmit: (data: Record<string, unknown>) => void;
disabled?: boolean;
}
let { data = $bindable(), onSubmit, disabled }: Props = $props();
// Form state
let formData = $derived(YAML.stringify(data, { indent: 2 }));
let errorMessage = $state<string | null>(null);
let isValid = $state(true);
// Validate YAML and return boolean
function validateYAML(input: string): boolean {
try {
YAML.parse(input);
errorMessage = null;
return true;
} catch (e) {
errorMessage = e instanceof Error ? e.message : 'Invalid YAML';
return false;
}
}
// Format YAML with proper indentation
function formatYAML(input: string): string {
try {
const parsed = YAML.parse(input);
return YAML.stringify(parsed, { indent: 2 });
} catch {
return input;
}
}
// Handle input changes
function handleInput(e: Event) {
const input = (e.target as HTMLTextAreaElement).value;
formData = input;
isValid = validateYAML(input);
}
// Format on blur
function handleBlur() {
if (isValid) {
formData = formatYAML(formData);
}
}
// Handle tab key
function handleKeydown(e: KeyboardEvent & { currentTarget: HTMLTextAreaElement }) {
if (e.key === 'Tab') {
e.preventDefault();
const target = e.currentTarget;
const start = target.selectionStart;
const end = target.selectionEnd;
const newPosition = start + 2;
// Handle selected text
if (start !== end) {
const lines = formData.split('\n');
let startLine = formData.substring(0, start).split('\n').length - 1;
let endLine = formData.substring(0, end).split('\n').length - 1;
// Indent or unindent selected lines
const newLines = lines.map((line, i) => {
if (i >= startLine && i <= endLine) {
return e.shiftKey ? line.replace(/^ {2}/, '') : ' ' + line;
}
return line;
});
formData = newLines.join('\n');
requestAnimationFrame(() => {
target.setSelectionRange(newPosition, newPosition);
});
} else {
// Insert tab at cursor position
formData = formData.substring(0, start) + ' ' + formData.substring(end);
requestAnimationFrame(() => {
target.setSelectionRange(newPosition, newPosition);
});
}
}
}
// Handle form submission
function handleSubmit(e: Event) {
e.preventDefault();
if (!isValid) return;
try {
const parsed = YAML.parse(formData);
onSubmit(parsed);
} catch (e) {
errorMessage = e instanceof Error ? e.message : 'Failed to parse YAML';
}
}
</script>
<form onsubmit={handleSubmit}>
<div class="grid gap-4">
<Textarea
value={formData}
rows={formData.split('\n').length}
class={!isValid ? 'border-red-500 font-mono' : 'font-mono'}
oninput={handleInput}
onblur={handleBlur}
onkeydown={handleKeydown}
{disabled}
/>
{#if errorMessage}
<p class="text-sm text-red-500">{errorMessage}</p>
{/if}
</div>
<Separator class="my-4" />
<Button type="submit" class="w-full" disabled={$loading || !isValid || disabled}>Save</Button>
</form>

View File

@@ -1,27 +1,27 @@
<script lang="ts">
import * as Select from '$lib/components/ui/select/index.js';
import { Badge } from '$lib/components/ui/badge/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import type { Middleware as HttpMiddleware } from '$lib/gen/tygo/dynamic';
import { type Middleware } from '$lib/gen/mantrae/v1/middleware_pb';
import { unmarshalConfig, marshalConfig, HTTPMiddlewareKeys } from '$lib/types';
import { unmarshalConfig, marshalConfig } from '$lib/types';
import {
MiddlewareSchema,
type Middleware as HTTPMiddleware
} from '$lib/gen/zen/traefik-schemas';
import DynamicForm from './DynamicForm.svelte';
let { middleware = $bindable() }: { middleware: Middleware } = $props();
let config = $state(unmarshalConfig(middleware.config) as HttpMiddleware);
let selectedType = $state(config ? Object.keys(config)[0] : '');
let config = $state(unmarshalConfig(middleware.config) as HTTPMiddleware);
let selectedType = $derived(config ? Object.keys(config)[0] : '');
$effect(() => {
if (config) middleware.config = marshalConfig(config);
// if (selectedType) {
// config = {
// ...config,
// [selectedType]: {}
// };
// // middleware.config = marshalConfig(config);
// }
});
const middlewareTypes = Object.keys(MiddlewareSchema.shape).map((key) => ({
value: key,
label: key.replace(/([A-Z])/g, ' $1').replace(/^./, (s) => s.toUpperCase()) // dumb prettifier
}));
</script>
<div class="flex flex-col gap-3">
@@ -31,11 +31,11 @@
<Select.Root type="single" bind:value={selectedType}>
<Select.Trigger class="w-full">
{selectedType
? HTTPMiddlewareKeys.find((t) => t.value === selectedType)?.label
? middlewareTypes.find((t) => t.value === selectedType)?.label
: 'Select type'}
</Select.Trigger>
<Select.Content class="no-scrollbar max-h-[300px] overflow-y-auto">
{#each HTTPMiddlewareKeys as t (t.value)}
{#each middlewareTypes as t (t.value)}
<Select.Item value={t.value}>{t.label}</Select.Item>
{/each}
</Select.Content>
@@ -43,6 +43,12 @@
</div>
{#if selectedType}
<!-- dynamic form -->
<DynamicForm
schema={MiddlewareSchema.shape[selectedType as keyof typeof MiddlewareSchema.shape]}
data={(config[selectedType as keyof HTTPMiddleware] as Record<string, unknown>) || {}}
onUpdate={(updatedData) => {
config = { [selectedType]: updatedData } as HTTPMiddleware;
}}
/>
{/if}
</div>

View File

@@ -5,7 +5,7 @@
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import { RouterType, type Router } from '$lib/gen/mantrae/v1/router_pb';
import type { Router as HttpRouter, RouterTLSConfig } from '$lib/gen/tygo/dynamic';
import type { Router as HTTPRouter, RouterTLSConfig } from '$lib/gen/zen/traefik-schemas';
import { Star } from '@lucide/svelte';
import { entryPointClient, middlewareClient, routerClient } from '$lib/api';
import { MiddlewareType } from '$lib/gen/mantrae/v1/middleware_pb';
@@ -16,7 +16,7 @@
let { router = $bindable() }: { router: Router } = $props();
let certResolvers: string[] = $state([]);
let config = $state(unmarshalConfig(router.config) as HttpRouter);
let config = $state(unmarshalConfig(router.config) as HTTPRouter);
$effect(() => {
if (config) router.config = marshalConfig(config);
@@ -33,12 +33,12 @@
const resolverSet = new Set(
response.routers
.filter((r) => {
let tmp = unmarshalConfig(r.config) as HttpRouter;
let tmp = unmarshalConfig(r.config) as HTTPRouter;
if (!tmp?.tls?.certResolver) return false;
return true;
})
.map((r) => {
let tmp = unmarshalConfig(r.config) as HttpRouter;
let tmp = unmarshalConfig(r.config) as HTTPRouter;
return tmp.tls?.certResolver ?? '';
})
);

View File

@@ -4,7 +4,7 @@
import { Switch } from '$lib/components/ui/switch/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { ServiceType, type Service } from '$lib/gen/mantrae/v1/service_pb';
import type { Service as HTTPService } from '$lib/gen/tygo/dynamic';
import type { Service as HTTPService } from '$lib/gen/zen/traefik-schemas';
import { Plus, Trash } from '@lucide/svelte';
import { marshalConfig } from '$lib/types';
@@ -19,7 +19,7 @@
if (service.config) {
config = service.config as HTTPService;
}
if (!config.loadBalancer) config.loadBalancer = {};
if (!config.loadBalancer) config.loadBalancer = { passHostHeader: true };
if (!config.loadBalancer.servers) config.loadBalancer.servers = [];
if (config.loadBalancer.servers.length === 0) {
config.loadBalancer.servers = [{ url: '' }];
@@ -35,7 +35,7 @@
class="col-span-3"
checked={config.loadBalancer?.passHostHeader ?? true}
onCheckedChange={(value) => {
if (!config.loadBalancer) config.loadBalancer = {};
if (!config.loadBalancer) config.loadBalancer = { passHostHeader: true };
config.loadBalancer.passHostHeader = value;
service.config = marshalConfig(config);
}}
@@ -65,7 +65,7 @@
class="text-red-500"
onclick={() => {
if (i === 0) return;
if (!config.loadBalancer) config.loadBalancer = {};
if (!config.loadBalancer) config.loadBalancer = { passHostHeader: true };
if (!config.loadBalancer.servers) config.loadBalancer.servers = [];
config.loadBalancer.servers = config.loadBalancer.servers.filter((_, j) => j !== i);
service.config = marshalConfig(config);
@@ -81,7 +81,7 @@
variant="outline"
class="w-full"
onclick={() => {
if (!config.loadBalancer) config.loadBalancer = {};
if (!config.loadBalancer) config.loadBalancer = { passHostHeader: true };
if (!config.loadBalancer.servers) config.loadBalancer.servers = [];
config.loadBalancer.servers = [...config.loadBalancer.servers, { url: '' }];
service.config = marshalConfig(config);

View File

@@ -0,0 +1,51 @@
<script lang="ts">
import * as Select from '$lib/components/ui/select/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import { type Middleware } from '$lib/gen/mantrae/v1/middleware_pb';
import { unmarshalConfig, marshalConfig } from '$lib/types';
import { TCPMiddlewareSchema, type TCPMiddleware } from '$lib/gen/zen/traefik-schemas';
import DynamicForm from './DynamicForm.svelte';
let { middleware = $bindable() }: { middleware: Middleware } = $props();
let config = $state(unmarshalConfig(middleware.config) as TCPMiddleware);
let selectedType = $derived(config ? Object.keys(config)[0] : '');
$effect(() => {
if (config) middleware.config = marshalConfig(config);
});
const middlewareTypes = Object.keys(TCPMiddlewareSchema.shape).map((key) => ({
value: key,
label: key.replace(/([A-Z])/g, ' $1').replace(/^./, (s) => s.toUpperCase()) // dumb prettifier
}));
</script>
<div class="flex flex-col gap-3">
<!-- Middleware Type -->
<div class="flex flex-col gap-2">
<Label class="mr-2">Type</Label>
<Select.Root type="single" bind:value={selectedType}>
<Select.Trigger class="w-full">
{selectedType
? middlewareTypes.find((t) => t.value === selectedType)?.label
: 'Select type'}
</Select.Trigger>
<Select.Content class="no-scrollbar max-h-[300px] overflow-y-auto">
{#each middlewareTypes as t (t.value)}
<Select.Item value={t.value}>{t.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
{#if selectedType}
<DynamicForm
schema={TCPMiddlewareSchema.shape[selectedType as keyof typeof TCPMiddlewareSchema.shape]}
data={(config[selectedType as keyof TCPMiddleware] as Record<string, unknown>) || {}}
onUpdate={(updatedData) => {
config = { [selectedType]: updatedData } as TCPMiddleware;
}}
/>
{/if}
</div>

View File

@@ -5,7 +5,7 @@
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import { RouterType, type Router } from '$lib/gen/mantrae/v1/router_pb';
import type { RouterTCPTLSConfig, TCPRouter } from '$lib/gen/tygo/dynamic';
import type { RouterTCPTLSConfig, TCPRouter } from '$lib/gen/zen/traefik-schemas';
import { Star } from '@lucide/svelte';
import { entryPointClient, middlewareClient, routerClient } from '$lib/api';
import { MiddlewareType } from '$lib/gen/mantrae/v1/middleware_pb';

View File

@@ -5,7 +5,7 @@
import { ServiceType, type Service } from '$lib/gen/mantrae/v1/service_pb';
import { Plus, Trash } from '@lucide/svelte';
import { marshalConfig } from '$lib/types';
import type { TCPService } from '$lib/gen/tygo/dynamic';
import type { TCPService } from '$lib/gen/zen/traefik-schemas';
interface Props {
service: Service;

View File

@@ -2,7 +2,7 @@
import * as Select from '$lib/components/ui/select/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import { type Router } from '$lib/gen/mantrae/v1/router_pb';
import type { UDPRouter } from '$lib/gen/tygo/dynamic';
import type { UDPRouter } from '$lib/gen/zen/traefik-schemas';
import { Star } from '@lucide/svelte';
import { entryPointClient } from '$lib/api';
import { unmarshalConfig, marshalConfig } from '$lib/types';

View File

@@ -5,7 +5,7 @@
import { ServiceType, type Service } from '$lib/gen/mantrae/v1/service_pb';
import { Plus, Trash } from '@lucide/svelte';
import { marshalConfig } from '$lib/types';
import type { UDPService } from '$lib/gen/tygo/dynamic';
import type { UDPService } from '$lib/gen/zen/traefik-schemas';
interface Props {
service: Service;

View File

@@ -12,7 +12,8 @@
import { ConnectError } from '@connectrpc/connect';
import { profile } from '$lib/stores/profile';
import { pageIndex, pageSize } from '$lib/stores/common';
import HttpMiddleware from '../forms/httpMiddleware.svelte';
import HTTPMiddlewareForm from '../forms/httpMiddleware.svelte';
import TCPMiddlewareForm from '../forms/tcpMiddleware.svelte';
interface Props {
data: Middleware[];
@@ -28,7 +29,8 @@
id: item.id,
name: item.name,
config: item.config,
type: item.type
type: item.type,
enabled: item.enabled
});
toast.success('Middleware updated successfully');
} else {
@@ -84,7 +86,7 @@
<Dialog.Description>Configure your Traefik middleware</Dialog.Description>
</Dialog.Header>
<form onsubmit={handleSubmit} class="flex flex-col gap-4">
<form class="flex flex-col gap-4">
<div class="grid w-full grid-cols-3 gap-2">
<div class="col-span-2 flex flex-col gap-2">
<Label for="name">Name</Label>
@@ -117,7 +119,10 @@
</div>
{#if item.type === MiddlewareType.HTTP}
<HttpMiddleware bind:middleware={item} />
<HTTPMiddlewareForm bind:middleware={item} />
{/if}
{#if item.type === MiddlewareType.TCP}
<TCPMiddlewareForm bind:middleware={item} />
{/if}
<Separator />

View File

@@ -185,48 +185,50 @@
<!-- DNS Providers -->
{#await dnsClient.listDnsProviders({ limit: -1n, offset: 0n }) then value}
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<div bind:this={dnsAnchor}>
<Button
variant="ghost"
size="sm"
class="flex items-center gap-2"
onclick={() => (selectDNSOpen = true)}
>
<Globe size={16} />
<Badge>
{item.dnsProviders.length > 0
? item.dnsProviders.map((p) => p.name).join(', ')
: 'None'}
</Badge>
</Button>
</div>
</Tooltip.Trigger>
<Tooltip.Content side="left" align="center">
<p>Select DNS Provider</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
{#if value.dnsProviders.length > 0}
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<div bind:this={dnsAnchor}>
<Button
variant="ghost"
size="sm"
class="flex items-center gap-2"
onclick={() => (selectDNSOpen = true)}
>
<Globe size={16} />
<Badge>
{item.dnsProviders?.length > 0
? item.dnsProviders?.map((p) => p.name).join(', ')
: 'None'}
</Badge>
</Button>
</div>
</Tooltip.Trigger>
<Tooltip.Content side="left" align="center">
<p>Select DNS Provider</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
<Select.Root
type="multiple"
value={item.dnsProviders.map((item) => item.id.toString())}
onValueChange={handleDNSProviderChange}
bind:open={selectDNSOpen}
>
<Select.Content customAnchor={dnsAnchor} align="end">
{#each value.dnsProviders as dns (dns.id)}
<Select.Item value={dns.id.toString()} class="flex items-center gap-2">
{dns.name}
{#if dns.isActive}
<CircleCheck size="1rem" class="text-green-400" />
{/if}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
<Select.Root
type="multiple"
value={item.dnsProviders?.map((item) => item.id.toString())}
onValueChange={handleDNSProviderChange}
bind:open={selectDNSOpen}
>
<Select.Content customAnchor={dnsAnchor} align="end">
{#each value.dnsProviders as dns (dns.id)}
<Select.Item value={dns.id.toString()} class="flex items-center gap-2">
{dns.name}
{#if dns.isActive}
<CircleCheck size="1rem" class="text-green-400" />
{/if}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
{/if}
{/await}
</Card.Header>
<Card.Content class="flex flex-col gap-3">

View File

@@ -5,7 +5,7 @@
import { AlertTriangle, Eye, Globe, Key, type IconProps } from '@lucide/svelte';
import { type Component } from 'svelte';
import type { Column } from '@tanstack/table-core';
import type { RouterTCPTLSConfig } from '$lib/gen/tygo/dynamic';
import type { RouterTCPTLSConfig } from '$lib/gen/zen/traefik-schemas';
type IconComponent = Component<IconProps, Record<string, never>, ''>;
type Props = ComponentProps<typeof Badge> & {

View File

@@ -0,0 +1,149 @@
import type { ZodSchema, ZodTypeAny } from "zod";
export interface FormField {
type:
| "string"
| "number"
| "boolean"
| "array"
| "object"
| "record"
| "plugin";
label: string;
optional: boolean;
description?: string;
arrayItemType?: FormField;
nestedSchema?: ZodSchema;
recordValueType?: FormField;
}
export function extractSchemaFields(
schema: ZodSchema,
): Record<string, FormField> {
let actualSchema = schema;
// Unwrap ZodOptional if present
if ((schema as any)._def.typeName === "ZodOptional") {
actualSchema = (schema as any)._def.innerType;
}
// Handle the case where the entire schema is a ZodRecord (like for plugins)
const schemaType = (actualSchema as any)._def.typeName;
if (schemaType === "ZodRecord") {
// For ZodRecord schemas, create a single plugin field
return {
plugin: {
type: "plugin",
label: "Plugin Configuration",
optional: false,
description: "YAML configuration for plugins",
},
};
}
// Check if it's a ZodObject
if ((actualSchema as any)._def.typeName !== "ZodObject") {
console.error(
"Expected ZodObject, got:",
(actualSchema as any)._def.typeName,
);
return {};
}
const shape = (actualSchema as any)._def.shape();
const fields: Record<string, FormField> = {};
for (const [key, zodType] of Object.entries(shape)) {
fields[key] = parseZodType(zodType as ZodTypeAny, key);
}
return fields;
}
function parseZodType(zodType: ZodTypeAny, key: string): FormField {
const def = zodType._def;
let type = def.typeName;
let optional = false;
let currentType = zodType;
// Handle optional fields
if (type === "ZodOptional") {
optional = true;
currentType = def.innerType;
type = currentType._def.typeName;
}
// Special handling for plugin field - handle it as plugin type regardless of Zod type
if (key === "plugin") {
return {
type: "plugin",
label: formatLabel(key),
optional,
description: "YAML or JSON configuration for plugins",
};
}
// Map Zod types to form field types
switch (type) {
case "ZodString":
return {
type: "string",
label: formatLabel(key),
optional,
description: def.description,
};
case "ZodNumber":
return {
type: "number",
label: formatLabel(key),
optional,
description: def.description,
};
case "ZodBoolean":
return {
type: "boolean",
label: formatLabel(key),
optional,
description: def.description,
};
case "ZodArray":
return {
type: "array",
label: formatLabel(key),
optional,
description: def.description,
arrayItemType: parseZodType(currentType._def.type, `${key}Item`),
};
case "ZodObject":
return {
type: "object",
label: formatLabel(key),
optional,
description: def.description,
nestedSchema: currentType,
};
case "ZodRecord":
return {
type: "record",
label: formatLabel(key),
optional,
description: def.description,
recordValueType: parseZodType(
currentType._def.valueType,
`${key}Value`,
),
};
default:
return {
type: "string",
label: formatLabel(key),
optional,
description: def.description,
};
}
}
function formatLabel(key: string): string {
return key.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
}

View File

@@ -13,7 +13,7 @@ import type { JsonObject, Message } from "@bufbuild/protobuf";
* Describes the file mantrae/v1/middleware.proto.
*/
export const file_mantrae_v1_middleware: GenFile = /*@__PURE__*/
fileDesc("ChttYW50cmFlL3YxL21pZGRsZXdhcmUucHJvdG8SCm1hbnRyYWUudjEi/wEKCk1pZGRsZXdhcmUSCgoCaWQYASABKAMSEgoKcHJvZmlsZV9pZBgCIAEoAxIQCghhZ2VudF9pZBgDIAEoCRIMCgRuYW1lGAQgASgJEicKBmNvbmZpZxgFIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSKAoEdHlwZRgGIAEoDjIaLm1hbnRyYWUudjEuTWlkZGxld2FyZVR5cGUSLgoKY3JlYXRlZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgIIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAipgIKBlBsdWdpbhIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhQKDGRpc3BsYXlfbmFtZRgDIAEoCRIOCgZhdXRob3IYBCABKAkSDAoEdHlwZRgFIAEoCRIOCgZpbXBvcnQYBiABKAkSDwoHc3VtbWFyeRgHIAEoCRIQCghpY29uX3VybBgIIAEoCRISCgpiYW5uZXJfdXJsGAkgASgJEg4KBnJlYWRtZRgKIAEoCRIWCg5sYXRlc3RfdmVyc2lvbhgLIAEoCRIQCgh2ZXJzaW9ucxgMIAMoCRINCgVzdGFycxgNIAEoAxIqCgdzbmlwcGV0GA4gASgLMhkubWFudHJhZS52MS5QbHVnaW5TbmlwcGV0EhIKCmNyZWF0ZWRfYXQYDyABKAkiOAoNUGx1Z2luU25pcHBldBILCgNrOHMYASABKAkSDAoEeWFtbBgCIAEoCRIMCgR0b21sGAMgASgJImUKFEdldE1pZGRsZXdhcmVSZXF1ZXN0EhYKAmlkGAEgASgDQgq6SAfIAQEiAiAAEjUKBHR5cGUYAiABKA4yGi5tYW50cmFlLnYxLk1pZGRsZXdhcmVUeXBlQgu6SAjIAQGCAQIQASJDChVHZXRNaWRkbGV3YXJlUmVzcG9uc2USKgoKbWlkZGxld2FyZRgBIAEoCzIWLm1hbnRyYWUudjEuTWlkZGxld2FyZSLFAQoXQ3JlYXRlTWlkZGxld2FyZVJlcXVlc3QSHgoKcHJvZmlsZV9pZBgBIAEoA0IKukgHyAEBIgIgABIQCghhZ2VudF9pZBgCIAEoCRIYCgRuYW1lGAMgASgJQgq6SAfIAQFyAhABEjUKBHR5cGUYBCABKA4yGi5tYW50cmFlLnYxLk1pZGRsZXdhcmVUeXBlQgu6SAjIAQGCAQIQARInCgZjb25maWcYBSABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0IkYKGENyZWF0ZU1pZGRsZXdhcmVSZXNwb25zZRIqCgptaWRkbGV3YXJlGAEgASgLMhYubWFudHJhZS52MS5NaWRkbGV3YXJlIrwBChdVcGRhdGVNaWRkbGV3YXJlUmVxdWVzdBIWCgJpZBgBIAEoA0IKukgHyAEBIgIgABIYCgRuYW1lGAIgASgJQgq6SAfIAQFyAhABEjUKBHR5cGUYAyABKA4yGi5tYW50cmFlLnYxLk1pZGRsZXdhcmVUeXBlQgu6SAjIAQGCAQIQARInCgZjb25maWcYBCABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0Eg8KB2VuYWJsZWQYBSABKAgiRgoYVXBkYXRlTWlkZGxld2FyZVJlc3BvbnNlEioKCm1pZGRsZXdhcmUYASABKAsyFi5tYW50cmFlLnYxLk1pZGRsZXdhcmUiaAoXRGVsZXRlTWlkZGxld2FyZVJlcXVlc3QSFgoCaWQYASABKANCCrpIB8gBASICIAASNQoEdHlwZRgCIAEoDjIaLm1hbnRyYWUudjEuTWlkZGxld2FyZVR5cGVCC7pICMgBAYIBAhABIhoKGERlbGV0ZU1pZGRsZXdhcmVSZXNwb25zZSLGAgoWTGlzdE1pZGRsZXdhcmVzUmVxdWVzdBIeCgpwcm9maWxlX2lkGAEgASgDQgq6SAfIAQEiAiAAEh4KCGFnZW50X2lkGAIgASgJQge6SARyAhABSACIAQESNwoEdHlwZRgDIAEoDjIaLm1hbnRyYWUudjEuTWlkZGxld2FyZVR5cGVCCLpIBYIBAhABSAGIAQESagoFbGltaXQYBCABKANCVrpIU7oBUAoLbGltaXQudmFsaWQSKWxpbWl0IG11c3QgYmUgZWl0aGVyIC0xIG9yIGdyZWF0ZXIgdGhhbiAwGhZ0aGlzID09IC0xIHx8IHRoaXMgPiAwSAKIAQESHAoGb2Zmc2V0GAUgASgDQge6SAQiAigASAOIAQFCCwoJX2FnZW50X2lkQgcKBV90eXBlQggKBl9saW1pdEIJCgdfb2Zmc2V0IlsKF0xpc3RNaWRkbGV3YXJlc1Jlc3BvbnNlEisKC21pZGRsZXdhcmVzGAEgAygLMhYubWFudHJhZS52MS5NaWRkbGV3YXJlEhMKC3RvdGFsX2NvdW50GAIgASgDIh0KG0dldE1pZGRsZXdhcmVQbHVnaW5zUmVxdWVzdCJDChxHZXRNaWRkbGV3YXJlUGx1Z2luc1Jlc3BvbnNlEiMKB3BsdWdpbnMYASADKAsyEi5tYW50cmFlLnYxLlBsdWdpbipkCg5NaWRkbGV3YXJlVHlwZRIfChtNSURETEVXQVJFX1RZUEVfVU5TUEVDSUZJRUQQABIYChRNSURETEVXQVJFX1RZUEVfSFRUUBABEhcKE01JRERMRVdBUkVfVFlQRV9UQ1AQAjLcBAoRTWlkZGxld2FyZVNlcnZpY2USWQoNR2V0TWlkZGxld2FyZRIgLm1hbnRyYWUudjEuR2V0TWlkZGxld2FyZVJlcXVlc3QaIS5tYW50cmFlLnYxLkdldE1pZGRsZXdhcmVSZXNwb25zZSIDkAIBEl0KEENyZWF0ZU1pZGRsZXdhcmUSIy5tYW50cmFlLnYxLkNyZWF0ZU1pZGRsZXdhcmVSZXF1ZXN0GiQubWFudHJhZS52MS5DcmVhdGVNaWRkbGV3YXJlUmVzcG9uc2USXQoQVXBkYXRlTWlkZGxld2FyZRIjLm1hbnRyYWUudjEuVXBkYXRlTWlkZGxld2FyZVJlcXVlc3QaJC5tYW50cmFlLnYxLlVwZGF0ZU1pZGRsZXdhcmVSZXNwb25zZRJdChBEZWxldGVNaWRkbGV3YXJlEiMubWFudHJhZS52MS5EZWxldGVNaWRkbGV3YXJlUmVxdWVzdBokLm1hbnRyYWUudjEuRGVsZXRlTWlkZGxld2FyZVJlc3BvbnNlEl8KD0xpc3RNaWRkbGV3YXJlcxIiLm1hbnRyYWUudjEuTGlzdE1pZGRsZXdhcmVzUmVxdWVzdBojLm1hbnRyYWUudjEuTGlzdE1pZGRsZXdhcmVzUmVzcG9uc2UiA5ACARJuChRHZXRNaWRkbGV3YXJlUGx1Z2lucxInLm1hbnRyYWUudjEuR2V0TWlkZGxld2FyZVBsdWdpbnNSZXF1ZXN0GigubWFudHJhZS52MS5HZXRNaWRkbGV3YXJlUGx1Z2luc1Jlc3BvbnNlIgOQAgFCqQEKDmNvbS5tYW50cmFlLnYxQg9NaWRkbGV3YXJlUHJvdG9QAVo9Z2l0aHViLmNvbS9taXp1Y2hpbGFicy9tYW50cmFlL3Byb3RvL2dlbi9tYW50cmFlL3YxO21hbnRyYWV2MaICA01YWKoCCk1hbnRyYWUuVjHKAgpNYW50cmFlXFYx4gIWTWFudHJhZVxWMVxHUEJNZXRhZGF0YeoCC01hbnRyYWU6OlYxYgZwcm90bzM", [file_buf_validate_validate, file_google_protobuf_struct, file_google_protobuf_timestamp]);
fileDesc("ChttYW50cmFlL3YxL21pZGRsZXdhcmUucHJvdG8SCm1hbnRyYWUudjEikAIKCk1pZGRsZXdhcmUSCgoCaWQYASABKAMSEgoKcHJvZmlsZV9pZBgCIAEoAxIQCghhZ2VudF9pZBgDIAEoCRIMCgRuYW1lGAQgASgJEicKBmNvbmZpZxgFIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSDwoHZW5hYmxlZBgGIAEoCBIoCgR0eXBlGAcgASgOMhoubWFudHJhZS52MS5NaWRkbGV3YXJlVHlwZRIuCgpjcmVhdGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKmAgoGUGx1Z2luEgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSFAoMZGlzcGxheV9uYW1lGAMgASgJEg4KBmF1dGhvchgEIAEoCRIMCgR0eXBlGAUgASgJEg4KBmltcG9ydBgGIAEoCRIPCgdzdW1tYXJ5GAcgASgJEhAKCGljb25fdXJsGAggASgJEhIKCmJhbm5lcl91cmwYCSABKAkSDgoGcmVhZG1lGAogASgJEhYKDmxhdGVzdF92ZXJzaW9uGAsgASgJEhAKCHZlcnNpb25zGAwgAygJEg0KBXN0YXJzGA0gASgDEioKB3NuaXBwZXQYDiABKAsyGS5tYW50cmFlLnYxLlBsdWdpblNuaXBwZXQSEgoKY3JlYXRlZF9hdBgPIAEoCSI4Cg1QbHVnaW5TbmlwcGV0EgsKA2s4cxgBIAEoCRIMCgR5YW1sGAIgASgJEgwKBHRvbWwYAyABKAkiZQoUR2V0TWlkZGxld2FyZVJlcXVlc3QSFgoCaWQYASABKANCCrpIB8gBASICIAASNQoEdHlwZRgCIAEoDjIaLm1hbnRyYWUudjEuTWlkZGxld2FyZVR5cGVCC7pICMgBAYIBAhABIkMKFUdldE1pZGRsZXdhcmVSZXNwb25zZRIqCgptaWRkbGV3YXJlGAEgASgLMhYubWFudHJhZS52MS5NaWRkbGV3YXJlIsUBChdDcmVhdGVNaWRkbGV3YXJlUmVxdWVzdBIeCgpwcm9maWxlX2lkGAEgASgDQgq6SAfIAQEiAiAAEhAKCGFnZW50X2lkGAIgASgJEhgKBG5hbWUYAyABKAlCCrpIB8gBAXICEAESNQoEdHlwZRgEIAEoDjIaLm1hbnRyYWUudjEuTWlkZGxld2FyZVR5cGVCC7pICMgBAYIBAhABEicKBmNvbmZpZxgFIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QiRgoYQ3JlYXRlTWlkZGxld2FyZVJlc3BvbnNlEioKCm1pZGRsZXdhcmUYASABKAsyFi5tYW50cmFlLnYxLk1pZGRsZXdhcmUivAEKF1VwZGF0ZU1pZGRsZXdhcmVSZXF1ZXN0EhYKAmlkGAEgASgDQgq6SAfIAQEiAiAAEhgKBG5hbWUYAiABKAlCCrpIB8gBAXICEAESNQoEdHlwZRgDIAEoDjIaLm1hbnRyYWUudjEuTWlkZGxld2FyZVR5cGVCC7pICMgBAYIBAhABEicKBmNvbmZpZxgEIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSDwoHZW5hYmxlZBgFIAEoCCJGChhVcGRhdGVNaWRkbGV3YXJlUmVzcG9uc2USKgoKbWlkZGxld2FyZRgBIAEoCzIWLm1hbnRyYWUudjEuTWlkZGxld2FyZSJoChdEZWxldGVNaWRkbGV3YXJlUmVxdWVzdBIWCgJpZBgBIAEoA0IKukgHyAEBIgIgABI1CgR0eXBlGAIgASgOMhoubWFudHJhZS52MS5NaWRkbGV3YXJlVHlwZUILukgIyAEBggECEAEiGgoYRGVsZXRlTWlkZGxld2FyZVJlc3BvbnNlIsYCChZMaXN0TWlkZGxld2FyZXNSZXF1ZXN0Eh4KCnByb2ZpbGVfaWQYASABKANCCrpIB8gBASICIAASHgoIYWdlbnRfaWQYAiABKAlCB7pIBHICEAFIAIgBARI3CgR0eXBlGAMgASgOMhoubWFudHJhZS52MS5NaWRkbGV3YXJlVHlwZUIIukgFggECEAFIAYgBARJqCgVsaW1pdBgEIAEoA0JWukhTugFQCgtsaW1pdC52YWxpZBIpbGltaXQgbXVzdCBiZSBlaXRoZXIgLTEgb3IgZ3JlYXRlciB0aGFuIDAaFnRoaXMgPT0gLTEgfHwgdGhpcyA+IDBIAogBARIcCgZvZmZzZXQYBSABKANCB7pIBCICKABIA4gBAUILCglfYWdlbnRfaWRCBwoFX3R5cGVCCAoGX2xpbWl0QgkKB19vZmZzZXQiWwoXTGlzdE1pZGRsZXdhcmVzUmVzcG9uc2USKwoLbWlkZGxld2FyZXMYASADKAsyFi5tYW50cmFlLnYxLk1pZGRsZXdhcmUSEwoLdG90YWxfY291bnQYAiABKAMiHQobR2V0TWlkZGxld2FyZVBsdWdpbnNSZXF1ZXN0IkMKHEdldE1pZGRsZXdhcmVQbHVnaW5zUmVzcG9uc2USIwoHcGx1Z2lucxgBIAMoCzISLm1hbnRyYWUudjEuUGx1Z2luKmQKDk1pZGRsZXdhcmVUeXBlEh8KG01JRERMRVdBUkVfVFlQRV9VTlNQRUNJRklFRBAAEhgKFE1JRERMRVdBUkVfVFlQRV9IVFRQEAESFwoTTUlERExFV0FSRV9UWVBFX1RDUBACMtwEChFNaWRkbGV3YXJlU2VydmljZRJZCg1HZXRNaWRkbGV3YXJlEiAubWFudHJhZS52MS5HZXRNaWRkbGV3YXJlUmVxdWVzdBohLm1hbnRyYWUudjEuR2V0TWlkZGxld2FyZVJlc3BvbnNlIgOQAgESXQoQQ3JlYXRlTWlkZGxld2FyZRIjLm1hbnRyYWUudjEuQ3JlYXRlTWlkZGxld2FyZVJlcXVlc3QaJC5tYW50cmFlLnYxLkNyZWF0ZU1pZGRsZXdhcmVSZXNwb25zZRJdChBVcGRhdGVNaWRkbGV3YXJlEiMubWFudHJhZS52MS5VcGRhdGVNaWRkbGV3YXJlUmVxdWVzdBokLm1hbnRyYWUudjEuVXBkYXRlTWlkZGxld2FyZVJlc3BvbnNlEl0KEERlbGV0ZU1pZGRsZXdhcmUSIy5tYW50cmFlLnYxLkRlbGV0ZU1pZGRsZXdhcmVSZXF1ZXN0GiQubWFudHJhZS52MS5EZWxldGVNaWRkbGV3YXJlUmVzcG9uc2USXwoPTGlzdE1pZGRsZXdhcmVzEiIubWFudHJhZS52MS5MaXN0TWlkZGxld2FyZXNSZXF1ZXN0GiMubWFudHJhZS52MS5MaXN0TWlkZGxld2FyZXNSZXNwb25zZSIDkAIBEm4KFEdldE1pZGRsZXdhcmVQbHVnaW5zEicubWFudHJhZS52MS5HZXRNaWRkbGV3YXJlUGx1Z2luc1JlcXVlc3QaKC5tYW50cmFlLnYxLkdldE1pZGRsZXdhcmVQbHVnaW5zUmVzcG9uc2UiA5ACAUKpAQoOY29tLm1hbnRyYWUudjFCD01pZGRsZXdhcmVQcm90b1ABWj1naXRodWIuY29tL21penVjaGlsYWJzL21hbnRyYWUvcHJvdG8vZ2VuL21hbnRyYWUvdjE7bWFudHJhZXYxogIDTVhYqgIKTWFudHJhZS5WMcoCCk1hbnRyYWVcVjHiAhZNYW50cmFlXFYxXEdQQk1ldGFkYXRh6gILTWFudHJhZTo6VjFiBnByb3RvMw", [file_buf_validate_validate, file_google_protobuf_struct, file_google_protobuf_timestamp]);
/**
* @generated from message mantrae.v1.Middleware
@@ -45,17 +45,22 @@ export type Middleware = Message<"mantrae.v1.Middleware"> & {
config?: JsonObject;
/**
* @generated from field: mantrae.v1.MiddlewareType type = 6;
* @generated from field: bool enabled = 6;
*/
enabled: boolean;
/**
* @generated from field: mantrae.v1.MiddlewareType type = 7;
*/
type: MiddlewareType;
/**
* @generated from field: google.protobuf.Timestamp created_at = 7;
* @generated from field: google.protobuf.Timestamp created_at = 8;
*/
createdAt?: Timestamp;
/**
* @generated from field: google.protobuf.Timestamp updated_at = 8;
* @generated from field: google.protobuf.Timestamp updated_at = 9;
*/
updatedAt?: Timestamp;
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,112 +0,0 @@
// Code generated by tygo. DO NOT EDIT.
import type { Domain } from '../types';
//////////
// source: certificate.go
/**
* Certificates defines traefik certificates type
* Certs and Keys could be either a file path, or the file content itself.
*/
export type Certificates = Certificate[];
/**
* Certificate holds a SSL cert/key pair
* Certs and Key could be either a file path, or the file content itself.
*/
export interface Certificate {
certFile?: any /* types.FileOrContent */;
keyFile?: any /* types.FileOrContent */;
}
/**
* FileOrContent hold a file path or content.
*/
export type FileOrContent = string;
//////////
// source: certificate_store.go
/**
* CertificateStore store for dynamic certificates.
*/
export interface CertificateStore {
DynamicCerts?: any /* safe.Safe */;
DefaultCertificate?: any /* tls.Certificate */;
CertCache?: any /* cache.Cache */;
}
//////////
// source: tls.go
/**
* ClientAuth defines the parameters of the client authentication part of the TLS connection, if any.
*/
export interface ClientAuth {
caFiles?: any /* types.FileOrContent */[];
/**
* ClientAuthType defines the client authentication type to apply.
* The available values are: "NoClientCert", "RequestClientCert", "VerifyClientCertIfGiven" and "RequireAndVerifyClientCert".
*/
clientAuthType?: string;
}
/**
* Options configures TLS for an entry point.
*/
export interface Options {
minVersion?: string;
maxVersion?: string;
cipherSuites?: string[];
curvePreferences?: string[];
clientAuth?: ClientAuth;
sniStrict?: boolean;
alpnProtocols?: string[];
disableSessionTickets?: boolean;
/**
* Deprecated: https://github.com/golang/go/issues/45430
*/
preferServerCipherSuites?: boolean;
}
/**
* Store holds the options for a given Store.
*/
export interface Store {
defaultCertificate?: Certificate;
defaultGeneratedCert?: GeneratedCert;
}
/**
* GeneratedCert defines the default generated certificate configuration.
*/
export interface GeneratedCert {
/**
* Resolver is the name of the resolver that will be used to issue the DefaultCertificate.
*/
resolver?: string;
/**
* Domain is the domain definition for the DefaultCertificate.
*/
domain?: Domain;
}
/**
* CertAndStores allows mapping a TLS certificate to a list of entry points.
*/
export interface CertAndStores {
Certificate: Certificate;
stores?: string[];
}
//////////
// source: tlsmanager.go
/**
* DefaultTLSConfigName is the name of the default set of options for configuring TLS.
*/
export const DefaultTLSConfigName = 'default';
/**
* DefaultTLSStoreName is the name of the default store of TLS certificates.
* Note that it actually is the only usable one for now.
*/
export const DefaultTLSStoreName = 'default';
/**
* Manager is the TLS option/store/configuration factory.
*/
export interface Manager {}

View File

@@ -1,264 +0,0 @@
// Code generated by tygo. DO NOT EDIT.
//////////
// source: domains.go
/**
* Domain holds a domain name with SANs.
*/
export interface Domain {
/**
* Main defines the main domain name.
*/
main?: string;
/**
* SANs defines the subject alternative domain names.
*/
sans?: string[];
}
//////////
// source: file_or_content.go
/**
* FileOrContent holds a file path or content.
*/
export type FileOrContent = string;
//////////
// source: host_resolver.go
/**
* HostResolverConfig contain configuration for CNAME Flattening.
*/
export interface HostResolverConfig {
cnameFlattening?: boolean;
resolvConfig?: string;
resolvDepth?: number /* int */;
}
//////////
// source: http_code_range.go
/**
* HTTPCodeRanges holds HTTP code ranges.
*/
export type HTTPCodeRanges = number /* int */[][];
//////////
// source: logs.go
/**
* AccessLogKeep is the keep string value.
*/
export const AccessLogKeep = "keep";
/**
* AccessLogDrop is the drop string value.
*/
export const AccessLogDrop = "drop";
/**
* AccessLogRedact is the redact string value.
*/
export const AccessLogRedact = "redact";
/**
* CommonFormat is the common logging format (CLF).
*/
export const CommonFormat: string = "common";
export const OTelTraefikServiceName = "traefik";
/**
* TraefikLog holds the configuration settings for the traefik logger.
*/
export interface TraefikLog {
level?: string;
format?: string;
noColor?: boolean;
filePath?: string;
maxSize?: number /* int */;
maxAge?: number /* int */;
maxBackups?: number /* int */;
compress?: boolean;
otlp?: OTelLog;
}
/**
* AccessLog holds the configuration settings for the access logger (middlewares/accesslog).
*/
export interface AccessLog {
filePath?: string;
format?: string;
filters?: AccessLogFilters;
fields?: AccessLogFields;
bufferingSize?: number /* int64 */;
addInternals?: boolean;
otlp?: OTelLog;
}
/**
* AccessLogFilters holds filters configuration.
*/
export interface AccessLogFilters {
statusCodes?: string[];
retryAttempts?: boolean;
minDuration?: any /* types.Duration */;
}
/**
* FieldHeaders holds configuration for access log headers.
*/
export interface FieldHeaders {
defaultMode?: string;
names?: { [key: string]: string};
}
/**
* AccessLogFields holds configuration for access log fields.
*/
export interface AccessLogFields {
defaultMode?: string;
names?: { [key: string]: string};
headers?: FieldHeaders;
}
/**
* OTelLog provides configuration settings for the open-telemetry logger.
*/
export interface OTelLog {
serviceName?: string;
resourceAttributes?: { [key: string]: string};
grpc?: OTelGRPC;
http?: OTelHTTP;
}
//////////
// source: metrics.go
/**
* Metrics provides options to expose and send Traefik metrics to different third party monitoring systems.
*/
export interface Metrics {
addInternals?: boolean;
prometheus?: Prometheus;
datadog?: Datadog;
statsD?: Statsd;
influxDB2?: InfluxDB2;
otlp?: OTLP;
}
/**
* Prometheus can contain specific configuration used by the Prometheus Metrics exporter.
*/
export interface Prometheus {
buckets?: number /* float64 */[];
addEntryPointsLabels?: boolean;
addRoutersLabels?: boolean;
addServicesLabels?: boolean;
entryPoint?: string;
manualRouting?: boolean;
headerLabels?: { [key: string]: string};
}
/**
* Datadog contains address and metrics pushing interval configuration.
*/
export interface Datadog {
address?: string;
pushInterval?: any /* types.Duration */;
addEntryPointsLabels?: boolean;
addRoutersLabels?: boolean;
addServicesLabels?: boolean;
prefix?: string;
}
/**
* Statsd contains address and metrics pushing interval configuration.
*/
export interface Statsd {
address?: string;
pushInterval?: any /* types.Duration */;
addEntryPointsLabels?: boolean;
addRoutersLabels?: boolean;
addServicesLabels?: boolean;
prefix?: string;
}
/**
* InfluxDB2 contains address, token and metrics pushing interval configuration.
*/
export interface InfluxDB2 {
address?: string;
token?: string;
pushInterval?: any /* types.Duration */;
org?: string;
bucket?: string;
addEntryPointsLabels?: boolean;
addRoutersLabels?: boolean;
addServicesLabels?: boolean;
additionalLabels?: { [key: string]: string};
}
/**
* OTLP contains specific configuration used by the OpenTelemetry Metrics exporter.
*/
export interface OTLP {
grpc?: OTelGRPC;
http?: OTelHTTP;
addEntryPointsLabels?: boolean;
addRoutersLabels?: boolean;
addServicesLabels?: boolean;
explicitBoundaries?: number /* float64 */[];
pushInterval?: any /* types.Duration */;
serviceName?: string;
}
/**
* Statistics provides options for monitoring request and response stats.
*/
export interface Statistics {
recentErrors?: number /* int */;
}
//////////
// source: otel.go
/**
* OTelGRPC provides configuration settings for the gRPC open-telemetry.
*/
export interface OTelGRPC {
endpoint?: string;
insecure?: boolean;
tls?: ClientTLS;
headers?: { [key: string]: string};
}
/**
* OTelHTTP provides configuration settings for the HTTP open-telemetry.
*/
export interface OTelHTTP {
endpoint?: string;
tls?: ClientTLS;
headers?: { [key: string]: string};
}
//////////
// source: route_appender.go
/**
* RouteAppender appends routes on a router (/api, /ping ...).
*/
export type RouteAppender = any;
//////////
// source: tls.go
/**
* ClientTLS holds TLS specific configurations as client
* CA, Cert and Key can be either path or file contents.
*/
export interface ClientTLS {
ca?: string;
cert?: string;
key?: string;
insecureSkipVerify?: boolean;
}
//////////
// source: tracing.go
/**
* OTelTracing provides configuration settings for the open-telemetry tracer.
*/
export interface OTelTracing {
grpc?: OTelGRPC;
http?: OTelHTTP;
}
/**
* tpCloser converts a TraceProvider into an io.Closer.
*/

View File

@@ -0,0 +1,588 @@
// This file is auto-generated via `zen.StructToZodSchema`.
// Do not edit manually.
import { z } from "zod";
export const CookieSchema = z.object({
name: z.string().optional(),
secure: z.boolean().optional(),
httpOnly: z.boolean().optional(),
sameSite: z.string().optional(),
maxAge: z.number().optional(),
path: z.string().optional(),
domain: z.string().optional(),
});
export type Cookie = z.infer<typeof CookieSchema>;
export const StickySchema = z.object({
cookie: CookieSchema.optional(),
});
export type Sticky = z.infer<typeof StickySchema>;
export const ServerSchema = z.object({
url: z.string().optional(),
weight: z.number().optional(),
preservePath: z.boolean().optional(),
fenced: z.boolean().optional(),
});
export type Server = z.infer<typeof ServerSchema>;
export const ServerHealthCheckSchema = z.object({
scheme: z.string().optional(),
mode: z.string().optional(),
path: z.string().optional(),
method: z.string().optional(),
status: z.number().optional(),
port: z.number().optional(),
interval: z.number().optional(),
timeout: z.number().optional(),
hostname: z.string().optional(),
followRedirects: z.boolean().optional(),
headers: z.record(z.string(), z.string()).optional(),
});
export type ServerHealthCheck = z.infer<typeof ServerHealthCheckSchema>;
export const ResponseForwardingSchema = z.object({
flushInterval: z.number().optional(),
});
export type ResponseForwarding = z.infer<typeof ResponseForwardingSchema>;
export const ServersLoadBalancerSchema = z.object({
sticky: StickySchema.optional(),
servers: ServerSchema.array().optional(),
strategy: z.string().optional(),
healthCheck: ServerHealthCheckSchema.optional(),
passHostHeader: z.boolean().nullable(),
responseForwarding: ResponseForwardingSchema.optional(),
serversTransport: z.string().optional(),
});
export type ServersLoadBalancer = z.infer<typeof ServersLoadBalancerSchema>;
export const WRRServiceSchema = z.object({
name: z.string().optional(),
weight: z.number().optional(),
});
export type WRRService = z.infer<typeof WRRServiceSchema>;
export const HealthCheckSchema = z.object({});
export type HealthCheck = z.infer<typeof HealthCheckSchema>;
export const WeightedRoundRobinSchema = z.object({
services: WRRServiceSchema.array().optional(),
sticky: StickySchema.optional(),
healthCheck: HealthCheckSchema.optional(),
});
export type WeightedRoundRobin = z.infer<typeof WeightedRoundRobinSchema>;
export const MirrorServiceSchema = z.object({
name: z.string().optional(),
percent: z.number().optional(),
});
export type MirrorService = z.infer<typeof MirrorServiceSchema>;
export const MirroringSchema = z.object({
service: z.string().optional(),
mirrorBody: z.boolean().optional(),
maxBodySize: z.number().optional(),
mirrors: MirrorServiceSchema.array().optional(),
healthCheck: HealthCheckSchema.optional(),
});
export type Mirroring = z.infer<typeof MirroringSchema>;
export const FailoverSchema = z.object({
service: z.string().optional(),
fallback: z.string().optional(),
healthCheck: HealthCheckSchema.optional(),
});
export type Failover = z.infer<typeof FailoverSchema>;
export const ServiceSchema = z.object({
loadBalancer: ServersLoadBalancerSchema.optional(),
weighted: WeightedRoundRobinSchema.optional(),
mirroring: MirroringSchema.optional(),
failover: FailoverSchema.optional(),
});
export type Service = z.infer<typeof ServiceSchema>;
export const ProxyProtocolSchema = z.object({
version: z.number().optional(),
});
export type ProxyProtocol = z.infer<typeof ProxyProtocolSchema>;
export const TCPServerSchema = z.object({
address: z.string().optional(),
tls: z.boolean().optional(),
});
export type TCPServer = z.infer<typeof TCPServerSchema>;
export const TCPServersLoadBalancerSchema = z.object({
proxyProtocol: ProxyProtocolSchema.optional(),
servers: TCPServerSchema.array().optional(),
serversTransport: z.string().optional(),
terminationDelay: z.number().optional(),
});
export type TCPServersLoadBalancer = z.infer<
typeof TCPServersLoadBalancerSchema
>;
export const TCPWRRServiceSchema = z.object({
name: z.string().optional(),
weight: z.number().optional(),
});
export type TCPWRRService = z.infer<typeof TCPWRRServiceSchema>;
export const TCPWeightedRoundRobinSchema = z.object({
services: TCPWRRServiceSchema.array().optional(),
});
export type TCPWeightedRoundRobin = z.infer<typeof TCPWeightedRoundRobinSchema>;
export const TCPServiceSchema = z.object({
loadBalancer: TCPServersLoadBalancerSchema.optional(),
weighted: TCPWeightedRoundRobinSchema.optional(),
});
export type TCPService = z.infer<typeof TCPServiceSchema>;
export const UDPServerSchema = z.object({
address: z.string().optional(),
});
export type UDPServer = z.infer<typeof UDPServerSchema>;
export const UDPServersLoadBalancerSchema = z.object({
servers: UDPServerSchema.array().optional(),
});
export type UDPServersLoadBalancer = z.infer<
typeof UDPServersLoadBalancerSchema
>;
export const UDPWRRServiceSchema = z.object({
name: z.string().optional(),
weight: z.number().optional(),
});
export type UDPWRRService = z.infer<typeof UDPWRRServiceSchema>;
export const UDPWeightedRoundRobinSchema = z.object({
services: UDPWRRServiceSchema.array().optional(),
});
export type UDPWeightedRoundRobin = z.infer<typeof UDPWeightedRoundRobinSchema>;
export const UDPServiceSchema = z.object({
loadBalancer: UDPServersLoadBalancerSchema.optional(),
weighted: UDPWeightedRoundRobinSchema.optional(),
});
export type UDPService = z.infer<typeof UDPServiceSchema>;
export const AddPrefixSchema = z.object({
prefix: z.string().optional(),
});
export type AddPrefix = z.infer<typeof AddPrefixSchema>;
export const StripPrefixSchema = z.object({
prefixes: z.string().array().optional(),
forceSlash: z.boolean().optional(),
});
export type StripPrefix = z.infer<typeof StripPrefixSchema>;
export const StripPrefixRegexSchema = z.object({
regex: z.string().array().optional(),
});
export type StripPrefixRegex = z.infer<typeof StripPrefixRegexSchema>;
export const ReplacePathSchema = z.object({
path: z.string().optional(),
});
export type ReplacePath = z.infer<typeof ReplacePathSchema>;
export const ReplacePathRegexSchema = z.object({
regex: z.string().optional(),
replacement: z.string().optional(),
});
export type ReplacePathRegex = z.infer<typeof ReplacePathRegexSchema>;
export const ChainSchema = z.object({
middlewares: z.string().array().optional(),
});
export type Chain = z.infer<typeof ChainSchema>;
export const IPStrategySchema = z.object({
depth: z.number().optional(),
excludedIPs: z.string().array().optional(),
ipv6Subnet: z.number().optional(),
});
export type IPStrategy = z.infer<typeof IPStrategySchema>;
export const IPWhiteListSchema = z.object({
sourceRange: z.string().array().optional(),
ipStrategy: IPStrategySchema.optional(),
});
export type IPWhiteList = z.infer<typeof IPWhiteListSchema>;
export const IPAllowListSchema = z.object({
sourceRange: z.string().array().optional(),
ipStrategy: IPStrategySchema.optional(),
rejectStatusCode: z.number().optional(),
});
export type IPAllowList = z.infer<typeof IPAllowListSchema>;
export const HeadersSchema = z.object({
customRequestHeaders: z.record(z.string(), z.string()).optional(),
customResponseHeaders: z.record(z.string(), z.string()).optional(),
accessControlAllowCredentials: z.boolean().optional(),
accessControlAllowHeaders: z.string().array().optional(),
accessControlAllowMethods: z.string().array().optional(),
accessControlAllowOriginList: z.string().array().optional(),
accessControlAllowOriginListRegex: z.string().array().optional(),
accessControlExposeHeaders: z.string().array().optional(),
accessControlMaxAge: z.number().optional(),
addVaryHeader: z.boolean().optional(),
allowedHosts: z.string().array().optional(),
hostsProxyHeaders: z.string().array().optional(),
sslProxyHeaders: z.record(z.string(), z.string()).optional(),
stsSeconds: z.number().optional(),
stsIncludeSubdomains: z.boolean().optional(),
stsPreload: z.boolean().optional(),
forceSTSHeader: z.boolean().optional(),
frameDeny: z.boolean().optional(),
customFrameOptionsValue: z.string().optional(),
contentTypeNosniff: z.boolean().optional(),
browserXssFilter: z.boolean().optional(),
customBrowserXSSValue: z.string().optional(),
contentSecurityPolicy: z.string().optional(),
contentSecurityPolicyReportOnly: z.string().optional(),
publicKey: z.string().optional(),
referrerPolicy: z.string().optional(),
permissionsPolicy: z.string().optional(),
isDevelopment: z.boolean().optional(),
featurePolicy: z.string().optional(),
sslRedirect: z.boolean().optional(),
sslTemporaryRedirect: z.boolean().optional(),
sslHost: z.string().optional(),
sslForceHost: z.boolean().optional(),
});
export type Headers = z.infer<typeof HeadersSchema>;
export const ErrorPageSchema = z.object({
status: z.string().array().optional(),
statusRewrites: z.record(z.string(), z.number()).optional(),
service: z.string().optional(),
query: z.string().optional(),
});
export type ErrorPage = z.infer<typeof ErrorPageSchema>;
export const SourceCriterionSchema = z.object({
ipStrategy: IPStrategySchema.optional(),
requestHeaderName: z.string().optional(),
requestHost: z.boolean().optional(),
});
export type SourceCriterion = z.infer<typeof SourceCriterionSchema>;
export const ClientTLSSchema = z.object({
ca: z.string().optional(),
cert: z.string().optional(),
key: z.string().optional(),
insecureSkipVerify: z.boolean().optional(),
});
export type ClientTLS = z.infer<typeof ClientTLSSchema>;
export const RedisSchema = z.object({
endpoints: z.string().array().optional(),
tls: ClientTLSSchema.optional(),
username: z.string().optional(),
password: z.string().optional(),
db: z.number().optional(),
poolSize: z.number().optional(),
minIdleConns: z.number().optional(),
maxActiveConns: z.number().optional(),
readTimeout: z.number().optional(),
writeTimeout: z.number().optional(),
dialTimeout: z.number().optional(),
});
export type Redis = z.infer<typeof RedisSchema>;
export const RateLimitSchema = z.object({
average: z.number().optional(),
period: z.number().optional(),
burst: z.number().optional(),
sourceCriterion: SourceCriterionSchema.optional(),
redis: RedisSchema.optional(),
});
export type RateLimit = z.infer<typeof RateLimitSchema>;
export const RedirectRegexSchema = z.object({
regex: z.string().optional(),
replacement: z.string().optional(),
permanent: z.boolean().optional(),
});
export type RedirectRegex = z.infer<typeof RedirectRegexSchema>;
export const RedirectSchemeSchema = z.object({
scheme: z.string().optional(),
port: z.string().optional(),
permanent: z.boolean().optional(),
});
export type RedirectScheme = z.infer<typeof RedirectSchemeSchema>;
export const BasicAuthSchema = z.object({
users: z.string().array().optional(),
usersFile: z.string().optional(),
realm: z.string().optional(),
removeHeader: z.boolean().optional(),
headerField: z.string().optional(),
});
export type BasicAuth = z.infer<typeof BasicAuthSchema>;
export const DigestAuthSchema = z.object({
users: z.string().array().optional(),
usersFile: z.string().optional(),
removeHeader: z.boolean().optional(),
realm: z.string().optional(),
headerField: z.string().optional(),
});
export type DigestAuth = z.infer<typeof DigestAuthSchema>;
export const ForwardAuthSchema = z.object({
address: z.string().optional(),
tls: ClientTLSSchema.optional(),
trustForwardHeader: z.boolean().optional(),
authResponseHeaders: z.string().array().optional(),
authResponseHeadersRegex: z.string().optional(),
authRequestHeaders: z.string().array().optional(),
addAuthCookiesToResponse: z.string().array().optional(),
headerField: z.string().optional(),
forwardBody: z.boolean().optional(),
maxBodySize: z.number().optional(),
preserveLocationHeader: z.boolean().optional(),
preserveRequestMethod: z.boolean().optional(),
});
export type ForwardAuth = z.infer<typeof ForwardAuthSchema>;
export const InFlightReqSchema = z.object({
amount: z.number().optional(),
sourceCriterion: SourceCriterionSchema.optional(),
});
export type InFlightReq = z.infer<typeof InFlightReqSchema>;
export const BufferingSchema = z.object({
maxRequestBodyBytes: z.number().optional(),
memRequestBodyBytes: z.number().optional(),
maxResponseBodyBytes: z.number().optional(),
memResponseBodyBytes: z.number().optional(),
retryExpression: z.string().optional(),
});
export type Buffering = z.infer<typeof BufferingSchema>;
export const CircuitBreakerSchema = z.object({
expression: z.string().optional(),
checkPeriod: z.number().optional(),
fallbackDuration: z.number().optional(),
recoveryDuration: z.number().optional(),
responseCode: z.number().optional(),
});
export type CircuitBreaker = z.infer<typeof CircuitBreakerSchema>;
export const CompressSchema = z.object({
excludedContentTypes: z.string().array().optional(),
includedContentTypes: z.string().array().optional(),
minResponseBodyBytes: z.number().optional(),
encodings: z.string().array().optional(),
defaultEncoding: z.string().optional(),
});
export type Compress = z.infer<typeof CompressSchema>;
export const TLSClientCertificateSubjectDNInfoSchema = z.object({
country: z.boolean().optional(),
province: z.boolean().optional(),
locality: z.boolean().optional(),
organization: z.boolean().optional(),
organizationalUnit: z.boolean().optional(),
commonName: z.boolean().optional(),
serialNumber: z.boolean().optional(),
domainComponent: z.boolean().optional(),
});
export type TLSClientCertificateSubjectDNInfo = z.infer<
typeof TLSClientCertificateSubjectDNInfoSchema
>;
export const TLSClientCertificateIssuerDNInfoSchema = z.object({
country: z.boolean().optional(),
province: z.boolean().optional(),
locality: z.boolean().optional(),
organization: z.boolean().optional(),
commonName: z.boolean().optional(),
serialNumber: z.boolean().optional(),
domainComponent: z.boolean().optional(),
});
export type TLSClientCertificateIssuerDNInfo = z.infer<
typeof TLSClientCertificateIssuerDNInfoSchema
>;
export const TLSClientCertificateInfoSchema = z.object({
notAfter: z.boolean().optional(),
notBefore: z.boolean().optional(),
sans: z.boolean().optional(),
serialNumber: z.boolean().optional(),
subject: TLSClientCertificateSubjectDNInfoSchema.optional(),
issuer: TLSClientCertificateIssuerDNInfoSchema.optional(),
});
export type TLSClientCertificateInfo = z.infer<
typeof TLSClientCertificateInfoSchema
>;
export const PassTLSClientCertSchema = z.object({
pem: z.boolean().optional(),
info: TLSClientCertificateInfoSchema.optional(),
});
export type PassTLSClientCert = z.infer<typeof PassTLSClientCertSchema>;
export const RetrySchema = z.object({
attempts: z.number().optional(),
initialInterval: z.number().optional(),
});
export type Retry = z.infer<typeof RetrySchema>;
export const ContentTypeSchema = z.object({
autoDetect: z.boolean().optional(),
});
export type ContentType = z.infer<typeof ContentTypeSchema>;
export const GrpcWebSchema = z.object({
allowOrigins: z.string().array().optional(),
});
export type GrpcWeb = z.infer<typeof GrpcWebSchema>;
export const HeaderModifierSchema = z.object({
set: z.record(z.string(), z.string()).optional(),
add: z.record(z.string(), z.string()).optional(),
remove: z.string().array().optional(),
});
export type HeaderModifier = z.infer<typeof HeaderModifierSchema>;
export const RequestRedirectSchema = z.object({
scheme: z.string().optional(),
hostname: z.string().optional(),
port: z.string().optional(),
path: z.string().optional(),
pathPrefix: z.string().optional(),
statusCode: z.number().optional(),
});
export type RequestRedirect = z.infer<typeof RequestRedirectSchema>;
export const URLRewriteSchema = z.object({
hostname: z.string().optional(),
path: z.string().optional(),
pathPrefix: z.string().optional(),
});
export type URLRewrite = z.infer<typeof URLRewriteSchema>;
export const MiddlewareSchema = z.object({
addPrefix: AddPrefixSchema.optional(),
stripPrefix: StripPrefixSchema.optional(),
stripPrefixRegex: StripPrefixRegexSchema.optional(),
replacePath: ReplacePathSchema.optional(),
replacePathRegex: ReplacePathRegexSchema.optional(),
chain: ChainSchema.optional(),
ipWhiteList: IPWhiteListSchema.optional(),
ipAllowList: IPAllowListSchema.optional(),
headers: HeadersSchema.optional(),
errors: ErrorPageSchema.optional(),
rateLimit: RateLimitSchema.optional(),
redirectRegex: RedirectRegexSchema.optional(),
redirectScheme: RedirectSchemeSchema.optional(),
basicAuth: BasicAuthSchema.optional(),
digestAuth: DigestAuthSchema.optional(),
forwardAuth: ForwardAuthSchema.optional(),
inFlightReq: InFlightReqSchema.optional(),
buffering: BufferingSchema.optional(),
circuitBreaker: CircuitBreakerSchema.optional(),
compress: CompressSchema.optional(),
passTLSClientCert: PassTLSClientCertSchema.optional(),
retry: RetrySchema.optional(),
contentType: ContentTypeSchema.optional(),
grpcWeb: GrpcWebSchema.optional(),
plugin: z.record(z.string(), z.record(z.string(), z.any())).optional(),
requestHeaderModifier: HeaderModifierSchema.optional(),
responseHeaderModifier: HeaderModifierSchema.optional(),
requestRedirect: RequestRedirectSchema.optional(),
URLRewrite: URLRewriteSchema.optional(),
});
export type Middleware = z.infer<typeof MiddlewareSchema>;
export const TCPInFlightConnSchema = z.object({
amount: z.number().optional(),
});
export type TCPInFlightConn = z.infer<typeof TCPInFlightConnSchema>;
export const TCPIPWhiteListSchema = z.object({
sourceRange: z.string().array().optional(),
});
export type TCPIPWhiteList = z.infer<typeof TCPIPWhiteListSchema>;
export const TCPIPAllowListSchema = z.object({
sourceRange: z.string().array().optional(),
});
export type TCPIPAllowList = z.infer<typeof TCPIPAllowListSchema>;
export const TCPMiddlewareSchema = z.object({
inFlightConn: TCPInFlightConnSchema.optional(),
ipWhiteList: TCPIPWhiteListSchema.optional(),
ipAllowList: TCPIPAllowListSchema.optional(),
});
export type TCPMiddleware = z.infer<typeof TCPMiddlewareSchema>;
export const DomainSchema = z.object({
main: z.string().optional(),
sans: z.string().array().optional(),
});
export type Domain = z.infer<typeof DomainSchema>;
export const RouterTLSConfigSchema = z.object({
options: z.string().optional(),
certResolver: z.string().optional(),
domains: DomainSchema.array().optional(),
});
export type RouterTLSConfig = z.infer<typeof RouterTLSConfigSchema>;
export const RouterObservabilityConfigSchema = z.object({
accessLogs: z.boolean().optional(),
tracing: z.boolean().optional(),
metrics: z.boolean().optional(),
});
export type RouterObservabilityConfig = z.infer<
typeof RouterObservabilityConfigSchema
>;
export const RouterSchema = z.object({
entryPoints: z.string().array().optional(),
middlewares: z.string().array().optional(),
service: z.string().optional(),
rule: z.string().optional(),
ruleSyntax: z.string().optional(),
priority: z.number().optional(),
tls: RouterTLSConfigSchema.optional(),
observability: RouterObservabilityConfigSchema.optional(),
});
export type Router = z.infer<typeof RouterSchema>;
export const RouterTCPTLSConfigSchema = z.object({
passthrough: z.boolean(),
options: z.string().optional(),
certResolver: z.string().optional(),
domains: DomainSchema.array().optional(),
});
export type RouterTCPTLSConfig = z.infer<typeof RouterTCPTLSConfigSchema>;
export const TCPRouterSchema = z.object({
entryPoints: z.string().array().optional(),
middlewares: z.string().array().optional(),
service: z.string().optional(),
rule: z.string().optional(),
ruleSyntax: z.string().optional(),
priority: z.number().optional(),
tls: RouterTCPTLSConfigSchema.optional(),
});
export type TCPRouter = z.infer<typeof TCPRouterSchema>;
export const UDPRouterSchema = z.object({
entryPoints: z.string().array().optional(),
service: z.string().optional(),
});
export type UDPRouter = z.infer<typeof UDPRouterSchema>;

View File

@@ -1,8 +1,8 @@
import { DnsProviderType } from './gen/mantrae/v1/dns_provider_pb';
import { MiddlewareType } from './gen/mantrae/v1/middleware_pb';
import { RouterType } from './gen/mantrae/v1/router_pb';
import { ServiceType } from './gen/mantrae/v1/service_pb';
import type { JsonObject } from '@bufbuild/protobuf';
import { DnsProviderType } from "./gen/mantrae/v1/dns_provider_pb";
import { MiddlewareType } from "./gen/mantrae/v1/middleware_pb";
import { RouterType } from "./gen/mantrae/v1/router_pb";
import { ServiceType } from "./gen/mantrae/v1/service_pb";
import type { JsonObject } from "@bufbuild/protobuf";
// Parse protobuf config
export function unmarshalConfig<T>(json: JsonObject | undefined): T {
@@ -17,62 +17,62 @@ export function marshalConfig<T>(config: T): JsonObject {
// Convert enum to select options
export const routerTypes = Object.keys(RouterType)
.filter((key) => isNaN(Number(key)) && key !== 'UNSPECIFIED')
.filter((key) => isNaN(Number(key)) && key !== "UNSPECIFIED")
.map((key) => ({
label: key.toUpperCase(),
value: RouterType[key as keyof typeof RouterType]
value: RouterType[key as keyof typeof RouterType],
}));
export const serviceTypes = Object.keys(ServiceType)
.filter((key) => isNaN(Number(key)) && key !== 'UNSPECIFIED')
.filter((key) => isNaN(Number(key)) && key !== "UNSPECIFIED")
.map((key) => ({
label: key.toUpperCase(),
value: ServiceType[key as keyof typeof ServiceType]
value: ServiceType[key as keyof typeof ServiceType],
}));
export const middlewareTypes = Object.keys(MiddlewareType)
.filter((key) => isNaN(Number(key)) && key !== 'UNSPECIFIED')
.filter((key) => isNaN(Number(key)) && key !== "UNSPECIFIED")
.map((key) => ({
label: key.toUpperCase(),
value: MiddlewareType[key as keyof typeof MiddlewareType]
value: MiddlewareType[key as keyof typeof MiddlewareType],
}));
export const dnsProviderTypes = Object.keys(DnsProviderType)
.filter((key) => isNaN(Number(key)) && key !== 'UNSPECIFIED')
.filter((key) => isNaN(Number(key)) && key !== "UNSPECIFIED")
.map((key) => ({
label: key
.replace('DNS_PROVIDER_TYPE_', '')
.replace("DNS_PROVIDER_TYPE_", "")
.toLowerCase()
.replace(/^\w/, (c) => c.toUpperCase()),
value: DnsProviderType[key as keyof typeof DnsProviderType]
value: DnsProviderType[key as keyof typeof DnsProviderType],
}));
export const HTTPMiddlewareKeys = [
{ value: 'addPrefix', label: 'Add Prefix' },
{ value: 'basicAuth', label: 'Basic Auth' },
{ value: 'digestAuth', label: 'Digest Auth' },
{ value: 'buffering', label: 'Buffering' },
{ value: 'chain', label: 'Chain' },
{ value: 'circuitBreaker', label: 'Circuit Breaker' },
{ value: 'compress', label: 'Compress' },
{ value: 'errorPage', label: 'Error Page' },
{ value: 'forwardAuth', label: 'Forward Auth' },
{ value: 'headers', label: 'Headers' },
{ value: 'ipAllowList', label: 'IP Allow List' },
{ value: 'inFlightReq', label: 'In-Flight Request' },
{ value: 'passTLSClientCert', label: 'Pass TLS Client Cert' },
{ value: 'rateLimit', label: 'Rate Limit' },
{ value: 'redirectRegex', label: 'Redirect Regex' },
{ value: 'redirectScheme', label: 'Redirect Scheme' },
{ value: 'replacePath', label: 'Replace Path' },
{ value: 'replacePathRegex', label: 'Replace Path Regex' },
{ value: 'retry', label: 'Retry' },
{ value: 'stripPrefix', label: 'Strip Prefix' },
{ value: 'stripPrefixRegex', label: 'Strip Prefix Regex' },
{ value: 'plugin', label: 'Plugin' }
{ value: "addPrefix", label: "Add Prefix" },
{ value: "basicAuth", label: "Basic Auth" },
{ value: "digestAuth", label: "Digest Auth" },
{ value: "buffering", label: "Buffering" },
{ value: "chain", label: "Chain" },
{ value: "circuitBreaker", label: "Circuit Breaker" },
{ value: "compress", label: "Compress" },
{ value: "errorPage", label: "Error Page" },
{ value: "forwardAuth", label: "Forward Auth" },
{ value: "headers", label: "Headers" },
{ value: "ipAllowList", label: "IP Allow List" },
{ value: "inFlightReq", label: "In-Flight Request" },
{ value: "passTLSClientCert", label: "Pass TLS Client Cert" },
{ value: "rateLimit", label: "Rate Limit" },
{ value: "redirectRegex", label: "Redirect Regex" },
{ value: "redirectScheme", label: "Redirect Scheme" },
{ value: "replacePath", label: "Replace Path" },
{ value: "replacePathRegex", label: "Replace Path Regex" },
{ value: "retry", label: "Retry" },
{ value: "stripPrefix", label: "Strip Prefix" },
{ value: "stripPrefixRegex", label: "Strip Prefix Regex" },
{ value: "plugin", label: "Plugin" },
];
export const TCPMiddlewareKeys = [
{ value: 'ipAllowList', label: 'IP Allow List' },
{ value: 'inFlightConn', label: 'In-Flight Connection' }
{ value: "ipAllowList", label: "IP Allow List" },
{ value: "inFlightConn", label: "In-Flight Connection" },
];

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import ColumnBadge from '$lib/components/tables/ColumnBadge.svelte';
import DataTable from '$lib/components/tables/DataTable.svelte';
import ColumnCheck from '$lib/components/tables/ColumnCheck.svelte';
import MiddlewareModal from '$lib/components/modals/middleware.svelte';
import TableActions from '$lib/components/tables/TableActions.svelte';
import type { ColumnDef, PaginationState } from '@tanstack/table-core';
@@ -68,6 +69,15 @@
});
}
},
{
header: 'Enabled',
accessorKey: 'enabled',
enableSorting: true,
cell: ({ row }) => {
let checked = row.getValue('enabled') as boolean;
return renderComponent(ColumnCheck, { checked: checked });
}
},
{
id: 'actions',
enableHiding: false,
@@ -117,13 +127,15 @@
toast.success('Router deleted');
} catch (err) {
const e = ConnectError.from(err);
toast.error('Failed to delete router', { description: e.message });
toast.error('Failed to delete middleware', { description: e.message });
}
};
async function bulkDelete(selectedRows: Middleware[]) {
try {
const confirmed = confirm(`Are you sure you want to delete ${selectedRows.length} routers?`);
const confirmed = confirm(
`Are you sure you want to delete ${selectedRows.length} middlewares?`
);
if (!confirmed) return;
const rows = selectedRows.map((row) => ({ id: row.id, type: row.type }));
@@ -131,10 +143,10 @@
await middlewareClient.deleteMiddleware({ id: row.id, type: row.type });
}
await refreshData(pageSize.value ?? 10, 0);
toast.success(`Successfully deleted ${selectedRows.length} routers`);
toast.success(`Successfully deleted ${selectedRows.length} middlewares`);
} catch (err) {
const e = ConnectError.from(err);
toast.error('Failed to delete routers', { description: e.message });
toast.error('Failed to delete middlewares', { description: e.message });
}
}
async function refreshData(pageSize: number, pageIndex: number) {

View File

@@ -9,7 +9,7 @@
import type { BulkAction } from '$lib/components/tables/types';
import { renderComponent } from '$lib/components/ui/data-table';
import { RouterType, type Router } from '$lib/gen/mantrae/v1/router_pb';
import type { RouterTLSConfig } from '$lib/gen/tygo/dynamic';
import type { RouterTLSConfig } from '$lib/gen/zen/traefik-schemas';
import { pageIndex, pageSize } from '$lib/stores/common';
import { profile } from '$lib/stores/profile';
import { ConnectError } from '@connectrpc/connect';