diff --git a/.gitignore b/.gitignore index e6512f01..4f366aa0 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,9 @@ tmp .release_notes.md contracts/build + +# Node.js dependencies +node_modules/ +**/node_modules/ +package-lock.json +**/package-lock.json diff --git a/Makefile b/Makefile index b6d08815..4166e968 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,10 @@ CORE_SQL_ARTIFACTS := $(wildcard pkg/core/db/*.sql.go) ETH_SQL_SRCS := $(shell find pkg/eth/db/sql -type f -name '*.sql') pkg/eth/db/sqlc.yaml ETH_SQL_ARTIFACTS := $(wildcard pkg/eth/db/*.sql.go) -SQL_ARTIFACTS := $(CORE_SQL_ARTIFACTS) $(ETH_SQL_ARTIFACTS) +ETL_SQL_SRCS := $(shell find pkg/etl/db/sql -type f -name '*.sql') pkg/etl/db/sqlc.yaml +ETL_SQL_ARTIFACTS := $(wildcard pkg/etl/db/*.sql.go) + +SQL_ARTIFACTS := $(CORE_SQL_ARTIFACTS) $(ETH_SQL_ARTIFACTS) $(ETL_SQL_ARTIFACTS) ###### PROTO PROTO_SRCS := $(shell find proto -type f -name '*.proto') @@ -27,6 +30,12 @@ PROTO_ARTIFACTS := $(shell find pkg/api -type f -name '*.pb.go') TEMPL_SRCS := $(shell find pkg/core/console -type f -name "*.templ") TEMPL_ARTIFACTS := $(shell find pkg/core/console -type f -name "*_templ.go") +EXPLORER_TEMPL_SRCS := $(shell find pkg/console/templates -type f -name "*.templ") +EXPLORER_TEMPL_ARTIFACTS := $(shell find pkg/console/templates -type f -name "*_templ.go") + +###### CSS +EXPLORER_CSS_INPUT := pkg/console/assets/input.css +EXPLORER_CSS_OUTPUT := pkg/console/assets/css/output.css ###### CODE JSON_SRCS := $(wildcard pkg/core/config/genesis/*.json) @@ -92,12 +101,29 @@ go.mod: $(GO_SRCS) gen: regen-templ regen-proto regen-sql .PHONY: regen-templ -regen-templ: $(TEMPL_ARTIFACTS) +regen-templ: $(TEMPL_ARTIFACTS) $(EXPLORER_TEMPL_ARTIFACTS) regen-css $(TEMPL_ARTIFACTS): $(TEMPL_SRCS) - @echo Regenerating templ code + @echo Regenerating console templ code cd pkg/core/console && templ generate -log-level error +$(EXPLORER_TEMPL_ARTIFACTS): $(EXPLORER_TEMPL_SRCS) + @echo Regenerating explorer templ code + cd pkg/console/templates && templ generate -log-level error + @touch pkg/console/templates/layouts/frame.templ 2>/dev/null || touch pkg/console/console.go 2>/dev/null || true + +.PHONY: regen-css +regen-css: $(EXPLORER_CSS_OUTPUT) + +$(EXPLORER_CSS_OUTPUT): $(EXPLORER_CSS_INPUT) $(EXPLORER_TEMPL_SRCS) + @echo Regenerating explorer CSS + @cd pkg/console/assets && \ + (npm list @tailwindcss/postcss > /dev/null 2>&1 || npm install --no-save @tailwindcss/postcss postcss-cli > /dev/null 2>&1) && \ + npx postcss input.css -o css/output.css --minify || \ + (echo "Error: Failed to regenerate CSS. You may need to run manually:" && \ + echo " cd pkg/console/assets && npm install && npx postcss input.css -o css/output.css" && exit 1) + @touch pkg/console/templates/layouts/frame.templ 2>/dev/null || touch pkg/console/console.go 2>/dev/null || true + .PHONY: regen-proto regen-proto: $(PROTO_ARTIFACTS) @@ -107,7 +133,7 @@ $(PROTO_ARTIFACTS): $(PROTO_SRCS) buf generate .PHONY: regen-sql -regen-sql: regen-core-sql regen-eth-sql +regen-sql: regen-core-sql regen-eth-sql regen-etl-sql .PHONY: regen-core-sql regen-core-sql: $(CORE_SQL_ARTIFACTS) @@ -123,6 +149,13 @@ $(ETH_SQL_ARTIFACTS): $(ETH_SQL_SRCS) @echo Regenerating eth sql code cd pkg/eth/db && sqlc generate +.PHONY: regen-etl-sql +regen-etl-sql: $(ETL_SQL_ARTIFACTS) + +$(ETL_SQL_ARTIFACTS): $(ETL_SQL_SRCS) + @echo Regenerating etl sql code + cd pkg/etl/db && sqlc generate + .PHONY: regen-contracts regen-contracts: @echo Regenerating contracts diff --git a/cmd/openaudio/main.go b/cmd/openaudio/main.go index d017c848..ad68f845 100644 --- a/cmd/openaudio/main.go +++ b/cmd/openaudio/main.go @@ -28,10 +28,12 @@ import ( "connectrpc.com/connect" "github.com/OpenAudio/go-openaudio/pkg/common" + "github.com/OpenAudio/go-openaudio/pkg/console" "github.com/OpenAudio/go-openaudio/pkg/core" "github.com/OpenAudio/go-openaudio/pkg/core/config" coreServer "github.com/OpenAudio/go-openaudio/pkg/core/server" "github.com/OpenAudio/go-openaudio/pkg/eth" + "github.com/OpenAudio/go-openaudio/pkg/etl" "github.com/OpenAudio/go-openaudio/pkg/lifecycle" aLogger "github.com/OpenAudio/go-openaudio/pkg/logger" "github.com/OpenAudio/go-openaudio/pkg/mediorum" @@ -51,6 +53,7 @@ import ( v1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" corev1connect "github.com/OpenAudio/go-openaudio/pkg/api/core/v1/v1connect" ethv1connect "github.com/OpenAudio/go-openaudio/pkg/api/eth/v1/v1connect" + etlv1connect "github.com/OpenAudio/go-openaudio/pkg/api/etl/v1/v1connect" storagev1connect "github.com/OpenAudio/go-openaudio/pkg/api/storage/v1/v1connect" systemv1connect "github.com/OpenAudio/go-openaudio/pkg/api/system/v1/v1connect" "github.com/ethereum/go-ethereum/crypto" @@ -110,6 +113,12 @@ func main() { setupDelegateKeyPair(rootLogger) + // Read config to check feature flags + cfg, err := config.ReadConfig() + if err != nil { + panic(fmt.Sprintf("failed to read config: %v", err)) + } + ethService := eth.NewEthService(dbUrl, config.GetEthRPC(), config.GetRegistryAddress(), rootLogger, config.GetRuntimeEnvironment()) coreService := coreServer.NewCoreService() storageService := server.NewStorageService() @@ -117,6 +126,29 @@ func main() { if isStorageEnabled() { coreService.SetStorageService(storageService) } + + // Initialize ETL service if enabled + var etlService *etl.ETLService + if cfg.EnableETL { + // Create an HTTP client that will connect to the local service + // The ETL service needs a CoreServiceClient, so we create one that connects via HTTP + httpClient := &http.Client{ + Timeout: 30 * time.Second, + } + // Determine the base URL - we'll use localhost with the port from hostUrl + baseURL := fmt.Sprintf("http://localhost") + if hostUrl.Port() != "" { + baseURL = fmt.Sprintf("http://localhost:%s", hostUrl.Port()) + } else { + // Default to port 80 if no port specified + baseURL = "http://localhost:80" + } + coreClient := corev1connect.NewCoreServiceClient(httpClient, baseURL, connectJSONOpt) + etlService = etl.NewETLService(coreClient, rootLogger) + etlService.SetDBURL(dbUrl) + etlService.SetCheckReadiness(false) // Don't wait for core to be ready when console is enabled + } + systemService := system.NewSystemService(coreService, storageService) services := []struct { @@ -127,7 +159,7 @@ func main() { { "audiusd-echo-server", func() error { - return startEchoProxy(hostUrl, rootLogger, coreService, storageService, systemService, ethService) + return startEchoProxy(hostUrl, rootLogger, coreService, storageService, systemService, ethService, etlService, cfg) }, true, }, @@ -153,6 +185,17 @@ func main() { func() error { return ethService.Run(ctx) }, true, }, + { + "etl", + func() error { + if etlService == nil { + return nil + } + etlService.SetRunDownMigrations(os.Getenv("OPENAUDIO_ETL_RUN_DOWN_MIGRATIONS") == "true") + return etlService.Run() + }, + cfg.EnableETL, + }, } for _, svc := range services { @@ -489,10 +532,24 @@ func setProtobufField(msgReflect protoreflect.Message, field protoreflect.FieldD return nil } -func startEchoProxy(hostUrl *url.URL, logger *zap.Logger, coreService *coreServer.CoreService, storageService *server.StorageService, systemService *system.SystemService, ethService *eth.EthService) error { +func startEchoProxy(hostUrl *url.URL, logger *zap.Logger, coreService *coreServer.CoreService, storageService *server.StorageService, systemService *system.SystemService, ethService *eth.EthService, etlService *etl.ETLService, cfg *config.Config) error { + // Explorer console requires ETL to be enabled + consoleEnabled := cfg.EnableExplorer && cfg.EnableETL && etlService != nil e := echo.New() e.HideBanner = true - e.Use(middleware.Logger(), middleware.Recover(), common.InjectRealIP()) + + // Configure logger to skip internal ETL requests + loggerConfig := middleware.LoggerConfig{ + Skipper: func(c echo.Context) bool { + // Skip logging for internal ETL requests to Core service + path := c.Request().URL.Path + if strings.Contains(path, "/core.v1.CoreService/GetBlock") { + return true + } + return false + }, + } + e.Use(middleware.LoggerWithConfig(loggerConfig), middleware.Recover(), common.InjectRealIP()) rpcGroup := e.Group("") rpcGroup.Use(common.CORS()) @@ -508,6 +565,18 @@ func startEchoProxy(hostUrl *url.URL, logger *zap.Logger, coreService *coreServe ethPath, ethHandler := ethv1connect.NewEthServiceHandler(ethService, connectJSONOpt) rpcGroup.POST(ethPath+"*", echo.WrapHandler(ethHandler)) + // Register ETL routes if enabled + if cfg.EnableETL && etlService != nil { + etlPath, etlHandler := etlv1connect.NewETLServiceHandler(etlService, connectJSONOpt) + rpcGroup.POST(etlPath+"*", echo.WrapHandler(etlHandler)) + } + + // Initialize explorer console if enabled + if consoleEnabled { + c := console.NewConsole(etlService, e, cfg.Environment) + c.Initialize() + } + // register GET routes // core GET routes @@ -544,6 +613,10 @@ func startEchoProxy(hostUrl *url.URL, logger *zap.Logger, coreService *coreServe grpcServerGroup.Any(storagePath+"*", echo.WrapHandler(storageHandler)) grpcServerGroup.Any(systemPath+"*", echo.WrapHandler(systemHandler)) grpcServerGroup.Any(ethPath+"*", echo.WrapHandler(ethHandler)) + if cfg.EnableETL && etlService != nil { + etlPath, etlHandler := etlv1connect.NewETLServiceHandler(etlService, connectJSONOpt) + grpcServerGroup.Any(etlPath+"*", echo.WrapHandler(etlHandler)) + } // Create h2c-compatible server h2cServer := &http.Server{ @@ -559,9 +632,12 @@ func startEchoProxy(hostUrl *url.URL, logger *zap.Logger, coreService *coreServe baseRoutes := e.Group("") baseRoutes.Use(common.CORS()) - baseRoutes.GET("/", func(c echo.Context) error { - return c.JSON(http.StatusOK, map[string]int{"a": 440}) - }) + // Base route collides with console dashboard when explorer is enabled + if !consoleEnabled { + baseRoutes.GET("/", func(c echo.Context) error { + return c.JSON(http.StatusOK, map[string]int{"a": 440}) + }) + } baseRoutes.GET("/console", func(c echo.Context) error { return c.Redirect(http.StatusMovedPermanently, "/console/overview") }) diff --git a/dev/env/openaudio-1.env b/dev/env/openaudio-1.env index 7c51cf05..a1de50fc 100644 --- a/dev/env/openaudio-1.env +++ b/dev/env/openaudio-1.env @@ -8,3 +8,7 @@ externalAddress="openaudio-1:26656" archive=true stateSyncServeSnapshots=true stateSyncEnable=false + +# Enable ETL and Explorer functionality +# OPENAUDIO_ETL_ENABLED=true +# OPENAUDIO_EXPLORER_ENABLED=true diff --git a/docs/developers.md b/docs/developers.md index bf325e87..38308201 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -84,12 +84,6 @@ open https://node1.oap.devnet/console/uptime > By default, hot reloading is only enabled on node1.oap.devnet to conserve system resources. > To enable on other nodes, update the corresponding env file in [dev/env](../dev/env). -**Cleanup** - -```bash -make down -``` - ## Develop against stage or prod Build a local docker image @@ -127,3 +121,23 @@ Run only integration tests ```bash make test-integration ``` + +### ETL + +The ETL service indexes blockchain data into the postgres database, enabling faster queries for certain views. + +```bash +OPENAUDIO_ETL_ENABLED=true +``` + +### Explorer + +The Explorer provides a web-based interface to browse blocks, transactions, validators, and other data. If enabled, the explorer runs at the site root, e.g. https://node1.oap.devnet/. Explorer requires ETL. + +```bash +OPENAUDIO_ETL_ENABLED=true +OPENAUDIO_EXPLORER_ENABLED=true + +# View explorer in browser +open https://node1.oap.devnet/ +``` diff --git a/go.mod b/go.mod index 6a5bf1bb..63b49e4b 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,7 @@ require ( github.com/tus/tusd/v2 v2.8.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.38.0 + modernc.org/sqlite v1.38.0 ) require ( @@ -182,6 +183,7 @@ require ( github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20220708102147-0a8a51822cae // indirect github.com/opencontainers/image-spec v1.1.1 // indirect @@ -199,6 +201,7 @@ require ( github.com/quic-go/quic-go v0.44.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/refraction-networking/utls v1.5.3 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/sasha-s/go-deadlock v0.3.5 // indirect @@ -235,6 +238,9 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.65.10 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index fca9bd18..007de85b 100644 --- a/go.sum +++ b/go.sum @@ -485,6 +485,8 @@ github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2 github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -555,6 +557,8 @@ github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0 github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/refraction-networking/utls v1.5.3 h1:Ds5Ocg1+MC1ahNx5iBEcHe0jHeLaA/fLey61EENm7ro= github.com/refraction-networking/utls v1.5.3/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -862,5 +866,29 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c= lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= +modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s= +modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA= +modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc= +modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI= +modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE= +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= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/pkg/api/etl/v1/service.pb.go b/pkg/api/etl/v1/service.pb.go new file mode 100644 index 00000000..78ecc8b0 --- /dev/null +++ b/pkg/api/etl/v1/service.pb.go @@ -0,0 +1,137 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: etl/v1/service.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +var File_etl_v1_service_proto protoreflect.FileDescriptor + +var file_etl_v1_service_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x65, 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x1a, 0x12, + 0x65, 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0xd6, 0x04, 0x0a, 0x0a, 0x45, 0x54, 0x4c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x33, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x13, 0x2e, 0x65, 0x74, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, + 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x12, 0x18, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, + 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, + 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x18, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x19, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x54, + 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x1e, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1f, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, + 0x12, 0x17, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, + 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x65, 0x74, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x65, 0x74, 0x6c, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x65, + 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x4e, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x73, 0x12, 0x1c, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x56, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x1a, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x65, + 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x32, 0x5a, 0x30, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x75, + 0x64, 0x69, 0x6f, 0x2f, 0x67, 0x6f, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x75, 0x64, 0x69, 0x6f, + 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x65, 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var file_etl_v1_service_proto_goTypes = []interface{}{ + (*PingRequest)(nil), // 0: etl.v1.PingRequest + (*GetHealthRequest)(nil), // 1: etl.v1.GetHealthRequest + (*GetBlocksRequest)(nil), // 2: etl.v1.GetBlocksRequest + (*GetTransactionsRequest)(nil), // 3: etl.v1.GetTransactionsRequest + (*GetPlaysRequest)(nil), // 4: etl.v1.GetPlaysRequest + (*GetManageEntitiesRequest)(nil), // 5: etl.v1.GetManageEntitiesRequest + (*GetValidatorsRequest)(nil), // 6: etl.v1.GetValidatorsRequest + (*GetLocationRequest)(nil), // 7: etl.v1.GetLocationRequest + (*PingResponse)(nil), // 8: etl.v1.PingResponse + (*GetHealthResponse)(nil), // 9: etl.v1.GetHealthResponse + (*GetBlocksResponse)(nil), // 10: etl.v1.GetBlocksResponse + (*GetTransactionsResponse)(nil), // 11: etl.v1.GetTransactionsResponse + (*GetPlaysResponse)(nil), // 12: etl.v1.GetPlaysResponse + (*GetManageEntitiesResponse)(nil), // 13: etl.v1.GetManageEntitiesResponse + (*GetValidatorsResponse)(nil), // 14: etl.v1.GetValidatorsResponse + (*GetLocationResponse)(nil), // 15: etl.v1.GetLocationResponse +} +var file_etl_v1_service_proto_depIdxs = []int32{ + 0, // 0: etl.v1.ETLService.Ping:input_type -> etl.v1.PingRequest + 1, // 1: etl.v1.ETLService.GetHealth:input_type -> etl.v1.GetHealthRequest + 2, // 2: etl.v1.ETLService.GetBlocks:input_type -> etl.v1.GetBlocksRequest + 3, // 3: etl.v1.ETLService.GetTransactions:input_type -> etl.v1.GetTransactionsRequest + 4, // 4: etl.v1.ETLService.GetPlays:input_type -> etl.v1.GetPlaysRequest + 5, // 5: etl.v1.ETLService.GetManageEntities:input_type -> etl.v1.GetManageEntitiesRequest + 6, // 6: etl.v1.ETLService.GetValidators:input_type -> etl.v1.GetValidatorsRequest + 7, // 7: etl.v1.ETLService.GetLocation:input_type -> etl.v1.GetLocationRequest + 8, // 8: etl.v1.ETLService.Ping:output_type -> etl.v1.PingResponse + 9, // 9: etl.v1.ETLService.GetHealth:output_type -> etl.v1.GetHealthResponse + 10, // 10: etl.v1.ETLService.GetBlocks:output_type -> etl.v1.GetBlocksResponse + 11, // 11: etl.v1.ETLService.GetTransactions:output_type -> etl.v1.GetTransactionsResponse + 12, // 12: etl.v1.ETLService.GetPlays:output_type -> etl.v1.GetPlaysResponse + 13, // 13: etl.v1.ETLService.GetManageEntities:output_type -> etl.v1.GetManageEntitiesResponse + 14, // 14: etl.v1.ETLService.GetValidators:output_type -> etl.v1.GetValidatorsResponse + 15, // 15: etl.v1.ETLService.GetLocation:output_type -> etl.v1.GetLocationResponse + 8, // [8:16] is the sub-list for method output_type + 0, // [0:8] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_etl_v1_service_proto_init() } +func file_etl_v1_service_proto_init() { + if File_etl_v1_service_proto != nil { + return + } + file_etl_v1_types_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_etl_v1_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_etl_v1_service_proto_goTypes, + DependencyIndexes: file_etl_v1_service_proto_depIdxs, + }.Build() + File_etl_v1_service_proto = out.File + file_etl_v1_service_proto_rawDesc = nil + file_etl_v1_service_proto_goTypes = nil + file_etl_v1_service_proto_depIdxs = nil +} diff --git a/pkg/api/etl/v1/types.pb.go b/pkg/api/etl/v1/types.pb.go new file mode 100644 index 00000000..3f5b4d03 --- /dev/null +++ b/pkg/api/etl/v1/types.pb.go @@ -0,0 +1,2265 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: etl/v1/types.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PingRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PingRequest) Reset() { + *x = PingRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingRequest) ProtoMessage() {} + +func (x *PingRequest) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. +func (*PingRequest) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{0} +} + +type PingResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *PingResponse) Reset() { + *x = PingResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingResponse) ProtoMessage() {} + +func (x *PingResponse) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. +func (*PingResponse) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{1} +} + +func (x *PingResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type GetHealthRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetHealthRequest) Reset() { + *x = GetHealthRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetHealthRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetHealthRequest) ProtoMessage() {} + +func (x *GetHealthRequest) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetHealthRequest.ProtoReflect.Descriptor instead. +func (*GetHealthRequest) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{2} +} + +type GetHealthResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetHealthResponse) Reset() { + *x = GetHealthResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetHealthResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetHealthResponse) ProtoMessage() {} + +func (x *GetHealthResponse) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetHealthResponse.ProtoReflect.Descriptor instead. +func (*GetHealthResponse) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{3} +} + +type GetBlocksRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetBlocksRequest) Reset() { + *x = GetBlocksRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBlocksRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBlocksRequest) ProtoMessage() {} + +func (x *GetBlocksRequest) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBlocksRequest.ProtoReflect.Descriptor instead. +func (*GetBlocksRequest) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{4} +} + +type GetBlocksResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetBlocksResponse) Reset() { + *x = GetBlocksResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBlocksResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBlocksResponse) ProtoMessage() {} + +func (x *GetBlocksResponse) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBlocksResponse.ProtoReflect.Descriptor instead. +func (*GetBlocksResponse) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{5} +} + +type GetTransactionsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetTransactionsRequest) Reset() { + *x = GetTransactionsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTransactionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTransactionsRequest) ProtoMessage() {} + +func (x *GetTransactionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTransactionsRequest.ProtoReflect.Descriptor instead. +func (*GetTransactionsRequest) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{6} +} + +type GetTransactionsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetTransactionsResponse) Reset() { + *x = GetTransactionsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTransactionsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTransactionsResponse) ProtoMessage() {} + +func (x *GetTransactionsResponse) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTransactionsResponse.ProtoReflect.Descriptor instead. +func (*GetTransactionsResponse) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{7} +} + +type GetPlaysRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Query: + // + // *GetPlaysRequest_GetPlays + // *GetPlaysRequest_GetPlaysByAddress + // *GetPlaysRequest_GetPlaysByUser + // *GetPlaysRequest_GetPlaysByTimeRange + // *GetPlaysRequest_GetPlaysByLocation + Query isGetPlaysRequest_Query `protobuf_oneof:"query"` +} + +func (x *GetPlaysRequest) Reset() { + *x = GetPlaysRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetPlaysRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPlaysRequest) ProtoMessage() {} + +func (x *GetPlaysRequest) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPlaysRequest.ProtoReflect.Descriptor instead. +func (*GetPlaysRequest) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{8} +} + +func (m *GetPlaysRequest) GetQuery() isGetPlaysRequest_Query { + if m != nil { + return m.Query + } + return nil +} + +func (x *GetPlaysRequest) GetGetPlays() *GetPlays { + if x, ok := x.GetQuery().(*GetPlaysRequest_GetPlays); ok { + return x.GetPlays + } + return nil +} + +func (x *GetPlaysRequest) GetGetPlaysByAddress() *GetPlaysByAddress { + if x, ok := x.GetQuery().(*GetPlaysRequest_GetPlaysByAddress); ok { + return x.GetPlaysByAddress + } + return nil +} + +func (x *GetPlaysRequest) GetGetPlaysByUser() *GetPlaysByUser { + if x, ok := x.GetQuery().(*GetPlaysRequest_GetPlaysByUser); ok { + return x.GetPlaysByUser + } + return nil +} + +func (x *GetPlaysRequest) GetGetPlaysByTimeRange() *GetPlaysByTimeRange { + if x, ok := x.GetQuery().(*GetPlaysRequest_GetPlaysByTimeRange); ok { + return x.GetPlaysByTimeRange + } + return nil +} + +func (x *GetPlaysRequest) GetGetPlaysByLocation() *GetPlaysByLocation { + if x, ok := x.GetQuery().(*GetPlaysRequest_GetPlaysByLocation); ok { + return x.GetPlaysByLocation + } + return nil +} + +type isGetPlaysRequest_Query interface { + isGetPlaysRequest_Query() +} + +type GetPlaysRequest_GetPlays struct { + GetPlays *GetPlays `protobuf:"bytes,1,opt,name=get_plays,json=getPlays,proto3,oneof"` +} + +type GetPlaysRequest_GetPlaysByAddress struct { + GetPlaysByAddress *GetPlaysByAddress `protobuf:"bytes,2,opt,name=get_plays_by_address,json=getPlaysByAddress,proto3,oneof"` +} + +type GetPlaysRequest_GetPlaysByUser struct { + GetPlaysByUser *GetPlaysByUser `protobuf:"bytes,3,opt,name=get_plays_by_user,json=getPlaysByUser,proto3,oneof"` +} + +type GetPlaysRequest_GetPlaysByTimeRange struct { + GetPlaysByTimeRange *GetPlaysByTimeRange `protobuf:"bytes,4,opt,name=get_plays_by_time_range,json=getPlaysByTimeRange,proto3,oneof"` +} + +type GetPlaysRequest_GetPlaysByLocation struct { + GetPlaysByLocation *GetPlaysByLocation `protobuf:"bytes,5,opt,name=get_plays_by_location,json=getPlaysByLocation,proto3,oneof"` +} + +func (*GetPlaysRequest_GetPlays) isGetPlaysRequest_Query() {} + +func (*GetPlaysRequest_GetPlaysByAddress) isGetPlaysRequest_Query() {} + +func (*GetPlaysRequest_GetPlaysByUser) isGetPlaysRequest_Query() {} + +func (*GetPlaysRequest_GetPlaysByTimeRange) isGetPlaysRequest_Query() {} + +func (*GetPlaysRequest_GetPlaysByLocation) isGetPlaysRequest_Query() {} + +type GetPlays struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetPlays) Reset() { + *x = GetPlays{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetPlays) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPlays) ProtoMessage() {} + +func (x *GetPlays) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPlays.ProtoReflect.Descriptor instead. +func (*GetPlays) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{9} +} + +type GetPlaysByAddress struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetPlaysByAddress) Reset() { + *x = GetPlaysByAddress{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetPlaysByAddress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPlaysByAddress) ProtoMessage() {} + +func (x *GetPlaysByAddress) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPlaysByAddress.ProtoReflect.Descriptor instead. +func (*GetPlaysByAddress) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{10} +} + +type GetPlaysByUser struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetPlaysByUser) Reset() { + *x = GetPlaysByUser{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetPlaysByUser) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPlaysByUser) ProtoMessage() {} + +func (x *GetPlaysByUser) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPlaysByUser.ProtoReflect.Descriptor instead. +func (*GetPlaysByUser) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{11} +} + +type GetPlaysByTrack struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetPlaysByTrack) Reset() { + *x = GetPlaysByTrack{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetPlaysByTrack) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPlaysByTrack) ProtoMessage() {} + +func (x *GetPlaysByTrack) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPlaysByTrack.ProtoReflect.Descriptor instead. +func (*GetPlaysByTrack) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{12} +} + +type GetPlaysByTimeRange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetPlaysByTimeRange) Reset() { + *x = GetPlaysByTimeRange{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetPlaysByTimeRange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPlaysByTimeRange) ProtoMessage() {} + +func (x *GetPlaysByTimeRange) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPlaysByTimeRange.ProtoReflect.Descriptor instead. +func (*GetPlaysByTimeRange) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{13} +} + +type GetPlaysByLocation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetPlaysByLocation) Reset() { + *x = GetPlaysByLocation{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetPlaysByLocation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPlaysByLocation) ProtoMessage() {} + +func (x *GetPlaysByLocation) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPlaysByLocation.ProtoReflect.Descriptor instead. +func (*GetPlaysByLocation) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{14} +} + +type GetPlaysResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plays []*GetPlayResponse `protobuf:"bytes,1,rep,name=plays,proto3" json:"plays,omitempty"` +} + +func (x *GetPlaysResponse) Reset() { + *x = GetPlaysResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetPlaysResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPlaysResponse) ProtoMessage() {} + +func (x *GetPlaysResponse) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPlaysResponse.ProtoReflect.Descriptor instead. +func (*GetPlaysResponse) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{15} +} + +func (x *GetPlaysResponse) GetPlays() []*GetPlayResponse { + if x != nil { + return x.Plays + } + return nil +} + +type GetPlayResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + TrackId string `protobuf:"bytes,2,opt,name=track_id,json=trackId,proto3" json:"track_id,omitempty"` + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + City string `protobuf:"bytes,4,opt,name=city,proto3" json:"city,omitempty"` + Country string `protobuf:"bytes,5,opt,name=country,proto3" json:"country,omitempty"` + Region string `protobuf:"bytes,6,opt,name=region,proto3" json:"region,omitempty"` + BlockHeight int64 `protobuf:"varint,7,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + TxHash string `protobuf:"bytes,8,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` +} + +func (x *GetPlayResponse) Reset() { + *x = GetPlayResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetPlayResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPlayResponse) ProtoMessage() {} + +func (x *GetPlayResponse) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPlayResponse.ProtoReflect.Descriptor instead. +func (*GetPlayResponse) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{16} +} + +func (x *GetPlayResponse) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *GetPlayResponse) GetTrackId() string { + if x != nil { + return x.TrackId + } + return "" +} + +func (x *GetPlayResponse) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *GetPlayResponse) GetCity() string { + if x != nil { + return x.City + } + return "" +} + +func (x *GetPlayResponse) GetCountry() string { + if x != nil { + return x.Country + } + return "" +} + +func (x *GetPlayResponse) GetRegion() string { + if x != nil { + return x.Region + } + return "" +} + +func (x *GetPlayResponse) GetBlockHeight() int64 { + if x != nil { + return x.BlockHeight + } + return 0 +} + +func (x *GetPlayResponse) GetTxHash() string { + if x != nil { + return x.TxHash + } + return "" +} + +type GetManageEntitiesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetManageEntitiesRequest) Reset() { + *x = GetManageEntitiesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetManageEntitiesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetManageEntitiesRequest) ProtoMessage() {} + +func (x *GetManageEntitiesRequest) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetManageEntitiesRequest.ProtoReflect.Descriptor instead. +func (*GetManageEntitiesRequest) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{17} +} + +type GetManageEntitiesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ManageEntities []*GetManageEntityResponse `protobuf:"bytes,1,rep,name=manage_entities,json=manageEntities,proto3" json:"manage_entities,omitempty"` +} + +func (x *GetManageEntitiesResponse) Reset() { + *x = GetManageEntitiesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetManageEntitiesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetManageEntitiesResponse) ProtoMessage() {} + +func (x *GetManageEntitiesResponse) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetManageEntitiesResponse.ProtoReflect.Descriptor instead. +func (*GetManageEntitiesResponse) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{18} +} + +func (x *GetManageEntitiesResponse) GetManageEntities() []*GetManageEntityResponse { + if x != nil { + return x.ManageEntities + } + return nil +} + +type GetManageEntityResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + EntityType string `protobuf:"bytes,2,opt,name=entity_type,json=entityType,proto3" json:"entity_type,omitempty"` + EntityId int64 `protobuf:"varint,3,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"` + Action string `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"` + Metadata string `protobuf:"bytes,5,opt,name=metadata,proto3" json:"metadata,omitempty"` + Signature string `protobuf:"bytes,6,opt,name=signature,proto3" json:"signature,omitempty"` + Signer string `protobuf:"bytes,7,opt,name=signer,proto3" json:"signer,omitempty"` + Nonce string `protobuf:"bytes,8,opt,name=nonce,proto3" json:"nonce,omitempty"` + Block int64 `protobuf:"varint,9,opt,name=block,proto3" json:"block,omitempty"` + TxHash string `protobuf:"bytes,10,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` +} + +func (x *GetManageEntityResponse) Reset() { + *x = GetManageEntityResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetManageEntityResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetManageEntityResponse) ProtoMessage() {} + +func (x *GetManageEntityResponse) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetManageEntityResponse.ProtoReflect.Descriptor instead. +func (*GetManageEntityResponse) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{19} +} + +func (x *GetManageEntityResponse) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *GetManageEntityResponse) GetEntityType() string { + if x != nil { + return x.EntityType + } + return "" +} + +func (x *GetManageEntityResponse) GetEntityId() int64 { + if x != nil { + return x.EntityId + } + return 0 +} + +func (x *GetManageEntityResponse) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *GetManageEntityResponse) GetMetadata() string { + if x != nil { + return x.Metadata + } + return "" +} + +func (x *GetManageEntityResponse) GetSignature() string { + if x != nil { + return x.Signature + } + return "" +} + +func (x *GetManageEntityResponse) GetSigner() string { + if x != nil { + return x.Signer + } + return "" +} + +func (x *GetManageEntityResponse) GetNonce() string { + if x != nil { + return x.Nonce + } + return "" +} + +func (x *GetManageEntityResponse) GetBlock() int64 { + if x != nil { + return x.Block + } + return 0 +} + +func (x *GetManageEntityResponse) GetTxHash() string { + if x != nil { + return x.TxHash + } + return "" +} + +type GetValidatorsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Query: + // + // *GetValidatorsRequest_GetRegisteredValidators + // *GetValidatorsRequest_GetValidatorRegistrations + // *GetValidatorsRequest_GetValidatorDeregistrations + Query isGetValidatorsRequest_Query `protobuf_oneof:"query"` +} + +func (x *GetValidatorsRequest) Reset() { + *x = GetValidatorsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetValidatorsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetValidatorsRequest) ProtoMessage() {} + +func (x *GetValidatorsRequest) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetValidatorsRequest.ProtoReflect.Descriptor instead. +func (*GetValidatorsRequest) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{20} +} + +func (m *GetValidatorsRequest) GetQuery() isGetValidatorsRequest_Query { + if m != nil { + return m.Query + } + return nil +} + +func (x *GetValidatorsRequest) GetGetRegisteredValidators() *GetRegisteredValidators { + if x, ok := x.GetQuery().(*GetValidatorsRequest_GetRegisteredValidators); ok { + return x.GetRegisteredValidators + } + return nil +} + +func (x *GetValidatorsRequest) GetGetValidatorRegistrations() *GetValidatorRegistrations { + if x, ok := x.GetQuery().(*GetValidatorsRequest_GetValidatorRegistrations); ok { + return x.GetValidatorRegistrations + } + return nil +} + +func (x *GetValidatorsRequest) GetGetValidatorDeregistrations() *GetValidatorDeregistrations { + if x, ok := x.GetQuery().(*GetValidatorsRequest_GetValidatorDeregistrations); ok { + return x.GetValidatorDeregistrations + } + return nil +} + +type isGetValidatorsRequest_Query interface { + isGetValidatorsRequest_Query() +} + +type GetValidatorsRequest_GetRegisteredValidators struct { + GetRegisteredValidators *GetRegisteredValidators `protobuf:"bytes,1,opt,name=get_registered_validators,json=getRegisteredValidators,proto3,oneof"` +} + +type GetValidatorsRequest_GetValidatorRegistrations struct { + GetValidatorRegistrations *GetValidatorRegistrations `protobuf:"bytes,2,opt,name=get_validator_registrations,json=getValidatorRegistrations,proto3,oneof"` +} + +type GetValidatorsRequest_GetValidatorDeregistrations struct { + GetValidatorDeregistrations *GetValidatorDeregistrations `protobuf:"bytes,3,opt,name=get_validator_deregistrations,json=getValidatorDeregistrations,proto3,oneof"` +} + +func (*GetValidatorsRequest_GetRegisteredValidators) isGetValidatorsRequest_Query() {} + +func (*GetValidatorsRequest_GetValidatorRegistrations) isGetValidatorsRequest_Query() {} + +func (*GetValidatorsRequest_GetValidatorDeregistrations) isGetValidatorsRequest_Query() {} + +type GetRegisteredValidators struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetRegisteredValidators) Reset() { + *x = GetRegisteredValidators{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRegisteredValidators) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRegisteredValidators) ProtoMessage() {} + +func (x *GetRegisteredValidators) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRegisteredValidators.ProtoReflect.Descriptor instead. +func (*GetRegisteredValidators) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{21} +} + +type GetValidatorRegistrations struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetValidatorRegistrations) Reset() { + *x = GetValidatorRegistrations{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetValidatorRegistrations) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetValidatorRegistrations) ProtoMessage() {} + +func (x *GetValidatorRegistrations) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetValidatorRegistrations.ProtoReflect.Descriptor instead. +func (*GetValidatorRegistrations) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{22} +} + +type GetValidatorDeregistrations struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetValidatorDeregistrations) Reset() { + *x = GetValidatorDeregistrations{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetValidatorDeregistrations) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetValidatorDeregistrations) ProtoMessage() {} + +func (x *GetValidatorDeregistrations) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetValidatorDeregistrations.ProtoReflect.Descriptor instead. +func (*GetValidatorDeregistrations) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{23} +} + +type GetValidatorsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetValidatorsResponse) Reset() { + *x = GetValidatorsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetValidatorsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetValidatorsResponse) ProtoMessage() {} + +func (x *GetValidatorsResponse) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetValidatorsResponse.ProtoReflect.Descriptor instead. +func (*GetValidatorsResponse) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{24} +} + +type GetValidatorResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + ValidatorAddress string `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` + BlockHeight int64 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + TxHash string `protobuf:"bytes,4,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (x *GetValidatorResponse) Reset() { + *x = GetValidatorResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetValidatorResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetValidatorResponse) ProtoMessage() {} + +func (x *GetValidatorResponse) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetValidatorResponse.ProtoReflect.Descriptor instead. +func (*GetValidatorResponse) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{25} +} + +func (x *GetValidatorResponse) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *GetValidatorResponse) GetValidatorAddress() string { + if x != nil { + return x.ValidatorAddress + } + return "" +} + +func (x *GetValidatorResponse) GetBlockHeight() int64 { + if x != nil { + return x.BlockHeight + } + return 0 +} + +func (x *GetValidatorResponse) GetTxHash() string { + if x != nil { + return x.TxHash + } + return "" +} + +func (x *GetValidatorResponse) GetTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +type GetLocationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Query: + // + // *GetLocationRequest_GetAvailableCities + // *GetLocationRequest_GetAvailableRegions + // *GetLocationRequest_GetAvailableCountries + Query isGetLocationRequest_Query `protobuf_oneof:"query"` +} + +func (x *GetLocationRequest) Reset() { + *x = GetLocationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetLocationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetLocationRequest) ProtoMessage() {} + +func (x *GetLocationRequest) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetLocationRequest.ProtoReflect.Descriptor instead. +func (*GetLocationRequest) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{26} +} + +func (m *GetLocationRequest) GetQuery() isGetLocationRequest_Query { + if m != nil { + return m.Query + } + return nil +} + +func (x *GetLocationRequest) GetGetAvailableCities() *GetAvailableCities { + if x, ok := x.GetQuery().(*GetLocationRequest_GetAvailableCities); ok { + return x.GetAvailableCities + } + return nil +} + +func (x *GetLocationRequest) GetGetAvailableRegions() *GetAvailableRegions { + if x, ok := x.GetQuery().(*GetLocationRequest_GetAvailableRegions); ok { + return x.GetAvailableRegions + } + return nil +} + +func (x *GetLocationRequest) GetGetAvailableCountries() *GetAvailableCountries { + if x, ok := x.GetQuery().(*GetLocationRequest_GetAvailableCountries); ok { + return x.GetAvailableCountries + } + return nil +} + +type isGetLocationRequest_Query interface { + isGetLocationRequest_Query() +} + +type GetLocationRequest_GetAvailableCities struct { + GetAvailableCities *GetAvailableCities `protobuf:"bytes,1,opt,name=get_available_cities,json=getAvailableCities,proto3,oneof"` +} + +type GetLocationRequest_GetAvailableRegions struct { + GetAvailableRegions *GetAvailableRegions `protobuf:"bytes,2,opt,name=get_available_regions,json=getAvailableRegions,proto3,oneof"` +} + +type GetLocationRequest_GetAvailableCountries struct { + GetAvailableCountries *GetAvailableCountries `protobuf:"bytes,3,opt,name=get_available_countries,json=getAvailableCountries,proto3,oneof"` +} + +func (*GetLocationRequest_GetAvailableCities) isGetLocationRequest_Query() {} + +func (*GetLocationRequest_GetAvailableRegions) isGetLocationRequest_Query() {} + +func (*GetLocationRequest_GetAvailableCountries) isGetLocationRequest_Query() {} + +type GetAvailableCities struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetAvailableCities) Reset() { + *x = GetAvailableCities{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetAvailableCities) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAvailableCities) ProtoMessage() {} + +func (x *GetAvailableCities) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAvailableCities.ProtoReflect.Descriptor instead. +func (*GetAvailableCities) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{27} +} + +type GetAvailableRegions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetAvailableRegions) Reset() { + *x = GetAvailableRegions{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetAvailableRegions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAvailableRegions) ProtoMessage() {} + +func (x *GetAvailableRegions) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAvailableRegions.ProtoReflect.Descriptor instead. +func (*GetAvailableRegions) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{28} +} + +type GetAvailableCountries struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetAvailableCountries) Reset() { + *x = GetAvailableCountries{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetAvailableCountries) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAvailableCountries) ProtoMessage() {} + +func (x *GetAvailableCountries) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAvailableCountries.ProtoReflect.Descriptor instead. +func (*GetAvailableCountries) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{29} +} + +type GetLocationResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetLocationResponse) Reset() { + *x = GetLocationResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_etl_v1_types_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetLocationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetLocationResponse) ProtoMessage() {} + +func (x *GetLocationResponse) ProtoReflect() protoreflect.Message { + mi := &file_etl_v1_types_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetLocationResponse.ProtoReflect.Descriptor instead. +func (*GetLocationResponse) Descriptor() ([]byte, []int) { + return file_etl_v1_types_proto_rawDescGZIP(), []int{30} +} + +var File_etl_v1_types_proto protoreflect.FileDescriptor + +var file_etl_v1_types_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x65, 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 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, 0x0d, 0x0a, + 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x28, 0x0a, 0x0c, + 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x13, 0x0a, 0x11, 0x47, 0x65, + 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0x13, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x84, 0x03, + 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2f, 0x0a, 0x09, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x6c, 0x61, 0x79, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x48, 0x00, 0x52, 0x08, 0x67, 0x65, 0x74, 0x50, 0x6c, 0x61, + 0x79, 0x73, 0x12, 0x4c, 0x0a, 0x14, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x6c, 0x61, 0x79, 0x73, 0x5f, + 0x62, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, + 0x79, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x00, 0x52, 0x11, 0x67, + 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x12, 0x43, 0x0a, 0x11, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x6c, 0x61, 0x79, 0x73, 0x5f, 0x62, 0x79, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x65, 0x74, + 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x42, 0x79, 0x55, + 0x73, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0e, 0x67, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x42, + 0x79, 0x55, 0x73, 0x65, 0x72, 0x12, 0x53, 0x0a, 0x17, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x6c, 0x61, + 0x79, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x42, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, + 0x6e, 0x67, 0x65, 0x48, 0x00, 0x52, 0x13, 0x67, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x42, + 0x79, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x4f, 0x0a, 0x15, 0x67, 0x65, + 0x74, 0x5f, 0x70, 0x6c, 0x61, 0x79, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x65, 0x74, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x42, 0x79, 0x4c, 0x6f, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x12, 0x67, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, + 0x73, 0x42, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x22, 0x0a, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, + 0x22, 0x13, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x42, 0x79, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, + 0x73, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x22, 0x11, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x50, 0x6c, + 0x61, 0x79, 0x73, 0x42, 0x79, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x22, 0x15, 0x0a, 0x13, 0x47, 0x65, + 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x42, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x22, 0x14, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x42, 0x79, 0x4c, + 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x41, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x50, 0x6c, + 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x70, + 0x6c, 0x61, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x65, 0x74, 0x6c, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x79, 0x73, 0x22, 0xe6, 0x01, 0x0a, 0x0f, 0x47, + 0x65, 0x74, 0x50, 0x6c, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, + 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, + 0x6b, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x63, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, + 0x61, 0x73, 0x68, 0x22, 0x1a, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x65, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0f, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0xa0, 0x02, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1c, + 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x22, 0xce, 0x02, 0x0a, 0x14, 0x47, 0x65, + 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x19, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x48, 0x00, 0x52, 0x17, 0x67, 0x65, 0x74, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, + 0x73, 0x12, 0x63, 0x0a, 0x1b, 0x67, 0x65, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x19, 0x67, 0x65, 0x74, + 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x69, 0x0a, 0x1d, 0x67, 0x65, 0x74, 0x5f, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, + 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x6f, 0x72, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x48, 0x00, 0x52, 0x1b, 0x67, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x6f, 0x72, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x42, 0x07, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x1b, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x22, 0x1d, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x6f, 0x72, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x22, 0x17, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xd3, 0x01, 0x0a, 0x14, 0x47, + 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, + 0x11, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x17, 0x0a, + 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x05, 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, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x22, 0x99, 0x02, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4e, 0x0a, 0x14, 0x67, 0x65, 0x74, 0x5f, 0x61, + 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x69, 0x74, 0x69, 0x65, + 0x73, 0x48, 0x00, 0x52, 0x12, 0x67, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, + 0x65, 0x43, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x51, 0x0a, 0x15, 0x67, 0x65, 0x74, 0x5f, 0x61, + 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x13, 0x67, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, + 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x57, 0x0a, 0x17, 0x67, 0x65, + 0x74, 0x5f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x65, 0x74, + 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, + 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x48, 0x00, 0x52, 0x15, 0x67, 0x65, + 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, + 0x69, 0x65, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x14, 0x0a, 0x12, + 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x69, 0x74, 0x69, + 0x65, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, + 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x17, 0x0a, 0x15, 0x47, 0x65, 0x74, + 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, + 0x65, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x75, 0x64, 0x69, + 0x6f, 0x2f, 0x67, 0x6f, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x2f, 0x70, + 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x65, 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_etl_v1_types_proto_rawDescOnce sync.Once + file_etl_v1_types_proto_rawDescData = file_etl_v1_types_proto_rawDesc +) + +func file_etl_v1_types_proto_rawDescGZIP() []byte { + file_etl_v1_types_proto_rawDescOnce.Do(func() { + file_etl_v1_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_etl_v1_types_proto_rawDescData) + }) + return file_etl_v1_types_proto_rawDescData +} + +var file_etl_v1_types_proto_msgTypes = make([]protoimpl.MessageInfo, 31) +var file_etl_v1_types_proto_goTypes = []interface{}{ + (*PingRequest)(nil), // 0: etl.v1.PingRequest + (*PingResponse)(nil), // 1: etl.v1.PingResponse + (*GetHealthRequest)(nil), // 2: etl.v1.GetHealthRequest + (*GetHealthResponse)(nil), // 3: etl.v1.GetHealthResponse + (*GetBlocksRequest)(nil), // 4: etl.v1.GetBlocksRequest + (*GetBlocksResponse)(nil), // 5: etl.v1.GetBlocksResponse + (*GetTransactionsRequest)(nil), // 6: etl.v1.GetTransactionsRequest + (*GetTransactionsResponse)(nil), // 7: etl.v1.GetTransactionsResponse + (*GetPlaysRequest)(nil), // 8: etl.v1.GetPlaysRequest + (*GetPlays)(nil), // 9: etl.v1.GetPlays + (*GetPlaysByAddress)(nil), // 10: etl.v1.GetPlaysByAddress + (*GetPlaysByUser)(nil), // 11: etl.v1.GetPlaysByUser + (*GetPlaysByTrack)(nil), // 12: etl.v1.GetPlaysByTrack + (*GetPlaysByTimeRange)(nil), // 13: etl.v1.GetPlaysByTimeRange + (*GetPlaysByLocation)(nil), // 14: etl.v1.GetPlaysByLocation + (*GetPlaysResponse)(nil), // 15: etl.v1.GetPlaysResponse + (*GetPlayResponse)(nil), // 16: etl.v1.GetPlayResponse + (*GetManageEntitiesRequest)(nil), // 17: etl.v1.GetManageEntitiesRequest + (*GetManageEntitiesResponse)(nil), // 18: etl.v1.GetManageEntitiesResponse + (*GetManageEntityResponse)(nil), // 19: etl.v1.GetManageEntityResponse + (*GetValidatorsRequest)(nil), // 20: etl.v1.GetValidatorsRequest + (*GetRegisteredValidators)(nil), // 21: etl.v1.GetRegisteredValidators + (*GetValidatorRegistrations)(nil), // 22: etl.v1.GetValidatorRegistrations + (*GetValidatorDeregistrations)(nil), // 23: etl.v1.GetValidatorDeregistrations + (*GetValidatorsResponse)(nil), // 24: etl.v1.GetValidatorsResponse + (*GetValidatorResponse)(nil), // 25: etl.v1.GetValidatorResponse + (*GetLocationRequest)(nil), // 26: etl.v1.GetLocationRequest + (*GetAvailableCities)(nil), // 27: etl.v1.GetAvailableCities + (*GetAvailableRegions)(nil), // 28: etl.v1.GetAvailableRegions + (*GetAvailableCountries)(nil), // 29: etl.v1.GetAvailableCountries + (*GetLocationResponse)(nil), // 30: etl.v1.GetLocationResponse + (*timestamppb.Timestamp)(nil), // 31: google.protobuf.Timestamp +} +var file_etl_v1_types_proto_depIdxs = []int32{ + 9, // 0: etl.v1.GetPlaysRequest.get_plays:type_name -> etl.v1.GetPlays + 10, // 1: etl.v1.GetPlaysRequest.get_plays_by_address:type_name -> etl.v1.GetPlaysByAddress + 11, // 2: etl.v1.GetPlaysRequest.get_plays_by_user:type_name -> etl.v1.GetPlaysByUser + 13, // 3: etl.v1.GetPlaysRequest.get_plays_by_time_range:type_name -> etl.v1.GetPlaysByTimeRange + 14, // 4: etl.v1.GetPlaysRequest.get_plays_by_location:type_name -> etl.v1.GetPlaysByLocation + 16, // 5: etl.v1.GetPlaysResponse.plays:type_name -> etl.v1.GetPlayResponse + 19, // 6: etl.v1.GetManageEntitiesResponse.manage_entities:type_name -> etl.v1.GetManageEntityResponse + 21, // 7: etl.v1.GetValidatorsRequest.get_registered_validators:type_name -> etl.v1.GetRegisteredValidators + 22, // 8: etl.v1.GetValidatorsRequest.get_validator_registrations:type_name -> etl.v1.GetValidatorRegistrations + 23, // 9: etl.v1.GetValidatorsRequest.get_validator_deregistrations:type_name -> etl.v1.GetValidatorDeregistrations + 31, // 10: etl.v1.GetValidatorResponse.timestamp:type_name -> google.protobuf.Timestamp + 27, // 11: etl.v1.GetLocationRequest.get_available_cities:type_name -> etl.v1.GetAvailableCities + 28, // 12: etl.v1.GetLocationRequest.get_available_regions:type_name -> etl.v1.GetAvailableRegions + 29, // 13: etl.v1.GetLocationRequest.get_available_countries:type_name -> etl.v1.GetAvailableCountries + 14, // [14:14] is the sub-list for method output_type + 14, // [14:14] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name +} + +func init() { file_etl_v1_types_proto_init() } +func file_etl_v1_types_proto_init() { + if File_etl_v1_types_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_etl_v1_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetHealthRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetHealthResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlocksRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlocksResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTransactionsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTransactionsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetPlaysRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetPlays); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetPlaysByAddress); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetPlaysByUser); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetPlaysByTrack); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetPlaysByTimeRange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetPlaysByLocation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetPlaysResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetPlayResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetManageEntitiesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetManageEntitiesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetManageEntityResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetValidatorsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRegisteredValidators); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetValidatorRegistrations); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetValidatorDeregistrations); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetValidatorsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetValidatorResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetLocationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetAvailableCities); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetAvailableRegions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetAvailableCountries); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_etl_v1_types_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetLocationResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_etl_v1_types_proto_msgTypes[8].OneofWrappers = []interface{}{ + (*GetPlaysRequest_GetPlays)(nil), + (*GetPlaysRequest_GetPlaysByAddress)(nil), + (*GetPlaysRequest_GetPlaysByUser)(nil), + (*GetPlaysRequest_GetPlaysByTimeRange)(nil), + (*GetPlaysRequest_GetPlaysByLocation)(nil), + } + file_etl_v1_types_proto_msgTypes[20].OneofWrappers = []interface{}{ + (*GetValidatorsRequest_GetRegisteredValidators)(nil), + (*GetValidatorsRequest_GetValidatorRegistrations)(nil), + (*GetValidatorsRequest_GetValidatorDeregistrations)(nil), + } + file_etl_v1_types_proto_msgTypes[26].OneofWrappers = []interface{}{ + (*GetLocationRequest_GetAvailableCities)(nil), + (*GetLocationRequest_GetAvailableRegions)(nil), + (*GetLocationRequest_GetAvailableCountries)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_etl_v1_types_proto_rawDesc, + NumEnums: 0, + NumMessages: 31, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_etl_v1_types_proto_goTypes, + DependencyIndexes: file_etl_v1_types_proto_depIdxs, + MessageInfos: file_etl_v1_types_proto_msgTypes, + }.Build() + File_etl_v1_types_proto = out.File + file_etl_v1_types_proto_rawDesc = nil + file_etl_v1_types_proto_goTypes = nil + file_etl_v1_types_proto_depIdxs = nil +} diff --git a/pkg/api/etl/v1/v1connect/service.connect.go b/pkg/api/etl/v1/v1connect/service.connect.go new file mode 100644 index 00000000..484b9381 --- /dev/null +++ b/pkg/api/etl/v1/v1connect/service.connect.go @@ -0,0 +1,307 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: etl/v1/service.proto + +package v1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1 "github.com/OpenAudio/go-openaudio/pkg/api/etl/v1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // ETLServiceName is the fully-qualified name of the ETLService service. + ETLServiceName = "etl.v1.ETLService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // ETLServicePingProcedure is the fully-qualified name of the ETLService's Ping RPC. + ETLServicePingProcedure = "/etl.v1.ETLService/Ping" + // ETLServiceGetHealthProcedure is the fully-qualified name of the ETLService's GetHealth RPC. + ETLServiceGetHealthProcedure = "/etl.v1.ETLService/GetHealth" + // ETLServiceGetBlocksProcedure is the fully-qualified name of the ETLService's GetBlocks RPC. + ETLServiceGetBlocksProcedure = "/etl.v1.ETLService/GetBlocks" + // ETLServiceGetTransactionsProcedure is the fully-qualified name of the ETLService's + // GetTransactions RPC. + ETLServiceGetTransactionsProcedure = "/etl.v1.ETLService/GetTransactions" + // ETLServiceGetPlaysProcedure is the fully-qualified name of the ETLService's GetPlays RPC. + ETLServiceGetPlaysProcedure = "/etl.v1.ETLService/GetPlays" + // ETLServiceGetManageEntitiesProcedure is the fully-qualified name of the ETLService's + // GetManageEntities RPC. + ETLServiceGetManageEntitiesProcedure = "/etl.v1.ETLService/GetManageEntities" + // ETLServiceGetValidatorsProcedure is the fully-qualified name of the ETLService's GetValidators + // RPC. + ETLServiceGetValidatorsProcedure = "/etl.v1.ETLService/GetValidators" + // ETLServiceGetLocationProcedure is the fully-qualified name of the ETLService's GetLocation RPC. + ETLServiceGetLocationProcedure = "/etl.v1.ETLService/GetLocation" +) + +// ETLServiceClient is a client for the etl.v1.ETLService service. +type ETLServiceClient interface { + Ping(context.Context, *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) + GetHealth(context.Context, *connect.Request[v1.GetHealthRequest]) (*connect.Response[v1.GetHealthResponse], error) + GetBlocks(context.Context, *connect.Request[v1.GetBlocksRequest]) (*connect.Response[v1.GetBlocksResponse], error) + GetTransactions(context.Context, *connect.Request[v1.GetTransactionsRequest]) (*connect.Response[v1.GetTransactionsResponse], error) + GetPlays(context.Context, *connect.Request[v1.GetPlaysRequest]) (*connect.Response[v1.GetPlaysResponse], error) + GetManageEntities(context.Context, *connect.Request[v1.GetManageEntitiesRequest]) (*connect.Response[v1.GetManageEntitiesResponse], error) + GetValidators(context.Context, *connect.Request[v1.GetValidatorsRequest]) (*connect.Response[v1.GetValidatorsResponse], error) + GetLocation(context.Context, *connect.Request[v1.GetLocationRequest]) (*connect.Response[v1.GetLocationResponse], error) +} + +// NewETLServiceClient constructs a client for the etl.v1.ETLService service. By default, it uses +// the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends +// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or +// connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewETLServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) ETLServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + eTLServiceMethods := v1.File_etl_v1_service_proto.Services().ByName("ETLService").Methods() + return &eTLServiceClient{ + ping: connect.NewClient[v1.PingRequest, v1.PingResponse]( + httpClient, + baseURL+ETLServicePingProcedure, + connect.WithSchema(eTLServiceMethods.ByName("Ping")), + connect.WithClientOptions(opts...), + ), + getHealth: connect.NewClient[v1.GetHealthRequest, v1.GetHealthResponse]( + httpClient, + baseURL+ETLServiceGetHealthProcedure, + connect.WithSchema(eTLServiceMethods.ByName("GetHealth")), + connect.WithClientOptions(opts...), + ), + getBlocks: connect.NewClient[v1.GetBlocksRequest, v1.GetBlocksResponse]( + httpClient, + baseURL+ETLServiceGetBlocksProcedure, + connect.WithSchema(eTLServiceMethods.ByName("GetBlocks")), + connect.WithClientOptions(opts...), + ), + getTransactions: connect.NewClient[v1.GetTransactionsRequest, v1.GetTransactionsResponse]( + httpClient, + baseURL+ETLServiceGetTransactionsProcedure, + connect.WithSchema(eTLServiceMethods.ByName("GetTransactions")), + connect.WithClientOptions(opts...), + ), + getPlays: connect.NewClient[v1.GetPlaysRequest, v1.GetPlaysResponse]( + httpClient, + baseURL+ETLServiceGetPlaysProcedure, + connect.WithSchema(eTLServiceMethods.ByName("GetPlays")), + connect.WithClientOptions(opts...), + ), + getManageEntities: connect.NewClient[v1.GetManageEntitiesRequest, v1.GetManageEntitiesResponse]( + httpClient, + baseURL+ETLServiceGetManageEntitiesProcedure, + connect.WithSchema(eTLServiceMethods.ByName("GetManageEntities")), + connect.WithClientOptions(opts...), + ), + getValidators: connect.NewClient[v1.GetValidatorsRequest, v1.GetValidatorsResponse]( + httpClient, + baseURL+ETLServiceGetValidatorsProcedure, + connect.WithSchema(eTLServiceMethods.ByName("GetValidators")), + connect.WithClientOptions(opts...), + ), + getLocation: connect.NewClient[v1.GetLocationRequest, v1.GetLocationResponse]( + httpClient, + baseURL+ETLServiceGetLocationProcedure, + connect.WithSchema(eTLServiceMethods.ByName("GetLocation")), + connect.WithClientOptions(opts...), + ), + } +} + +// eTLServiceClient implements ETLServiceClient. +type eTLServiceClient struct { + ping *connect.Client[v1.PingRequest, v1.PingResponse] + getHealth *connect.Client[v1.GetHealthRequest, v1.GetHealthResponse] + getBlocks *connect.Client[v1.GetBlocksRequest, v1.GetBlocksResponse] + getTransactions *connect.Client[v1.GetTransactionsRequest, v1.GetTransactionsResponse] + getPlays *connect.Client[v1.GetPlaysRequest, v1.GetPlaysResponse] + getManageEntities *connect.Client[v1.GetManageEntitiesRequest, v1.GetManageEntitiesResponse] + getValidators *connect.Client[v1.GetValidatorsRequest, v1.GetValidatorsResponse] + getLocation *connect.Client[v1.GetLocationRequest, v1.GetLocationResponse] +} + +// Ping calls etl.v1.ETLService.Ping. +func (c *eTLServiceClient) Ping(ctx context.Context, req *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) { + return c.ping.CallUnary(ctx, req) +} + +// GetHealth calls etl.v1.ETLService.GetHealth. +func (c *eTLServiceClient) GetHealth(ctx context.Context, req *connect.Request[v1.GetHealthRequest]) (*connect.Response[v1.GetHealthResponse], error) { + return c.getHealth.CallUnary(ctx, req) +} + +// GetBlocks calls etl.v1.ETLService.GetBlocks. +func (c *eTLServiceClient) GetBlocks(ctx context.Context, req *connect.Request[v1.GetBlocksRequest]) (*connect.Response[v1.GetBlocksResponse], error) { + return c.getBlocks.CallUnary(ctx, req) +} + +// GetTransactions calls etl.v1.ETLService.GetTransactions. +func (c *eTLServiceClient) GetTransactions(ctx context.Context, req *connect.Request[v1.GetTransactionsRequest]) (*connect.Response[v1.GetTransactionsResponse], error) { + return c.getTransactions.CallUnary(ctx, req) +} + +// GetPlays calls etl.v1.ETLService.GetPlays. +func (c *eTLServiceClient) GetPlays(ctx context.Context, req *connect.Request[v1.GetPlaysRequest]) (*connect.Response[v1.GetPlaysResponse], error) { + return c.getPlays.CallUnary(ctx, req) +} + +// GetManageEntities calls etl.v1.ETLService.GetManageEntities. +func (c *eTLServiceClient) GetManageEntities(ctx context.Context, req *connect.Request[v1.GetManageEntitiesRequest]) (*connect.Response[v1.GetManageEntitiesResponse], error) { + return c.getManageEntities.CallUnary(ctx, req) +} + +// GetValidators calls etl.v1.ETLService.GetValidators. +func (c *eTLServiceClient) GetValidators(ctx context.Context, req *connect.Request[v1.GetValidatorsRequest]) (*connect.Response[v1.GetValidatorsResponse], error) { + return c.getValidators.CallUnary(ctx, req) +} + +// GetLocation calls etl.v1.ETLService.GetLocation. +func (c *eTLServiceClient) GetLocation(ctx context.Context, req *connect.Request[v1.GetLocationRequest]) (*connect.Response[v1.GetLocationResponse], error) { + return c.getLocation.CallUnary(ctx, req) +} + +// ETLServiceHandler is an implementation of the etl.v1.ETLService service. +type ETLServiceHandler interface { + Ping(context.Context, *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) + GetHealth(context.Context, *connect.Request[v1.GetHealthRequest]) (*connect.Response[v1.GetHealthResponse], error) + GetBlocks(context.Context, *connect.Request[v1.GetBlocksRequest]) (*connect.Response[v1.GetBlocksResponse], error) + GetTransactions(context.Context, *connect.Request[v1.GetTransactionsRequest]) (*connect.Response[v1.GetTransactionsResponse], error) + GetPlays(context.Context, *connect.Request[v1.GetPlaysRequest]) (*connect.Response[v1.GetPlaysResponse], error) + GetManageEntities(context.Context, *connect.Request[v1.GetManageEntitiesRequest]) (*connect.Response[v1.GetManageEntitiesResponse], error) + GetValidators(context.Context, *connect.Request[v1.GetValidatorsRequest]) (*connect.Response[v1.GetValidatorsResponse], error) + GetLocation(context.Context, *connect.Request[v1.GetLocationRequest]) (*connect.Response[v1.GetLocationResponse], error) +} + +// NewETLServiceHandler builds an HTTP handler from the service implementation. It returns the path +// on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewETLServiceHandler(svc ETLServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + eTLServiceMethods := v1.File_etl_v1_service_proto.Services().ByName("ETLService").Methods() + eTLServicePingHandler := connect.NewUnaryHandler( + ETLServicePingProcedure, + svc.Ping, + connect.WithSchema(eTLServiceMethods.ByName("Ping")), + connect.WithHandlerOptions(opts...), + ) + eTLServiceGetHealthHandler := connect.NewUnaryHandler( + ETLServiceGetHealthProcedure, + svc.GetHealth, + connect.WithSchema(eTLServiceMethods.ByName("GetHealth")), + connect.WithHandlerOptions(opts...), + ) + eTLServiceGetBlocksHandler := connect.NewUnaryHandler( + ETLServiceGetBlocksProcedure, + svc.GetBlocks, + connect.WithSchema(eTLServiceMethods.ByName("GetBlocks")), + connect.WithHandlerOptions(opts...), + ) + eTLServiceGetTransactionsHandler := connect.NewUnaryHandler( + ETLServiceGetTransactionsProcedure, + svc.GetTransactions, + connect.WithSchema(eTLServiceMethods.ByName("GetTransactions")), + connect.WithHandlerOptions(opts...), + ) + eTLServiceGetPlaysHandler := connect.NewUnaryHandler( + ETLServiceGetPlaysProcedure, + svc.GetPlays, + connect.WithSchema(eTLServiceMethods.ByName("GetPlays")), + connect.WithHandlerOptions(opts...), + ) + eTLServiceGetManageEntitiesHandler := connect.NewUnaryHandler( + ETLServiceGetManageEntitiesProcedure, + svc.GetManageEntities, + connect.WithSchema(eTLServiceMethods.ByName("GetManageEntities")), + connect.WithHandlerOptions(opts...), + ) + eTLServiceGetValidatorsHandler := connect.NewUnaryHandler( + ETLServiceGetValidatorsProcedure, + svc.GetValidators, + connect.WithSchema(eTLServiceMethods.ByName("GetValidators")), + connect.WithHandlerOptions(opts...), + ) + eTLServiceGetLocationHandler := connect.NewUnaryHandler( + ETLServiceGetLocationProcedure, + svc.GetLocation, + connect.WithSchema(eTLServiceMethods.ByName("GetLocation")), + connect.WithHandlerOptions(opts...), + ) + return "/etl.v1.ETLService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case ETLServicePingProcedure: + eTLServicePingHandler.ServeHTTP(w, r) + case ETLServiceGetHealthProcedure: + eTLServiceGetHealthHandler.ServeHTTP(w, r) + case ETLServiceGetBlocksProcedure: + eTLServiceGetBlocksHandler.ServeHTTP(w, r) + case ETLServiceGetTransactionsProcedure: + eTLServiceGetTransactionsHandler.ServeHTTP(w, r) + case ETLServiceGetPlaysProcedure: + eTLServiceGetPlaysHandler.ServeHTTP(w, r) + case ETLServiceGetManageEntitiesProcedure: + eTLServiceGetManageEntitiesHandler.ServeHTTP(w, r) + case ETLServiceGetValidatorsProcedure: + eTLServiceGetValidatorsHandler.ServeHTTP(w, r) + case ETLServiceGetLocationProcedure: + eTLServiceGetLocationHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedETLServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedETLServiceHandler struct{} + +func (UnimplementedETLServiceHandler) Ping(context.Context, *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("etl.v1.ETLService.Ping is not implemented")) +} + +func (UnimplementedETLServiceHandler) GetHealth(context.Context, *connect.Request[v1.GetHealthRequest]) (*connect.Response[v1.GetHealthResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("etl.v1.ETLService.GetHealth is not implemented")) +} + +func (UnimplementedETLServiceHandler) GetBlocks(context.Context, *connect.Request[v1.GetBlocksRequest]) (*connect.Response[v1.GetBlocksResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("etl.v1.ETLService.GetBlocks is not implemented")) +} + +func (UnimplementedETLServiceHandler) GetTransactions(context.Context, *connect.Request[v1.GetTransactionsRequest]) (*connect.Response[v1.GetTransactionsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("etl.v1.ETLService.GetTransactions is not implemented")) +} + +func (UnimplementedETLServiceHandler) GetPlays(context.Context, *connect.Request[v1.GetPlaysRequest]) (*connect.Response[v1.GetPlaysResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("etl.v1.ETLService.GetPlays is not implemented")) +} + +func (UnimplementedETLServiceHandler) GetManageEntities(context.Context, *connect.Request[v1.GetManageEntitiesRequest]) (*connect.Response[v1.GetManageEntitiesResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("etl.v1.ETLService.GetManageEntities is not implemented")) +} + +func (UnimplementedETLServiceHandler) GetValidators(context.Context, *connect.Request[v1.GetValidatorsRequest]) (*connect.Response[v1.GetValidatorsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("etl.v1.ETLService.GetValidators is not implemented")) +} + +func (UnimplementedETLServiceHandler) GetLocation(context.Context, *connect.Request[v1.GetLocationRequest]) (*connect.Response[v1.GetLocationResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("etl.v1.ETLService.GetLocation is not implemented")) +} diff --git a/pkg/console/assets/css/output.css b/pkg/console/assets/css/output.css new file mode 100644 index 00000000..de1d02ce --- /dev/null +++ b/pkg/console/assets/css/output.css @@ -0,0 +1,2019 @@ +/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ +@layer properties; +@layer theme, base, components, utilities; +@layer theme { + :root, :host { + --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + --color-red-100: oklch(93.6% 0.032 17.717); + --color-red-200: oklch(88.5% 0.062 18.334); + --color-red-400: oklch(70.4% 0.191 22.216); + --color-red-500: oklch(63.7% 0.237 25.331); + --color-red-600: oklch(57.7% 0.245 27.325); + --color-red-800: oklch(44.4% 0.177 26.899); + --color-red-900: oklch(39.6% 0.141 25.723); + --color-orange-100: oklch(95.4% 0.038 75.164); + --color-orange-200: oklch(90.1% 0.076 70.697); + --color-orange-800: oklch(47% 0.157 37.304); + --color-orange-900: oklch(40.8% 0.123 38.172); + --color-amber-400: oklch(82.8% 0.189 84.429); + --color-amber-600: oklch(66.6% 0.179 58.318); + --color-yellow-400: oklch(85.2% 0.199 91.936); + --color-yellow-500: oklch(79.5% 0.184 86.047); + --color-yellow-600: oklch(68.1% 0.162 75.834); + --color-green-100: oklch(96.2% 0.044 156.743); + --color-green-200: oklch(92.5% 0.084 155.995); + --color-green-400: oklch(79.2% 0.209 151.711); + --color-green-500: oklch(72.3% 0.219 149.579); + --color-green-600: oklch(62.7% 0.194 149.214); + --color-green-800: oklch(44.8% 0.119 151.328); + --color-green-900: oklch(39.3% 0.095 152.535); + --color-blue-100: oklch(93.2% 0.032 255.585); + --color-blue-300: oklch(80.9% 0.105 251.813); + --color-blue-400: oklch(70.7% 0.165 254.624); + --color-blue-600: oklch(54.6% 0.245 262.881); + --color-blue-700: oklch(48.8% 0.243 264.376); + --color-blue-900: oklch(37.9% 0.146 265.522); + --color-purple-400: oklch(71.4% 0.203 305.504); + --color-purple-600: oklch(55.8% 0.288 302.321); + --color-slate-100: oklch(96.8% 0.007 247.896); + --color-gray-50: oklch(0.985 0 0); + --color-gray-100: oklch(0.97 0 0); + --color-gray-200: oklch(0.922 0 0); + --color-gray-300: oklch(0.87 0 0); + --color-gray-400: oklch(0.708 0 0); + --color-gray-500: oklch(0.556 0 0); + --color-gray-600: oklch(0.439 0 0); + --color-gray-700: oklch(0.371 0 0); + --color-gray-800: oklch(0.269 0 0); + --color-gray-900: oklch(0.205 0 0); + --color-stone-700: oklch(37.4% 0.01 67.558); + --color-black: #000; + --color-white: #fff; + --spacing: 0.25rem; + --container-sm: 24rem; + --container-md: 28rem; + --text-xs: 0.75rem; + --text-xs--line-height: calc(1 / 0.75); + --text-sm: 0.875rem; + --text-sm--line-height: calc(1.25 / 0.875); + --text-base: 1rem; + --text-base--line-height: calc(1.5 / 1); + --text-lg: 1.125rem; + --text-lg--line-height: calc(1.75 / 1.125); + --text-xl: 1.25rem; + --text-xl--line-height: calc(1.75 / 1.25); + --text-2xl: 1.5rem; + --text-2xl--line-height: calc(2 / 1.5); + --text-3xl: 1.875rem; + --text-3xl--line-height: calc(2.25 / 1.875); + --text-4xl: 2.25rem; + --text-4xl--line-height: calc(2.5 / 2.25); + --font-weight-light: 300; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + --tracking-tight: -0.025em; + --tracking-wide: 0.025em; + --leading-relaxed: 1.625; + --radius-sm: 0.25rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + --blur-sm: 8px; + --blur-md: 12px; + --default-transition-duration: 150ms; + --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + --default-font-family: var(--font-sans); + --default-mono-font-family: var(--font-mono); + } +} +@layer base { + *, ::after, ::before, ::backdrop, ::file-selector-button { + box-sizing: border-box; + margin: 0; + padding: 0; + border: 0 solid; + } + html, :host { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); + font-feature-settings: var(--default-font-feature-settings, normal); + font-variation-settings: var(--default-font-variation-settings, normal); + -webkit-tap-highlight-color: transparent; + } + hr { + height: 0; + color: inherit; + border-top-width: 1px; + } + abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + } + h1, h2, h3, h4, h5, h6 { + font-size: inherit; + font-weight: inherit; + } + a { + color: inherit; + -webkit-text-decoration: inherit; + text-decoration: inherit; + } + b, strong { + font-weight: bolder; + } + code, kbd, samp, pre { + font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); + font-feature-settings: var(--default-mono-font-feature-settings, normal); + font-variation-settings: var(--default-mono-font-variation-settings, normal); + font-size: 1em; + } + small { + font-size: 80%; + } + sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + sub { + bottom: -0.25em; + } + sup { + top: -0.5em; + } + table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; + } + :-moz-focusring { + outline: auto; + } + progress { + vertical-align: baseline; + } + summary { + display: list-item; + } + ol, ul, menu { + list-style: none; + } + img, svg, video, canvas, audio, iframe, embed, object { + display: block; + vertical-align: middle; + } + img, video { + max-width: 100%; + height: auto; + } + button, input, select, optgroup, textarea, ::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + border-radius: 0; + background-color: transparent; + opacity: 1; + } + :where(select:is([multiple], [size])) optgroup { + font-weight: bolder; + } + :where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; + } + ::file-selector-button { + margin-inline-end: 4px; + } + ::placeholder { + opacity: 1; + } + @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) { + ::placeholder { + color: currentcolor; + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, currentcolor 50%, transparent); + } + } + } + textarea { + resize: vertical; + } + ::-webkit-search-decoration { + -webkit-appearance: none; + } + ::-webkit-date-and-time-value { + min-height: 1lh; + text-align: inherit; + } + ::-webkit-datetime-edit { + display: inline-flex; + } + ::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { + padding-block: 0; + } + ::-webkit-calendar-picker-indicator { + line-height: 1; + } + :-moz-ui-invalid { + box-shadow: none; + } + button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button { + appearance: button; + } + ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { + height: auto; + } + [hidden]:where(:not([hidden="until-found"])) { + display: none !important; + } +} +@layer utilities { + .pointer-events-none { + pointer-events: none; + } + .visible { + visibility: visible; + } + .absolute { + position: absolute; + } + .relative { + position: relative; + } + .inset-0 { + inset: calc(var(--spacing) * 0); + } + .top-0 { + top: calc(var(--spacing) * 0); + } + .top-1\/2 { + top: calc(1/2 * 100%); + } + .top-4 { + top: calc(var(--spacing) * 4); + } + .top-14 { + top: calc(var(--spacing) * 14); + } + .top-full { + top: 100%; + } + .right-0 { + right: calc(var(--spacing) * 0); + } + .right-2 { + right: calc(var(--spacing) * 2); + } + .bottom-20 { + bottom: calc(var(--spacing) * 20); + } + .bottom-full { + bottom: 100%; + } + .left-1\/2 { + left: calc(1/2 * 100%); + } + .left-4 { + left: calc(var(--spacing) * 4); + } + .left-16 { + left: calc(var(--spacing) * 16); + } + .z-10 { + z-index: 10; + } + .z-20 { + z-index: 20; + } + .z-50 { + z-index: 50; + } + .container { + width: 100%; + @media (width >= 40rem) { + max-width: 40rem; + } + @media (width >= 48rem) { + max-width: 48rem; + } + @media (width >= 64rem) { + max-width: 64rem; + } + @media (width >= 80rem) { + max-width: 80rem; + } + @media (width >= 96rem) { + max-width: 96rem; + } + } + .m-0 { + margin: calc(var(--spacing) * 0); + } + .mx-1 { + margin-inline: calc(var(--spacing) * 1); + } + .mx-4 { + margin-inline: calc(var(--spacing) * 4); + } + .-mt-1 { + margin-top: calc(var(--spacing) * -1); + } + .mt-1 { + margin-top: calc(var(--spacing) * 1); + } + .mt-2 { + margin-top: calc(var(--spacing) * 2); + } + .mt-3 { + margin-top: calc(var(--spacing) * 3); + } + .mt-4 { + margin-top: calc(var(--spacing) * 4); + } + .mt-6 { + margin-top: calc(var(--spacing) * 6); + } + .mr-2 { + margin-right: calc(var(--spacing) * 2); + } + .mb-1 { + margin-bottom: calc(var(--spacing) * 1); + } + .mb-2 { + margin-bottom: calc(var(--spacing) * 2); + } + .mb-4 { + margin-bottom: calc(var(--spacing) * 4); + } + .mb-6 { + margin-bottom: calc(var(--spacing) * 6); + } + .ml-1 { + margin-left: calc(var(--spacing) * 1); + } + .ml-4 { + margin-left: calc(var(--spacing) * 4); + } + .ml-auto { + margin-left: auto; + } + .block { + display: block; + } + .flex { + display: flex; + } + .grid { + display: grid; + } + .hidden { + display: none; + } + .inline { + display: inline; + } + .inline-block { + display: inline-block; + } + .inline-flex { + display: inline-flex; + } + .h-0 { + height: calc(var(--spacing) * 0); + } + .h-2 { + height: calc(var(--spacing) * 2); + } + .h-3 { + height: calc(var(--spacing) * 3); + } + .h-4 { + height: calc(var(--spacing) * 4); + } + .h-5 { + height: calc(var(--spacing) * 5); + } + .h-8 { + height: calc(var(--spacing) * 8); + } + .h-10 { + height: calc(var(--spacing) * 10); + } + .h-12 { + height: calc(var(--spacing) * 12); + } + .h-16 { + height: calc(var(--spacing) * 16); + } + .h-96 { + height: calc(var(--spacing) * 96); + } + .h-\[3vh\] { + height: 3vh; + } + .h-full { + height: 100%; + } + .max-h-96 { + max-height: calc(var(--spacing) * 96); + } + .min-h-screen { + min-height: 100vh; + } + .w-0 { + width: calc(var(--spacing) * 0); + } + .w-1\/2 { + width: calc(1/2 * 100%); + } + .w-1\/4 { + width: calc(1/4 * 100%); + } + .w-1\/5 { + width: calc(1/5 * 100%); + } + .w-1\/12 { + width: calc(1/12 * 100%); + } + .w-1\/24 { + width: calc(1/24 * 100%); + } + .w-2 { + width: calc(var(--spacing) * 2); + } + .w-2\/5 { + width: calc(2/5 * 100%); + } + .w-3 { + width: calc(var(--spacing) * 3); + } + .w-3\/4 { + width: calc(3/4 * 100%); + } + .w-3\/5 { + width: calc(3/5 * 100%); + } + .w-4 { + width: calc(var(--spacing) * 4); + } + .w-4\/5 { + width: calc(4/5 * 100%); + } + .w-5 { + width: calc(var(--spacing) * 5); + } + .w-5\/6 { + width: calc(5/6 * 100%); + } + .w-6 { + width: calc(var(--spacing) * 6); + } + .w-8 { + width: calc(var(--spacing) * 8); + } + .w-11\/12 { + width: calc(11/12 * 100%); + } + .w-12 { + width: calc(var(--spacing) * 12); + } + .w-32 { + width: calc(var(--spacing) * 32); + } + .w-40 { + width: calc(var(--spacing) * 40); + } + .w-56 { + width: calc(var(--spacing) * 56); + } + .w-\[500px\] { + width: 500px; + } + .w-full { + width: 100%; + } + .w-px { + width: 1px; + } + .max-w-md { + max-width: var(--container-md); + } + .max-w-sm { + max-width: var(--container-sm); + } + .min-w-full { + min-width: 100%; + } + .min-w-max { + min-width: max-content; + } + .flex-1 { + flex: 1; + } + .flex-2 { + flex: 2; + } + .flex-none { + flex: none; + } + .flex-shrink-0 { + flex-shrink: 0; + } + .flex-grow { + flex-grow: 1; + } + .table-auto { + table-layout: auto; + } + .-translate-x-1\/2 { + --tw-translate-x: calc(calc(1/2 * 100%) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .-translate-y-1\/2 { + --tw-translate-y: calc(calc(1/2 * 100%) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .-translate-y-full { + --tw-translate-y: -100%; + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .translate-y-0 { + --tw-translate-y: calc(var(--spacing) * 0); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .translate-y-full { + --tw-translate-y: 100%; + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .transform { + transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); + } + .animate-pulse { + animation: var(--animate-pulse); + } + .cursor-not-allowed { + cursor: not-allowed; + } + .cursor-pointer { + cursor: pointer; + } + .list-none { + list-style-type: none; + } + .grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + .grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .flex-col { + flex-direction: column; + } + .flex-wrap { + flex-wrap: wrap; + } + .items-center { + align-items: center; + } + .items-start { + align-items: flex-start; + } + .justify-between { + justify-content: space-between; + } + .justify-center { + justify-content: center; + } + .justify-end { + justify-content: flex-end; + } + .gap-1 { + gap: calc(var(--spacing) * 1); + } + .gap-2 { + gap: calc(var(--spacing) * 2); + } + .gap-3 { + gap: calc(var(--spacing) * 3); + } + .gap-4 { + gap: calc(var(--spacing) * 4); + } + .gap-6 { + gap: calc(var(--spacing) * 6); + } + .space-y-1 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-1\.5 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-2 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-3 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-4 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-6 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-x-1 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse))); + } + } + .space-x-3 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse))); + } + } + .space-x-4 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse))); + } + } + .truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .overflow-hidden { + overflow: hidden; + } + .overflow-x-auto { + overflow-x: auto; + } + .overflow-y-auto { + overflow-y: auto; + } + .rounded { + border-radius: 0.25rem; + } + .rounded-full { + border-radius: calc(infinity * 1px); + } + .rounded-lg { + border-radius: var(--radius-lg); + } + .rounded-md { + border-radius: var(--radius-md); + } + .rounded-sm { + border-radius: var(--radius-sm); + } + .rounded-r-md { + border-top-right-radius: var(--radius-md); + border-bottom-right-radius: var(--radius-md); + } + .border { + border-style: var(--tw-border-style); + border-width: 1px; + } + .border-4 { + border-style: var(--tw-border-style); + border-width: 4px; + } + .border-t { + border-top-style: var(--tw-border-style); + border-top-width: 1px; + } + .border-t-4 { + border-top-style: var(--tw-border-style); + border-top-width: 4px; + } + .border-r { + border-right-style: var(--tw-border-style); + border-right-width: 1px; + } + .border-r-4 { + border-right-style: var(--tw-border-style); + border-right-width: 4px; + } + .border-b { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 1px; + } + .border-l { + border-left-style: var(--tw-border-style); + border-left-width: 1px; + } + .border-l-4 { + border-left-style: var(--tw-border-style); + border-left-width: 4px; + } + .border-gray-100 { + border-color: var(--color-gray-100); + } + .border-gray-200 { + border-color: var(--color-gray-200); + } + .border-gray-300 { + border-color: var(--color-gray-300); + } + .border-gray-700 { + border-color: var(--color-gray-700); + } + .border-red-500 { + border-color: var(--color-red-500); + } + .border-transparent { + border-color: transparent; + } + .border-white\/20 { + border-color: color-mix(in srgb, #fff 20%, transparent); + @supports (color: color-mix(in lab, red, red)) { + border-color: color-mix(in oklab, var(--color-white) 20%, transparent); + } + } + .border-t-gray-900 { + border-top-color: var(--color-gray-900); + } + .bg-black\/20 { + background-color: color-mix(in srgb, #000 20%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-black) 20%, transparent); + } + } + .bg-blue-100 { + background-color: var(--color-blue-100); + } + .bg-gray-50 { + background-color: var(--color-gray-50); + } + .bg-gray-100 { + background-color: var(--color-gray-100); + } + .bg-gray-200 { + background-color: var(--color-gray-200); + } + .bg-gray-300 { + background-color: var(--color-gray-300); + } + .bg-gray-500 { + background-color: var(--color-gray-500); + } + .bg-gray-600 { + background-color: var(--color-gray-600); + } + .bg-gray-800 { + background-color: var(--color-gray-800); + } + .bg-gray-900 { + background-color: var(--color-gray-900); + } + .bg-green-100 { + background-color: var(--color-green-100); + } + .bg-green-500 { + background-color: var(--color-green-500); + } + .bg-orange-100 { + background-color: var(--color-orange-100); + } + .bg-red-100 { + background-color: var(--color-red-100); + } + .bg-red-500 { + background-color: var(--color-red-500); + } + .bg-slate-100 { + background-color: var(--color-slate-100); + } + .bg-white { + background-color: var(--color-white); + } + .bg-white\/90 { + background-color: color-mix(in srgb, #fff 90%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-white) 90%, transparent); + } + } + .bg-yellow-500 { + background-color: var(--color-yellow-500); + } + .bg-gradient-to-l { + --tw-gradient-position: to left in oklab; + background-image: linear-gradient(var(--tw-gradient-stops)); + } + .from-gray-50 { + --tw-gradient-from: var(--color-gray-50); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + } + .via-gray-50 { + --tw-gradient-via: var(--color-gray-50); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-via-stops); + } + .to-transparent { + --tw-gradient-to: transparent; + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + } + .object-contain { + object-fit: contain; + } + .p-0 { + padding: calc(var(--spacing) * 0); + } + .p-2 { + padding: calc(var(--spacing) * 2); + } + .p-3 { + padding: calc(var(--spacing) * 3); + } + .p-4 { + padding: calc(var(--spacing) * 4); + } + .p-6 { + padding: calc(var(--spacing) * 6); + } + .p-8 { + padding: calc(var(--spacing) * 8); + } + .px-1 { + padding-inline: calc(var(--spacing) * 1); + } + .px-2 { + padding-inline: calc(var(--spacing) * 2); + } + .px-3 { + padding-inline: calc(var(--spacing) * 3); + } + .px-4 { + padding-inline: calc(var(--spacing) * 4); + } + .px-5 { + padding-inline: calc(var(--spacing) * 5); + } + .px-6 { + padding-inline: calc(var(--spacing) * 6); + } + .py-0\.5 { + padding-block: calc(var(--spacing) * 0.5); + } + .py-1 { + padding-block: calc(var(--spacing) * 1); + } + .py-2 { + padding-block: calc(var(--spacing) * 2); + } + .py-3 { + padding-block: calc(var(--spacing) * 3); + } + .py-4 { + padding-block: calc(var(--spacing) * 4); + } + .py-12 { + padding-block: calc(var(--spacing) * 12); + } + .pt-4 { + padding-top: calc(var(--spacing) * 4); + } + .pt-6 { + padding-top: calc(var(--spacing) * 6); + } + .pr-6 { + padding-right: calc(var(--spacing) * 6); + } + .pr-24 { + padding-right: calc(var(--spacing) * 24); + } + .pb-1 { + padding-bottom: calc(var(--spacing) * 1); + } + .pl-4 { + padding-left: calc(var(--spacing) * 4); + } + .pl-6 { + padding-left: calc(var(--spacing) * 6); + } + .pl-12 { + padding-left: calc(var(--spacing) * 12); + } + .text-center { + text-align: center; + } + .text-left { + text-align: left; + } + .text-right { + text-align: right; + } + .font-mono { + font-family: var(--font-mono); + } + .text-2xl { + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)); + } + .text-3xl { + font-size: var(--text-3xl); + line-height: var(--tw-leading, var(--text-3xl--line-height)); + } + .text-4xl { + font-size: var(--text-4xl); + line-height: var(--tw-leading, var(--text-4xl--line-height)); + } + .text-base { + font-size: var(--text-base); + line-height: var(--tw-leading, var(--text-base--line-height)); + } + .text-lg { + font-size: var(--text-lg); + line-height: var(--tw-leading, var(--text-lg--line-height)); + } + .text-sm { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + .text-xl { + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)); + } + .text-xs { + font-size: var(--text-xs); + line-height: var(--tw-leading, var(--text-xs--line-height)); + } + .leading-relaxed { + --tw-leading: var(--leading-relaxed); + line-height: var(--leading-relaxed); + } + .font-bold { + --tw-font-weight: var(--font-weight-bold); + font-weight: var(--font-weight-bold); + } + .font-light { + --tw-font-weight: var(--font-weight-light); + font-weight: var(--font-weight-light); + } + .font-medium { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium); + } + .font-semibold { + --tw-font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-semibold); + } + .tracking-tight { + --tw-tracking: var(--tracking-tight); + letter-spacing: var(--tracking-tight); + } + .tracking-wide { + --tw-tracking: var(--tracking-wide); + letter-spacing: var(--tracking-wide); + } + .break-all { + word-break: break-all; + } + .whitespace-nowrap { + white-space: nowrap; + } + .whitespace-pre-wrap { + white-space: pre-wrap; + } + .\!text-gray-500 { + color: var(--color-gray-500) !important; + } + .\!text-gray-600 { + color: var(--color-gray-600) !important; + } + .text-\[\#b3b3b3\] { + color: #b3b3b3; + } + .text-blue-700 { + color: var(--color-blue-700); + } + .text-gray-300 { + color: var(--color-gray-300); + } + .text-gray-400 { + color: var(--color-gray-400); + } + .text-gray-500 { + color: var(--color-gray-500); + } + .text-gray-600 { + color: var(--color-gray-600); + } + .text-gray-700 { + color: var(--color-gray-700); + } + .text-gray-800 { + color: var(--color-gray-800); + } + .text-gray-900 { + color: var(--color-gray-900); + } + .text-green-600 { + color: var(--color-green-600); + } + .text-green-800 { + color: var(--color-green-800); + } + .text-orange-800 { + color: var(--color-orange-800); + } + .text-red-600 { + color: var(--color-red-600); + } + .text-red-800 { + color: var(--color-red-800); + } + .text-white { + color: var(--color-white); + } + .text-white\/90 { + color: color-mix(in srgb, #fff 90%, transparent); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-white) 90%, transparent); + } + } + .text-yellow-600 { + color: var(--color-yellow-600); + } + .uppercase { + text-transform: uppercase; + } + .tabular-nums { + --tw-numeric-spacing: tabular-nums; + font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,); + } + .\!no-underline { + text-decoration-line: none !important; + } + .underline { + text-decoration-line: underline; + } + .opacity-0 { + opacity: 0%; + } + .opacity-100 { + opacity: 100%; + } + .shadow-lg { + --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-xl { + --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .invert { + --tw-invert: invert(100%); + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); + } + .filter { + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); + } + .backdrop-blur-md { + --tw-backdrop-blur: blur(var(--blur-md)); + -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); + backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); + } + .backdrop-blur-sm { + --tw-backdrop-blur: blur(var(--blur-sm)); + -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); + backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); + } + .transition-all { + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .transition-colors { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .transition-opacity { + transition-property: opacity; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .duration-200 { + --tw-duration: 200ms; + transition-duration: 200ms; + } + .duration-300 { + --tw-duration: 300ms; + transition-duration: 300ms; + } + .ease-in-out { + --tw-ease: var(--ease-in-out); + transition-timing-function: var(--ease-in-out); + } + .select-none { + -webkit-user-select: none; + user-select: none; + } + .group-hover\:opacity-60 { + &:is(:where(.group):hover *) { + @media (hover: hover) { + opacity: 60%; + } + } + } + .group-hover\:opacity-100 { + &:is(:where(.group):hover *) { + @media (hover: hover) { + opacity: 100%; + } + } + } + .hover\:scale-110 { + &:hover { + @media (hover: hover) { + --tw-scale-x: 110%; + --tw-scale-y: 110%; + --tw-scale-z: 110%; + scale: var(--tw-scale-x) var(--tw-scale-y); + } + } + } + .hover\:bg-gray-50 { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-50); + } + } + } + .hover\:bg-gray-100 { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-100); + } + } + } + .hover\:bg-gray-200 { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-200); + } + } + } + .hover\:\!text-gray-700 { + &:hover { + @media (hover: hover) { + color: var(--color-gray-700) !important; + } + } + } + .hover\:text-gray-600 { + &:hover { + @media (hover: hover) { + color: var(--color-gray-600); + } + } + } + .hover\:text-gray-800 { + &:hover { + @media (hover: hover) { + color: var(--color-gray-800); + } + } + } + .hover\:underline { + &:hover { + @media (hover: hover) { + text-decoration-line: underline; + } + } + } + .hover\:\!opacity-80 { + &:hover { + @media (hover: hover) { + opacity: 80% !important; + } + } + } + .hover\:opacity-80 { + &:hover { + @media (hover: hover) { + opacity: 80%; + } + } + } + .focus\:border-gray-500 { + &:focus { + border-color: var(--color-gray-500); + } + } + .focus\:border-red-500 { + &:focus { + border-color: var(--color-red-500); + } + } + .focus\:border-transparent { + &:focus { + border-color: transparent; + } + } + .focus\:ring-2 { + &:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus\:ring-gray-500 { + &:focus { + --tw-ring-color: var(--color-gray-500); + } + } + .focus\:outline-none { + &:focus { + --tw-outline-style: none; + outline-style: none; + } + } + .md\:col-span-2 { + @media (width >= 48rem) { + grid-column: span 2 / span 2; + } + } + .md\:grid-cols-2 { + @media (width >= 48rem) { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + } + .md\:grid-cols-3 { + @media (width >= 48rem) { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + } + .md\:grid-cols-4 { + @media (width >= 48rem) { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + } + .md\:grid-cols-5 { + @media (width >= 48rem) { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + } + .lg\:col-span-2 { + @media (width >= 64rem) { + grid-column: span 2 / span 2; + } + } + .lg\:col-span-3 { + @media (width >= 64rem) { + grid-column: span 3 / span 3; + } + } + .lg\:grid-cols-2 { + @media (width >= 64rem) { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + } + .lg\:grid-cols-3 { + @media (width >= 64rem) { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + } + .lg\:grid-cols-5 { + @media (width >= 64rem) { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + } + .dark\:border-gray-400 { + &:is(.dark, .dark *) { + border-color: var(--color-gray-400); + } + } + .dark\:border-gray-600 { + &:is(.dark, .dark *) { + border-color: var(--color-gray-600); + } + } + .dark\:border-gray-700 { + &:is(.dark, .dark *) { + border-color: var(--color-gray-700); + } + } + .dark\:border-gray-700\/50 { + &:is(.dark, .dark *) { + border-color: color-mix(in srgb, oklch(0.371 0 0) 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + border-color: color-mix(in oklab, var(--color-gray-700) 50%, transparent); + } + } + } + .dark\:border-red-500 { + &:is(.dark, .dark *) { + border-color: var(--color-red-500); + } + } + .dark\:border-t-gray-700 { + &:is(.dark, .dark *) { + border-top-color: var(--color-gray-700); + } + } + .dark\:bg-\[\#000000\] { + &:is(.dark, .dark *) { + background-color: #000000; + } + } + .dark\:bg-\[\#141414\] { + &:is(.dark, .dark *) { + background-color: #141414; + } + } + .dark\:bg-\[\#141414\]\/90 { + &:is(.dark, .dark *) { + background-color: color-mix(in oklab, #141414 90%, transparent); + } + } + .dark\:bg-\[\#383838\] { + &:is(.dark, .dark *) { + background-color: #383838; + } + } + .dark\:bg-blue-900 { + &:is(.dark, .dark *) { + background-color: var(--color-blue-900); + } + } + .dark\:bg-gray-100 { + &:is(.dark, .dark *) { + background-color: var(--color-gray-100); + } + } + .dark\:bg-gray-800 { + &:is(.dark, .dark *) { + background-color: var(--color-gray-800); + } + } + .dark\:bg-green-900 { + &:is(.dark, .dark *) { + background-color: var(--color-green-900); + } + } + .dark\:bg-orange-900 { + &:is(.dark, .dark *) { + background-color: var(--color-orange-900); + } + } + .dark\:bg-red-900 { + &:is(.dark, .dark *) { + background-color: var(--color-red-900); + } + } + .dark\:bg-stone-700 { + &:is(.dark, .dark *) { + background-color: var(--color-stone-700); + } + } + .dark\:from-\[\#000000\] { + &:is(.dark, .dark *) { + --tw-gradient-from: #000000; + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + } + } + .dark\:via-\[\#000000\] { + &:is(.dark, .dark *) { + --tw-gradient-via: #000000; + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-via-stops); + } + } + .dark\:\!text-\[\#b3b3b3\] { + &:is(.dark, .dark *) { + color: #b3b3b3 !important; + } + } + .dark\:\!text-white { + &:is(.dark, .dark *) { + color: var(--color-white) !important; + } + } + .dark\:text-\[\#b3b3b3\] { + &:is(.dark, .dark *) { + color: #b3b3b3; + } + } + .dark\:text-blue-300 { + &:is(.dark, .dark *) { + color: var(--color-blue-300); + } + } + .dark\:text-gray-100 { + &:is(.dark, .dark *) { + color: var(--color-gray-100); + } + } + .dark\:text-gray-200 { + &:is(.dark, .dark *) { + color: var(--color-gray-200); + } + } + .dark\:text-gray-300 { + &:is(.dark, .dark *) { + color: var(--color-gray-300); + } + } + .dark\:text-gray-400 { + &:is(.dark, .dark *) { + color: var(--color-gray-400); + } + } + .dark\:text-gray-500 { + &:is(.dark, .dark *) { + color: var(--color-gray-500); + } + } + .dark\:text-gray-600 { + &:is(.dark, .dark *) { + color: var(--color-gray-600); + } + } + .dark\:text-gray-900 { + &:is(.dark, .dark *) { + color: var(--color-gray-900); + } + } + .dark\:text-green-200 { + &:is(.dark, .dark *) { + color: var(--color-green-200); + } + } + .dark\:text-green-400 { + &:is(.dark, .dark *) { + color: var(--color-green-400); + } + } + .dark\:text-orange-200 { + &:is(.dark, .dark *) { + color: var(--color-orange-200); + } + } + .dark\:text-red-200 { + &:is(.dark, .dark *) { + color: var(--color-red-200); + } + } + .dark\:text-red-400 { + &:is(.dark, .dark *) { + color: var(--color-red-400); + } + } + .dark\:text-white { + &:is(.dark, .dark *) { + color: var(--color-white); + } + } + .dark\:text-yellow-400 { + &:is(.dark, .dark *) { + color: var(--color-yellow-400); + } + } + .dark\:placeholder-\[\#b3b3b3\] { + &:is(.dark, .dark *) { + &::placeholder { + color: #b3b3b3; + } + } + } + .dark\:invert-0 { + &:is(.dark, .dark *) { + --tw-invert: invert(0%); + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); + } + } + .dark\:hover\:bg-\[\#141414\] { + &:is(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: #141414; + } + } + } + } + .dark\:hover\:bg-\[\#383838\] { + &:is(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: #383838; + } + } + } + } + .dark\:hover\:bg-gray-600 { + &:is(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-600); + } + } + } + } + .dark\:hover\:bg-gray-700 { + &:is(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-700); + } + } + } + } + .dark\:hover\:\!text-white { + &:is(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-white) !important; + } + } + } + } + .dark\:hover\:text-white { + &:is(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-white); + } + } + } + } + .dark\:focus\:border-gray-400 { + &:is(.dark, .dark *) { + &:focus { + border-color: var(--color-gray-400); + } + } + } + .dark\:focus\:border-red-500 { + &:is(.dark, .dark *) { + &:focus { + border-color: var(--color-red-500); + } + } + } +} +.json-key { + --tw-font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-semibold); + color: var(--color-blue-600); +} +.dark .json-key { + color: var(--color-blue-400); +} +.json-string { + color: var(--color-green-600); +} +.dark .json-string { + color: var(--color-green-400); +} +.json-number { + color: var(--color-amber-600); +} +.dark .json-number { + color: var(--color-amber-400); +} +.json-boolean { + color: var(--color-purple-600); +} +.dark .json-boolean { + color: var(--color-purple-400); +} +.json-null { + color: var(--color-red-500); + font-style: italic; +} +.dark .json-null { + color: var(--color-red-400); +} +.json-brace { + --tw-font-weight: var(--font-weight-bold); + font-weight: var(--font-weight-bold); + color: var(--color-gray-500); +} +.dark .json-brace { + color: var(--color-gray-400); +} +@property --tw-translate-x { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-y { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-z { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-rotate-x { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-y { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-z { + syntax: "*"; + inherits: false; +} +@property --tw-skew-x { + syntax: "*"; + inherits: false; +} +@property --tw-skew-y { + syntax: "*"; + inherits: false; +} +@property --tw-space-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-space-x-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-gradient-position { + syntax: "*"; + inherits: false; +} +@property --tw-gradient-from { + syntax: ""; + inherits: false; + initial-value: #0000; +} +@property --tw-gradient-via { + syntax: ""; + inherits: false; + initial-value: #0000; +} +@property --tw-gradient-to { + syntax: ""; + inherits: false; + initial-value: #0000; +} +@property --tw-gradient-stops { + syntax: "*"; + inherits: false; +} +@property --tw-gradient-via-stops { + syntax: "*"; + inherits: false; +} +@property --tw-gradient-from-position { + syntax: ""; + inherits: false; + initial-value: 0%; +} +@property --tw-gradient-via-position { + syntax: ""; + inherits: false; + initial-value: 50%; +} +@property --tw-gradient-to-position { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-leading { + syntax: "*"; + inherits: false; +} +@property --tw-font-weight { + syntax: "*"; + inherits: false; +} +@property --tw-tracking { + syntax: "*"; + inherits: false; +} +@property --tw-ordinal { + syntax: "*"; + inherits: false; +} +@property --tw-slashed-zero { + syntax: "*"; + inherits: false; +} +@property --tw-numeric-figure { + syntax: "*"; + inherits: false; +} +@property --tw-numeric-spacing { + syntax: "*"; + inherits: false; +} +@property --tw-numeric-fraction { + syntax: "*"; + inherits: false; +} +@property --tw-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-inset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-ring-inset { + syntax: "*"; + inherits: false; +} +@property --tw-ring-offset-width { + syntax: ""; + inherits: false; + initial-value: 0px; +} +@property --tw-ring-offset-color { + syntax: "*"; + inherits: false; + initial-value: #fff; +} +@property --tw-ring-offset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-blur { + syntax: "*"; + inherits: false; +} +@property --tw-brightness { + syntax: "*"; + inherits: false; +} +@property --tw-contrast { + syntax: "*"; + inherits: false; +} +@property --tw-grayscale { + syntax: "*"; + inherits: false; +} +@property --tw-hue-rotate { + syntax: "*"; + inherits: false; +} +@property --tw-invert { + syntax: "*"; + inherits: false; +} +@property --tw-opacity { + syntax: "*"; + inherits: false; +} +@property --tw-saturate { + syntax: "*"; + inherits: false; +} +@property --tw-sepia { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-drop-shadow-size { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-blur { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-brightness { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-contrast { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-grayscale { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-hue-rotate { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-invert { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-opacity { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-saturate { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-sepia { + syntax: "*"; + inherits: false; +} +@property --tw-duration { + syntax: "*"; + inherits: false; +} +@property --tw-ease { + syntax: "*"; + inherits: false; +} +@property --tw-scale-x { + syntax: "*"; + inherits: false; + initial-value: 1; +} +@property --tw-scale-y { + syntax: "*"; + inherits: false; + initial-value: 1; +} +@property --tw-scale-z { + syntax: "*"; + inherits: false; + initial-value: 1; +} +@keyframes pulse { + 50% { + opacity: 0.5; + } +} +@layer properties { + @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { + *, ::before, ::after, ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-translate-z: 0; + --tw-rotate-x: initial; + --tw-rotate-y: initial; + --tw-rotate-z: initial; + --tw-skew-x: initial; + --tw-skew-y: initial; + --tw-space-y-reverse: 0; + --tw-space-x-reverse: 0; + --tw-border-style: solid; + --tw-gradient-position: initial; + --tw-gradient-from: #0000; + --tw-gradient-via: #0000; + --tw-gradient-to: #0000; + --tw-gradient-stops: initial; + --tw-gradient-via-stops: initial; + --tw-gradient-from-position: 0%; + --tw-gradient-via-position: 50%; + --tw-gradient-to-position: 100%; + --tw-leading: initial; + --tw-font-weight: initial; + --tw-tracking: initial; + --tw-ordinal: initial; + --tw-slashed-zero: initial; + --tw-numeric-figure: initial; + --tw-numeric-spacing: initial; + --tw-numeric-fraction: initial; + --tw-shadow: 0 0 #0000; + --tw-shadow-color: initial; + --tw-shadow-alpha: 100%; + --tw-inset-shadow: 0 0 #0000; + --tw-inset-shadow-color: initial; + --tw-inset-shadow-alpha: 100%; + --tw-ring-color: initial; + --tw-ring-shadow: 0 0 #0000; + --tw-inset-ring-color: initial; + --tw-inset-ring-shadow: 0 0 #0000; + --tw-ring-inset: initial; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-offset-shadow: 0 0 #0000; + --tw-blur: initial; + --tw-brightness: initial; + --tw-contrast: initial; + --tw-grayscale: initial; + --tw-hue-rotate: initial; + --tw-invert: initial; + --tw-opacity: initial; + --tw-saturate: initial; + --tw-sepia: initial; + --tw-drop-shadow: initial; + --tw-drop-shadow-color: initial; + --tw-drop-shadow-alpha: 100%; + --tw-drop-shadow-size: initial; + --tw-backdrop-blur: initial; + --tw-backdrop-brightness: initial; + --tw-backdrop-contrast: initial; + --tw-backdrop-grayscale: initial; + --tw-backdrop-hue-rotate: initial; + --tw-backdrop-invert: initial; + --tw-backdrop-opacity: initial; + --tw-backdrop-saturate: initial; + --tw-backdrop-sepia: initial; + --tw-duration: initial; + --tw-ease: initial; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-scale-z: 1; + } + } +} diff --git a/pkg/console/assets/images/OAPLogo.svg b/pkg/console/assets/images/OAPLogo.svg new file mode 100644 index 00000000..7fe4c586 --- /dev/null +++ b/pkg/console/assets/images/OAPLogo.svg @@ -0,0 +1,3 @@ + + + diff --git a/pkg/console/assets/images/favicon.png b/pkg/console/assets/images/favicon.png new file mode 100644 index 00000000..0bdfdbc7 Binary files /dev/null and b/pkg/console/assets/images/favicon.png differ diff --git a/pkg/console/assets/input.css b/pkg/console/assets/input.css new file mode 100644 index 00000000..fc79cda5 --- /dev/null +++ b/pkg/console/assets/input.css @@ -0,0 +1,70 @@ +@import "tailwindcss"; + +@source "../templates/**/*.templ"; +@source "../templates/**/*_templ.go"; + +@variant dark (&:is(.dark, .dark *)); + +/* Override gray colors to be monochrome (no blue tint) - matching console's high contrast scheme */ +@theme { + --color-gray-50: oklch(0.985 0 0); + --color-gray-100: oklch(0.97 0 0); + --color-gray-200: oklch(0.922 0 0); + --color-gray-300: oklch(0.87 0 0); + --color-gray-400: oklch(0.708 0 0); + --color-gray-500: oklch(0.556 0 0); + --color-gray-600: oklch(0.439 0 0); + --color-gray-700: oklch(0.371 0 0); + --color-gray-800: oklch(0.269 0 0); + --color-gray-900: oklch(0.205 0 0); + --color-gray-950: oklch(0.145 0 0); +} + +/* JSON Syntax Highlighting */ +.json-key { + @apply text-blue-600 font-semibold; +} + +.dark .json-key { + @apply text-blue-400; +} + +.json-string { + @apply text-green-600; +} + +.dark .json-string { + @apply text-green-400; +} + +.json-number { + @apply text-amber-600; +} + +.dark .json-number { + @apply text-amber-400; +} + +.json-boolean { + @apply text-purple-600; +} + +.dark .json-boolean { + @apply text-purple-400; +} + +.json-null { + @apply text-red-500 italic; +} + +.dark .json-null { + @apply text-red-400; +} + +.json-brace { + @apply text-gray-500 font-bold; +} + +.dark .json-brace { + @apply text-gray-400; +} diff --git a/pkg/console/assets/js/search.js b/pkg/console/assets/js/search.js new file mode 100644 index 00000000..8378f3af --- /dev/null +++ b/pkg/console/assets/js/search.js @@ -0,0 +1,277 @@ +function searchBar() { + return { + query: '', + showSuggestions: false, + searchType: '', + suggestions: [], + isLoading: false, + isSelectingSuggestion: false, + hasNoResults: false, + audiusApiBase: '', + + init() { + // Read environment from data attribute and set appropriate endpoint + const env = this.$el.dataset.env || 'prod'; + switch (env) { + case 'stage': + case 'staging': + this.audiusApiBase = 'api.staging.audius.co'; + break; + case 'dev': + case 'development': + this.audiusApiBase = 'api.audius.co'; // or whatever dev endpoint you use + break; + case 'prod': + case 'production': + default: + this.audiusApiBase = 'api.audius.co'; + break; + } + console.log('Search component initialized with environment:', env, 'endpoint:', this.audiusApiBase); + }, + + async fetchAllSuggestions() { + this.isLoading = true; + try { + // For empty query, show popular/recent items + const response = await fetch('/search?q=1'); + const data = await response.json(); + + this.searchType = 'All'; + this.suggestions = this.groupSuggestionsByType(data.results || []); + this.showSuggestions = true; + } catch (error) { + console.error('Error fetching suggestions:', error); + this.suggestions = []; + } finally { + this.isLoading = false; + } + }, + + async handleInput() { + console.log('Input changed:', this.query); + + // Skip handling if we're in the middle of selecting a suggestion + if (this.isSelectingSuggestion) { + return; + } + + // Reset no results state + this.hasNoResults = false; + + // Hide suggestions and clear type if query is empty or too short + if (!this.query.trim()) { + this.suggestions = []; + this.showSuggestions = false; + this.searchType = ''; + return; + } + + this.isLoading = true; + try { + // Make both API calls in parallel + const [localResponse, audiusResponse] = await Promise.all([ + fetch(`/search?q=${encodeURIComponent(this.query)}`), + fetch(`https://${this.audiusApiBase}/v1/users/search?query=${encodeURIComponent(this.query)}&limit=5`) + .catch(error => { + console.warn('Audius API error:', error); + return null; // Don't fail the entire search if Audius is down + }) + ]); + + const localData = await localResponse.json(); + let audiusData = null; + + if (audiusResponse && audiusResponse.ok) { + audiusData = await audiusResponse.json(); + } + + if (localData.error) { + console.error('Search error:', localData.error); + this.suggestions = []; + this.showSuggestions = false; + this.flashNoResults(); + return; + } + + const localResults = localData.results || []; + const audiusUsers = audiusData?.data || []; + + // Convert Audius users to our suggestion format + const audiusSuggestions = audiusUsers.map(user => ({ + id: user.erc_wallet || user.wallet, // Use wallet address as ID for navigation + title: user.name, + subtitle: `@${user.handle} • ${user.follower_count?.toLocaleString() || 0} followers`, + type: 'audius_user', + url: `/account/${user.erc_wallet || user.wallet}`, + audiusData: { + handle: user.handle, + bio: user.bio, + followerCount: user.follower_count, + trackCount: user.track_count, + isVerified: user.is_verified, + profilePicture: user.profile_picture, + wallet: user.erc_wallet || user.wallet + } + })).filter(user => user.id); // Filter out users without wallet addresses + + // Combine results + const allResults = [...localResults, ...audiusSuggestions]; + + // Check if no results found + if (allResults.length === 0) { + this.suggestions = []; + this.showSuggestions = false; + this.searchType = ''; + this.flashNoResults(); + return; + } + + // Determine search type based on query pattern and results + if (this.query.startsWith('0x')) { + if (this.query.match(/^0x[a-fA-F0-9]{40}$/)) { + this.searchType = 'Account'; + } else if (this.query.match(/^0x[a-fA-F0-9]{64}$/)) { + this.searchType = 'Transaction'; + } else if (this.query.length <= 42) { + this.searchType = 'Account'; + } else { + this.searchType = 'Transaction'; + } + } else if (this.query.match(/^[0-9]+$/)) { + this.searchType = 'Block'; + } else { + this.searchType = 'Mixed'; + } + + // Group suggestions by type + this.suggestions = this.groupSuggestionsByType(allResults); + this.showSuggestions = true; + } catch (error) { + console.error('Error handling input:', error); + this.suggestions = []; + this.showSuggestions = false; + this.flashNoResults(); + } finally { + this.isLoading = false; + } + }, + + groupSuggestionsByType(suggestions) { + const grouped = {}; + suggestions.forEach(suggestion => { + if (!grouped[suggestion.type]) { + grouped[suggestion.type] = []; + } + grouped[suggestion.type].push(suggestion); + }); + + // Convert grouped object to array with headers + const result = []; + Object.entries(grouped).forEach(([type, items]) => { + if (items.length > 0) { + result.push({ + id: `header-${type}`, + isHeader: true, + title: this.formatTypeHeader(type), + type: type + }); + result.push(...items); + } + }); + return result; + }, + + formatTypeHeader(type) { + const headers = { + 'track': 'Tracks', + 'username': 'Artists', + 'playlist': 'Playlists', + 'album': 'Albums', + 'block': 'Blocks', + 'account': 'Accounts', + 'transaction': 'Transactions', + 'validator': 'Validators', + 'audius_user': 'Audius Artists' + }; + return headers[type] || type.charAt(0).toUpperCase() + type.slice(1) + 's'; + }, + + getNavigationPath(suggestion) { + if (suggestion.isHeader) return ''; + + // Use the URL from the API response if available + if (suggestion.url) { + return suggestion.url; + } + + // Fallback to original logic + switch (suggestion.type) { + case 'block': + const blockNum = suggestion.title.match(/#(\d+)/)?.[1] || suggestion.id; + return blockNum ? `/block/${blockNum}` : ''; + case 'account': + return `/account/${suggestion.id}`; + case 'transaction': + return `/transaction/${suggestion.id}`; + case 'validator': + return `/validator/${suggestion.id}`; + case 'track': + return `/tracks/${suggestion.id}`; + case 'username': + return `/users/${suggestion.title}`; + case 'playlist': + return `/playlists/${suggestion.id}`; + case 'album': + return `/albums/${suggestion.id}`; + case 'audius_user': + return `/account/${suggestion.id}`; + default: + return ''; + } + }, + + selectSuggestion(suggestion) { + if (suggestion.isHeader) return; + + // Set flag to prevent handleInput from running + this.isSelectingSuggestion = true; + + const path = this.getNavigationPath(suggestion); + if (path) { + // Navigate immediately without setting query + window.location.href = path; + return; + } + + // If for some reason navigation fails, reset the flag + this.isSelectingSuggestion = false; + this.showSuggestions = false; + }, + + handleKeydown(event) { + // Handle Enter key + if (event.key === 'Enter') { + event.preventDefault(); + + // Find the first non-header suggestion + const firstSuggestion = this.suggestions.find(s => !s.isHeader); + + if (firstSuggestion) { + this.selectSuggestion(firstSuggestion); + } else if (this.query.trim()) { + // If no suggestions but there's a query, flash red + this.flashNoResults(); + } + } + }, + + flashNoResults() { + this.hasNoResults = true; + // Reset the flash after 500ms + setTimeout(() => { + this.hasNoResults = false; + }, 500); + } + } +} diff --git a/pkg/console/assets/js/theme.js b/pkg/console/assets/js/theme.js new file mode 100644 index 00000000..01d4b286 --- /dev/null +++ b/pkg/console/assets/js/theme.js @@ -0,0 +1,30 @@ +function themeToggle() { + return { + theme: 'light', + + init() { + console.log('Theme toggle initialized'); + // Read current state from document (set by inline script) + const isDark = document.documentElement.classList.contains('dark'); + this.theme = isDark ? 'dark' : 'light'; + console.log('Current theme:', this.theme); + }, + + toggleTheme() { + console.log('Toggle clicked, current theme:', this.theme); + this.theme = this.theme === 'light' ? 'dark' : 'light'; + console.log('New theme:', this.theme); + localStorage.setItem('theme', this.theme); + this.applyTheme(); + }, + + applyTheme() { + console.log('Applying theme:', this.theme); + if (this.theme === 'dark') { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + } + } +} diff --git a/pkg/console/assets/js/timezone.js b/pkg/console/assets/js/timezone.js new file mode 100644 index 00000000..51614f73 --- /dev/null +++ b/pkg/console/assets/js/timezone.js @@ -0,0 +1,55 @@ +// Timezone conversion utility for time tooltips +(function () { + function convertTimeToLocal() { + // Find all elements with time data + const timeElements = document.querySelectorAll('[data-utc-time]'); + + timeElements.forEach(element => { + const utcTimeString = element.getAttribute('data-utc-time'); + const displayElement = element.querySelector('.local-time-display'); + + if (utcTimeString && displayElement) { + try { + // Parse the UTC time + const utcDate = new Date(utcTimeString); + + // Format date separately + const localDate = utcDate.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + + // Format time separately + const localTime = utcDate.toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + hour12: true + }); + + // Get timezone abbreviation + const tzAbbr = utcDate.toLocaleTimeString('en-US', { + timeZoneName: 'short' + }).split(' ').pop(); + + // Update the display with stacked format + displayElement.innerHTML = `${localDate}
${localTime} ${tzAbbr}`; + } catch (error) { + console.warn('Error converting time:', error); + // Fallback to original UTC display + } + } + }); + } + + // Convert times when the DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', convertTimeToLocal); + } else { + convertTimeToLocal(); + } + + // Also convert times when new content is loaded via HTMX + document.addEventListener('htmx:afterSettle', convertTimeToLocal); +})(); diff --git a/pkg/console/assets/package.json b/pkg/console/assets/package.json new file mode 100644 index 00000000..2dd9f1a0 --- /dev/null +++ b/pkg/console/assets/package.json @@ -0,0 +1,18 @@ +{ + "name": "assets", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@tailwindcss/postcss": "^4.1.18", + "postcss": "^8.5.6", + "postcss-cli": "^11.0.1", + "tailwindcss": "^4.1.18" + } +} diff --git a/pkg/console/assets/postcss.config.js b/pkg/console/assets/postcss.config.js new file mode 100644 index 00000000..e5640725 --- /dev/null +++ b/pkg/console/assets/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + '@tailwindcss/postcss': {}, + }, +}; diff --git a/pkg/console/cache.go b/pkg/console/cache.go new file mode 100644 index 00000000..c582262a --- /dev/null +++ b/pkg/console/cache.go @@ -0,0 +1,79 @@ +package console + +import ( + "context" + "sync" + "time" + + "go.uber.org/zap" +) + +// Cache is a generic thread-safe cache that periodically refreshes data in the background +type Cache[T any] struct { + mu sync.RWMutex + data T + lastUpdated time.Time + updateFunc func(context.Context) (T, error) + refreshRate time.Duration + logger *zap.Logger +} + +// NewCache creates a new cache with the given update function and refresh rate +func NewCache[T any](updateFunc func(context.Context) (T, error), refreshRate time.Duration, logger *zap.Logger) *Cache[T] { + return &Cache[T]{ + updateFunc: updateFunc, + refreshRate: refreshRate, + logger: logger, + } +} + +// Get returns the cached data (thread-safe read) +func (c *Cache[T]) Get() T { + c.mu.RLock() + defer c.mu.RUnlock() + return c.data +} + +// GetLastUpdated returns when the cache was last updated +func (c *Cache[T]) GetLastUpdated() time.Time { + c.mu.RLock() + defer c.mu.RUnlock() + return c.lastUpdated +} + +// StartRefresh begins the background refresh loop +// Should be called in a goroutine +func (c *Cache[T]) StartRefresh(ctx context.Context) { + ticker := time.NewTicker(c.refreshRate) + defer ticker.Stop() + + // Do initial refresh immediately (non-blocking) + c.refresh(ctx) + + for { + select { + case <-ctx.Done(): + c.logger.Info("Cache refresh stopped") + return + case <-ticker.C: + c.refresh(ctx) + } + } +} + +// refresh updates the cached data by calling the update function +func (c *Cache[T]) refresh(ctx context.Context) { + start := time.Now() + data, err := c.updateFunc(ctx) + if err != nil { + c.logger.Warn("Cache refresh failed", zap.Error(err), zap.Duration("duration", time.Since(start))) + return + } + + c.mu.Lock() + c.data = data + c.lastUpdated = time.Now() + c.mu.Unlock() + + c.logger.Debug("Cache refreshed", zap.Duration("duration", time.Since(start))) +} diff --git a/pkg/console/console.go b/pkg/console/console.go new file mode 100644 index 00000000..174f3ee9 --- /dev/null +++ b/pkg/console/console.go @@ -0,0 +1,1647 @@ +package console + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "sync/atomic" + "time" + + "connectrpc.com/connect" + corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" + "go.uber.org/zap" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/pages" + "github.com/OpenAudio/go-openaudio/pkg/etl" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "github.com/OpenAudio/go-openaudio/pkg/sdk" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/jackc/pgx/v5/pgtype" + "github.com/labstack/echo/v4" + "golang.org/x/sync/errgroup" + + "embed" +) + +//go:embed assets/css +var cssFS embed.FS + +//go:embed assets/images +var imagesFS embed.FS + +//go:embed assets/js +var jsFS embed.FS + +type Console struct { + env string + e *echo.Echo + etl *etl.ETLService + logger *zap.Logger + trustedNode *sdk.OpenAudioSDK + latestTrustedBlock atomic.Int64 + lastRefreshTime atomic.Int64 // Unix timestamp of last refresh + refreshInterval time.Duration // How often to refresh + dashboardCache *Cache[*pages.DashboardProps] +} + +func NewConsole(etl *etl.ETLService, e *echo.Echo, env string) *Console { + if e == nil { + e = echo.New() + } + if env == "" { + env = "prod" + } + + trustedNodeURL := "" + + switch env { + case "prod", "production", "mainnet": + trustedNodeURL = "creatornode.audius.co" + case "staging", "stage", "testnet": + trustedNodeURL = "creatornode11.staging.audius.co" + case "dev": + trustedNodeURL = "node2.oap.devnet" + } + + return &Console{ + etl: etl, + e: e, + logger: zap.NewNop().With(zap.String("service", "console")), + env: env, + trustedNode: sdk.NewOpenAudioSDK(trustedNodeURL), + refreshInterval: 10 * time.Second, + } +} + +func (con *Console) Initialize() { + // Initialize dashboard cache with 5 second refresh rate + con.dashboardCache = NewCache(con.buildDashboardProps, 5*time.Second, con.logger.With(zap.String("service", "dashboard-cache"))) + + // Start background refreshers (but not the dashboard cache yet - that needs ETL to be ready) + go con.refreshTrustedBlock() + + e := con.e + e.HideBanner = true + + // Add environment context middleware + envMiddleware := func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + // Add environment to the request context + ctx := context.WithValue(c.Request().Context(), "env", con.env) + c.SetRequest(c.Request().WithContext(ctx)) + return next(c) + } + } + + // Add cache control middleware for static assets + cacheControl := func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + path := c.Request().URL.Path + // Only apply caching to image files + if strings.HasPrefix(path, "/assets/") && (strings.HasSuffix(path, ".svg") || strings.HasSuffix(path, ".png") || strings.HasSuffix(path, ".jpg") || strings.HasSuffix(path, ".jpeg") || strings.HasSuffix(path, ".gif")) { + c.Response().Header().Set("Cache-Control", "public, max-age=604800") // Cache for 1 week + } + return next(c) + } + } + + cssHandler := echo.MustSubFS(cssFS, "assets/css") + imagesHandler := echo.MustSubFS(imagesFS, "assets/images") + jsHandler := echo.MustSubFS(jsFS, "assets/js") + e.StaticFS("/assets/css", cssHandler) + e.StaticFS("/assets/images", imagesHandler) + e.StaticFS("/assets/js", jsHandler) + + // Apply middlewares + e.Use(cacheControl) + e.Use(envMiddleware) + + e.GET("/", con.Dashboard) + e.GET("/hello", con.Hello) + + e.GET("/validators", con.Validators) + e.GET("/validator/:address", con.Validator) + e.GET("/validators/uptime", con.ValidatorsUptime) + e.GET("/validators/uptime/:rollupid", con.ValidatorsUptimeByRollup) + + e.GET("/rollups", con.Rollups) + + e.GET("/blocks", con.Blocks) + e.GET("/block/:height", con.Block) + + e.GET("/transactions", con.Transactions) + e.GET("/transaction/:hash", con.Transaction) + + e.GET("/account/:address", con.Account) + e.GET("/account/:address/transactions", con.stubRoute) + e.GET("/account/:address/uploads", con.stubRoute) + e.GET("/account/:address/releases", con.stubRoute) + + e.GET("/content", con.Content) + e.GET("/content/:address", con.Content) + + e.GET("/release/:address", con.stubRoute) + + e.GET("/search", con.Search) + + // SSE endpoints + e.GET("/sse/events", con.LiveEventsSSE) + + // HTMX Fragment routes + e.GET("/fragments/stats-header", con.StatsHeaderFragment) + e.GET("/fragments/tps", con.TPSFragment) + e.GET("/fragments/total-transactions", con.TotalTransactionsFragment) +} + +func (con *Console) Run() error { + g, ctx := errgroup.WithContext(context.Background()) + + g.Go(func() error { + info, err := con.trustedNode.Core.GetNodeInfo(context.Background(), &connect.Request[corev1.GetNodeInfoRequest]{}) + if err != nil { + con.logger.Warn("Failed to initialize node info", zap.Error(err)) + con.latestTrustedBlock.Store(0) + } else { + con.logger.Info("Initialized node info", zap.Int64("height", info.Msg.CurrentHeight)) + con.latestTrustedBlock.Store(info.Msg.CurrentHeight) + con.lastRefreshTime.Store(time.Now().Unix()) + } + + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + + for range ticker.C { + info, err := con.trustedNode.Core.GetNodeInfo(context.Background(), &connect.Request[corev1.GetNodeInfoRequest]{}) + if err != nil { + con.logger.Warn("Failed to get node info", zap.Error(err)) + continue + } + con.latestTrustedBlock.Store(info.Msg.CurrentHeight) + con.lastRefreshTime.Store(time.Now().Unix()) + con.logger.Info("Updated node info", zap.Int64("height", info.Msg.CurrentHeight)) + } + return nil + }) + + g.Go(func() error { + if err := con.etl.Run(); err != nil { + return err + } + return nil + }) + + // Start dashboard cache refresh after ETL is ready + g.Go(func() error { + // Wait a moment for ETL to initialize + time.Sleep(2 * time.Second) + con.dashboardCache.StartRefresh(ctx) + return nil + }) + + g.Go(func() error { + if err := con.e.Start(":3000"); err != nil { + return err + } + return nil + }) + + return g.Wait() +} + +func (con *Console) Stop() { + con.e.Shutdown(context.Background()) +} + +// getTransactionsWithBlockHeights is a helper method to get transactions with their block heights +func (con *Console) getTransactionsWithBlockHeights(ctx context.Context, limit, offset int32) ([]*db.EtlTransaction, map[string]int64, error) { + // Use GetTransactionsByPage for proper offset-based pagination + transactions, err := con.etl.GetDB().GetTransactionsByPage(ctx, db.GetTransactionsByPageParams{ + Limit: limit, + Offset: offset, + }) + if err != nil { + return nil, nil, err + } + + // Convert to pointers and create block heights map + txPointers := make([]*db.EtlTransaction, len(transactions)) + blockHeights := make(map[string]int64) + for i := range transactions { + txPointers[i] = &transactions[i] + blockHeights[transactions[i].TxHash] = transactions[i].BlockHeight + } + + return txPointers, blockHeights, nil +} + +func (con *Console) Hello(c echo.Context) error { + param := "sup" + if name := c.QueryParam("name"); name != "" { + param = name + } + p := pages.Hello(param) + + // Use context with environment + ctx := c.Request().Context() + return p.Render(ctx, c.Response().Writer) +} + +// buildDashboardProps builds the dashboard props by querying the database +// This is used by the cache to refresh dashboard data periodically +func (con *Console) buildDashboardProps(ctx context.Context) (*pages.DashboardProps, error) { + // Get dashboard transaction stats from materialized view + txStats, err := con.etl.GetDB().GetDashboardTransactionStats(ctx) + if err != nil { + con.logger.Warn("Failed to get dashboard transaction stats", zap.Error(err)) + // Use fallback empty stats + txStats = db.MvDashboardTransactionStat{} + } + + // Get transaction type breakdown from materialized view + txTypes, err2 := con.etl.GetDB().GetDashboardTransactionTypes(ctx) + if err2 != nil { + con.logger.Warn("Failed to get dashboard transaction types", zap.Error(err2)) + txTypes = []db.MvDashboardTransactionType{} + } + + // Get latest indexed block + latestBlockHeight, err := con.etl.GetDB().GetLatestIndexedBlock(ctx) + if err != nil { + con.logger.Warn("Failed to get latest block height", zap.Error(err)) + latestBlockHeight = 0 + } + + // Get latest trusted block height from the trusted node + trustedBlockHeight := int64(con.latestTrustedBlock.Load()) + + // Calculate sync status - consider synced if within 60 blocks of the head + const syncThreshold = 60 + var isSyncing bool + var blockDelta int64 + syncProgressPercentage := float64(100) // Default to fully synced + + if trustedBlockHeight >= 0 && latestBlockHeight >= 0 { + blockDelta = trustedBlockHeight - latestBlockHeight + isSyncing = blockDelta > syncThreshold + + if isSyncing && trustedBlockHeight > 0 { + syncProgressPercentage = (float64(latestBlockHeight) / float64(trustedBlockHeight)) * 100 + // Ensure percentage doesn't exceed 100 + if syncProgressPercentage > 100 { + syncProgressPercentage = 100 + } + } else { + syncProgressPercentage = 100 // Fully synced + } + } else { + // If we don't have trusted block info, assume synced + isSyncing = false + blockDelta = 0 + syncProgressPercentage = 100 + } + + // Get latest SLA rollup for BPS/TPS data + var bps, tps float64 = 0, 0 + var avgBlockTime float32 = 0 + latestSlaRollup, err := con.etl.GetDB().GetLatestSlaRollup(ctx) + if err != nil { + con.logger.Debug("Failed to get latest SLA rollup", zap.Error(err)) + // Fall back to default values + bps = 0.5 + tps = 0.1 + avgBlockTime = 2.0 + } else { + bps = latestSlaRollup.Bps + tps = latestSlaRollup.Tps + // Calculate average block time from BPS (if BPS > 0) + if bps > 0 { + avgBlockTime = float32(1.0 / bps) + } else { + avgBlockTime = 2.0 // Default 2 seconds + } + } + + // Get some recent transactions for the dashboard + transactions, blockHeights, err := con.getTransactionsWithBlockHeights(ctx, 10, 0) + if err != nil { + con.logger.Warn("Failed to get transactions", zap.Error(err)) + transactions = []*db.EtlTransaction{} + blockHeights = make(map[string]int64) + } + + blocks, err := con.etl.GetDB().GetBlocksByPage(ctx, db.GetBlocksByPageParams{ + Limit: 10, + Offset: 0, + }) + if err != nil { + con.logger.Warn("Failed to get blocks", zap.Error(err)) + blocks = []db.EtlBlock{} + } + + blockPointers := make([]*db.EtlBlock, len(blocks)) + for i := range blocks { + blockPointers[i] = &blocks[i] + } + + // Get active validator count + validatorCount, err := con.etl.GetDB().GetActiveValidatorCount(ctx) + if err != nil { + con.logger.Warn("Failed to get validator count", zap.Error(err)) + validatorCount = 0 + } + + // Build stats using materialized view data + stats := &pages.DashboardStats{ + CurrentBlockHeight: latestBlockHeight, + ChainID: con.etl.ChainID, + BPS: bps, + TPS: tps, + TotalTransactions: txStats.TotalTransactions, + ValidatorCount: validatorCount, + LatestBlock: nil, // TODO: Implement + RecentProposers: nil, // TODO: Implement + IsSyncing: isSyncing, + LatestIndexedHeight: latestBlockHeight, + LatestChainHeight: trustedBlockHeight, + BlockDelta: blockDelta, + TotalTransactions24h: txStats.Transactions24h, + TotalTransactionsPrevious24h: txStats.TransactionsPrevious24h, + TotalTransactions7d: txStats.Transactions7d, + TotalTransactions30d: txStats.Transactions30d, + AvgBlockTime: avgBlockTime, + } + + // Convert materialized view transaction types to template format + maxTypes := 5 // only show up to 5 transaction types + if len(txTypes) < maxTypes { + maxTypes = len(txTypes) + } + transactionBreakdown := make([]*pages.TransactionTypeBreakdown, maxTypes) + colors := []string{"bg-blue-500", "bg-green-500", "bg-purple-500", "bg-yellow-500", "bg-red-500", "bg-indigo-500", "bg-pink-500"} + for i := 0; i < maxTypes; i++ { + txType := txTypes[i] + color := colors[i%len(colors)] + transactionBreakdown[i] = &pages.TransactionTypeBreakdown{ + Type: txType.TxType, + Count: txType.TransactionCount, + Color: color, + } + } + + // Get SLA performance data for the chart (most recent 50 rollups) + slaRollupsData, err := con.etl.GetDB().GetSlaRollupsWithPagination(ctx, db.GetSlaRollupsWithPaginationParams{ + Limit: 50, + Offset: 0, + }) + if err != nil { + con.logger.Warn("Failed to get SLA rollups for performance chart", zap.Error(err)) + slaRollupsData = []db.EtlSlaRollup{} + } + + // Build SLA performance data points for chart - Initialize as empty slice, not nil + slaPerformanceData := make([]*pages.SLAPerformanceDataPoint, 0) + + // Build chart data if we have any rollups + if len(slaRollupsData) > 0 { + + // Extract rollup IDs for healthy validators query + rollupIDs := make([]int32, len(slaRollupsData)) + for i, rollup := range slaRollupsData { + rollupIDs[i] = rollup.ID + } + + // Get healthy validator counts for these rollups + healthyValidatorData, err := con.etl.GetDB().GetHealthyValidatorCountsForRollups(ctx, rollupIDs) + if err != nil { + con.logger.Warn("Failed to get healthy validator counts", zap.Error(err)) + healthyValidatorData = []db.GetHealthyValidatorCountsForRollupsRow{} + } + + // Build a map for quick lookup of healthy validator counts + healthyValidatorsMap := make(map[int32]int32) + for _, hvData := range healthyValidatorData { + if healthyCount, ok := hvData.HealthyValidators.(int64); ok { + healthyValidatorsMap[hvData.RollupID] = int32(healthyCount) + } else { + healthyValidatorsMap[hvData.RollupID] = 0 + } + } + + // Filter out invalid rollups and build valid data points + validDataPoints := make([]*pages.SLAPerformanceDataPoint, 0, len(slaRollupsData)) + for _, rollup := range slaRollupsData { + // Skip invalid rollups + if rollup.ID <= 0 || !rollup.CreatedAt.Valid || rollup.BlockHeight <= 0 || + rollup.Bps < 0 || rollup.Tps < 0 || + rollup.BlockStart < 0 || rollup.BlockEnd <= 0 || rollup.BlockStart > rollup.BlockEnd { + continue + } + + // Use the validator count from the rollup data itself + validatorCount := rollup.ValidatorCount + if validatorCount <= 0 { + validatorCount = 1 // Minimum of 1 validator + } + + // Get healthy validators count for this rollup + healthyValidators := int32(0) + if healthyCount, exists := healthyValidatorsMap[rollup.ID]; exists { + healthyValidators = healthyCount + } + + // Create a fully validated data point + dataPoint := &pages.SLAPerformanceDataPoint{ + RollupID: rollup.ID, + BlockHeight: rollup.BlockHeight, + Timestamp: rollup.CreatedAt.Time.Format(time.RFC3339), + ValidatorCount: validatorCount, + HealthyValidators: healthyValidators, + BPS: rollup.Bps, + TPS: rollup.Tps, + BlockStart: rollup.BlockStart, + BlockEnd: rollup.BlockEnd, + } + + // Extra safety check - ensure we're not adding nil + if dataPoint != nil { + validDataPoints = append(validDataPoints, dataPoint) + } + } + + // Use the data if we have any valid points after filtering + if len(validDataPoints) > 0 { + slaPerformanceData = validDataPoints + } + } + + // Convert rollups to pointers for template + recentSLARollups := make([]*db.EtlSlaRollup, len(slaRollupsData)) + for i := range slaRollupsData { + recentSLARollups[i] = &slaRollupsData[i] + } + + props := &pages.DashboardProps{ + Stats: stats, + TransactionBreakdown: transactionBreakdown, + RecentBlocks: blockPointers, + RecentTransactions: transactions, + RecentSLARollups: recentSLARollups, + SLAPerformanceData: slaPerformanceData, + BlockHeights: blockHeights, + SyncProgressPercentage: syncProgressPercentage, + } + + return props, nil +} + +func (con *Console) Dashboard(c echo.Context) error { + // Get cached dashboard props + props := con.dashboardCache.Get() + + // If cache isn't ready yet, build props on-demand + if props == nil { + var err error + props, err = con.buildDashboardProps(c.Request().Context()) + if err != nil { + con.logger.Error("Failed to build dashboard props", zap.Error(err)) + return c.String(http.StatusInternalServerError, "Failed to load dashboard") + } + } + + // Render the dashboard + p := pages.Dashboard(*props) + return p.Render(c.Request().Context(), c.Response().Writer) +} + +func (con *Console) Validators(c echo.Context) error { + // Parse query parameters + pageParam := c.QueryParam("page") + countParam := c.QueryParam("count") + queryType := c.QueryParam("type") // "active", "registrations", "deregistrations" + endpointFilter := c.QueryParam("endpoint_filter") + + page := int32(1) // default to page 1 + if pageParam != "" { + if parsedPage, err := strconv.ParseInt(pageParam, 10, 32); err == nil && parsedPage > 0 { + page = int32(parsedPage) + } + } + + count := int32(50) // default to 50 per page + if countParam != "" { + if parsedCount, err := strconv.ParseInt(countParam, 10, 32); err == nil && parsedCount > 0 && parsedCount <= 200 { + count = int32(parsedCount) + } + } + + // Default to active validators + if queryType == "" { + queryType = "active" + } + + // Calculate offset from page number + offset := (page - 1) * count + + var validators []*db.EtlValidator + validatorUptimeMap := make(map[string][]*db.EtlSlaNodeReport) + + ctx := c.Request().Context() + + switch queryType { + case "active": + // Get active validators + validatorsData, err := con.etl.GetDB().GetActiveValidators(ctx, db.GetActiveValidatorsParams{ + Limit: count, + Offset: offset, + }) + if err != nil { + con.logger.Warn("Failed to get active validators", zap.Error(err)) + validatorsData = []db.EtlValidator{} + } + + // Convert to pointers and apply endpoint filter + for i := range validatorsData { + if endpointFilter == "" || strings.Contains(strings.ToLower(validatorsData[i].Endpoint), strings.ToLower(endpointFilter)) { + validators = append(validators, &validatorsData[i]) + + // Get uptime data for each validator + reports, err := con.etl.GetDB().GetSlaNodeReportsByAddress(ctx, db.GetSlaNodeReportsByAddressParams{ + Lower: validatorsData[i].CometAddress, + Limit: 5, // Get last 5 SLA reports + }) + if err != nil { + con.logger.Warn("Failed to get SLA reports", zap.String("address", validatorsData[i].CometAddress), zap.Error(err)) + } else { + reportPointers := make([]*db.EtlSlaNodeReport, len(reports)) + for j := range reports { + reportPointers[j] = &reports[j] + } + validatorUptimeMap[validatorsData[i].CometAddress] = reportPointers + } + } + } + + case "registrations": + // Get validator registrations - this will need a different approach since it's a different table + regsData, err := con.etl.GetDB().GetValidatorRegistrations(ctx, db.GetValidatorRegistrationsParams{ + Limit: count, + Offset: offset, + }) + if err != nil { + con.logger.Warn("Failed to get validator registrations", zap.Error(err)) + regsData = []db.GetValidatorRegistrationsRow{} + } + + // Convert registrations to validator format for template + for i := range regsData { + validator := &db.EtlValidator{ + ID: regsData[i].ID, + Address: regsData[i].Address, + Endpoint: regsData[i].Endpoint, // Already a string + CometAddress: regsData[i].CometAddress, + NodeType: regsData[i].NodeType, // Already a string + Spid: regsData[i].Spid, // Already a string + VotingPower: regsData[i].VotingPower, // Already int64 + Status: "registered", + RegisteredAt: regsData[i].BlockHeight, + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, // Manual timestamp + } + if endpointFilter == "" || strings.Contains(strings.ToLower(validator.Endpoint), strings.ToLower(endpointFilter)) { + validators = append(validators, validator) + } + } + + case "deregistrations": + // Get validator deregistrations + deregsData, err := con.etl.GetDB().GetValidatorDeregistrations(ctx, db.GetValidatorDeregistrationsParams{ + Limit: count, + Offset: offset, + }) + if err != nil { + con.logger.Warn("Failed to get validator deregistrations", zap.Error(err)) + deregsData = []db.GetValidatorDeregistrationsRow{} + } + + // Convert deregistrations to validator format for template + for i := range deregsData { + endpoint := "" + if deregsData[i].Endpoint.Valid { + endpoint = deregsData[i].Endpoint.String + } + nodeType := "" + if deregsData[i].NodeType.Valid { + nodeType = deregsData[i].NodeType.String + } + spid := "" + if deregsData[i].Spid.Valid { + spid = deregsData[i].Spid.String + } + votingPower := int64(0) + if deregsData[i].VotingPower.Valid { + votingPower = deregsData[i].VotingPower.Int64 + } + + validator := &db.EtlValidator{ + ID: deregsData[i].ID, + Address: "", + Endpoint: endpoint, + CometAddress: deregsData[i].CometAddress, + NodeType: nodeType, + Spid: spid, + VotingPower: votingPower, + Status: "deregistered", + RegisteredAt: deregsData[i].BlockHeight, + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, // placeholder + } + if endpointFilter == "" || strings.Contains(strings.ToLower(validator.Endpoint), strings.ToLower(endpointFilter)) { + validators = append(validators, validator) + } + } + } + + // Calculate pagination state + hasNext := len(validators) == int(count) // Simple check - if we got the full limit, there might be more + hasPrev := page > 1 + + props := pages.ValidatorsProps{ + Validators: validators, + ValidatorUptimeMap: validatorUptimeMap, + CurrentPage: page, + HasNext: hasNext, + HasPrev: hasPrev, + PageSize: count, + QueryType: queryType, + EndpointFilter: endpointFilter, + } + + p := pages.Validators(props) + return p.Render(ctx, c.Response().Writer) +} + +func (con *Console) Validator(c echo.Context) error { + address := c.Param("address") + if address == "" { + return c.String(http.StatusBadRequest, "Validator address required") + } + + ctx := c.Request().Context() + + // Get validator by address + validator, err := con.etl.GetDB().GetValidatorByAddress(ctx, address) + if err != nil { + return c.String(http.StatusNotFound, fmt.Sprintf("Validator not found: %s", address)) + } + + // Get SLA rollup reports for this validator + reports, err := con.etl.GetDB().GetSlaNodeReportsByAddress(ctx, db.GetSlaNodeReportsByAddressParams{ + Lower: validator.CometAddress, + Limit: 10, // Get last 10 reports + }) + if err != nil { + con.logger.Warn("Failed to get SLA reports for validator", zap.String("address", address), zap.Error(err)) + reports = []db.EtlSlaNodeReport{} + } + + // Convert reports to pointers + rollups := make([]*db.EtlSlaNodeReport, len(reports)) + for i := range reports { + rollups[i] = &reports[i] + } + + // TODO: Get validator events from registration/deregistration tables + // For now, create empty events slice + events := []*pages.ValidatorEvent{} + + props := pages.ValidatorProps{ + Validator: &validator, + Events: events, + Rollups: rollups, + } + + p := pages.Validator(props) + return p.Render(ctx, c.Response().Writer) +} + +func (con *Console) ValidatorsUptime(c echo.Context) error { + // Parse query parameters for pagination + pageParam := c.QueryParam("page") + countParam := c.QueryParam("count") + + page := int32(1) // default to page 1 + if pageParam != "" { + if parsedPage, err := strconv.ParseInt(pageParam, 10, 32); err == nil && parsedPage > 0 { + page = int32(parsedPage) + } + } + + count := int32(20) // default to 20 per page for rollups + if countParam != "" { + if parsedCount, err := strconv.ParseInt(countParam, 10, 32); err == nil && parsedCount > 0 && parsedCount <= 100 { + count = int32(parsedCount) + } + } + + // Calculate offset from page number + offset := (page - 1) * count + + ctx := c.Request().Context() + + // Get paginated SLA rollups + rollupsData, err := con.etl.GetDB().GetSlaRollupsWithPagination(ctx, db.GetSlaRollupsWithPaginationParams{ + Limit: count, + Offset: offset, + }) + if err != nil { + con.logger.Warn("Failed to get SLA rollups", zap.Error(err)) + rollupsData = []db.EtlSlaRollup{} + } + + // Convert to pointers + rollups := make([]*db.EtlSlaRollup, len(rollupsData)) + for i := range rollupsData { + rollups[i] = &rollupsData[i] + } + + // Calculate pagination state + hasNext := len(rollupsData) == int(count) + hasPrev := page > 1 + + // TODO: Get actual total count from database + totalCount := int64(len(rollupsData)) // Placeholder + + props := pages.RollupsProps{ + Rollups: rollups, + RollupValidators: []*db.EtlSlaNodeReport{}, // Not needed for rollups list view + CurrentPage: page, + HasNext: hasNext, + HasPrev: hasPrev, + PageSize: count, + TotalCount: totalCount, + } + + p := pages.Rollups(props) + return p.Render(ctx, c.Response().Writer) +} + +func (con *Console) ValidatorsUptimeByRollup(c echo.Context) error { + rollupIDParam := c.Param("rollupid") + if rollupIDParam == "" { + return c.String(http.StatusBadRequest, "Rollup ID required") + } + + rollupID, err := strconv.ParseInt(rollupIDParam, 10, 32) + if err != nil { + return c.String(http.StatusBadRequest, "Invalid rollup ID") + } + + ctx := c.Request().Context() + + // First, get the actual SLA rollup data to get tx_hash, created_at, block quota, etc. + rollupInfo, err := con.etl.GetDB().GetSlaRollupById(ctx, int32(rollupID)) + if err != nil { + con.logger.Warn("Failed to get SLA rollup by ID", zap.Int64("rollupID", rollupID), zap.Error(err)) + return c.String(http.StatusNotFound, fmt.Sprintf("SLA rollup not found: %d", rollupID)) + } + + // Get validators for this specific SLA rollup + validatorsData, err := con.etl.GetDB().GetValidatorsForSlaRollup(ctx, int32(rollupID)) + if err != nil { + con.logger.Warn("Failed to get validators for SLA rollup", zap.Int64("rollupID", rollupID), zap.Error(err)) + validatorsData = []db.GetValidatorsForSlaRollupRow{} + } + + // Calculate challenge statistics dynamically for this rollup's block range + // This ensures we get the current accurate data instead of potentially stale pre-calculated values + challengeStats, err := con.etl.GetDB().GetChallengeStatisticsForBlockRange(ctx, db.GetChallengeStatisticsForBlockRangeParams{ + Height: rollupInfo.BlockStart, + Height_2: rollupInfo.BlockEnd, + }) + if err != nil { + con.logger.Warn("Failed to get challenge statistics", zap.Int64("rollupID", rollupID), zap.Error(err)) + challengeStats = []db.GetChallengeStatisticsForBlockRangeRow{} + } + + // Create a map for quick lookup of challenge statistics by address + challengeStatsMap := make(map[string]db.GetChallengeStatisticsForBlockRangeRow) + for _, stat := range challengeStats { + challengeStatsMap[stat.Address] = stat + } + + // Build validator uptime info for each validator + validators := make([]*pages.ValidatorUptimeInfo, 0, len(validatorsData)) + for i := range validatorsData { + validator := &db.EtlValidator{ + ID: validatorsData[i].ID, + Address: validatorsData[i].Address, + Endpoint: validatorsData[i].Endpoint, + CometAddress: validatorsData[i].CometAddress, + NodeType: validatorsData[i].NodeType, + Spid: validatorsData[i].Spid, + VotingPower: validatorsData[i].VotingPower, + Status: validatorsData[i].Status, + RegisteredAt: validatorsData[i].RegisteredAt, + CreatedAt: validatorsData[i].CreatedAt, + UpdatedAt: validatorsData[i].UpdatedAt, + } + + // Create a full SLA report for this rollup with all the required fields + var reportPointers []*db.EtlSlaNodeReport + slaReport := &db.EtlSlaNodeReport{ + SlaRollupID: int32(rollupID), + Address: validatorsData[i].CometAddress, + NumBlocksProposed: 0, // Default to 0 + ChallengesReceived: 0, // Default to 0 + ChallengesFailed: 0, // Default to 0 + TxHash: rollupInfo.TxHash, + CreatedAt: rollupInfo.CreatedAt, + BlockHeight: rollupInfo.BlockHeight, + } + + // Override with actual data if validator has report data (for blocks proposed) + if validatorsData[i].NumBlocksProposed.Valid { + slaReport.NumBlocksProposed = validatorsData[i].NumBlocksProposed.Int32 + } + + // Use dynamically calculated challenge statistics instead of potentially stale pre-calculated values + if stat, exists := challengeStatsMap[validatorsData[i].CometAddress]; exists { + slaReport.ChallengesReceived = int32(stat.ChallengesReceived) + slaReport.ChallengesFailed = int32(stat.ChallengesFailed) + } + + reportPointers = []*db.EtlSlaNodeReport{slaReport} + + validators = append(validators, &pages.ValidatorUptimeInfo{ + Validator: validator, + RecentRollups: reportPointers, + }) + } + + props := pages.ValidatorsUptimeByRollupProps{ + Validators: validators, + RollupID: int32(rollupID), + RollupData: &rollupInfo, + } + + p := pages.ValidatorsUptimeByRollup(props) + return p.Render(ctx, c.Response().Writer) +} + +func (con *Console) Rollups(c echo.Context) error { + // Parse query parameters for pagination + pageParam := c.QueryParam("page") + countParam := c.QueryParam("count") + + page := int32(1) // default to page 1 + if pageParam != "" { + if parsedPage, err := strconv.ParseInt(pageParam, 10, 32); err == nil && parsedPage > 0 { + page = int32(parsedPage) + } + } + + count := int32(20) // default to 20 per page + if countParam != "" { + if parsedCount, err := strconv.ParseInt(countParam, 10, 32); err == nil && parsedCount > 0 && parsedCount <= 100 { + count = int32(parsedCount) + } + } + + // Calculate offset from page number + offset := (page - 1) * count + + ctx := c.Request().Context() + + // Get paginated SLA rollups + rollupsData, err := con.etl.GetDB().GetSlaRollupsWithPagination(ctx, db.GetSlaRollupsWithPaginationParams{ + Limit: count, + Offset: offset, + }) + if err != nil { + con.logger.Warn("Failed to get SLA rollups", zap.Error(err)) + rollupsData = []db.EtlSlaRollup{} + } + + // Convert to pointers + rollups := make([]*db.EtlSlaRollup, len(rollupsData)) + for i := range rollupsData { + rollups[i] = &rollupsData[i] + } + + // Calculate pagination state + hasNext := len(rollupsData) == int(count) + hasPrev := page > 1 + + // TODO: Get actual total count from database + totalCount := int64(len(rollupsData)) // Placeholder + + props := pages.RollupsProps{ + Rollups: rollups, + RollupValidators: []*db.EtlSlaNodeReport{}, // Not needed for rollups list view + CurrentPage: page, + HasNext: hasNext, + HasPrev: hasPrev, + PageSize: count, + TotalCount: totalCount, + } + + p := pages.Rollups(props) + return p.Render(ctx, c.Response().Writer) +} + +func (con *Console) Blocks(c echo.Context) error { + // Parse query parameters + pageParam := c.QueryParam("page") + countParam := c.QueryParam("count") + + page := int32(1) // default to page 1 + if pageParam != "" { + if parsedPage, err := strconv.ParseInt(pageParam, 10, 32); err == nil && parsedPage > 0 { + page = int32(parsedPage) + } + } + + count := int32(50) // default to 50 per page + if countParam != "" { + if parsedCount, err := strconv.ParseInt(countParam, 10, 32); err == nil && parsedCount > 0 && parsedCount <= 200 { + count = int32(parsedCount) + } + } + + // Calculate offset from page number + offset := (page - 1) * count + + // Get blocks from database + blocksData, err := con.etl.GetDB().GetBlocksByPage(c.Request().Context(), db.GetBlocksByPageParams{ + Limit: count, + Offset: offset, + }) + if err != nil { + con.logger.Warn("Failed to get blocks", zap.Error(err)) + blocksData = []db.EtlBlock{} + } + + // Convert to pointers + blocks := make([]*db.EtlBlock, len(blocksData)) + blockTransactions := make([]int32, len(blocksData)) + for i := range blocksData { + blocks[i] = &blocksData[i] + // Get transaction count for each block + txCount, err := con.etl.GetDB().GetBlockTransactionCount(c.Request().Context(), blocksData[i].BlockHeight) + if err != nil { + con.logger.Warn("Failed to get transaction count for block", zap.Int64("height", blocksData[i].BlockHeight), zap.Error(err)) + txCount = 0 + } + blockTransactions[i] = int32(txCount) + } + + // Calculate pagination state + hasNext := len(blocks) == int(count) // Simple check - if we got the full limit, there might be more + hasPrev := page > 1 + + props := pages.BlocksProps{ + Blocks: blocks, + BlockTransactions: blockTransactions, + CurrentPage: page, + HasNext: hasNext, + HasPrev: hasPrev, + PageSize: count, + } + + p := pages.Blocks(props) + ctx := c.Request().Context() + return p.Render(ctx, c.Response().Writer) +} + +func (con *Console) Transactions(c echo.Context) error { + // Parse query parameters + pageParam := c.QueryParam("page") + countParam := c.QueryParam("count") + + page := int32(1) // default to page 1 + if pageParam != "" { + if parsedPage, err := strconv.ParseInt(pageParam, 10, 32); err == nil && parsedPage > 0 { + page = int32(parsedPage) + } + } + + count := int32(50) // default to 50 per page + if countParam != "" { + if parsedCount, err := strconv.ParseInt(countParam, 10, 32); err == nil && parsedCount > 0 && parsedCount <= 200 { + count = int32(parsedCount) + } + } + + // Calculate offset from page number + offset := (page - 1) * count + + transactions, blockHeights, err := con.getTransactionsWithBlockHeights(c.Request().Context(), count, offset) + if err != nil { + return c.String(http.StatusInternalServerError, "Failed to get transactions") + } + + // Calculate pagination state + hasNext := len(transactions) == int(count) // Simple check - if we got the full limit, there might be more + hasPrev := page > 1 + + props := pages.TransactionsProps{ + Transactions: transactions, + BlockHeights: blockHeights, + CurrentPage: page, + HasNext: hasNext, + HasPrev: hasPrev, + PageSize: count, + } + + p := pages.Transactions(props) + ctx := c.Request().Context() + return p.Render(ctx, c.Response().Writer) +} + +func (con *Console) Content(c echo.Context) error { + p := pages.Content() + ctx := c.Request().Context() + return p.Render(ctx, c.Response().Writer) +} + +func (con *Console) Block(c echo.Context) error { + height, err := strconv.ParseInt(c.Param("height"), 10, 64) + if err != nil { + return c.String(http.StatusBadRequest, "Invalid block height") + } + + ctx := c.Request().Context() + + // Get block by height + block, err := con.etl.GetDB().GetBlockByHeight(ctx, height) + if err != nil { + return c.String(http.StatusNotFound, fmt.Sprintf("Block not found at height %d", height)) + } + + // Get transactions for this block + // First get all transactions and filter by block height + // This is not the most efficient but will work for now - TODO: add GetTransactionsByBlockHeight query + transactionsData, err := con.etl.GetDB().GetTransactionsByPage(ctx, db.GetTransactionsByPageParams{ + Limit: 1000, // Get a large number to ensure we get all for this block + Offset: 0, + }) + if err != nil { + con.logger.Warn("Failed to get transactions", zap.Error(err)) + transactionsData = []db.EtlTransaction{} + } + + // Filter transactions for this specific block height + var blockTransactions []*db.EtlTransaction + for i := range transactionsData { + if transactionsData[i].BlockHeight == height { + blockTransactions = append(blockTransactions, &transactionsData[i]) + } + } + + // Create block props + props := pages.BlockProps{ + Block: &block, + Transactions: blockTransactions, + } + + p := pages.Block(props) + return p.Render(ctx, c.Response().Writer) +} + +func (con *Console) Transaction(c echo.Context) error { + txHash := c.Param("hash") + if txHash == "" { + return c.String(http.StatusBadRequest, "Transaction hash required") + } + + ctx := c.Request().Context() + + // Get transaction by hash + transaction, err := con.etl.GetDB().GetTransactionByHash(ctx, txHash) + if err != nil { + return c.String(http.StatusNotFound, fmt.Sprintf("Transaction not found: %s", txHash)) + } + + // Get block info for this transaction + block, err := con.etl.GetDB().GetBlockByHeight(ctx, transaction.BlockHeight) + if err != nil { + con.logger.Warn("Failed to get block for transaction", zap.Int64("blockHeight", transaction.BlockHeight), zap.Error(err)) + return c.String(http.StatusNotFound, fmt.Sprintf("Block not found at height %d", transaction.BlockHeight)) + } + + // Fetch transaction content based on type + var content interface{} + switch transaction.TxType { + case "play": + plays, err := con.etl.GetDB().GetPlaysByTxHash(ctx, txHash) + if err != nil { + con.logger.Warn("Failed to get plays for transaction", zap.String("txHash", txHash), zap.Error(err)) + } else if len(plays) > 0 { + // Convert to pointers for template + playPointers := make([]*db.EtlPlay, len(plays)) + for i := range plays { + playPointers[i] = &plays[i] + } + content = playPointers + } + + case "manage_entity": + entity, err := con.etl.GetDB().GetManageEntityByTxHash(ctx, txHash) + if err != nil { + con.logger.Warn("Failed to get manage entity for transaction", zap.String("txHash", txHash), zap.Error(err)) + } else { + content = &entity + } + + case "validator_registration": + registration, err := con.etl.GetDB().GetValidatorRegistrationByTxHash(ctx, txHash) + if err != nil { + con.logger.Warn("Failed to get validator registration for transaction", zap.String("txHash", txHash), zap.Error(err)) + } else { + content = ®istration + } + + case "validator_deregistration": + deregistration, err := con.etl.GetDB().GetValidatorDeregistrationByTxHash(ctx, txHash) + if err != nil { + con.logger.Warn("Failed to get validator deregistration for transaction", zap.String("txHash", txHash), zap.Error(err)) + } else { + content = &deregistration + } + + case "sla_rollup": + slaRollup, err := con.etl.GetDB().GetSlaRollupByTxHash(ctx, txHash) + if err != nil { + con.logger.Warn("Failed to get SLA rollup for transaction", zap.String("txHash", txHash), zap.Error(err)) + } else { + content = &slaRollup + } + + case "storage_proof": + storageProof, err := con.etl.GetDB().GetStorageProofByTxHash(ctx, txHash) + if err != nil { + con.logger.Warn("Failed to get storage proof for transaction", zap.String("txHash", txHash), zap.Error(err)) + } else { + content = &storageProof + } + + case "storage_proof_verification": + storageProofVerification, err := con.etl.GetDB().GetStorageProofVerificationByTxHash(ctx, txHash) + if err != nil { + con.logger.Warn("Failed to get storage proof verification for transaction", zap.String("txHash", txHash), zap.Error(err)) + } else { + content = &storageProofVerification + } + } + + // Create transaction props + props := pages.TransactionProps{ + Transaction: &transaction, + Proposer: block.ProposerAddress, + Content: content, + } + + p := pages.Transaction(props) + return p.Render(ctx, c.Response().Writer) +} + +func (con *Console) Account(c echo.Context) error { + address := c.Param("address") + if address == "" { + return c.String(http.StatusBadRequest, "Address parameter is required") + } + + isEthAddress := ethcommon.IsHexAddress(address) + if !isEthAddress { + // assume handle and query audius api + res, err := http.Get(fmt.Sprintf("https://api.audius.co/v1/users/handle/%s", address)) + if err != nil { + return c.String(http.StatusBadRequest, "Invalid address") + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return c.String(http.StatusBadRequest, "Invalid address") + } + + type audiusUser struct { + Wallet string `json:"wallet"` + } + + type audiusResponse struct { + Data audiusUser `json:"data"` + } + + var response audiusResponse + err = json.Unmarshal(body, &response) + if err != nil { + return c.String(http.StatusBadRequest, "Invalid address") + } + address = response.Data.Wallet + + return c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("/account/%s", address)) + } + + // Parse query parameters + pageParam := c.QueryParam("page") + countParam := c.QueryParam("count") + relationFilter := c.QueryParam("relation") + startDate := c.QueryParam("start_date") + endDate := c.QueryParam("end_date") + + page := int32(1) // default to page 1 + if pageParam != "" { + if parsedPage, err := strconv.ParseInt(pageParam, 10, 32); err == nil && parsedPage > 0 { + page = int32(parsedPage) + } + } + + count := int32(50) // default to 50 per page + if countParam != "" { + if parsedCount, err := strconv.ParseInt(countParam, 10, 32); err == nil && parsedCount > 0 && parsedCount <= 200 { + count = int32(parsedCount) + } + } + + // Calculate offset from page number + offset := (page - 1) * count + + ctx := c.Request().Context() + etlDB := con.etl.GetDB() + + // Parse date filters + var startTimestamp, endTimestamp pgtype.Timestamp + if startDate != "" { + if t, err := time.Parse("2006-01-02", startDate); err == nil { + startTimestamp = pgtype.Timestamp{Time: t, Valid: true} + } + } + if endDate != "" { + if t, err := time.Parse("2006-01-02", endDate); err == nil { + // Add 24 hours to include the entire end date + endTimestamp = pgtype.Timestamp{Time: t.Add(24 * time.Hour), Valid: true} + } + } + + // Get transactions for this address + transactionRows, err := etlDB.GetTransactionsByAddress(ctx, db.GetTransactionsByAddressParams{ + Lower: address, + Column2: relationFilter, // empty string means all relations + Column3: startTimestamp, + Column4: endTimestamp, + Limit: count, + Offset: offset, + }) + if err != nil { + con.logger.Error("Failed to get transactions for address", zap.String("address", address), zap.Error(err)) + return c.String(http.StatusInternalServerError, "Failed to get transactions") + } + + // Get total count for pagination + totalCount, err := etlDB.GetTransactionCountByAddress(ctx, db.GetTransactionCountByAddressParams{ + Lower: address, + Column2: relationFilter, + Column3: startTimestamp, + Column4: endTimestamp, + }) + if err != nil { + con.logger.Error("Failed to get transaction count for address", zap.String("address", address), zap.Error(err)) + return c.String(http.StatusInternalServerError, "Failed to get transaction count") + } + + // Get available relation types for filter dropdown + relationTypesRaw, err := etlDB.GetRelationTypesByAddress(ctx, address) + if err != nil { + con.logger.Error("Failed to get relation types for address", zap.String("address", address), zap.Error(err)) + // Don't fail the request, just log the error + relationTypesRaw = []interface{}{} + } + + // Convert interface{} slice to string slice + relationTypes := make([]string, len(relationTypesRaw)) + for i, rt := range relationTypesRaw { + if str, ok := rt.(string); ok { + relationTypes[i] = str + } else { + relationTypes[i] = fmt.Sprintf("%v", rt) + } + } + + // Convert transaction rows to transactions and extract relations + transactions := make([]*db.EtlTransaction, len(transactionRows)) + txRelations := make([]string, len(transactionRows)) + for i, row := range transactionRows { + transactions[i] = &db.EtlTransaction{ + ID: row.ID, + TxHash: row.TxHash, + BlockHeight: row.BlockHeight, + TxIndex: row.TxIndex, + TxType: row.TxType, + CreatedAt: row.CreatedAt, + } + // Handle relation type assertion + if str, ok := row.Relation.(string); ok { + txRelations[i] = str + } else { + txRelations[i] = fmt.Sprintf("%v", row.Relation) + } + } + + // Calculate pagination state + hasNext := int64(offset+count) < totalCount + hasPrev := page > 1 + + props := pages.AccountProps{ + Address: address, + Transactions: transactions, + TxRelations: txRelations, + CurrentPage: page, + HasNext: hasNext, + HasPrev: hasPrev, + PageSize: count, + RelationTypes: relationTypes, + CurrentFilter: relationFilter, + StartDate: startDate, + EndDate: endDate, + } + + p := pages.Account(props) + ctx = c.Request().Context() + return p.Render(ctx, c.Response().Writer) +} + +func (con *Console) stubRoute(c echo.Context) error { + return c.String(http.StatusOK, "Hello, World!") +} + +// HTMX Fragment Handlers +func (con *Console) StatsHeaderFragment(c echo.Context) error { + ctx := c.Request().Context() + + // Get latest indexed block + latestBlockHeight, err := con.etl.GetDB().GetLatestIndexedBlock(ctx) + if err != nil { + con.logger.Warn("Failed to get latest block height", zap.Error(err)) + latestBlockHeight = 0 + } + + // Get latest trusted block height from the trusted node + trustedBlockHeight := int64(con.latestTrustedBlock.Load()) + + // Calculate sync status - consider synced if within 60 blocks of the head + const syncThreshold = 60 + var isSyncing bool + var blockDelta int64 + syncProgressPercentage := float64(100) // Default to fully synced + + if trustedBlockHeight > 0 && latestBlockHeight > 0 { + blockDelta = trustedBlockHeight - latestBlockHeight + isSyncing = blockDelta > syncThreshold + + if isSyncing && trustedBlockHeight > 0 { + syncProgressPercentage = (float64(latestBlockHeight) / float64(trustedBlockHeight)) * 100 + // Ensure percentage doesn't exceed 100 + if syncProgressPercentage > 100 { + syncProgressPercentage = 100 + } + } else { + syncProgressPercentage = 100 // Fully synced + } + } else { + // If we don't have trusted block info, assume synced + isSyncing = false + blockDelta = 0 + syncProgressPercentage = 100 + } + + // Get latest SLA rollup for BPS/TPS data + var bps float64 = 0 + var avgBlockTime float32 = 0 + latestSlaRollup, err := con.etl.GetDB().GetLatestSlaRollup(ctx) + if err != nil { + con.logger.Debug("Failed to get latest SLA rollup", zap.Error(err)) + // Fall back to default values + bps = 0.5 + avgBlockTime = 2.0 + } else { + bps = latestSlaRollup.Bps + // Calculate average block time from BPS (if BPS > 0) + if bps > 0 { + avgBlockTime = float32(1.0 / bps) + } else { + avgBlockTime = 2.0 // Default 2 seconds + } + } + + // Get active validator count + validatorCount, err := con.etl.GetDB().GetActiveValidatorCount(ctx) + if err != nil { + con.logger.Warn("Failed to get validator count", zap.Error(err)) + validatorCount = 0 + } + + stats := &pages.DashboardStats{ + CurrentBlockHeight: latestBlockHeight, + ChainID: con.etl.ChainID, + BPS: bps, + ValidatorCount: validatorCount, + AvgBlockTime: avgBlockTime, + IsSyncing: isSyncing, + LatestIndexedHeight: latestBlockHeight, + LatestChainHeight: trustedBlockHeight, + BlockDelta: blockDelta, + } + + // Render the stats header fragment template + fragment := pages.StatsHeaderFragment(stats, syncProgressPercentage) + return fragment.Render(ctx, c.Response().Writer) +} + +func (con *Console) NetworkSidebarFragment(c echo.Context) error { + // TODO: Implement network sidebar fragment using database queries + return c.String(http.StatusNotImplemented, "TODO: Implement network sidebar fragment") +} + +func (con *Console) TPSFragment(c echo.Context) error { + ctx := c.Request().Context() + + // Get latest SLA rollup for TPS data + var tps float64 = 0 + latestSlaRollup, err := con.etl.GetDB().GetLatestSlaRollup(ctx) + if err != nil { + con.logger.Debug("Failed to get latest SLA rollup", zap.Error(err)) + // Fall back to default value + tps = 0.1 + } else { + tps = latestSlaRollup.Tps + } + + // Get dashboard transaction stats from materialized view + txStats, err := con.etl.GetDB().GetDashboardTransactionStats(ctx) + if err != nil { + con.logger.Warn("Failed to get dashboard transaction stats", zap.Error(err)) + txStats = db.MvDashboardTransactionStat{} + } + + stats := &pages.DashboardStats{ + TPS: tps, + TotalTransactions30d: txStats.Transactions30d, + } + + // Render the TPS fragment template + fragment := pages.TPSFragment(stats) + return fragment.Render(ctx, c.Response().Writer) +} + +func (con *Console) TotalTransactionsFragment(c echo.Context) error { + ctx := c.Request().Context() + + // Get dashboard transaction stats from materialized view + txStats, err := con.etl.GetDB().GetDashboardTransactionStats(ctx) + if err != nil { + con.logger.Warn("Failed to get dashboard transaction stats", zap.Error(err)) + txStats = db.MvDashboardTransactionStat{} + } + + stats := &pages.DashboardStats{ + TotalTransactions: txStats.TotalTransactions, + TotalTransactions24h: txStats.Transactions24h, + TotalTransactionsPrevious24h: txStats.TransactionsPrevious24h, + } + + // Render the total transactions fragment template + fragment := pages.TotalTransactionsFragment(stats) + return fragment.Render(ctx, c.Response().Writer) +} + +type SSEEvent struct { + Event string `json:"event"` + Data any `json:"data"` +} + +const sseConnectionTTL = 1 * time.Minute + +func (con *Console) LiveEventsSSE(c echo.Context) error { + c.Response().Header().Set("Content-Type", "text/event-stream") + c.Response().Header().Set("Cache-Control", "no-cache") + c.Response().Header().Set("Connection", "keep-alive") + c.Response().WriteHeader(http.StatusOK) + + flusher, ok := c.Response().Writer.(http.Flusher) + if !ok { + return nil + } + + flusher.Flush() + + // Subscribe to both block and play events from ETL pubsub + blockCh := con.etl.GetBlockPubsub().Subscribe(etl.BlockTopic, 10) + playCh := con.etl.GetPlayPubsub().Subscribe(etl.PlayTopic, 10) + + // Ensure cleanup on connection close + defer func() { + con.etl.GetBlockPubsub().Unsubscribe(etl.BlockTopic, blockCh) + con.etl.GetPlayPubsub().Unsubscribe(etl.PlayTopic, playCh) + }() + + // Throttle state for block events + var ( + latestBlock *db.EtlBlock + lastSentHeight int64 + blockTicker = time.NewTicker(1 * time.Second) + ) + defer blockTicker.Stop() + + flusher.Flush() + + timeout := time.After(sseConnectionTTL) + + for { + select { + case <-c.Request().Context().Done(): + return nil + + case <-timeout: + return nil + + case blockEvent := <-blockCh: + if blockEvent != nil { + latestBlock = blockEvent + } + + case <-blockTicker.C: + if latestBlock != nil && latestBlock.BlockHeight > lastSentHeight { + // Send block event + blockEvent := SSEEvent{ + Event: "block", + Data: map[string]interface{}{ + "height": latestBlock.BlockHeight, + "proposer": latestBlock.ProposerAddress, + "time": latestBlock.BlockTime.Time.Format(time.RFC3339), + }, + } + eventData, _ := json.Marshal(blockEvent) + fmt.Fprintf(c.Response(), "data: %s\n\n", string(eventData)) + lastSentHeight = latestBlock.BlockHeight + flusher.Flush() + } + + case play := <-playCh: + if play != nil { + // Get coordinates for the play location + if play.City != "" && play.Region != "" && play.Country != "" { + if latLong, err := con.etl.GetLocationDB().GetLatLong(c.Request().Context(), play.City, play.Region, play.Country); err == nil { + lat := latLong.Latitude + lng := latLong.Longitude + // Send play event with coordinates + playEvent := SSEEvent{ + Event: "play", + Data: map[string]interface{}{ + "lat": lat, + "lng": lng, + "timestamp": time.Now().Format(time.RFC3339), + "duration": 5, // Default 5 seconds for animation + }, + } + eventData, _ := json.Marshal(playEvent) + fmt.Fprintf(c.Response(), "data: %s\n\n", string(eventData)) + flusher.Flush() + } + } + } + + } + } +} + +func (con *Console) Search(c echo.Context) error { + query := c.QueryParam("q") + if query == "" { + return c.JSON(http.StatusOK, map[string]interface{}{ + "results": []interface{}{}, + }) + } + + // TODO: Implement search using database queries + return c.JSON(http.StatusOK, map[string]interface{}{ + "results": []interface{}{}, + }) +} + +// refreshTrustedBlock refreshes the trusted block height every 10 seconds +func (con *Console) refreshTrustedBlock() { + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + + for range ticker.C { + info, err := con.trustedNode.Core.GetNodeInfo(context.Background(), &connect.Request[corev1.GetNodeInfoRequest]{}) + if err != nil { + con.logger.Warn("Failed to refresh node info", zap.Error(err)) + // Use the cached value if refresh fails + } else { + con.latestTrustedBlock.Store(info.Msg.CurrentHeight) + con.lastRefreshTime.Store(time.Now().Unix()) + } + } +} diff --git a/pkg/console/templates/layouts/base.templ b/pkg/console/templates/layouts/base.templ new file mode 100644 index 00000000..c17e7368 --- /dev/null +++ b/pkg/console/templates/layouts/base.templ @@ -0,0 +1,32 @@ +package layouts + +import "fmt" + +templ Base(pageTitle string) { + + { fmt.Sprintf("%s | Open Audio Explorer", pageTitle) } + + + + + + + + + + + + + + + @Frame(pageTitle) { + { children... } + } + + +} diff --git a/pkg/console/templates/layouts/base_templ.go b/pkg/console/templates/layouts/base_templ.go new file mode 100644 index 00000000..30cf6cae --- /dev/null +++ b/pkg/console/templates/layouts/base_templ.go @@ -0,0 +1,81 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package layouts + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "fmt" + +func Base(pageTitle string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s | Open Audio Explorer", pageTitle)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `layouts/base.templ`, Line: 7, Col: 61} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Frame(pageTitle).Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/layouts/frame.templ b/pkg/console/templates/layouts/frame.templ new file mode 100644 index 00000000..37d45117 --- /dev/null +++ b/pkg/console/templates/layouts/frame.templ @@ -0,0 +1,111 @@ +package layouts + +import "context" + +func getEnvFromContext(ctx context.Context) string { + if env, ok := ctx.Value("env").(string); ok { + return env + } + return "prod" // fallback +} + +templ Frame(pageTitle string) { +
+ +
+
+
+ + Audius Logo +

Open Audio Explorer

+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+ { children... } +
+
+ +
+} diff --git a/pkg/console/templates/layouts/frame_templ.go b/pkg/console/templates/layouts/frame_templ.go new file mode 100644 index 00000000..4f8b3be7 --- /dev/null +++ b/pkg/console/templates/layouts/frame_templ.go @@ -0,0 +1,70 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package layouts + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "context" + +func getEnvFromContext(ctx context.Context) string { + if env, ok := ctx.Value("env").(string); ok { + return env + } + return "prod" // fallback +} + +func Frame(pageTitle string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
0\" @click.away=\"showSuggestions = false\">
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/pages/account.templ b/pkg/console/templates/pages/account.templ new file mode 100644 index 00000000..62b0fd72 --- /dev/null +++ b/pkg/console/templates/pages/account.templ @@ -0,0 +1,203 @@ +package pages + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" +) + +func buildPaginationURL(address string, page int32, pageSize int32, currentFilter string, startDate string, endDate string) string { + baseURL := fmt.Sprintf("/account/%s?page=%d&count=%d", address, page, pageSize) + if currentFilter != "" && currentFilter != "all" { + baseURL += "&relation=" + currentFilter + } + if startDate != "" { + baseURL += "&start_date=" + startDate + } + if endDate != "" { + baseURL += "&end_date=" + endDate + } + return baseURL +} + +type AccountProps struct { + Address string + Transactions []*db.EtlTransaction + TxRelations []string + CurrentPage int32 + HasNext bool + HasPrev bool + PageSize int32 + RelationTypes []string + CurrentFilter string + StartDate string + EndDate string +} + +templ Account(props AccountProps) { + @layouts.Base(fmt.Sprintf("Account %s", props.Address[:6])) { +
+
+

+ Account + + { props.Address } + +

+
+ Page { fmt.Sprint(props.CurrentPage) } • { fmt.Sprint(props.PageSize) } transactions per page +
+
+
+
+
+
Address
+
{ props.Address }
+
+
+
Total Transactions
+
{ fmt.Sprint(len(props.Transactions)) }
+
+ if len(props.RelationTypes) > 0 { +
+
Filter by Relation
+ +
+ } +
+
From Date
+ +
+
+
To Date
+ +
+
+
+ if len(props.Transactions) > 0 { + +
+
+ if props.HasPrev { + + ← Previous + + } else { + + ← Previous + + } + if props.HasNext { + + Next → + + } else { + + Next → + + } +
+
+ } else { +
+

No transactions found for this address

+
+ } +
+ + } +} diff --git a/pkg/console/templates/pages/account_templ.go b/pkg/console/templates/pages/account_templ.go new file mode 100644 index 00000000..a7441c14 --- /dev/null +++ b/pkg/console/templates/pages/account_templ.go @@ -0,0 +1,395 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package pages + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" +) + +func buildPaginationURL(address string, page int32, pageSize int32, currentFilter string, startDate string, endDate string) string { + baseURL := fmt.Sprintf("/account/%s?page=%d&count=%d", address, page, pageSize) + if currentFilter != "" && currentFilter != "all" { + baseURL += "&relation=" + currentFilter + } + if startDate != "" { + baseURL += "&start_date=" + startDate + } + if endDate != "" { + baseURL += "&end_date=" + endDate + } + return baseURL +} + +type AccountProps struct { + Address string + Transactions []*db.EtlTransaction + TxRelations []string + CurrentPage int32 + HasNext bool + HasPrev bool + PageSize int32 + RelationTypes []string + CurrentFilter string + StartDate string + EndDate string +} + +func Account(props AccountProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Account ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(props.Address) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/account.templ`, Line: 45, Col: 21} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

Page ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.CurrentPage)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/account.templ`, Line: 49, Col: 41} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.PageSize)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/account.templ`, Line: 49, Col: 76} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " transactions per page
Address
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(props.Address) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/account.templ`, Line: 56, Col: 92} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
Total Transactions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(len(props.Transactions))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/account.templ`, Line: 60, Col: 108} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(props.RelationTypes) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
Filter by Relation
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
From Date
To Date
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(props.Transactions) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for i, tx := range props.Transactions { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
Hash
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(tx.TxHash).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
Type
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(tx.TxType) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/account.templ`, Line: 114, Col: 22} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
Relation
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(props.TxRelations[i]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/account.templ`, Line: 120, Col: 33} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
Block
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", tx.BlockHeight)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/account.templ`, Line: 126, Col: 46} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
Time
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.TimeWithTooltip(tx.CreatedAt.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.HasPrev { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "← Previous ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "← Previous ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if props.HasNext { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "Next →") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "Next →") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "

No transactions found for this address

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base(fmt.Sprintf("Account %s", props.Address[:6])).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/pages/block.templ b/pkg/console/templates/pages/block.templ new file mode 100644 index 00000000..db1177a6 --- /dev/null +++ b/pkg/console/templates/pages/block.templ @@ -0,0 +1,90 @@ +package pages + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" +) + +type BlockProps struct { + Block *db.EtlBlock + Transactions []*db.EtlTransaction +} + +templ Block(props BlockProps) { + @layouts.Base(fmt.Sprintf("Block %d", props.Block.BlockHeight)) { +
+ +
+
+

Block { fmt.Sprintf("%d", props.Block.BlockHeight) }

+
+ @templates.TimeWithTooltip(props.Block.BlockTime.Time) +
+
+
+
+
Height
+
+ { fmt.Sprintf("%d", props.Block.BlockHeight) } +
+
+ +
+
Transactions
+
+ { fmt.Sprintf("%d", len(props.Transactions)) } +
+
+
+
+ +
+
+

Transactions

+
+ { fmt.Sprintf("%d", len(props.Transactions)) } total +
+
+ if len(props.Transactions) > 0 { +
+ for _, tx := range props.Transactions { +
+
+ +
+
Type
+ + { tx.TxType } + +
+
+
+
Time
+
+ @templates.TimeWithTooltip(tx.CreatedAt.Time) +
+
+
+ } +
+ } else { +
+

No transactions in this block

+
+ } +
+
+ } +} diff --git a/pkg/console/templates/pages/block_templ.go b/pkg/console/templates/pages/block_templ.go new file mode 100644 index 00000000..a88ca2b4 --- /dev/null +++ b/pkg/console/templates/pages/block_templ.go @@ -0,0 +1,228 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package pages + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" +) + +type BlockProps struct { + Block *db.EtlBlock + Transactions []*db.EtlTransaction +} + +func Block(props BlockProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Block ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", props.Block.BlockHeight)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/block.templ`, Line: 21, Col: 117} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.TimeWithTooltip(props.Block.BlockTime.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
Height
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", props.Block.BlockHeight)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/block.templ`, Line: 30, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
Transactions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(props.Transactions))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/block.templ`, Line: 42, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

Transactions

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(props.Transactions))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/block.templ`, Line: 52, Col: 50} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " total
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(props.Transactions) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, tx := range props.Transactions { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
Type
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(tx.TxType) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/block.templ`, Line: 69, Col: 22} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
Time
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.TimeWithTooltip(tx.CreatedAt.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

No transactions in this block

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base(fmt.Sprintf("Block %d", props.Block.BlockHeight)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/pages/blocks.templ b/pkg/console/templates/pages/blocks.templ new file mode 100644 index 00000000..1dd8e320 --- /dev/null +++ b/pkg/console/templates/pages/blocks.templ @@ -0,0 +1,92 @@ +package pages + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" +) + +type BlocksProps struct { + Blocks []*db.EtlBlock + BlockTransactions []int32 + CurrentPage int32 + HasNext bool + HasPrev bool + PageSize int32 +} + +templ Blocks(props BlocksProps) { + @layouts.Base("Blocks") { +
+
+

Latest Blocks

+
+ Page { fmt.Sprint(props.CurrentPage) } • { fmt.Sprint(props.PageSize) } blocks per page +
+
+ if len(props.Blocks) > 0 { + +
+
+ if props.HasPrev { + + ← Previous + + } else { + + ← Previous + + } + if props.HasNext { + + Next → + + } else { + + Next → + + } +
+
+ } else { +
+

No blocks found

+
+ } +
+ } +} diff --git a/pkg/console/templates/pages/blocks_templ.go b/pkg/console/templates/pages/blocks_templ.go new file mode 100644 index 00000000..cba96ae2 --- /dev/null +++ b/pkg/console/templates/pages/blocks_templ.go @@ -0,0 +1,237 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package pages + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" +) + +type BlocksProps struct { + Blocks []*db.EtlBlock + BlockTransactions []int32 + CurrentPage int32 + HasNext bool + HasPrev bool + PageSize int32 +} + +func Blocks(props BlocksProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Latest Blocks

Page ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.CurrentPage)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/blocks.templ`, Line: 25, Col: 41} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.PageSize)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/blocks.templ`, Line: 25, Col: 76} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " blocks per page
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(props.Blocks) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for i, block := range props.Blocks { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
Height
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", block.BlockHeight)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/blocks.templ`, Line: 37, Col: 49} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
Proposer
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(block.ProposerAddress) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/blocks.templ`, Line: 43, Col: 34} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
Transactions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", props.BlockTransactions[i])) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/blocks.templ`, Line: 49, Col: 58} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
Time
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.TimeWithTooltip(block.BlockTime.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.HasPrev { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "← Previous ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "← Previous ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if props.HasNext { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "Next →") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "Next →") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

No blocks found

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Blocks").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/pages/content.templ b/pkg/console/templates/pages/content.templ new file mode 100644 index 00000000..4de9df14 --- /dev/null +++ b/pkg/console/templates/pages/content.templ @@ -0,0 +1,11 @@ +package pages + +import "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + +templ Content() { + @layouts.Base("Content") { +
+
Content
+
+ } +} diff --git a/pkg/console/templates/pages/content_templ.go b/pkg/console/templates/pages/content_templ.go new file mode 100644 index 00000000..c9c3c971 --- /dev/null +++ b/pkg/console/templates/pages/content_templ.go @@ -0,0 +1,60 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package pages + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + +func Content() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Content
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Content").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/pages/dashboard.templ b/pkg/console/templates/pages/dashboard.templ new file mode 100644 index 00000000..16936fa8 --- /dev/null +++ b/pkg/console/templates/pages/dashboard.templ @@ -0,0 +1,1572 @@ +package pages + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "strconv" + "strings" +) + +type DashboardStats struct { + CurrentBlockHeight int64 + ChainID string + BPS float64 + TPS float64 + TotalTransactions int64 + ValidatorCount int64 + LatestBlock *db.EtlBlock + RecentProposers []string + IsSyncing bool + LatestIndexedHeight int64 + LatestChainHeight int64 + BlockDelta int64 + TotalTransactions24h int64 + TotalTransactionsPrevious24h int64 + TotalTransactions7d int64 + TotalTransactions30d int64 + AvgBlockTime float32 // Average block time from latest SLA rollup in seconds +} + +type TransactionTypeBreakdown struct { + Type string + Count int64 + Color string +} + +type PlayEvent struct { + Timestamp string `json:"timestamp"` + Lat float64 `json:"lat"` + Lng float64 `json:"lng"` + Duration int `json:"duration"` +} + +type SLAPerformanceDataPoint struct { + RollupID int32 `json:"rollupId"` + BlockHeight int64 `json:"blockHeight"` + Timestamp string `json:"timestamp"` + ValidatorCount int32 `json:"validatorCount"` + HealthyValidators int32 `json:"healthyValidators"` + BPS float64 `json:"bps"` + TPS float64 `json:"tps"` + BlockStart int64 `json:"blockStart"` + BlockEnd int64 `json:"blockEnd"` +} + +type DashboardProps struct { + Stats *DashboardStats + TransactionBreakdown []*TransactionTypeBreakdown + RecentBlocks []*db.EtlBlock + RecentTransactions []*db.EtlTransaction + RecentSLARollups []*db.EtlSlaRollup + SLAPerformanceData []*SLAPerformanceDataPoint + BlockHeights map[string]int64 + SyncProgressPercentage float64 +} + +func formatNumber(n int64) string { + str := strconv.FormatInt(n, 10) + if len(str) <= 3 { + return str + } + + var result strings.Builder + for i, char := range str { + if i > 0 && (len(str)-i)%3 == 0 { + result.WriteString(",") + } + result.WriteRune(char) + } + return result.String() +} + +func getTotalTransactionCount(breakdown []*TransactionTypeBreakdown) int64 { + var total int64 + for _, b := range breakdown { + total += b.Count + } + return total +} + +func getProgressBarClass(percentage float64) string { + switch { + case percentage >= 99: + return "w-full" + case percentage >= 95: + return "w-11/12" + case percentage >= 90: + return "w-5/6" + case percentage >= 80: + return "w-4/5" + case percentage >= 75: + return "w-3/4" + case percentage >= 60: + return "w-3/5" + case percentage >= 50: + return "w-1/2" + case percentage >= 40: + return "w-2/5" + case percentage >= 25: + return "w-1/4" + case percentage >= 20: + return "w-1/5" + case percentage >= 10: + return "w-1/12" + case percentage >= 5: + return "w-1/24" + default: + return "w-px" + } +} + +func getPercentageChangeText(current, previous int64) string { + if previous == 0 { + return "No previous data" + } + + change := float64(current-previous) / float64(previous) * 100 + if change >= 0 { + return fmt.Sprintf("+%.1f%% from yesterday", change) + } else { + return fmt.Sprintf("%.1f%% from yesterday", change) + } +} + +func getPercentageChangeColorClass(current, previous int64) string { + if previous == 0 { + return "text-gray-500 dark:text-gray-400" + } + + change := float64(current-previous) / float64(previous) * 100 + if change >= 0 { + return "text-green-600 dark:text-green-400" + } else { + return "text-red-600 dark:text-red-400" + } +} + +templ Dashboard(props DashboardProps) { + @layouts.Base("Dashboard") { +
+ +
+
+
+
+
+ +
+

BLOCK HEIGHT

+
+
+ if props.Stats.AvgBlockTime > 0 { +

{ fmt.Sprintf("%.2fs", props.Stats.AvgBlockTime) }

+ } else if props.Stats.BPS > 0 { +

{ fmt.Sprintf("%.2fs", 1.0/props.Stats.BPS) }

+

{ fmt.Sprintf("%.2f BPS", props.Stats.BPS) }

+ } else { +

-

+

No data

+ } +

BLOCK TIME

+
+
+ if props.Stats.IsSyncing { +
+
+

Syncing

+
+ } else { +
+
+

Synced

+
+ } +

SYNC STATUS

+
+
+ + if props.Stats.IsSyncing && props.Stats.LatestChainHeight > 0 { +
+
+ + Sync Progress + if props.Stats.BlockDelta > 0 { + | { fmt.Sprintf("%d blocks to go", props.Stats.BlockDelta) } + } + + + { fmt.Sprintf("%d / %d", props.Stats.LatestIndexedHeight, props.Stats.LatestChainHeight) } + ({ fmt.Sprintf("%.1f%%", props.SyncProgressPercentage) }) + +
+
+ +
+
+
+ } +
+
+ +
+
+ +
+ +
+ +
+ Connecting... +
+
+ +
+
+ @NetworkSidebarFragment(props.Stats) +
+
+
+
+ +
+
+

Transaction Analytics

+
+
+ +
+ +
+
+ @TotalTransactionsFragment(props.Stats) +
+
+ @TPSFragment(props.Stats) +
+
+ +
+
+
24h Volume
+
{ formatNumber(props.Stats.TotalTransactions24h) }
+
Daily rate
+
+
+
7d Volume
+
{ formatNumber(props.Stats.TotalTransactions7d) }
+
Avg: { formatNumber(props.Stats.TotalTransactions7d / 7) }/day
+
+
+
30d Volume
+
{ formatNumber(props.Stats.TotalTransactions30d) }
+
Avg: { formatNumber(props.Stats.TotalTransactions30d / 30) }/day
+
+
+
+ +
+
Transaction Types
+
+ if len(props.TransactionBreakdown) > 0 { + for _, breakdown := range props.TransactionBreakdown { +
+ { breakdown.Type } + { formatNumber(breakdown.Count) } +
+ } + } else { +
No transaction data available
+ } +
+
+
+
+ + if props.SLAPerformanceData != nil && len(props.SLAPerformanceData) > 0 { +
+
+
+
+
+

Network Performance

+
+ View rollups → +
+
+
+
+
+
+
+
+
+
+ LIVE +
+
+
+
+
+ +
+
+ } + +
+ +
+
+

Latest Blocks

+ View all → +
+ if len(props.RecentBlocks) > 0 { +
+ for _, block := range props.RecentBlocks { + + } +
+ } else { +

No blocks found

+ } +
+ +
+
+

Latest Transactions

+ View all → +
+ if len(props.RecentTransactions) > 0 { +
+ for _, tx := range props.RecentTransactions { +
+ +
+ if tx.CreatedAt.Valid { + @templates.TimeWithTooltip(tx.CreatedAt.Time) + } else { + + } +
+
+ } +
+ } else { +

No transactions found

+ } +
+
+
+ @SSEEventScript() + @LivePlayMapScript() + @BlockEventsScript() + @SLAChartScript() + } +} + +// HTMX Fragment Templates +templ StatsHeaderFragment(stats *DashboardStats, syncProgressPercentage float64) { +
+
+
+

Block Height

+
+ +
+
+
+

Block Time

+ if stats.AvgBlockTime > 0 { +

{ fmt.Sprintf("%.2fs", stats.AvgBlockTime) }

+ } else if stats.BPS > 0 { +

{ fmt.Sprintf("%.2fs", 1.0/stats.BPS) }

+

{ fmt.Sprintf("%.2f BPS", stats.BPS) }

+ } else { +

-

+

No data

+ } +
+
+

Sync Status

+ if stats.IsSyncing { +
+
+

Syncing

+
+ } else { +
+
+

Synced

+
+

Up to date

+ } +
+
+ + if stats.IsSyncing && stats.LatestChainHeight > 0 { +
+
+ + Sync Progress + if stats.BlockDelta > 0 { + | { fmt.Sprintf("%d blocks to go", stats.BlockDelta) } + } + + + { fmt.Sprintf("%d / %d", stats.LatestIndexedHeight, stats.LatestChainHeight) } + ({ fmt.Sprintf("%.1f%%", syncProgressPercentage) }) + +
+
+ +
+
+
+ } +
+} + +templ TPSFragment(stats *DashboardStats) { +
+
Transactions/sec
+
{ fmt.Sprintf("%.1f", stats.TPS) }
+
30d Avg: { fmt.Sprintf("%.1f", float64(stats.TotalTransactions30d)/(30*24*60*60)) } TPS
+
+} + +templ TotalTransactionsFragment(stats *DashboardStats) { +
+
Total Transactions
+
{ formatNumber(stats.TotalTransactions) }
+
{ getPercentageChangeText(stats.TotalTransactions24h, stats.TotalTransactionsPrevious24h) }
+
+} + +templ SSEEventScript() { + +} + +// Live Play Map Alpine.js Component +templ LivePlayMapScript() { + +} + +// Block Events Alpine.js Component +templ BlockEventsScript() { + +} + +// SLA Chart Alpine.js Component with Chart.js +templ SLAChartScript() { + + +} + +templ NetworkSidebarFragment(stats *DashboardStats) { +
+
+

Network Info

+
+
+ Validators + { fmt.Sprintf("%d", stats.ValidatorCount) } +
+ +
+
+
+

Recent Proposers

+
+ + +
+
+
+} diff --git a/pkg/console/templates/pages/dashboard_templ.go b/pkg/console/templates/pages/dashboard_templ.go new file mode 100644 index 00000000..f9b9554b --- /dev/null +++ b/pkg/console/templates/pages/dashboard_templ.go @@ -0,0 +1,1310 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package pages + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "strconv" + "strings" +) + +type DashboardStats struct { + CurrentBlockHeight int64 + ChainID string + BPS float64 + TPS float64 + TotalTransactions int64 + ValidatorCount int64 + LatestBlock *db.EtlBlock + RecentProposers []string + IsSyncing bool + LatestIndexedHeight int64 + LatestChainHeight int64 + BlockDelta int64 + TotalTransactions24h int64 + TotalTransactionsPrevious24h int64 + TotalTransactions7d int64 + TotalTransactions30d int64 + AvgBlockTime float32 // Average block time from latest SLA rollup in seconds +} + +type TransactionTypeBreakdown struct { + Type string + Count int64 + Color string +} + +type PlayEvent struct { + Timestamp string `json:"timestamp"` + Lat float64 `json:"lat"` + Lng float64 `json:"lng"` + Duration int `json:"duration"` +} + +type SLAPerformanceDataPoint struct { + RollupID int32 `json:"rollupId"` + BlockHeight int64 `json:"blockHeight"` + Timestamp string `json:"timestamp"` + ValidatorCount int32 `json:"validatorCount"` + HealthyValidators int32 `json:"healthyValidators"` + BPS float64 `json:"bps"` + TPS float64 `json:"tps"` + BlockStart int64 `json:"blockStart"` + BlockEnd int64 `json:"blockEnd"` +} + +type DashboardProps struct { + Stats *DashboardStats + TransactionBreakdown []*TransactionTypeBreakdown + RecentBlocks []*db.EtlBlock + RecentTransactions []*db.EtlTransaction + RecentSLARollups []*db.EtlSlaRollup + SLAPerformanceData []*SLAPerformanceDataPoint + BlockHeights map[string]int64 + SyncProgressPercentage float64 +} + +func formatNumber(n int64) string { + str := strconv.FormatInt(n, 10) + if len(str) <= 3 { + return str + } + + var result strings.Builder + for i, char := range str { + if i > 0 && (len(str)-i)%3 == 0 { + result.WriteString(",") + } + result.WriteRune(char) + } + return result.String() +} + +func getTotalTransactionCount(breakdown []*TransactionTypeBreakdown) int64 { + var total int64 + for _, b := range breakdown { + total += b.Count + } + return total +} + +func getProgressBarClass(percentage float64) string { + switch { + case percentage >= 99: + return "w-full" + case percentage >= 95: + return "w-11/12" + case percentage >= 90: + return "w-5/6" + case percentage >= 80: + return "w-4/5" + case percentage >= 75: + return "w-3/4" + case percentage >= 60: + return "w-3/5" + case percentage >= 50: + return "w-1/2" + case percentage >= 40: + return "w-2/5" + case percentage >= 25: + return "w-1/4" + case percentage >= 20: + return "w-1/5" + case percentage >= 10: + return "w-1/12" + case percentage >= 5: + return "w-1/24" + default: + return "w-px" + } +} + +func getPercentageChangeText(current, previous int64) string { + if previous == 0 { + return "No previous data" + } + + change := float64(current-previous) / float64(previous) * 100 + if change >= 0 { + return fmt.Sprintf("+%.1f%% from yesterday", change) + } else { + return fmt.Sprintf("%.1f%% from yesterday", change) + } +} + +func getPercentageChangeColorClass(current, previous int64) string { + if previous == 0 { + return "text-gray-500 dark:text-gray-400" + } + + change := float64(current-previous) / float64(previous) * 100 + if change >= 0 { + return "text-green-600 dark:text-green-400" + } else { + return "text-red-600 dark:text-red-400" + } +} + +func Dashboard(props DashboardProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

BLOCK HEIGHT

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.Stats.AvgBlockTime > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.2fs", props.Stats.AvgBlockTime)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 183, Col: 116} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else if props.Stats.BPS > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.2fs", 1.0/props.Stats.BPS)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 185, Col: 111} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.2f BPS", props.Stats.BPS)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 186, Col: 102} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

-

No data

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

BLOCK TIME

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.Stats.IsSyncing { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

Syncing

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "

Synced

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

SYNC STATUS

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.Stats.IsSyncing && props.Stats.LatestChainHeight > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
Sync Progress ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.Stats.BlockDelta > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "| ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d blocks to go", props.Stats.BlockDelta)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 215, Col: 68} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d / %d", props.Stats.LatestIndexedHeight, props.Stats.LatestChainHeight)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 219, Col: 97} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " (") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f%%", props.SyncProgressPercentage)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 220, Col: 63} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, ")
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 = []any{fmt.Sprintf("bg-gray-600 h-2 rounded-full transition-all duration-300 ease-in-out %s", getProgressBarClass(props.SyncProgressPercentage))} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
Connecting...
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = NetworkSidebarFragment(props.Stats).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

Transaction Analytics

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = TotalTransactionsFragment(props.Stats).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = TPSFragment(props.Stats).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
24h Volume
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(formatNumber(props.Stats.TotalTransactions24h)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 278, Col: 127} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
Daily rate
7d Volume
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(formatNumber(props.Stats.TotalTransactions7d)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 283, Col: 126} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
Avg: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(formatNumber(props.Stats.TotalTransactions7d / 7)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 284, Col: 118} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "/day
30d Volume
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(formatNumber(props.Stats.TotalTransactions30d)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 288, Col: 127} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
Avg: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(formatNumber(props.Stats.TotalTransactions30d / 30)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 289, Col: 120} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "/day
Transaction Types
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(props.TransactionBreakdown) > 0 { + for _, breakdown := range props.TransactionBreakdown { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(breakdown.Type) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 300, Col: 129} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(formatNumber(breakdown.Count)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 301, Col: 121} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
No transaction data available
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.SLAPerformanceData != nil && len(props.SLAPerformanceData) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "

Network Performance

View rollups →
LIVE
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "

Latest Blocks

View all →
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(props.RecentBlocks) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, block := range props.RecentBlocks { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("#%d", block.BlockHeight)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 366, Col: 50} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(block.ProposerAddress) > 8 { + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(block.ProposerAddress[:12]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 370, Col: 40} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "...") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var25 string + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(block.ProposerAddress[len(block.ProposerAddress)-4:]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 370, Col: 99} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var26 string + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(block.ProposerAddress) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 372, Col: 35} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if block.BlockTime.Valid { + templ_7745c5c3_Err = templates.TimeWithTooltip(block.BlockTime.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "

No blocks found

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "

Latest Transactions

View all →
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(props.RecentTransactions) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, tx := range props.RecentTransactions { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(tx.TxHash) > 8 { + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(tx.TxHash[:12]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 403, Col: 28} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "...") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(tx.TxHash[len(tx.TxHash)-4:]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 403, Col: 63} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var30 string + templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(tx.TxHash) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 405, Col: 23} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var31 string + templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(tx.TxType) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 409, Col: 22} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if blockHeight, exists := props.BlockHeights[tx.TxHash]; exists { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var33 string + templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("#%d", blockHeight)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 413, Col: 45} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if tx.CreatedAt.Valid { + templ_7745c5c3_Err = templates.TimeWithTooltip(tx.CreatedAt.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "

No transactions found

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = SSEEventScript().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = LivePlayMapScript().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = BlockEventsScript().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = SLAChartScript().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Dashboard").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// HTMX Fragment Templates +func StatsHeaderFragment(stats *DashboardStats, syncProgressPercentage float64) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var34 := templ.GetChildren(ctx) + if templ_7745c5c3_Var34 == nil { + templ_7745c5c3_Var34 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "

Block Height

Block Time

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if stats.AvgBlockTime > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var35 string + templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.2fs", stats.AvgBlockTime)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 472, Col: 110} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else if stats.BPS > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var36 string + templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.2fs", 1.0/stats.BPS)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 474, Col: 105} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var37 string + templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.2f BPS", stats.BPS)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 475, Col: 93} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "

-

No data

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "

Sync Status

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if stats.IsSyncing { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "

Syncing

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "

Synced

Up to date

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if stats.IsSyncing && stats.LatestChainHeight > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "
Sync Progress ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if stats.BlockDelta > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "| ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var38 string + templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d blocks to go", stats.BlockDelta)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 504, Col: 59} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var39 string + templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d / %d", stats.LatestIndexedHeight, stats.LatestChainHeight)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 508, Col: 82} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, " (") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var40 string + templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f%%", syncProgressPercentage)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 509, Col: 54} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, ")
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var41 = []any{fmt.Sprintf("bg-gray-600 h-2 rounded-full transition-all duration-300 ease-in-out %s", getProgressBarClass(syncProgressPercentage))} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var41...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func TPSFragment(stats *DashboardStats) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var43 := templ.GetChildren(ctx) + if templ_7745c5c3_Var43 == nil { + templ_7745c5c3_Var43 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "
Transactions/sec
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var44 string + templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f", stats.TPS)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 526, Col: 105} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "
30d Avg: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var45 string + templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f", float64(stats.TotalTransactions30d)/(30*24*60*60))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 527, Col: 137} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, " TPS
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func TotalTransactionsFragment(stats *DashboardStats) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var46 := templ.GetChildren(ctx) + if templ_7745c5c3_Var46 == nil { + templ_7745c5c3_Var46 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "
Total Transactions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var47 string + templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(formatNumber(stats.TotalTransactions)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 534, Col: 112} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var48 = []any{fmt.Sprintf("text-xs %s", getPercentageChangeColorClass(stats.TotalTransactions24h, stats.TotalTransactionsPrevious24h))} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var48...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var50 string + templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(getPercentageChangeText(stats.TotalTransactions24h, stats.TotalTransactionsPrevious24h)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 535, Col: 227} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func SSEEventScript() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var51 := templ.GetChildren(ctx) + if templ_7745c5c3_Var51 == nil { + templ_7745c5c3_Var51 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Live Play Map Alpine.js Component +func LivePlayMapScript() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var52 := templ.GetChildren(ctx) + if templ_7745c5c3_Var52 == nil { + templ_7745c5c3_Var52 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Block Events Alpine.js Component +func BlockEventsScript() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var53 := templ.GetChildren(ctx) + if templ_7745c5c3_Var53 == nil { + templ_7745c5c3_Var53 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// SLA Chart Alpine.js Component with Chart.js +func SLAChartScript() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var54 := templ.GetChildren(ctx) + if templ_7745c5c3_Var54 == nil { + templ_7745c5c3_Var54 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func NetworkSidebarFragment(stats *DashboardStats) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var55 := templ.GetChildren(ctx) + if templ_7745c5c3_Var55 == nil { + templ_7745c5c3_Var55 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "

Network Info

Validators ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var56 string + templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", stats.ValidatorCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/dashboard.templ`, Line: 1530, Col: 139} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "

Recent Proposers

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/pages/hello.templ b/pkg/console/templates/pages/hello.templ new file mode 100644 index 00000000..775308ab --- /dev/null +++ b/pkg/console/templates/pages/hello.templ @@ -0,0 +1,18 @@ +package pages + +import "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + +templ Hello(name string) { + @layouts.Base("Hello") { +
+
Sup, { name }!
+
+

Test timezone conversion:

+

Current time: @templates.TimeWithTooltip(time.Now())

+

+ Hover over the time to see it in your local timezone! +

+
+
+ } +} diff --git a/pkg/console/templates/pages/hello_templ.go b/pkg/console/templates/pages/hello_templ.go new file mode 100644 index 00000000..c4c54575 --- /dev/null +++ b/pkg/console/templates/pages/hello_templ.go @@ -0,0 +1,73 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package pages + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + +func Hello(name string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Sup, ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/hello.templ`, Line: 8, Col: 89} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "!

Test timezone conversion:

Current time: @templates.TimeWithTooltip(time.Now())

Hover over the time to see it in your local timezone!

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Hello").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/pages/rollups.templ b/pkg/console/templates/pages/rollups.templ new file mode 100644 index 00000000..0e27ef89 --- /dev/null +++ b/pkg/console/templates/pages/rollups.templ @@ -0,0 +1,101 @@ +package pages + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" +) + +type RollupsProps struct { + Rollups []*db.EtlSlaRollup + RollupValidators []*db.EtlSlaNodeReport + CurrentPage int32 + HasNext bool + HasPrev bool + PageSize int32 + TotalCount int64 +} + +templ Rollups(props RollupsProps) { + @layouts.Base("SLA Rollups") { +
+
+

SLA Rollups

+
+ Page { fmt.Sprint(props.CurrentPage) } • { fmt.Sprint(props.PageSize) } rollups per page • { fmt.Sprint(props.TotalCount) } total +
+
+ if len(props.Rollups) > 0 { +
+ for _, rollup := range props.Rollups { +
+
+ +
+
Block Range
+
+ { fmt.Sprintf("%d - %d", rollup.BlockStart, rollup.BlockEnd) } +
+
+
+
Validators
+
+ { fmt.Sprintf("%d", rollup.ValidatorCount) } +
+
+ +
+
+
Time
+
+ if rollup.CreatedAt.Valid { + @templates.TimeWithTooltip(rollup.CreatedAt.Time) + } else { + + } +
+
+
+ } +
+
+
+ if props.HasPrev { + + ← Previous + + } else { + + ← Previous + + } + if props.HasNext { + + Next → + + } else { + + Next → + + } +
+
+ } else { +
+

No rollups found

+
+ } +
+ } +} diff --git a/pkg/console/templates/pages/rollups_templ.go b/pkg/console/templates/pages/rollups_templ.go new file mode 100644 index 00000000..3936e0bf --- /dev/null +++ b/pkg/console/templates/pages/rollups_templ.go @@ -0,0 +1,279 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package pages + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" +) + +type RollupsProps struct { + Rollups []*db.EtlSlaRollup + RollupValidators []*db.EtlSlaNodeReport + CurrentPage int32 + HasNext bool + HasPrev bool + PageSize int32 + TotalCount int64 +} + +func Rollups(props RollupsProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

SLA Rollups

Page ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.CurrentPage)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/rollups.templ`, Line: 26, Col: 41} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.PageSize)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/rollups.templ`, Line: 26, Col: 76} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " rollups per page • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.TotalCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/rollups.templ`, Line: 26, Col: 130} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " total
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(props.Rollups) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, rollup := range props.Rollups { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
Block Range
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d - %d", rollup.BlockStart, rollup.BlockEnd)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/rollups.templ`, Line: 43, Col: 70} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
Validators
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", rollup.ValidatorCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/rollups.templ`, Line: 49, Col: 52} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
Time
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if rollup.CreatedAt.Valid { + templ_7745c5c3_Err = templates.TimeWithTooltip(rollup.CreatedAt.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.HasPrev { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "← Previous ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "← Previous ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if props.HasNext { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "Next →") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "Next →") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "

No rollups found

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("SLA Rollups").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/pages/transaction.templ b/pkg/console/templates/pages/transaction.templ new file mode 100644 index 00000000..b81df9a6 --- /dev/null +++ b/pkg/console/templates/pages/transaction.templ @@ -0,0 +1,379 @@ +package pages + +import ( + "encoding/json" + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "regexp" +) + +func formatJSON(jsonStr string) string { + var obj interface{} + if err := json.Unmarshal([]byte(jsonStr), &obj); err != nil { + return jsonStr // Return original if not valid JSON + } + + formatted, err := json.MarshalIndent(obj, "", " ") + if err != nil { + return jsonStr // Return original if formatting fails + } + + return string(formatted) +} + +func highlightJSON(jsonStr string) string { + formatted := formatJSON(jsonStr) + + // Simple regex-based syntax highlighting + // Replace strings (quoted values) + formatted = regexp.MustCompile(`"([^"\\]*(\\.[^"\\]*)*)":`).ReplaceAllString(formatted, `"$1":`) + formatted = regexp.MustCompile(`:\s*"([^"\\]*(\\.[^"\\]*)*)"([,\}\]])`).ReplaceAllString(formatted, `: "$1"$3`) + + // Replace numbers + formatted = regexp.MustCompile(`:\s*(-?\d+\.?\d*)([,\}\]])`).ReplaceAllString(formatted, `: $1$2`) + + // Replace booleans + formatted = regexp.MustCompile(`:\s*(true|false)([,\}\]])`).ReplaceAllString(formatted, `: $1$2`) + + // Replace null + formatted = regexp.MustCompile(`:\s*(null)([,\}\]])`).ReplaceAllString(formatted, `: $1$2`) + + // Replace braces and brackets + formatted = regexp.MustCompile(`([\{\}\[\]])`).ReplaceAllString(formatted, `$1`) + + return formatted +} + +type TransactionProps struct { + Transaction *db.EtlTransaction + Proposer string + Content interface{} +} + +templ Transaction(props TransactionProps) { + @layouts.Base(fmt.Sprintf("Transaction %s", props.Transaction.TxHash[:8])) { +
+ +
+
+
+

Transaction Details

+
+ { props.Transaction.TxHash } +
+
+
+ + { props.Transaction.TxType } + +
+
+
+ +
+ +

{ fmt.Sprintf("%d", props.Transaction.TxIndex) }

+
+ if props.Transaction.CreatedAt.Valid { +
+ +

+ @templates.TimeWithTooltip(props.Transaction.CreatedAt.Time) +

+
+ } + if props.Proposer != "" { +
+ +

+ + { props.Proposer } + +

+
+ } +
+ +

{ props.Transaction.TxType }

+
+
+
+ + switch props.Content.(type) { + case []*db.EtlPlay: + @TrackPlaysSection(props.Content.([]*db.EtlPlay)) + case *db.EtlManageEntity: + @ManageEntitySection(props.Content.(*db.EtlManageEntity)) + case *db.EtlValidatorRegistration: + @ValidatorRegistrationSection(props.Content.(*db.EtlValidatorRegistration)) + case *db.EtlValidatorDeregistration: + @ValidatorDeregistrationSection(props.Content.(*db.EtlValidatorDeregistration)) + case *db.EtlSlaRollup: + @SlaRollupSection(props.Content.(*db.EtlSlaRollup)) + case *db.EtlStorageProof: + @StorageProofSection(props.Content.(*db.EtlStorageProof)) + case *db.EtlStorageProofVerification: + @StorageProofVerificationSection(props.Content.(*db.EtlStorageProofVerification)) + default: +
+

Transaction Content

+

No detailed transaction content available for this transaction type.

+
+ } +
+ } +} + +templ TrackPlaysSection(plays []*db.EtlPlay) { +
+

Track Plays ({ fmt.Sprintf("%d", len(plays)) })

+
+ + + + + + + + + + + for _, play := range plays { + + + + + + + } + +
UserTrack IDLocationPlayed At
+ @templates.StringWithTooltip(play.UserID) + + @templates.StringWithTooltip(play.TrackID) + + { play.City } + if play.Region != "" { + , { play.Region } + } + if play.Country != "" { + , { play.Country } + } + + if play.PlayedAt.Valid { + @templates.TimeWithTooltip(play.PlayedAt.Time) + } +
+
+
+} + +templ ManageEntitySection(entity *db.EtlManageEntity) { +
+

Manage Entity

+
+ + + + + + + + + + + + if entity != nil { + + + + + + + + } + +
AddressEntity TypeEntity IDActionSigner
+ @templates.StringWithTooltip(entity.Address) + { entity.EntityType }{ fmt.Sprintf("%d", entity.EntityID) } + + { entity.Action } + + + @templates.StringWithTooltip(entity.Signer) +
+ + if entity.Metadata.Valid { +
+

Metadata

+
+
+							@templ.Raw(highlightJSON(string(entity.Metadata.String)))
+						
+
+
+ } +
+
+} + +templ ValidatorRegistrationSection(registration *db.EtlValidatorRegistration) { +
+

Validator Registrations 1

+
+ + + + + + + + + + + + if registration != nil { + + + + + + + + } + +
AddressComet AddressNode TypeVoting PowerSPID
+ + @templates.StringWithTooltip(registration.Address) + + + @templates.StringWithTooltip(registration.CometAddress) + { registration.NodeType }{ fmt.Sprintf("%d", registration.VotingPower) }{ registration.Spid }
+
+
+} + +templ ValidatorDeregistrationSection(deregistration *db.EtlValidatorDeregistration) { +
+

Validator Deregistrations 1

+
+ + + + + + + + + if deregistration != nil { + + + + + } + +
Comet AddressPublic Key
+ + @templates.StringWithTooltip(deregistration.CometAddress) + + + @templates.StringWithTooltip(fmt.Sprintf("%x", deregistration.CometPubkey)) +
+
+
+} + +templ SlaRollupSection(slaRollup *db.EtlSlaRollup) { +
+

SLA Rollup

+
+
+ +

+ { fmt.Sprintf("%d - %d", slaRollup.BlockStart, slaRollup.BlockEnd) } +

+
+ if slaRollup.CreatedAt.Valid { +
+ +

+ @templates.TimeWithTooltip(slaRollup.CreatedAt.Time) +

+
+ } +
+ +

{ fmt.Sprintf("%d", slaRollup.ValidatorCount) }

+
+
+
+} + +templ StorageProofSection(storageProof *db.EtlStorageProof) { +
+

Storage Proof

+
+
+ +

{ fmt.Sprintf("%d", storageProof.BlockHeight) }

+
+
+ +

+ @templates.StringWithTooltip(storageProof.Address) +

+
+
+ +

+ @templates.StringWithTooltip(storageProof.Cid) +

+
+
+ +
+ for _, addr := range storageProof.ProverAddresses { + + @templates.StringWithTooltip(addr) + + } +
+
+ if storageProof.ProofSignature != nil { +
+ +

+ @templates.StringWithTooltip(fmt.Sprintf("%x", storageProof.ProofSignature)) +

+
+ } +
+
+} + +templ StorageProofVerificationSection(storageProofVerification *db.EtlStorageProofVerification) { +
+

Storage Proof Verification

+
+
+ +

{ fmt.Sprintf("%d", storageProofVerification.Height) }

+
+
+ +

+ @templates.StringWithTooltip(fmt.Sprintf("%x", storageProofVerification.Proof)) +

+
+
+
+} diff --git a/pkg/console/templates/pages/transaction_templ.go b/pkg/console/templates/pages/transaction_templ.go new file mode 100644 index 00000000..1ff4f9da --- /dev/null +++ b/pkg/console/templates/pages/transaction_templ.go @@ -0,0 +1,917 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package pages + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "encoding/json" + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "regexp" +) + +func formatJSON(jsonStr string) string { + var obj interface{} + if err := json.Unmarshal([]byte(jsonStr), &obj); err != nil { + return jsonStr // Return original if not valid JSON + } + + formatted, err := json.MarshalIndent(obj, "", " ") + if err != nil { + return jsonStr // Return original if formatting fails + } + + return string(formatted) +} + +func highlightJSON(jsonStr string) string { + formatted := formatJSON(jsonStr) + + // Simple regex-based syntax highlighting + // Replace strings (quoted values) + formatted = regexp.MustCompile(`"([^"\\]*(\\.[^"\\]*)*)":`).ReplaceAllString(formatted, `"$1":`) + formatted = regexp.MustCompile(`:\s*"([^"\\]*(\\.[^"\\]*)*)"([,\}\]])`).ReplaceAllString(formatted, `: "$1"$3`) + + // Replace numbers + formatted = regexp.MustCompile(`:\s*(-?\d+\.?\d*)([,\}\]])`).ReplaceAllString(formatted, `: $1$2`) + + // Replace booleans + formatted = regexp.MustCompile(`:\s*(true|false)([,\}\]])`).ReplaceAllString(formatted, `: $1$2`) + + // Replace null + formatted = regexp.MustCompile(`:\s*(null)([,\}\]])`).ReplaceAllString(formatted, `: $1$2`) + + // Replace braces and brackets + formatted = regexp.MustCompile(`([\{\}\[\]])`).ReplaceAllString(formatted, `$1`) + + return formatted +} + +type TransactionProps struct { + Transaction *db.EtlTransaction + Proposer string + Content interface{} +} + +func Transaction(props TransactionProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Transaction Details

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(props.Transaction.TxHash) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 64, Col: 33} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(props.Transaction.TxType) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 69, Col: 33} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", props.Transaction.TxIndex)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 84, Col: 93} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.Transaction.CreatedAt.Valid { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.TimeWithTooltip(props.Transaction.CreatedAt.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if props.Proposer != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(props.Transaction.TxType) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 106, Col: 73} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + switch props.Content.(type) { + case []*db.EtlPlay: + templ_7745c5c3_Err = TrackPlaysSection(props.Content.([]*db.EtlPlay)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case *db.EtlManageEntity: + templ_7745c5c3_Err = ManageEntitySection(props.Content.(*db.EtlManageEntity)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case *db.EtlValidatorRegistration: + templ_7745c5c3_Err = ValidatorRegistrationSection(props.Content.(*db.EtlValidatorRegistration)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case *db.EtlValidatorDeregistration: + templ_7745c5c3_Err = ValidatorDeregistrationSection(props.Content.(*db.EtlValidatorDeregistration)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case *db.EtlSlaRollup: + templ_7745c5c3_Err = SlaRollupSection(props.Content.(*db.EtlSlaRollup)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case *db.EtlStorageProof: + templ_7745c5c3_Err = StorageProofSection(props.Content.(*db.EtlStorageProof)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case *db.EtlStorageProofVerification: + templ_7745c5c3_Err = StorageProofVerificationSection(props.Content.(*db.EtlStorageProofVerification)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + default: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

Transaction Content

No detailed transaction content available for this transaction type.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base(fmt.Sprintf("Transaction %s", props.Transaction.TxHash[:8])).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func TrackPlaysSection(plays []*db.EtlPlay) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

Track Plays (") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(plays))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 138, Col: 114} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, ")

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, play := range plays { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
UserTrack IDLocationPlayed At
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(play.UserID).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(play.TrackID).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(play.City) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 159, Col: 19} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if play.Region != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, ", ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(play.Region) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 161, Col: 24} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if play.Country != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, ", ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(play.Country) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 164, Col: 25} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if play.PlayedAt.Valid { + templ_7745c5c3_Err = templates.TimeWithTooltip(play.PlayedAt.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func ManageEntitySection(entity *db.EtlManageEntity) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var16 := templ.GetChildren(ctx) + if templ_7745c5c3_Var16 == nil { + templ_7745c5c3_Var16 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "

Manage Entity

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if entity != nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
AddressEntity TypeEntity IDActionSigner
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(entity.Address).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(entity.EntityType) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 200, Col: 48} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", entity.EntityID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 201, Col: 65} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(entity.Action) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 204, Col: 24} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(entity.Signer).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if entity.Metadata.Valid { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "

Metadata

")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			templ_7745c5c3_Err = templ.Raw(highlightJSON(string(entity.Metadata.String))).Render(ctx, templ_7745c5c3_Buffer)
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func ValidatorRegistrationSection(registration *db.EtlValidatorRegistration) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var20 := templ.GetChildren(ctx) + if templ_7745c5c3_Var20 == nil { + templ_7745c5c3_Var20 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "

Validator Registrations 1

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if registration != nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
AddressComet AddressNode TypeVoting PowerSPID
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(registration.Address).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(registration.CometAddress).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(registration.NodeType) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 254, Col: 52} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var23 string + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", registration.VotingPower)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 255, Col: 74} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(registration.Spid) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 256, Col: 48} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func ValidatorDeregistrationSection(deregistration *db.EtlValidatorDeregistration) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var25 := templ.GetChildren(ctx) + if templ_7745c5c3_Var25 == nil { + templ_7745c5c3_Var25 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "

Validator Deregistrations 1

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if deregistration != nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
Comet AddressPublic Key
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(deregistration.CometAddress).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(fmt.Sprintf("%x", deregistration.CometPubkey)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func SlaRollupSection(slaRollup *db.EtlSlaRollup) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var27 := templ.GetChildren(ctx) + if templ_7745c5c3_Var27 == nil { + templ_7745c5c3_Var27 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "

SLA Rollup

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d - %d", slaRollup.BlockStart, slaRollup.BlockEnd)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 302, Col: 71} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if slaRollup.CreatedAt.Valid { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.TimeWithTooltip(slaRollup.CreatedAt.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", slaRollup.ValidatorCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 315, Col: 93} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func StorageProofSection(storageProof *db.EtlStorageProof) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var30 := templ.GetChildren(ctx) + if templ_7745c5c3_Var30 == nil { + templ_7745c5c3_Var30 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "

Storage Proof

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var31 string + templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", storageProof.BlockHeight)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 327, Col: 93} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(storageProof.Address).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(storageProof.Cid).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, addr := range storageProof.ProverAddresses { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(addr).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if storageProof.ProofSignature != nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(fmt.Sprintf("%x", storageProof.ProofSignature)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func StorageProofVerificationSection(storageProofVerification *db.EtlStorageProofVerification) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var32 := templ.GetChildren(ctx) + if templ_7745c5c3_Var32 == nil { + templ_7745c5c3_Var32 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "

Storage Proof Verification

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var33 string + templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", storageProofVerification.Height)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transaction.templ`, Line: 369, Col: 100} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltip(fmt.Sprintf("%x", storageProofVerification.Proof)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/pages/transactions.templ b/pkg/console/templates/pages/transactions.templ new file mode 100644 index 00000000..45e6207b --- /dev/null +++ b/pkg/console/templates/pages/transactions.templ @@ -0,0 +1,102 @@ +package pages + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" +) + +type TransactionsProps struct { + Transactions []*db.EtlTransaction + BlockHeights map[string]int64 + CurrentPage int32 + HasNext bool + HasPrev bool + PageSize int32 +} + +templ Transactions(props TransactionsProps) { + @layouts.Base("Transactions") { +
+
+

Latest Transactions

+
+ Page { fmt.Sprint(props.CurrentPage) } • { fmt.Sprint(props.PageSize) } transactions per page +
+
+ if len(props.Transactions) > 0 { + +
+
+ if props.HasPrev { + + ← Previous + + } else { + + ← Previous + + } + if props.HasNext { + + Next → + + } else { + + Next → + + } +
+
+ } else { +
+

No transactions found

+
+ } +
+ } +} diff --git a/pkg/console/templates/pages/transactions_templ.go b/pkg/console/templates/pages/transactions_templ.go new file mode 100644 index 00000000..cad256c6 --- /dev/null +++ b/pkg/console/templates/pages/transactions_templ.go @@ -0,0 +1,267 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package pages + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" +) + +type TransactionsProps struct { + Transactions []*db.EtlTransaction + BlockHeights map[string]int64 + CurrentPage int32 + HasNext bool + HasPrev bool + PageSize int32 +} + +func Transactions(props TransactionsProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Latest Transactions

Page ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.CurrentPage)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transactions.templ`, Line: 25, Col: 41} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.PageSize)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transactions.templ`, Line: 25, Col: 76} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " transactions per page
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(props.Transactions) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, tx := range props.Transactions { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
Hash
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltipCustom(tx.TxHash, 10, 6).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
Type
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(tx.TxType) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transactions.templ`, Line: 43, Col: 22} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
Block
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if blockHeight, exists := props.BlockHeights[tx.TxHash]; exists { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", blockHeight)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transactions.templ`, Line: 50, Col: 44} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", tx.BlockHeight)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/transactions.templ`, Line: 54, Col: 47} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
Time
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if tx.CreatedAt.Valid { + templ_7745c5c3_Err = templates.TimeWithTooltip(tx.CreatedAt.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.HasPrev { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "← Previous ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "← Previous ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if props.HasNext { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "Next →") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "Next →") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

No transactions found

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Transactions").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/pages/validator.templ b/pkg/console/templates/pages/validator.templ new file mode 100644 index 00000000..a572768a --- /dev/null +++ b/pkg/console/templates/pages/validator.templ @@ -0,0 +1,229 @@ +package pages + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "strings" + "time" +) + +func extractHostFromValidator(endpoint string) string { + // Same logic as extractHost but renamed to avoid conflicts + if strings.HasPrefix(endpoint, "http://") { + endpoint = endpoint[7:] + } else if strings.HasPrefix(endpoint, "https://") { + endpoint = endpoint[8:] + } + if idx := strings.Index(endpoint, "/"); idx != -1 { + endpoint = endpoint[:idx] + } + if idx := strings.Index(endpoint, ":"); idx != -1 { + endpoint = endpoint[:idx] + } + return endpoint +} + +func getUptimeBarColor(report *db.EtlSlaNodeReport) string { + if report.NumBlocksProposed == 0 { + return "bg-gray-800" + } + // Simplified SLA check - assume meeting SLA if proposed blocks > 0 and not too many challenge failures + if report.NumBlocksProposed > 0 && report.ChallengesFailed <= (report.ChallengesReceived/5) { + return "bg-green-500" + } + return "bg-red-500" +} + +func formatRollupTime(report *db.EtlSlaNodeReport) string { + if report.CreatedAt.Valid { + return report.CreatedAt.Time.Format("01/02") + } + return "Unknown" +} + +type ValidatorProps struct { + Validator *db.EtlValidator + Events []*ValidatorEvent + Rollups []*db.EtlSlaNodeReport +} + +type ValidatorEvent struct { + Type string + BlockHeight int64 + TxHash string + Timestamp time.Time +} + +templ Validator(props ValidatorProps) { + @layouts.Base(fmt.Sprintf("Validator %s", props.Validator.Address[:8])) { +
+ +
+
+
+

Validator Details

+

{ props.Validator.Address }

+
+
+ switch props.Validator.Status { + case "active": + Active + case "deregistered": + Deregistered + case "misbehavior_deregistered": + Misbehavior + default: + Unknown + } +
+
+
+
+ +

{ props.Validator.CometAddress }

+
+
+ +

+ { extractHostFromValidator(props.Validator.Endpoint) } +

+
+
+ +

{ props.Validator.NodeType }

+
+
+ +

{ props.Validator.Spid }

+
+
+ +

{ fmt.Sprintf("%d", props.Validator.VotingPower) }

+
+
+ +

+ { fmt.Sprintf("%d", props.Validator.RegisteredAt) } +

+
+ if props.Validator.RegisteredAt > 0 { +
+ +

+ @templates.TimeWithTooltip(props.Validator.CreatedAt.Time) +

+
+ } + if props.Validator.UpdatedAt.Valid { +
+ +

+ @templates.TimeWithTooltip(props.Validator.UpdatedAt.Time) +

+
+ } +
+
+ + if len(props.Events) > 0 { +
+

Event History

+
+ for _, event := range props.Events { +
+
+
+ switch event.Type { + case "registration": + Registration + case "deregistration": + Deregistration + case "misbehavior_deregistration": + Misbehavior Deregistration + default: + Unknown + } +
+
+ @templates.TimeWithTooltip(event.Timestamp) +
+
+ +
+ } +
+
+ } + +
+

SLA Rollup History

+

+ Recent SLA rollup performance for this validator. Green indicates meeting SLA requirements, red indicates missing SLA, and dark gray indicates no activity. +

+
+
+
+ Meeting SLA +
+
+
+ Missing SLA +
+
+
+ Offline/Dead +
+
+ +
+ if len(props.Rollups) > 0 { + for _, rollup := range props.Rollups { +
+ + +
+
+
SLA Rollup #{ fmt.Sprint(rollup.SlaRollupID) }
+
+
Blocks: { fmt.Sprint(rollup.NumBlocksProposed) }
+
Challenges: { fmt.Sprint(rollup.ChallengesFailed) }/{ fmt.Sprint(rollup.ChallengesReceived) } failed
+
{ formatRollupTime(rollup) }
+
+
+ + +
+
+ } + } else { +
No SLA rollup data available
+ } +
+
+
+ } +} diff --git a/pkg/console/templates/pages/validator_templ.go b/pkg/console/templates/pages/validator_templ.go new file mode 100644 index 00000000..f4613165 --- /dev/null +++ b/pkg/console/templates/pages/validator_templ.go @@ -0,0 +1,486 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package pages + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "strings" + "time" +) + +func extractHostFromValidator(endpoint string) string { + // Same logic as extractHost but renamed to avoid conflicts + if strings.HasPrefix(endpoint, "http://") { + endpoint = endpoint[7:] + } else if strings.HasPrefix(endpoint, "https://") { + endpoint = endpoint[8:] + } + if idx := strings.Index(endpoint, "/"); idx != -1 { + endpoint = endpoint[:idx] + } + if idx := strings.Index(endpoint, ":"); idx != -1 { + endpoint = endpoint[:idx] + } + return endpoint +} + +func getUptimeBarColor(report *db.EtlSlaNodeReport) string { + if report.NumBlocksProposed == 0 { + return "bg-gray-800" + } + // Simplified SLA check - assume meeting SLA if proposed blocks > 0 and not too many challenge failures + if report.NumBlocksProposed > 0 && report.ChallengesFailed <= (report.ChallengesReceived/5) { + return "bg-green-500" + } + return "bg-red-500" +} + +func formatRollupTime(report *db.EtlSlaNodeReport) string { + if report.CreatedAt.Valid { + return report.CreatedAt.Time.Format("01/02") + } + return "Unknown" +} + +type ValidatorProps struct { + Validator *db.EtlValidator + Events []*ValidatorEvent + Rollups []*db.EtlSlaNodeReport +} + +type ValidatorEvent struct { + Type string + BlockHeight int64 + TxHash string + Timestamp time.Time +} + +func Validator(props ValidatorProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Validator Details

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(props.Validator.Address) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validator.templ`, Line: 67, Col: 99} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + switch props.Validator.Status { + case "active": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "Active") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "deregistered": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "Deregistered") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "misbehavior_deregistered": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "Misbehavior") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + default: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "Unknown") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(props.Validator.CometAddress) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validator.templ`, Line: 85, Col: 105} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(extractHostFromValidator(props.Validator.Endpoint)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validator.templ`, Line: 90, Col: 59} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(props.Validator.NodeType) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validator.templ`, Line: 95, Col: 73} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(props.Validator.Spid) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validator.templ`, Line: 99, Col: 69} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", props.Validator.VotingPower)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validator.templ`, Line: 103, Col: 109} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", props.Validator.RegisteredAt)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validator.templ`, Line: 108, Col: 56} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.Validator.RegisteredAt > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.TimeWithTooltip(props.Validator.CreatedAt.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if props.Validator.UpdatedAt.Valid { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.TimeWithTooltip(props.Validator.UpdatedAt.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(props.Events) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

Event History

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, event := range props.Events { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + switch event.Type { + case "registration": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "Registration") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "deregistration": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "Deregistration") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "misbehavior_deregistration": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "Misbehavior Deregistration") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + default: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "Unknown") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.TimeWithTooltip(event.Timestamp).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "

SLA Rollup History

Recent SLA rollup performance for this validator. Green indicates meeting SLA requirements, red indicates missing SLA, and dark gray indicates no activity.

Meeting SLA
Missing SLA
Offline/Dead
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(props.Rollups) > 0 { + for _, rollup := range props.Rollups { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 = []any{"w-4 h-16 rounded-lg cursor-pointer transition-all hover:opacity-80 hover:scale-110 block", getUptimeBarColor(rollup)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
SLA Rollup #") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(rollup.SlaRollupID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validator.templ`, Line: 210, Col: 81} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
Blocks: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(rollup.NumBlocksProposed)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validator.templ`, Line: 212, Col: 62} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
Challenges: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(rollup.ChallengesFailed)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validator.templ`, Line: 213, Col: 65} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "/") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var20 string + templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(rollup.ChallengesReceived)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validator.templ`, Line: 213, Col: 107} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, " failed
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(formatRollupTime(rollup)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validator.templ`, Line: 214, Col: 64} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "
No SLA rollup data available
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base(fmt.Sprintf("Validator %s", props.Validator.Address[:8])).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/pages/validators.templ b/pkg/console/templates/pages/validators.templ new file mode 100644 index 00000000..c0263099 --- /dev/null +++ b/pkg/console/templates/pages/validators.templ @@ -0,0 +1,269 @@ +package pages + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "strings" +) + +func extractHost(endpoint string) string { + // Remove protocol if present + if strings.HasPrefix(endpoint, "http://") { + endpoint = endpoint[7:] + } else if strings.HasPrefix(endpoint, "https://") { + endpoint = endpoint[8:] + } + + // Remove path if present + if idx := strings.Index(endpoint, "/"); idx != -1 { + endpoint = endpoint[:idx] + } + + // Remove port if present (optional, remove this if you want to keep ports) + if idx := strings.Index(endpoint, ":"); idx != -1 { + endpoint = endpoint[:idx] + } + + trimLen := 28 + + if len(endpoint) <= trimLen { + return endpoint + } + + return endpoint[:trimLen] + "..." +} + +func buildValidatorsPaginationURL(page int32, pageSize int32, queryType string, endpointFilter string) string { + baseURL := fmt.Sprintf("?page=%d&count=%d&type=%s", page, pageSize, queryType) + if endpointFilter != "" { + baseURL += "&endpoint_filter=" + endpointFilter + } + return baseURL +} + +// Helper functions for uptime display +func validatorMeetsSlaThreshold(report *db.EtlSlaNodeReport) bool { + if report.SlaRollupID == 0 { + return true + } + + // We don't have block quota in the node report, so we'll use a simplified check + // Assume they meet SLA if they have proposed blocks and not too many challenge failures + if report.NumBlocksProposed > 0 && report.ChallengesFailed <= (report.ChallengesReceived/5) { + return true + } + return false +} + +func validatorUptimeColor(report *db.EtlSlaNodeReport) string { + if report.NumBlocksProposed == 0 { + return "bg-gray-800" + } + if validatorMeetsSlaThreshold(report) { + return "bg-green-500" + } + return "bg-red-500" +} + +type ValidatorsProps struct { + Validators []*db.EtlValidator + ValidatorUptimeMap map[string][]*db.EtlSlaNodeReport + CurrentPage int32 + HasNext bool + HasPrev bool + PageSize int32 + QueryType string + EndpointFilter string +} + +templ Validators(props ValidatorsProps) { + @layouts.Base("Validators") { + + + + } +} diff --git a/pkg/console/templates/pages/validators_templ.go b/pkg/console/templates/pages/validators_templ.go new file mode 100644 index 00000000..ccc94e92 --- /dev/null +++ b/pkg/console/templates/pages/validators_templ.go @@ -0,0 +1,518 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package pages + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "strings" +) + +func extractHost(endpoint string) string { + // Remove protocol if present + if strings.HasPrefix(endpoint, "http://") { + endpoint = endpoint[7:] + } else if strings.HasPrefix(endpoint, "https://") { + endpoint = endpoint[8:] + } + + // Remove path if present + if idx := strings.Index(endpoint, "/"); idx != -1 { + endpoint = endpoint[:idx] + } + + // Remove port if present (optional, remove this if you want to keep ports) + if idx := strings.Index(endpoint, ":"); idx != -1 { + endpoint = endpoint[:idx] + } + + trimLen := 28 + + if len(endpoint) <= trimLen { + return endpoint + } + + return endpoint[:trimLen] + "..." +} + +func buildValidatorsPaginationURL(page int32, pageSize int32, queryType string, endpointFilter string) string { + baseURL := fmt.Sprintf("?page=%d&count=%d&type=%s", page, pageSize, queryType) + if endpointFilter != "" { + baseURL += "&endpoint_filter=" + endpointFilter + } + return baseURL +} + +// Helper functions for uptime display +func validatorMeetsSlaThreshold(report *db.EtlSlaNodeReport) bool { + if report.SlaRollupID == 0 { + return true + } + + // We don't have block quota in the node report, so we'll use a simplified check + // Assume they meet SLA if they have proposed blocks and not too many challenge failures + if report.NumBlocksProposed > 0 && report.ChallengesFailed <= (report.ChallengesReceived/5) { + return true + } + return false +} + +func validatorUptimeColor(report *db.EtlSlaNodeReport) string { + if report.NumBlocksProposed == 0 { + return "bg-gray-800" + } + if validatorMeetsSlaThreshold(report) { + return "bg-green-500" + } + return "bg-red-500" +} + +type ValidatorsProps struct { + Validators []*db.EtlValidator + ValidatorUptimeMap map[string][]*db.EtlSlaNodeReport + CurrentPage int32 + HasNext bool + HasPrev bool + PageSize int32 + QueryType string + EndpointFilter string +} + +func Validators(props ValidatorsProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Validators

Page ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.CurrentPage)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators.templ`, Line: 98, Col: 42} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.PageSize)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators.templ`, Line: 98, Col: 77} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " per page
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 = []any{"px-4 py-2 text-sm font-medium transition-colors", templ.KV("bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900", props.QueryType == "active"), templ.KV("text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700", props.QueryType != "active")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "Active ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 = []any{"px-4 py-2 text-sm font-medium border-l border-gray-300 dark:border-gray-400 transition-colors", templ.KV("bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900", props.QueryType == "registrations"), templ.KV("text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-[#383838]", props.QueryType != "registrations")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "Registrations ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 = []any{"px-4 py-2 text-sm font-medium border-l border-gray-300 dark:border-gray-400 transition-colors", templ.KV("bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900", props.QueryType == "deregistrations"), templ.KV("text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-[#383838]", props.QueryType != "deregistrations")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "Deregistrations
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(props.Validators) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, validator := range props.Validators { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
Endpoint
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(extractHost(validator.Endpoint)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators.templ`, Line: 132, Col: 45} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
Address
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltipCustom(validator.Address, 8, 4).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
Node Type
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(validator.NodeType) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators.templ`, Line: 146, Col: 31} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
Voting Power
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", validator.VotingPower)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators.templ`, Line: 152, Col: 53} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
Status
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + switch validator.Status { + case "active": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "Active ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "deregistered": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "Deregistered ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "misbehavior_deregistered": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "Misbehavior ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + default: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "Unknown ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "|
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if validator.RegisteredAt > 0 { + templ_7745c5c3_Err = templates.TimeWithTooltip(validator.CreatedAt.Time).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
Uptime
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if rollups, exists := props.ValidatorUptimeMap[validator.CometAddress]; exists && len(rollups) > 0 { + for i := len(rollups) - 1; i >= 0; i-- { + var templ_7745c5c3_Var19 = []any{"w-3 h-4 rounded-sm", validatorUptimeColor(rollups[i])} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var19...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for i := len(rollups); i < 5; i++ { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } else { + for i := 0; i < 5; i++ { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.HasPrev { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "← Previous ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "← Previous ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if props.HasNext { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "Next →") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "Next →") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "

No validators found

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Validators").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/pages/validators_uptime.templ b/pkg/console/templates/pages/validators_uptime.templ new file mode 100644 index 00000000..a374961d --- /dev/null +++ b/pkg/console/templates/pages/validators_uptime.templ @@ -0,0 +1,504 @@ +package pages + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "strings" +) + +const ( + slaMeetsThreshold = 0.8 // 80% threshold for both PoW and PoS +) + +func extractHostname(endpoint string) string { + // Remove protocol if present + if strings.HasPrefix(endpoint, "http://") { + endpoint = endpoint[7:] + } else if strings.HasPrefix(endpoint, "https://") { + endpoint = endpoint[8:] + } + + // Remove path if present + if idx := strings.Index(endpoint, "/"); idx != -1 { + endpoint = endpoint[:idx] + } + + // Remove port if present (optional, remove this if you want to keep ports) + if idx := strings.Index(endpoint, ":"); idx != -1 { + endpoint = endpoint[:idx] + } + + return endpoint +} + +func meetsPoWSla(report *db.EtlSlaNodeReport, blockQuota int32) bool { + if blockQuota <= 0 { + return true // No quota means no requirement + } + + faultRatio := float64(report.NumBlocksProposed) / float64(blockQuota) + return faultRatio >= slaMeetsThreshold +} + +func meetsPoSSla(report *db.EtlSlaNodeReport) bool { + if report.ChallengesReceived <= 0 { + return true // No challenges means passing + } + + successRatio := 1.0 - (float64(report.ChallengesFailed) / float64(report.ChallengesReceived)) + return successRatio >= slaMeetsThreshold +} + +func meetsSlaThreshold(report *db.EtlSlaNodeReport, blockQuota int32) bool { + // Node is considered dead if it proposed no blocks + if report.NumBlocksProposed == 0 { + return false + } + + // Must meet both PoW and PoS SLA requirements + return meetsPoWSla(report, blockQuota) && meetsPoSSla(report) +} + +func getUptimeColor(report *db.EtlSlaNodeReport, blockQuota int32) string { + if report.NumBlocksProposed == 0 { + return "bg-gray-800" + } + if meetsSlaThreshold(report, blockQuota) { + return "bg-green-500" + } + return "bg-red-500" +} + +func getStatusText(validatorStatus string, report *db.EtlSlaNodeReport, blockQuota int32) (string, string) { + // First determine validator registration status + var statusText, statusClass string + switch validatorStatus { + case "active": + statusText = "Active" + statusClass = "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200" + case "deregistered": + statusText = "Deregistered" + statusClass = "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200" + case "misbehavior_deregistered": + statusText = "Misbehavior" + statusClass = "bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200" + default: + statusText = "Unknown" + statusClass = "bg-gray-100 dark:bg-[#141414] text-gray-800 dark:text-gray-200" + } + + // Then determine SLA status for active validators + if validatorStatus == "active" && report != nil { + if report.NumBlocksProposed == 0 { + statusText += " / Dead" + statusClass = "bg-gray-800 text-white" + } else if meetsSlaThreshold(report, blockQuota) { + statusText += " / Pass" + statusClass = "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200" + } else { + statusText += " / Fail" + statusClass = "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200" + } + } + + return statusText, statusClass +} + +func getValidatorStatusDisplay(validatorInfo *ValidatorUptimeInfo, rollupData *db.EtlSlaRollup) (string, string) { + var blockQuota int32 = 0 + if rollupData != nil { + blockQuota = rollupData.BlockQuota + } + + var report *db.EtlSlaNodeReport = nil + if len(validatorInfo.RecentRollups) > 0 { + report = validatorInfo.RecentRollups[0] + } + + return getStatusText(validatorInfo.Validator.Status, report, blockQuota) +} + +func getValidatorStatusText(validatorInfo *ValidatorUptimeInfo, rollupData *db.EtlSlaRollup) string { + switch validatorInfo.Validator.Status { + case "active": + return "Active" + case "deregistered": + return "Deregistered" + case "misbehavior_deregistered": + return "Misbehavior" + default: + return "Unknown" + } +} + +func getValidatorStatusClass(validatorInfo *ValidatorUptimeInfo, rollupData *db.EtlSlaRollup) string { + switch validatorInfo.Validator.Status { + case "active": + return "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200" + case "deregistered": + return "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200" + case "misbehavior_deregistered": + return "bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200" + default: + return "bg-gray-100 dark:bg-[#141414] text-gray-800 dark:text-gray-200" + } +} + +func getSlaStatusText(validatorInfo *ValidatorUptimeInfo, rollupData *db.EtlSlaRollup) string { + if validatorInfo.Validator.Status != "active" { + return "" // Only show SLA for active validators + } + + if len(validatorInfo.RecentRollups) == 0 { + return "No Data" + } + + report := validatorInfo.RecentRollups[0] + var blockQuota int32 = 0 + if rollupData != nil { + blockQuota = rollupData.BlockQuota + } + + if report.NumBlocksProposed == 0 { + return "Dead" + } else if meetsSlaThreshold(report, blockQuota) { + return "Pass" + } else { + return "Fail" + } +} + +func getSlaStatusClass(validatorInfo *ValidatorUptimeInfo, rollupData *db.EtlSlaRollup) string { + if validatorInfo.Validator.Status != "active" { + return "" // Only show SLA for active validators + } + + if len(validatorInfo.RecentRollups) == 0 { + return "bg-gray-100 dark:bg-[#141414] text-gray-800 dark:text-gray-200" + } + + report := validatorInfo.RecentRollups[0] + var blockQuota int32 = 0 + if rollupData != nil { + blockQuota = rollupData.BlockQuota + } + + if report.NumBlocksProposed == 0 { + return "bg-gray-800 text-white" + } else if meetsSlaThreshold(report, blockQuota) { + return "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200" + } else { + return "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200" + } +} + +type ValidatorUptimeInfo struct { + Validator *db.EtlValidator + RecentRollups []*db.EtlSlaNodeReport +} + +type ValidatorsUptimeProps struct { + Validators []*ValidatorUptimeInfo + RollupData *db.EtlSlaRollup +} + +type ValidatorsUptimeByRollupProps struct { + Validators []*ValidatorUptimeInfo + RollupID int32 + RollupData *db.EtlSlaRollup +} + +func getLatestRollupInfo(validators []*ValidatorUptimeInfo) *db.EtlSlaNodeReport { + if len(validators) == 0 { + return nil + } + + for _, validator := range validators { + if len(validator.RecentRollups) > 0 { + return validator.RecentRollups[0] // Most recent rollup + } + } + return nil +} + +func getRollupInfo(validators []*ValidatorUptimeInfo, rollupId int32) *db.EtlSlaNodeReport { + if len(validators) == 0 { + return nil + } + + for _, validator := range validators { + for _, rollup := range validator.RecentRollups { + if rollup.SlaRollupID == rollupId { + return rollup + } + } + } + return nil +} + +func getTotalValidatorsCount(validators []*ValidatorUptimeInfo) int { + return len(validators) +} + +templ ValidatorsUptime(props ValidatorsUptimeProps) { + @layouts.Base("Validators Uptime") { +
+
+
+

Validators Uptime

+

+ Recent SLA rollup performance across all validators. Green indicates meeting SLA requirements, red indicates missing SLA. +

+
+
+
+
+ Meeting SLA +
+
+
+ Missing SLA +
+
+
+ Offline/Dead +
+
+
+ if props.RollupData != nil { + @ValidatorsUptimeSummaryForMain(props.RollupData) + } + @ValidatorsUptimeTable(props.Validators, props.RollupData) +
+ } +} + +templ ValidatorsUptimeByRollup(props ValidatorsUptimeByRollupProps) { + @layouts.Base(fmt.Sprintf("SLA Rollup #%d Uptime", props.RollupID)) { +
+
+
+

SLA Rollup #{ fmt.Sprint(props.RollupID) } Uptime

+

+ Validator performance for SLA rollup { fmt.Sprint(props.RollupID) } +

+
+
+ { fmt.Sprint(len(props.Validators)) } validators +
+
+ // Add summary cards section for the specific rollup + @ValidatorsUptimeSummaryForRollup(props.Validators, props.RollupID, props.RollupData) + @ValidatorsUptimeTable(props.Validators, props.RollupData) +
+ } +} + +templ ValidatorsUptimeSummaryForMain(rollupData *db.EtlSlaRollup) { +
+
+
+ if rollupData.CreatedAt.Valid { + { rollupData.CreatedAt.Time.Format("06-01-02") } +
+ { rollupData.CreatedAt.Time.Format("15:04:05 MST") } + } else { + — + } +
+
Latest Rollup Date
+
+ + +
+
+ { fmt.Sprintf("%d", rollupData.ValidatorCount) } +
+
Active Validators
+
+
+ // Add additional metrics row +
+
+
+ { fmt.Sprintf("%d", rollupData.BlockQuota) } +
+
Block Quota
+
+
+
+ if rollupData.TxHash != "" { + + @templates.StringWithTooltipCustom(rollupData.TxHash, 8, 4) + + } else { + — + } +
+
Transaction Hash
+
+
+} + +templ ValidatorsUptimeSummaryForRollup(validators []*ValidatorUptimeInfo, rollupId int32, rollupData *db.EtlSlaRollup) { + if rollupData != nil { +
+
+
+ if rollupData.CreatedAt.Valid { + { rollupData.CreatedAt.Time.Format("06-01-02") } +
+ { rollupData.CreatedAt.Time.Format("15:04:05 MST") } + } else { + — + } +
+
Date Created
+
+
+
+ { fmt.Sprintf("#%d", rollupData.ID) } +
+
SLA Rollup ID
+
+ +
+
+ { fmt.Sprintf("%d", rollupData.BlockEnd - rollupData.BlockStart + 1) } +
+
Total Blocks
+
+
+
+ { fmt.Sprintf("%d", rollupData.ValidatorCount) } +
+
Active Validators
+
+
+ // Add additional metrics row +
+
+
+ { fmt.Sprintf("%d", rollupData.BlockQuota) } +
+
Block Quota
+
+
+
+ if rollupData.TxHash != "" { + + @templates.StringWithTooltipCustom(rollupData.TxHash, 8, 4) + + } else { + — + } +
+
Transaction Hash
+
+
+ } +} + +templ ValidatorsUptimeTable(validators []*ValidatorUptimeInfo, rollupData *db.EtlSlaRollup) { + if len(validators) > 0 { +
+ + + + + + + + + + + + + for _, validatorInfo := range validators { + + + + + + + + + } + +
ValidatorEndpointBlocks ProposedChallenges ReceivedChallenges FailedStatus
+ + @templates.StringWithTooltipCustom(validatorInfo.Validator.Address, 8, 4) + + + { extractHostname(validatorInfo.Validator.Endpoint) } + + if len(validatorInfo.RecentRollups) > 0 { + { fmt.Sprintf("%d", validatorInfo.RecentRollups[0].NumBlocksProposed) } + if rollupData != nil { + / { fmt.Sprintf("%d", rollupData.BlockQuota) } + } + } else { + + } + + if len(validatorInfo.RecentRollups) > 0 { + { fmt.Sprintf("%d", validatorInfo.RecentRollups[0].ChallengesReceived) } + } else { + + } + + if len(validatorInfo.RecentRollups) > 0 { + { fmt.Sprintf("%d", validatorInfo.RecentRollups[0].ChallengesFailed) } + } else { + + } + +
+ + { getValidatorStatusText(validatorInfo, rollupData) } + + if getSlaStatusText(validatorInfo, rollupData) != "" { + + { getSlaStatusText(validatorInfo, rollupData) } + + } +
+
+
+ } else { +
+

No validator uptime data found

+
+ } +} diff --git a/pkg/console/templates/pages/validators_uptime_templ.go b/pkg/console/templates/pages/validators_uptime_templ.go new file mode 100644 index 00000000..d718696f --- /dev/null +++ b/pkg/console/templates/pages/validators_uptime_templ.go @@ -0,0 +1,1057 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package pages + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "github.com/OpenAudio/go-openaudio/pkg/console/templates" + "github.com/OpenAudio/go-openaudio/pkg/console/templates/layouts" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "strings" +) + +const ( + slaMeetsThreshold = 0.8 // 80% threshold for both PoW and PoS +) + +func extractHostname(endpoint string) string { + // Remove protocol if present + if strings.HasPrefix(endpoint, "http://") { + endpoint = endpoint[7:] + } else if strings.HasPrefix(endpoint, "https://") { + endpoint = endpoint[8:] + } + + // Remove path if present + if idx := strings.Index(endpoint, "/"); idx != -1 { + endpoint = endpoint[:idx] + } + + // Remove port if present (optional, remove this if you want to keep ports) + if idx := strings.Index(endpoint, ":"); idx != -1 { + endpoint = endpoint[:idx] + } + + return endpoint +} + +func meetsPoWSla(report *db.EtlSlaNodeReport, blockQuota int32) bool { + if blockQuota <= 0 { + return true // No quota means no requirement + } + + faultRatio := float64(report.NumBlocksProposed) / float64(blockQuota) + return faultRatio >= slaMeetsThreshold +} + +func meetsPoSSla(report *db.EtlSlaNodeReport) bool { + if report.ChallengesReceived <= 0 { + return true // No challenges means passing + } + + successRatio := 1.0 - (float64(report.ChallengesFailed) / float64(report.ChallengesReceived)) + return successRatio >= slaMeetsThreshold +} + +func meetsSlaThreshold(report *db.EtlSlaNodeReport, blockQuota int32) bool { + // Node is considered dead if it proposed no blocks + if report.NumBlocksProposed == 0 { + return false + } + + // Must meet both PoW and PoS SLA requirements + return meetsPoWSla(report, blockQuota) && meetsPoSSla(report) +} + +func getUptimeColor(report *db.EtlSlaNodeReport, blockQuota int32) string { + if report.NumBlocksProposed == 0 { + return "bg-gray-800" + } + if meetsSlaThreshold(report, blockQuota) { + return "bg-green-500" + } + return "bg-red-500" +} + +func getStatusText(validatorStatus string, report *db.EtlSlaNodeReport, blockQuota int32) (string, string) { + // First determine validator registration status + var statusText, statusClass string + switch validatorStatus { + case "active": + statusText = "Active" + statusClass = "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200" + case "deregistered": + statusText = "Deregistered" + statusClass = "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200" + case "misbehavior_deregistered": + statusText = "Misbehavior" + statusClass = "bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200" + default: + statusText = "Unknown" + statusClass = "bg-gray-100 dark:bg-[#141414] text-gray-800 dark:text-gray-200" + } + + // Then determine SLA status for active validators + if validatorStatus == "active" && report != nil { + if report.NumBlocksProposed == 0 { + statusText += " / Dead" + statusClass = "bg-gray-800 text-white" + } else if meetsSlaThreshold(report, blockQuota) { + statusText += " / Pass" + statusClass = "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200" + } else { + statusText += " / Fail" + statusClass = "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200" + } + } + + return statusText, statusClass +} + +func getValidatorStatusDisplay(validatorInfo *ValidatorUptimeInfo, rollupData *db.EtlSlaRollup) (string, string) { + var blockQuota int32 = 0 + if rollupData != nil { + blockQuota = rollupData.BlockQuota + } + + var report *db.EtlSlaNodeReport = nil + if len(validatorInfo.RecentRollups) > 0 { + report = validatorInfo.RecentRollups[0] + } + + return getStatusText(validatorInfo.Validator.Status, report, blockQuota) +} + +func getValidatorStatusText(validatorInfo *ValidatorUptimeInfo, rollupData *db.EtlSlaRollup) string { + switch validatorInfo.Validator.Status { + case "active": + return "Active" + case "deregistered": + return "Deregistered" + case "misbehavior_deregistered": + return "Misbehavior" + default: + return "Unknown" + } +} + +func getValidatorStatusClass(validatorInfo *ValidatorUptimeInfo, rollupData *db.EtlSlaRollup) string { + switch validatorInfo.Validator.Status { + case "active": + return "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200" + case "deregistered": + return "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200" + case "misbehavior_deregistered": + return "bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200" + default: + return "bg-gray-100 dark:bg-[#141414] text-gray-800 dark:text-gray-200" + } +} + +func getSlaStatusText(validatorInfo *ValidatorUptimeInfo, rollupData *db.EtlSlaRollup) string { + if validatorInfo.Validator.Status != "active" { + return "" // Only show SLA for active validators + } + + if len(validatorInfo.RecentRollups) == 0 { + return "No Data" + } + + report := validatorInfo.RecentRollups[0] + var blockQuota int32 = 0 + if rollupData != nil { + blockQuota = rollupData.BlockQuota + } + + if report.NumBlocksProposed == 0 { + return "Dead" + } else if meetsSlaThreshold(report, blockQuota) { + return "Pass" + } else { + return "Fail" + } +} + +func getSlaStatusClass(validatorInfo *ValidatorUptimeInfo, rollupData *db.EtlSlaRollup) string { + if validatorInfo.Validator.Status != "active" { + return "" // Only show SLA for active validators + } + + if len(validatorInfo.RecentRollups) == 0 { + return "bg-gray-100 dark:bg-[#141414] text-gray-800 dark:text-gray-200" + } + + report := validatorInfo.RecentRollups[0] + var blockQuota int32 = 0 + if rollupData != nil { + blockQuota = rollupData.BlockQuota + } + + if report.NumBlocksProposed == 0 { + return "bg-gray-800 text-white" + } else if meetsSlaThreshold(report, blockQuota) { + return "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200" + } else { + return "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200" + } +} + +type ValidatorUptimeInfo struct { + Validator *db.EtlValidator + RecentRollups []*db.EtlSlaNodeReport +} + +type ValidatorsUptimeProps struct { + Validators []*ValidatorUptimeInfo + RollupData *db.EtlSlaRollup +} + +type ValidatorsUptimeByRollupProps struct { + Validators []*ValidatorUptimeInfo + RollupID int32 + RollupData *db.EtlSlaRollup +} + +func getLatestRollupInfo(validators []*ValidatorUptimeInfo) *db.EtlSlaNodeReport { + if len(validators) == 0 { + return nil + } + + for _, validator := range validators { + if len(validator.RecentRollups) > 0 { + return validator.RecentRollups[0] // Most recent rollup + } + } + return nil +} + +func getRollupInfo(validators []*ValidatorUptimeInfo, rollupId int32) *db.EtlSlaNodeReport { + if len(validators) == 0 { + return nil + } + + for _, validator := range validators { + for _, rollup := range validator.RecentRollups { + if rollup.SlaRollupID == rollupId { + return rollup + } + } + } + return nil +} + +func getTotalValidatorsCount(validators []*ValidatorUptimeInfo) int { + return len(validators) +} + +func ValidatorsUptime(props ValidatorsUptimeProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Validators Uptime

Recent SLA rollup performance across all validators. Green indicates meeting SLA requirements, red indicates missing SLA.

Meeting SLA
Missing SLA
Offline/Dead
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.RollupData != nil { + templ_7745c5c3_Err = ValidatorsUptimeSummaryForMain(props.RollupData).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = ValidatorsUptimeTable(props.Validators, props.RollupData).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Validators Uptime").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func ValidatorsUptimeByRollup(props ValidatorsUptimeByRollupProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var3 := templ.GetChildren(ctx) + if templ_7745c5c3_Var3 == nil { + templ_7745c5c3_Var3 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "

SLA Rollup #") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.RollupID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 283, Col: 107} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " Uptime

Validator performance for SLA rollup ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(props.RollupID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 285, Col: 71} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(len(props.Validators))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 289, Col: 40} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " validators
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = ValidatorsUptimeSummaryForRollup(props.Validators, props.RollupID, props.RollupData).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = ValidatorsUptimeTable(props.Validators, props.RollupData).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base(fmt.Sprintf("SLA Rollup #%d Uptime", props.RollupID)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func ValidatorsUptimeSummaryForMain(rollupData *db.EtlSlaRollup) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var8 := templ.GetChildren(ctx) + if templ_7745c5c3_Var8 == nil { + templ_7745c5c3_Var8 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if rollupData.CreatedAt.Valid { + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(rollupData.CreatedAt.Time.Format("06-01-02")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 304, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(rollupData.CreatedAt.Time.Format("15:04:05 MST")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 306, Col: 55} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "—") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
Latest Rollup Date
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", rollupData.ValidatorCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 335, Col: 50} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
Active Validators
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", rollupData.BlockQuota)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 344, Col: 46} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
Block Quota
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if rollupData.TxHash != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltipCustom(rollupData.TxHash, 8, 4).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "—") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
Transaction Hash
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func ValidatorsUptimeSummaryForRollup(validators []*ValidatorUptimeInfo, rollupId int32, rollupData *db.EtlSlaRollup) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var20 := templ.GetChildren(ctx) + if templ_7745c5c3_Var20 == nil { + templ_7745c5c3_Var20 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + if rollupData != nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if rollupData.CreatedAt.Valid { + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(rollupData.CreatedAt.Time.Format("06-01-02")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 369, Col: 52} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(rollupData.CreatedAt.Time.Format("15:04:05 MST")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 371, Col: 56} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "—") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
Date Created
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var23 string + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("#%d", rollupData.ID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 380, Col: 40} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
SLA Rollup ID
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", rollupData.BlockEnd-rollupData.BlockStart+1)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 398, Col: 73} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
Total Blocks
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", rollupData.ValidatorCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 404, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
Active Validators
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var30 string + templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", rollupData.BlockQuota)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 413, Col: 47} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
Block Quota
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if rollupData.TxHash != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltipCustom(rollupData.TxHash, 8, 4).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "—") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
Transaction Hash
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +func ValidatorsUptimeTable(validators []*ValidatorUptimeInfo, rollupData *db.EtlSlaRollup) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var32 := templ.GetChildren(ctx) + if templ_7745c5c3_Var32 == nil { + templ_7745c5c3_Var32 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + if len(validators) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, validatorInfo := range validators { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "
ValidatorEndpointBlocks ProposedChallenges ReceivedChallenges FailedStatus
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templates.StringWithTooltipCustom(validatorInfo.Validator.Address, 8, 4).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var34 string + templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(extractHostname(validatorInfo.Validator.Endpoint)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 456, Col: 59} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(validatorInfo.RecentRollups) > 0 { + var templ_7745c5c3_Var35 string + templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", validatorInfo.RecentRollups[0].NumBlocksProposed)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 460, Col: 78} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if rollupData != nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "/ ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var36 string + templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", rollupData.BlockQuota)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 462, Col: 95} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(validatorInfo.RecentRollups) > 0 { + var templ_7745c5c3_Var37 string + templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", validatorInfo.RecentRollups[0].ChallengesReceived)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 470, Col: 79} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(validatorInfo.RecentRollups) > 0 { + var templ_7745c5c3_Var38 string + templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", validatorInfo.RecentRollups[0].ChallengesFailed)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 477, Col: 77} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var39 = []any{"px-2 py-1 text-xs rounded " + getValidatorStatusClass(validatorInfo, rollupData)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var39...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var41 string + templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(getValidatorStatusText(validatorInfo, rollupData)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 485, Col: 61} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if getSlaStatusText(validatorInfo, rollupData) != "" { + var templ_7745c5c3_Var42 = []any{"px-2 py-1 text-xs rounded " + getSlaStatusClass(validatorInfo, rollupData)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var42...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var44 string + templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(getSlaStatusText(validatorInfo, rollupData)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/validators_uptime.templ`, Line: 489, Col: 56} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "

No validator uptime data found

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/string.templ b/pkg/console/templates/string.templ new file mode 100644 index 00000000..ef13ddea --- /dev/null +++ b/pkg/console/templates/string.templ @@ -0,0 +1,106 @@ +package templates + +// shortenString takes a string and returns first 4 chars + "..." + last 4 chars +// if the string is short enough, it returns the original string +func shortenString(s string) string { + if len(s) <= 12 { + return s + } + return s[:4] + "..." + s[len(s)-4:] +} + +// shortenStringCustom takes a string and returns first `front` chars + "..." + last `back` chars +// if the string is short enough, it returns the original string +func shortenStringCustom(s string, front, back int) string { + if len(s) <= front+back+3 { // +3 for the "..." + return s + } + return s[:front] + "..." + s[len(s)-back:] +} + +// truncateString takes a string and returns it truncated with "..." if longer than maxLen +func truncateString(s string, maxLen int) string { + if len(s) <= maxLen { + return s + } + return s[:maxLen-3] + "..." +} + +script copyToClipboard(value string) { + // Get the clicked element + var element = event.currentTarget; + + navigator.clipboard.writeText(value); + + // Quick color flash - darker in light mode, lighter in dark mode + var isDark = document.documentElement.classList.contains('dark'); + var flashColor = isDark ? '#4b5563' : '#d1d5db'; // gray-600 for dark mode, gray-300 for light mode + + element.style.backgroundColor = flashColor; + element.style.transform = 'scale(1.02)'; + + setTimeout(() => { + element.style.backgroundColor = ''; + element.style.transform = ''; + }, 300); +} + +templ StringWithTooltip(value string) { + + { shortenString(value) } + + + + + +
+
+
{ value }
+
+
+} + +templ StringWithTooltipCustom(value string, front, back int) { + + { shortenStringCustom(value, front, back) } + + + + + +
+
+
{ value }
+
+
+} + +templ StringWithTooltipClass(value string, classes string) { + + { shortenString(value) } + + + + + +
+
+
{ value }
+
+
+} + +templ TruncatedStringWithTooltip(value string, maxLen int) { + + { truncateString(value, maxLen) } + + + + + +
+
+
{ value }
+
+
+} diff --git a/pkg/console/templates/string_templ.go b/pkg/console/templates/string_templ.go new file mode 100644 index 00000000..856d6fb8 --- /dev/null +++ b/pkg/console/templates/string_templ.go @@ -0,0 +1,352 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package templates + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +// shortenString takes a string and returns first 4 chars + "..." + last 4 chars +// if the string is short enough, it returns the original string +func shortenString(s string) string { + if len(s) <= 12 { + return s + } + return s[:4] + "..." + s[len(s)-4:] +} + +// shortenStringCustom takes a string and returns first `front` chars + "..." + last `back` chars +// if the string is short enough, it returns the original string +func shortenStringCustom(s string, front, back int) string { + if len(s) <= front+back+3 { // +3 for the "..." + return s + } + return s[:front] + "..." + s[len(s)-back:] +} + +// truncateString takes a string and returns it truncated with "..." if longer than maxLen +func truncateString(s string, maxLen int) string { + if len(s) <= maxLen { + return s + } + return s[:maxLen-3] + "..." +} + +func copyToClipboard(value string) templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_copyToClipboard_954e`, + Function: `function __templ_copyToClipboard_954e(value){// Get the clicked element + var element = event.currentTarget; + + navigator.clipboard.writeText(value); + + // Quick color flash - darker in light mode, lighter in dark mode + var isDark = document.documentElement.classList.contains('dark'); + var flashColor = isDark ? '#4b5563' : '#d1d5db'; // gray-600 for dark mode, gray-300 for light mode + + element.style.backgroundColor = flashColor; + element.style.transform = 'scale(1.02)'; + + setTimeout(() => { + element.style.backgroundColor = ''; + element.style.transform = ''; + }, 300); +}`, + Call: templ.SafeScript(`__templ_copyToClipboard_954e`, value), + CallInline: templ.SafeScriptInline(`__templ_copyToClipboard_954e`, value), + } +} + +func StringWithTooltip(value string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyToClipboard(value)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(shortenString(value)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `string.templ`, Line: 50, Col: 50} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(value) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `string.templ`, Line: 58, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func StringWithTooltipCustom(value string, front, back int) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var5 := templ.GetChildren(ctx) + if templ_7745c5c3_Var5 == nil { + templ_7745c5c3_Var5 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyToClipboard(value)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(shortenStringCustom(value, front, back)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `string.templ`, Line: 65, Col: 69} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(value) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `string.templ`, Line: 73, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func StringWithTooltipClass(value string, classes string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var9 := templ.GetChildren(ctx) + if templ_7745c5c3_Var9 == nil { + templ_7745c5c3_Var9 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var10 = []any{"relative group inline-flex items-center cursor-pointer font-mono hover:bg-gray-100 dark:hover:bg-gray-700 rounded px-1 py-0.5 transition-all duration-200 " + classes} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyToClipboard(value)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(shortenString(value)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `string.templ`, Line: 80, Col: 50} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(value) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `string.templ`, Line: 88, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func TruncatedStringWithTooltip(value string, maxLen int) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var15 := templ.GetChildren(ctx) + if templ_7745c5c3_Var15 == nil { + templ_7745c5c3_Var15 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyToClipboard(value)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(truncateString(value, maxLen)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `string.templ`, Line: 95, Col: 59} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(value) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `string.templ`, Line: 103, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/console/templates/time.templ b/pkg/console/templates/time.templ new file mode 100644 index 00000000..27211472 --- /dev/null +++ b/pkg/console/templates/time.templ @@ -0,0 +1,26 @@ +package templates + +import ( + "github.com/dustin/go-humanize" + "time" +) + +templ TimeWithTooltip(t time.Time) { + + { humanize.Time(t) } +
+
+ { t.Format("2006-01-02 15:04:05 UTC") } +
+
+} + +templ TimeWithTooltipClass(t time.Time, classes string) { + + { humanize.Time(t) } +
+
+ { t.Format("2006-01-02 15:04:05 UTC") } +
+
+} diff --git a/pkg/console/templates/time_templ.go b/pkg/console/templates/time_templ.go new file mode 100644 index 00000000..9509712e --- /dev/null +++ b/pkg/console/templates/time_templ.go @@ -0,0 +1,170 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.898 +package templates + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "github.com/dustin/go-humanize" + "time" +) + +func TimeWithTooltip(t time.Time) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(humanize.Time(t)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `time.templ`, Line: 10, Col: 20} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(t.Format("2006-01-02 15:04:05 UTC")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `time.templ`, Line: 13, Col: 73} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func TimeWithTooltipClass(t time.Time, classes string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var5 := templ.GetChildren(ctx) + if templ_7745c5c3_Var5 == nil { + templ_7745c5c3_Var5 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var6 = []any{"relative group inline-block " + classes} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(humanize.Time(t)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `time.templ`, Line: 20, Col: 20} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(t.Format("2006-01-02 15:04:05 UTC")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `time.templ`, Line: 23, Col: 73} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/core/config/config.go b/pkg/core/config/config.go index 14deddf8..2c84ee54 100644 --- a/pkg/core/config/config.go +++ b/pkg/core/config/config.go @@ -128,6 +128,8 @@ type Config struct { /* Feature flags */ ProgrammableDistributionEnabled bool SkipEthRegistration bool + EnableETL bool + EnableExplorer bool } func (c *Config) IsDev() bool { @@ -184,6 +186,8 @@ func ReadConfig() (*Config, error) { cfg.ProgrammableDistributionEnabled = common.IsProgrammableDistributionEnabled(cfg.Environment) cfg.SkipEthRegistration = GetEnvWithDefault("skipEthRegistration", "false") == "true" + cfg.EnableETL = GetEnvWithDefault("OPENAUDIO_ETL_ENABLED", "false") == "true" + cfg.EnableExplorer = GetEnvWithDefault("OPENAUDIO_EXPLORER_ENABLED", "false") == "true" ssRpcServers := "" switch cfg.Environment { diff --git a/pkg/core/console/assets/images/favicon.png b/pkg/core/console/assets/images/favicon.png new file mode 100644 index 00000000..0bdfdbc7 Binary files /dev/null and b/pkg/core/console/assets/images/favicon.png differ diff --git a/pkg/core/console/views/layout/base.templ b/pkg/core/console/views/layout/base.templ index 9ef0ed02..0bf95eec 100644 --- a/pkg/core/console/views/layout/base.templ +++ b/pkg/core/console/views/layout/base.templ @@ -1,14 +1,12 @@ package layout -import "github.com/OpenAudio/go-openaudio/pkg/core/console/assets" - templ (l *Layout) Base() { Audius Console - + diff --git a/pkg/core/console/views/layout/base_templ.go b/pkg/core/console/views/layout/base_templ.go index 5e93c425..47c4bada 100644 --- a/pkg/core/console/views/layout/base_templ.go +++ b/pkg/core/console/views/layout/base_templ.go @@ -8,8 +8,6 @@ package layout import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -import "github.com/OpenAudio/go-openaudio/pkg/core/console/assets" - func (l *Layout) Base() templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context @@ -31,20 +29,7 @@ func (l *Layout) Base() templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "Audius Console") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "Audius Console") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -52,7 +37,7 @@ func (l *Layout) Base() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/pkg/etl/db/db.go b/pkg/etl/db/db.go new file mode 100644 index 00000000..0e4c3f4a --- /dev/null +++ b/pkg/etl/db/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/pkg/etl/db/maintain_views.go b/pkg/etl/db/maintain_views.go new file mode 100644 index 00000000..8dc82896 --- /dev/null +++ b/pkg/etl/db/maintain_views.go @@ -0,0 +1,146 @@ +package db + +import ( + "context" + "fmt" + "log/slog" + "time" + + "github.com/jackc/pgx/v5/pgxpool" +) + +// MaterializedViewManager handles refreshing materialized views for optimal performance +type MaterializedViewManager struct { + db *pgxpool.Pool + logger *slog.Logger +} + +// NewMaterializedViewManager creates a new manager for materialized views +func NewMaterializedViewManager(db *pgxpool.Pool, logger *slog.Logger) *MaterializedViewManager { + return &MaterializedViewManager{ + db: db, + logger: logger, + } +} + +// RefreshSlaRollupViews refreshes all SLA rollup related materialized views +func (m *MaterializedViewManager) RefreshSlaRollupViews(ctx context.Context) error { + start := time.Now() + + // Use the database function we created in the migration + _, err := m.db.Exec(ctx, "SELECT refresh_sla_rollup_materialized_views()") + if err != nil { + m.logger.Error("Failed to refresh SLA rollup materialized views", "error", err, "duration", time.Since(start)) + return fmt.Errorf("failed to refresh SLA rollup materialized views: %w", err) + } + + // Also refresh the dashboard stats view + _, err = m.db.Exec(ctx, "REFRESH MATERIALIZED VIEW CONCURRENTLY mv_sla_rollup_dashboard_stats") + if err != nil { + m.logger.Error("Failed to refresh dashboard stats materialized view", "error", err, "duration", time.Since(start)) + return fmt.Errorf("failed to refresh dashboard stats materialized view: %w", err) + } + + m.logger.Info("Successfully refreshed SLA rollup materialized views", "duration", time.Since(start)) + return nil +} + +// StartPeriodicRefresh starts a background goroutine that refreshes materialized views periodically +func (m *MaterializedViewManager) StartPeriodicRefresh(ctx context.Context, interval time.Duration) { + ticker := time.NewTicker(interval) + go func() { + defer ticker.Stop() + + // Initial refresh + if err := m.RefreshSlaRollupViews(ctx); err != nil { + m.logger.Error("Initial materialized view refresh failed", "error", err) + } + + for { + select { + case <-ctx.Done(): + m.logger.Info("Stopping materialized view refresh due to context cancellation") + return + case <-ticker.C: + if err := m.RefreshSlaRollupViews(ctx); err != nil { + m.logger.Error("Periodic materialized view refresh failed", "error", err) + } + } + } + }() + + m.logger.Info("Started periodic materialized view refresh", "interval", interval) +} + +// GetViewRefreshStatus returns information about when views were last refreshed +func (m *MaterializedViewManager) GetViewRefreshStatus(ctx context.Context) (map[string]time.Time, error) { + query := ` + SELECT + schemaname || '.' || matviewname as view_name, + COALESCE(last_refresh, '1970-01-01'::timestamp) as last_refresh + FROM pg_matviews + WHERE matviewname IN ('mv_sla_rollup', 'mv_sla_rollup_score', 'mv_sla_rollup_dashboard_stats') + ` + + rows, err := m.db.Query(ctx, query) + if err != nil { + return nil, fmt.Errorf("failed to query materialized view status: %w", err) + } + defer rows.Close() + + status := make(map[string]time.Time) + for rows.Next() { + var viewName string + var lastRefresh time.Time + if err := rows.Scan(&viewName, &lastRefresh); err != nil { + return nil, fmt.Errorf("failed to scan view status: %w", err) + } + status[viewName] = lastRefresh + } + + return status, nil +} + +// ForceRefreshIfStale refreshes views if they haven't been refreshed recently +func (m *MaterializedViewManager) ForceRefreshIfStale(ctx context.Context, maxAge time.Duration) error { + status, err := m.GetViewRefreshStatus(ctx) + if err != nil { + return err + } + + now := time.Now() + needsRefresh := false + + for viewName, lastRefresh := range status { + age := now.Sub(lastRefresh) + if age > maxAge { + m.logger.Warn("Materialized view is stale", "view", viewName, "age", age, "max_age", maxAge) + needsRefresh = true + } + } + + if needsRefresh { + return m.RefreshSlaRollupViews(ctx) + } + + return nil +} + +// Health check for materialized views - useful for monitoring +func (m *MaterializedViewManager) HealthCheck(ctx context.Context) error { + // Check if views exist and are queryable + testQueries := []string{ + "SELECT COUNT(*) FROM mv_sla_rollup LIMIT 1", + "SELECT COUNT(*) FROM mv_sla_rollup_score LIMIT 1", + "SELECT COUNT(*) FROM mv_sla_rollup_dashboard_stats LIMIT 1", + } + + for _, query := range testQueries { + var count int64 + if err := m.db.QueryRow(ctx, query).Scan(&count); err != nil { + return fmt.Errorf("materialized view health check failed for query %s: %w", query, err) + } + } + + return nil +} diff --git a/pkg/etl/db/migrate.go b/pkg/etl/db/migrate.go new file mode 100644 index 00000000..3697b2f8 --- /dev/null +++ b/pkg/etl/db/migrate.go @@ -0,0 +1,79 @@ +package db + +import ( + "database/sql" + "embed" + "errors" + "fmt" + "time" + + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/postgres" + "github.com/golang-migrate/migrate/v4/source/iofs" + "go.uber.org/zap" +) + +//go:embed sql/migrations/* +var migrationsFS embed.FS + +func RunMigrations(logger *zap.Logger, pgConnectionString string, downFirst bool) error { + tries := 10 + var db *sql.DB + var err error + + for { + if tries < 0 { + return errors.New("ran out of retries for migrations") + } + db, err = sql.Open("postgres", pgConnectionString) + if err != nil { + logger.Error("error opening sql db", zap.Error(err)) + tries-- + time.Sleep(2 * time.Second) + continue + } + if err = db.Ping(); err != nil { + logger.Error("could not ping postgres", zap.Error(err)) + tries-- + time.Sleep(2 * time.Second) + continue + } + break + } + defer db.Close() + + return runMigrations(logger, db, downFirst) +} + +func runMigrations(logger *zap.Logger, db *sql.DB, downFirst bool) error { + driver, err := postgres.WithInstance(db, &postgres.Config{ + MigrationsTable: "etl_db_migrations", + }) + if err != nil { + return fmt.Errorf("error creating postgres driver: %w", err) + } + + source, err := iofs.New(migrationsFS, "sql/migrations") + if err != nil { + return fmt.Errorf("error creating iofs source: %w", err) + } + + m, err := migrate.NewWithInstance("iofs", source, "postgres", driver) + if err != nil { + return fmt.Errorf("error initializing migrate: %w", err) + } + defer m.Close() + + if downFirst { + if err := m.Down(); err != nil && err != migrate.ErrNoChange { + return fmt.Errorf("error running down migrations: %w", err) + } + } + + if err := m.Up(); err != nil && err != migrate.ErrNoChange { + return fmt.Errorf("error running up migrations: %w", err) + } + + logger.Info("Migrations applied successfully") + return nil +} diff --git a/pkg/etl/db/models.go b/pkg/etl/db/models.go new file mode 100644 index 00000000..f9f2ef4d --- /dev/null +++ b/pkg/etl/db/models.go @@ -0,0 +1,216 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package db + +import ( + "database/sql/driver" + "fmt" + + "github.com/jackc/pgx/v5/pgtype" +) + +type EtlProofStatus string + +const ( + EtlProofStatusUnresolved EtlProofStatus = "unresolved" + EtlProofStatusPass EtlProofStatus = "pass" + EtlProofStatusFail EtlProofStatus = "fail" +) + +func (e *EtlProofStatus) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = EtlProofStatus(s) + case string: + *e = EtlProofStatus(s) + default: + return fmt.Errorf("unsupported scan type for EtlProofStatus: %T", src) + } + return nil +} + +type NullEtlProofStatus struct { + EtlProofStatus EtlProofStatus `json:"etl_proof_status"` + Valid bool `json:"valid"` // Valid is true if EtlProofStatus is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullEtlProofStatus) Scan(value interface{}) error { + if value == nil { + ns.EtlProofStatus, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.EtlProofStatus.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullEtlProofStatus) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.EtlProofStatus), nil +} + +type EtlAddress struct { + ID int32 `json:"id"` + Address string `json:"address"` + PubKey []byte `json:"pub_key"` + FirstSeenBlockHeight pgtype.Int8 `json:"first_seen_block_height"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +type EtlBlock struct { + ID int32 `json:"id"` + ProposerAddress string `json:"proposer_address"` + BlockHeight int64 `json:"block_height"` + BlockTime pgtype.Timestamp `json:"block_time"` +} + +type EtlManageEntity struct { + ID int32 `json:"id"` + Address string `json:"address"` + EntityType string `json:"entity_type"` + EntityID int64 `json:"entity_id"` + Action string `json:"action"` + Metadata pgtype.Text `json:"metadata"` + Signature string `json:"signature"` + Signer string `json:"signer"` + Nonce string `json:"nonce"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +type EtlPlay struct { + ID int32 `json:"id"` + UserID string `json:"user_id"` + TrackID string `json:"track_id"` + City string `json:"city"` + Region string `json:"region"` + Country string `json:"country"` + PlayedAt pgtype.Timestamp `json:"played_at"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + ListenedAt pgtype.Timestamp `json:"listened_at"` + RecordedAt pgtype.Timestamp `json:"recorded_at"` +} + +type EtlSlaNodeReport struct { + ID int32 `json:"id"` + SlaRollupID int32 `json:"sla_rollup_id"` + Address string `json:"address"` + NumBlocksProposed int32 `json:"num_blocks_proposed"` + ChallengesReceived int32 `json:"challenges_received"` + ChallengesFailed int32 `json:"challenges_failed"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +type EtlSlaRollup struct { + ID int32 `json:"id"` + BlockStart int64 `json:"block_start"` + BlockEnd int64 `json:"block_end"` + BlockHeight int64 `json:"block_height"` + ValidatorCount int32 `json:"validator_count"` + BlockQuota int32 `json:"block_quota"` + Bps float64 `json:"bps"` + Tps float64 `json:"tps"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +type EtlStorageProof struct { + ID int32 `json:"id"` + Height int64 `json:"height"` + Address string `json:"address"` + ProverAddresses []string `json:"prover_addresses"` + Cid string `json:"cid"` + ProofSignature []byte `json:"proof_signature"` + Proof []byte `json:"proof"` + Status EtlProofStatus `json:"status"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +type EtlStorageProofVerification struct { + ID int32 `json:"id"` + Height int64 `json:"height"` + Proof []byte `json:"proof"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +type EtlTransaction struct { + ID int32 `json:"id"` + TxHash string `json:"tx_hash"` + BlockHeight int64 `json:"block_height"` + TxIndex int32 `json:"tx_index"` + TxType string `json:"tx_type"` + Address pgtype.Text `json:"address"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +type EtlValidator struct { + ID int32 `json:"id"` + Address string `json:"address"` + Endpoint string `json:"endpoint"` + CometAddress string `json:"comet_address"` + NodeType string `json:"node_type"` + Spid string `json:"spid"` + VotingPower int64 `json:"voting_power"` + Status string `json:"status"` + RegisteredAt int64 `json:"registered_at"` + DeregisteredAt pgtype.Int8 `json:"deregistered_at"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + +type EtlValidatorDeregistration struct { + ID int32 `json:"id"` + CometAddress string `json:"comet_address"` + CometPubkey []byte `json:"comet_pubkey"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` +} + +type EtlValidatorMisbehaviorDeregistration struct { + ID int32 `json:"id"` + CometAddress string `json:"comet_address"` + PubKey []byte `json:"pub_key"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +type EtlValidatorRegistration struct { + ID int32 `json:"id"` + Address string `json:"address"` + Endpoint string `json:"endpoint"` + CometAddress string `json:"comet_address"` + EthBlock string `json:"eth_block"` + NodeType string `json:"node_type"` + Spid string `json:"spid"` + CometPubkey []byte `json:"comet_pubkey"` + VotingPower int64 `json:"voting_power"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` +} + +type MvDashboardTransactionStat struct { + Transactions24h int64 `json:"transactions_24h"` + TransactionsPrevious24h int64 `json:"transactions_previous_24h"` + Transactions7d int64 `json:"transactions_7d"` + Transactions30d int64 `json:"transactions_30d"` + TotalTransactions int64 `json:"total_transactions"` +} + +type MvDashboardTransactionType struct { + TxType string `json:"tx_type"` + TransactionCount int64 `json:"transaction_count"` +} diff --git a/pkg/etl/db/reads.sql.go b/pkg/etl/db/reads.sql.go new file mode 100644 index 00000000..e90f943b --- /dev/null +++ b/pkg/etl/db/reads.sql.go @@ -0,0 +1,1629 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: reads.sql + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const getActiveValidatorCount = `-- name: GetActiveValidatorCount :one +select count(*) from etl_validators +where status = 'active' +` + +func (q *Queries) GetActiveValidatorCount(ctx context.Context) (int64, error) { + row := q.db.QueryRow(ctx, getActiveValidatorCount) + var count int64 + err := row.Scan(&count) + return count, err +} + +const getActiveValidators = `-- name: GetActiveValidators :many +select id, address, endpoint, comet_address, node_type, spid, voting_power, status, registered_at, deregistered_at, created_at, updated_at from etl_validators +where status = 'active' +order by comet_address +limit $1 offset $2 +` + +type GetActiveValidatorsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetActiveValidators(ctx context.Context, arg GetActiveValidatorsParams) ([]EtlValidator, error) { + rows, err := q.db.Query(ctx, getActiveValidators, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlValidator + for rows.Next() { + var i EtlValidator + if err := rows.Scan( + &i.ID, + &i.Address, + &i.Endpoint, + &i.CometAddress, + &i.NodeType, + &i.Spid, + &i.VotingPower, + &i.Status, + &i.RegisteredAt, + &i.DeregisteredAt, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getAllActiveValidatorsWithRecentRollups = `-- name: GetAllActiveValidatorsWithRecentRollups :many +select v.id, v.address, v.endpoint, v.comet_address, v.node_type, v.spid, v.voting_power, v.status, v.registered_at, v.deregistered_at, v.created_at, v.updated_at, snr.sla_rollup_id, snr.num_blocks_proposed, snr.challenges_received, snr.challenges_failed, snr.block_height as report_block_height, snr.created_at as report_created_at +from etl_validators v +left join etl_sla_node_reports snr on v.comet_address = snr.address +where v.status = 'active' +order by v.comet_address, v.id, snr.sla_rollup_id desc +` + +type GetAllActiveValidatorsWithRecentRollupsRow struct { + ID int32 `json:"id"` + Address string `json:"address"` + Endpoint string `json:"endpoint"` + CometAddress string `json:"comet_address"` + NodeType string `json:"node_type"` + Spid string `json:"spid"` + VotingPower int64 `json:"voting_power"` + Status string `json:"status"` + RegisteredAt int64 `json:"registered_at"` + DeregisteredAt pgtype.Int8 `json:"deregistered_at"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + SlaRollupID pgtype.Int4 `json:"sla_rollup_id"` + NumBlocksProposed pgtype.Int4 `json:"num_blocks_proposed"` + ChallengesReceived pgtype.Int4 `json:"challenges_received"` + ChallengesFailed pgtype.Int4 `json:"challenges_failed"` + ReportBlockHeight pgtype.Int8 `json:"report_block_height"` + ReportCreatedAt pgtype.Timestamp `json:"report_created_at"` +} + +func (q *Queries) GetAllActiveValidatorsWithRecentRollups(ctx context.Context) ([]GetAllActiveValidatorsWithRecentRollupsRow, error) { + rows, err := q.db.Query(ctx, getAllActiveValidatorsWithRecentRollups) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetAllActiveValidatorsWithRecentRollupsRow + for rows.Next() { + var i GetAllActiveValidatorsWithRecentRollupsRow + if err := rows.Scan( + &i.ID, + &i.Address, + &i.Endpoint, + &i.CometAddress, + &i.NodeType, + &i.Spid, + &i.VotingPower, + &i.Status, + &i.RegisteredAt, + &i.DeregisteredAt, + &i.CreatedAt, + &i.UpdatedAt, + &i.SlaRollupID, + &i.NumBlocksProposed, + &i.ChallengesReceived, + &i.ChallengesFailed, + &i.ReportBlockHeight, + &i.ReportCreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getBlockByHeight = `-- name: GetBlockByHeight :one +select id, proposer_address, block_height, block_time from etl_blocks +where block_height = $1 +` + +func (q *Queries) GetBlockByHeight(ctx context.Context, blockHeight int64) (EtlBlock, error) { + row := q.db.QueryRow(ctx, getBlockByHeight, blockHeight) + var i EtlBlock + err := row.Scan( + &i.ID, + &i.ProposerAddress, + &i.BlockHeight, + &i.BlockTime, + ) + return i, err +} + +const getBlockRangeFirst = `-- name: GetBlockRangeFirst :one +select id, proposer_address, block_height, block_time +from etl_blocks +where block_time >= $1 and block_time <= $2 +order by block_time +limit 1 +` + +type GetBlockRangeFirstParams struct { + BlockTime pgtype.Timestamp `json:"block_time"` + BlockTime_2 pgtype.Timestamp `json:"block_time_2"` +} + +func (q *Queries) GetBlockRangeFirst(ctx context.Context, arg GetBlockRangeFirstParams) (EtlBlock, error) { + row := q.db.QueryRow(ctx, getBlockRangeFirst, arg.BlockTime, arg.BlockTime_2) + var i EtlBlock + err := row.Scan( + &i.ID, + &i.ProposerAddress, + &i.BlockHeight, + &i.BlockTime, + ) + return i, err +} + +const getBlockRangeLast = `-- name: GetBlockRangeLast :one +select id, proposer_address, block_height, block_time +from etl_blocks +where block_time >= $1 and block_time <= $2 +order by block_time desc +limit 1 +` + +type GetBlockRangeLastParams struct { + BlockTime pgtype.Timestamp `json:"block_time"` + BlockTime_2 pgtype.Timestamp `json:"block_time_2"` +} + +func (q *Queries) GetBlockRangeLast(ctx context.Context, arg GetBlockRangeLastParams) (EtlBlock, error) { + row := q.db.QueryRow(ctx, getBlockRangeLast, arg.BlockTime, arg.BlockTime_2) + var i EtlBlock + err := row.Scan( + &i.ID, + &i.ProposerAddress, + &i.BlockHeight, + &i.BlockTime, + ) + return i, err +} + +const getBlockTransactionCount = `-- name: GetBlockTransactionCount :one +select count(*) from etl_transactions +where block_height = $1 +` + +func (q *Queries) GetBlockTransactionCount(ctx context.Context, blockHeight int64) (int64, error) { + row := q.db.QueryRow(ctx, getBlockTransactionCount, blockHeight) + var count int64 + err := row.Scan(&count) + return count, err +} + +const getBlocksByPage = `-- name: GetBlocksByPage :many +select id, proposer_address, block_height, block_time from etl_blocks +order by block_height desc +limit $1 offset $2 +` + +type GetBlocksByPageParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetBlocksByPage(ctx context.Context, arg GetBlocksByPageParams) ([]EtlBlock, error) { + rows, err := q.db.Query(ctx, getBlocksByPage, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlBlock + for rows.Next() { + var i EtlBlock + if err := rows.Scan( + &i.ID, + &i.ProposerAddress, + &i.BlockHeight, + &i.BlockTime, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getChallengeStatisticsForBlockRange = `-- name: GetChallengeStatisticsForBlockRange :many +select + sp.address, + count(*) as challenges_received, + count(*) filter (where sp.status = 'fail') as challenges_failed +from etl_storage_proofs sp +where sp.height >= $1 and sp.height <= $2 +group by sp.address +` + +type GetChallengeStatisticsForBlockRangeParams struct { + Height int64 `json:"height"` + Height_2 int64 `json:"height_2"` +} + +type GetChallengeStatisticsForBlockRangeRow struct { + Address string `json:"address"` + ChallengesReceived int64 `json:"challenges_received"` + ChallengesFailed int64 `json:"challenges_failed"` +} + +func (q *Queries) GetChallengeStatisticsForBlockRange(ctx context.Context, arg GetChallengeStatisticsForBlockRangeParams) ([]GetChallengeStatisticsForBlockRangeRow, error) { + rows, err := q.db.Query(ctx, getChallengeStatisticsForBlockRange, arg.Height, arg.Height_2) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetChallengeStatisticsForBlockRangeRow + for rows.Next() { + var i GetChallengeStatisticsForBlockRangeRow + if err := rows.Scan(&i.Address, &i.ChallengesReceived, &i.ChallengesFailed); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getDashboardTransactionStats = `-- name: GetDashboardTransactionStats :one +select transactions_24h, transactions_previous_24h, transactions_7d, transactions_30d, total_transactions from mv_dashboard_transaction_stats limit 1 +` + +// Dashboard materialized view queries +func (q *Queries) GetDashboardTransactionStats(ctx context.Context) (MvDashboardTransactionStat, error) { + row := q.db.QueryRow(ctx, getDashboardTransactionStats) + var i MvDashboardTransactionStat + err := row.Scan( + &i.Transactions24h, + &i.TransactionsPrevious24h, + &i.Transactions7d, + &i.Transactions30d, + &i.TotalTransactions, + ) + return i, err +} + +const getDashboardTransactionTypes = `-- name: GetDashboardTransactionTypes :many +select tx_type, transaction_count from mv_dashboard_transaction_types +` + +func (q *Queries) GetDashboardTransactionTypes(ctx context.Context) ([]MvDashboardTransactionType, error) { + rows, err := q.db.Query(ctx, getDashboardTransactionTypes) + if err != nil { + return nil, err + } + defer rows.Close() + var items []MvDashboardTransactionType + for rows.Next() { + var i MvDashboardTransactionType + if err := rows.Scan(&i.TxType, &i.TransactionCount); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getHealthyValidatorCountsForRollups = `-- name: GetHealthyValidatorCountsForRollups :many +SELECT + sr.id as rollup_id, + COALESCE(COUNT(*) FILTER (WHERE snr.challenges_failed = 0 OR snr.challenges_received = 0), 0) as healthy_validators +FROM etl_sla_rollups sr +LEFT JOIN etl_sla_node_reports snr ON sr.id = snr.sla_rollup_id +WHERE sr.id = ANY($1::int[]) +GROUP BY sr.id +ORDER BY sr.id +` + +type GetHealthyValidatorCountsForRollupsRow struct { + RollupID int32 `json:"rollup_id"` + HealthyValidators interface{} `json:"healthy_validators"` +} + +func (q *Queries) GetHealthyValidatorCountsForRollups(ctx context.Context, dollar_1 []int32) ([]GetHealthyValidatorCountsForRollupsRow, error) { + rows, err := q.db.Query(ctx, getHealthyValidatorCountsForRollups, dollar_1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetHealthyValidatorCountsForRollupsRow + for rows.Next() { + var i GetHealthyValidatorCountsForRollupsRow + if err := rows.Scan(&i.RollupID, &i.HealthyValidators); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getLatestIndexedBlock = `-- name: GetLatestIndexedBlock :one +SELECT block_height +FROM etl_blocks +ORDER BY id DESC +LIMIT 1 +` + +// get latest indexed block height +func (q *Queries) GetLatestIndexedBlock(ctx context.Context) (int64, error) { + row := q.db.QueryRow(ctx, getLatestIndexedBlock) + var block_height int64 + err := row.Scan(&block_height) + return block_height, err +} + +const getLatestSlaRollup = `-- name: GetLatestSlaRollup :one +select id, block_start, block_end, block_height, validator_count, block_quota, bps, tps, tx_hash, created_at from etl_sla_rollups +order by block_height desc, id desc +limit 1 +` + +func (q *Queries) GetLatestSlaRollup(ctx context.Context) (EtlSlaRollup, error) { + row := q.db.QueryRow(ctx, getLatestSlaRollup) + var i EtlSlaRollup + err := row.Scan( + &i.ID, + &i.BlockStart, + &i.BlockEnd, + &i.BlockHeight, + &i.ValidatorCount, + &i.BlockQuota, + &i.Bps, + &i.Tps, + &i.TxHash, + &i.CreatedAt, + ) + return i, err +} + +const getManageEntitiesByBlockHeightCursor = `-- name: GetManageEntitiesByBlockHeightCursor :many +select id, address, entity_type, entity_id, action, metadata, signature, signer, nonce, block_height, tx_hash, created_at from etl_manage_entities +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3 +` + +type GetManageEntitiesByBlockHeightCursorParams struct { + BlockHeight int64 `json:"block_height"` + ID int32 `json:"id"` + Limit int32 `json:"limit"` +} + +func (q *Queries) GetManageEntitiesByBlockHeightCursor(ctx context.Context, arg GetManageEntitiesByBlockHeightCursorParams) ([]EtlManageEntity, error) { + rows, err := q.db.Query(ctx, getManageEntitiesByBlockHeightCursor, arg.BlockHeight, arg.ID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlManageEntity + for rows.Next() { + var i EtlManageEntity + if err := rows.Scan( + &i.ID, + &i.Address, + &i.EntityType, + &i.EntityID, + &i.Action, + &i.Metadata, + &i.Signature, + &i.Signer, + &i.Nonce, + &i.BlockHeight, + &i.TxHash, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getManageEntityByTxHash = `-- name: GetManageEntityByTxHash :one +select id, address, entity_type, entity_id, action, metadata, signature, signer, nonce, block_height, tx_hash, created_at from etl_manage_entities +where tx_hash = $1 +` + +func (q *Queries) GetManageEntityByTxHash(ctx context.Context, txHash string) (EtlManageEntity, error) { + row := q.db.QueryRow(ctx, getManageEntityByTxHash, txHash) + var i EtlManageEntity + err := row.Scan( + &i.ID, + &i.Address, + &i.EntityType, + &i.EntityID, + &i.Action, + &i.Metadata, + &i.Signature, + &i.Signer, + &i.Nonce, + &i.BlockHeight, + &i.TxHash, + &i.CreatedAt, + ) + return i, err +} + +const getPlaysByBlockHeightCursor = `-- name: GetPlaysByBlockHeightCursor :many +select id, user_id, track_id, city, region, country, played_at, block_height, tx_hash, listened_at, recorded_at from etl_plays +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3 +` + +type GetPlaysByBlockHeightCursorParams struct { + BlockHeight int64 `json:"block_height"` + ID int32 `json:"id"` + Limit int32 `json:"limit"` +} + +func (q *Queries) GetPlaysByBlockHeightCursor(ctx context.Context, arg GetPlaysByBlockHeightCursorParams) ([]EtlPlay, error) { + rows, err := q.db.Query(ctx, getPlaysByBlockHeightCursor, arg.BlockHeight, arg.ID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlPlay + for rows.Next() { + var i EtlPlay + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.TrackID, + &i.City, + &i.Region, + &i.Country, + &i.PlayedAt, + &i.BlockHeight, + &i.TxHash, + &i.ListenedAt, + &i.RecordedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getPlaysByTxHash = `-- name: GetPlaysByTxHash :many +select id, user_id, track_id, city, region, country, played_at, block_height, tx_hash, listened_at, recorded_at from etl_plays +where tx_hash = $1 +` + +// Transaction content queries by hash +func (q *Queries) GetPlaysByTxHash(ctx context.Context, txHash string) ([]EtlPlay, error) { + rows, err := q.db.Query(ctx, getPlaysByTxHash, txHash) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlPlay + for rows.Next() { + var i EtlPlay + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.TrackID, + &i.City, + &i.Region, + &i.Country, + &i.PlayedAt, + &i.BlockHeight, + &i.TxHash, + &i.ListenedAt, + &i.RecordedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getRelationTypesByAddress = `-- name: GetRelationTypesByAddress :many +select distinct + case + when t.tx_type = 'manage_entity' then coalesce(me.action || me.entity_type, t.tx_type) + else t.tx_type + end as relation_type +from etl_transactions t +left join etl_manage_entities me on t.tx_hash = me.tx_hash and t.tx_type = 'manage_entity' +where lower(t.address) = lower($1) +order by relation_type +` + +func (q *Queries) GetRelationTypesByAddress(ctx context.Context, lower string) ([]interface{}, error) { + rows, err := q.db.Query(ctx, getRelationTypesByAddress, lower) + if err != nil { + return nil, err + } + defer rows.Close() + var items []interface{} + for rows.Next() { + var relation_type interface{} + if err := rows.Scan(&relation_type); err != nil { + return nil, err + } + items = append(items, relation_type) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSlaNodeReportsByAddress = `-- name: GetSlaNodeReportsByAddress :many +select id, sla_rollup_id, address, num_blocks_proposed, challenges_received, challenges_failed, block_height, tx_hash, created_at from etl_sla_node_reports +where lower(address) = lower($1) +order by block_height desc +limit $2 +` + +type GetSlaNodeReportsByAddressParams struct { + Lower string `json:"lower"` + Limit int32 `json:"limit"` +} + +func (q *Queries) GetSlaNodeReportsByAddress(ctx context.Context, arg GetSlaNodeReportsByAddressParams) ([]EtlSlaNodeReport, error) { + rows, err := q.db.Query(ctx, getSlaNodeReportsByAddress, arg.Lower, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlSlaNodeReport + for rows.Next() { + var i EtlSlaNodeReport + if err := rows.Scan( + &i.ID, + &i.SlaRollupID, + &i.Address, + &i.NumBlocksProposed, + &i.ChallengesReceived, + &i.ChallengesFailed, + &i.BlockHeight, + &i.TxHash, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSlaNodeReportsByBlockHeightCursor = `-- name: GetSlaNodeReportsByBlockHeightCursor :many +select id, sla_rollup_id, address, num_blocks_proposed, challenges_received, challenges_failed, block_height, tx_hash, created_at from etl_sla_node_reports +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3 +` + +type GetSlaNodeReportsByBlockHeightCursorParams struct { + BlockHeight int64 `json:"block_height"` + ID int32 `json:"id"` + Limit int32 `json:"limit"` +} + +func (q *Queries) GetSlaNodeReportsByBlockHeightCursor(ctx context.Context, arg GetSlaNodeReportsByBlockHeightCursorParams) ([]EtlSlaNodeReport, error) { + rows, err := q.db.Query(ctx, getSlaNodeReportsByBlockHeightCursor, arg.BlockHeight, arg.ID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlSlaNodeReport + for rows.Next() { + var i EtlSlaNodeReport + if err := rows.Scan( + &i.ID, + &i.SlaRollupID, + &i.Address, + &i.NumBlocksProposed, + &i.ChallengesReceived, + &i.ChallengesFailed, + &i.BlockHeight, + &i.TxHash, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSlaRollupById = `-- name: GetSlaRollupById :one +select id, block_start, block_end, block_height, validator_count, block_quota, bps, tps, tx_hash, created_at from etl_sla_rollups +where id = $1 +` + +func (q *Queries) GetSlaRollupById(ctx context.Context, id int32) (EtlSlaRollup, error) { + row := q.db.QueryRow(ctx, getSlaRollupById, id) + var i EtlSlaRollup + err := row.Scan( + &i.ID, + &i.BlockStart, + &i.BlockEnd, + &i.BlockHeight, + &i.ValidatorCount, + &i.BlockQuota, + &i.Bps, + &i.Tps, + &i.TxHash, + &i.CreatedAt, + ) + return i, err +} + +const getSlaRollupByTxHash = `-- name: GetSlaRollupByTxHash :one +select id, block_start, block_end, block_height, validator_count, block_quota, bps, tps, tx_hash, created_at from etl_sla_rollups +where tx_hash = $1 +` + +func (q *Queries) GetSlaRollupByTxHash(ctx context.Context, txHash string) (EtlSlaRollup, error) { + row := q.db.QueryRow(ctx, getSlaRollupByTxHash, txHash) + var i EtlSlaRollup + err := row.Scan( + &i.ID, + &i.BlockStart, + &i.BlockEnd, + &i.BlockHeight, + &i.ValidatorCount, + &i.BlockQuota, + &i.Bps, + &i.Tps, + &i.TxHash, + &i.CreatedAt, + ) + return i, err +} + +const getSlaRollupsByBlockHeightCursor = `-- name: GetSlaRollupsByBlockHeightCursor :many +select id, block_start, block_end, block_height, validator_count, block_quota, bps, tps, tx_hash, created_at from etl_sla_rollups +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3 +` + +type GetSlaRollupsByBlockHeightCursorParams struct { + BlockHeight int64 `json:"block_height"` + ID int32 `json:"id"` + Limit int32 `json:"limit"` +} + +func (q *Queries) GetSlaRollupsByBlockHeightCursor(ctx context.Context, arg GetSlaRollupsByBlockHeightCursorParams) ([]EtlSlaRollup, error) { + rows, err := q.db.Query(ctx, getSlaRollupsByBlockHeightCursor, arg.BlockHeight, arg.ID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlSlaRollup + for rows.Next() { + var i EtlSlaRollup + if err := rows.Scan( + &i.ID, + &i.BlockStart, + &i.BlockEnd, + &i.BlockHeight, + &i.ValidatorCount, + &i.BlockQuota, + &i.Bps, + &i.Tps, + &i.TxHash, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getSlaRollupsWithPagination = `-- name: GetSlaRollupsWithPagination :many +select id, block_start, block_end, block_height, validator_count, block_quota, bps, tps, tx_hash, created_at from etl_sla_rollups +order by id desc +limit $1 offset $2 +` + +type GetSlaRollupsWithPaginationParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetSlaRollupsWithPagination(ctx context.Context, arg GetSlaRollupsWithPaginationParams) ([]EtlSlaRollup, error) { + rows, err := q.db.Query(ctx, getSlaRollupsWithPagination, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlSlaRollup + for rows.Next() { + var i EtlSlaRollup + if err := rows.Scan( + &i.ID, + &i.BlockStart, + &i.BlockEnd, + &i.BlockHeight, + &i.ValidatorCount, + &i.BlockQuota, + &i.Bps, + &i.Tps, + &i.TxHash, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getStorageProofByTxHash = `-- name: GetStorageProofByTxHash :one +select id, height, address, prover_addresses, cid, proof_signature, proof, status, block_height, tx_hash, created_at from etl_storage_proofs +where tx_hash = $1 +` + +func (q *Queries) GetStorageProofByTxHash(ctx context.Context, txHash string) (EtlStorageProof, error) { + row := q.db.QueryRow(ctx, getStorageProofByTxHash, txHash) + var i EtlStorageProof + err := row.Scan( + &i.ID, + &i.Height, + &i.Address, + &i.ProverAddresses, + &i.Cid, + &i.ProofSignature, + &i.Proof, + &i.Status, + &i.BlockHeight, + &i.TxHash, + &i.CreatedAt, + ) + return i, err +} + +const getStorageProofVerificationByTxHash = `-- name: GetStorageProofVerificationByTxHash :one +select id, height, proof, block_height, tx_hash, created_at from etl_storage_proof_verifications +where tx_hash = $1 +` + +func (q *Queries) GetStorageProofVerificationByTxHash(ctx context.Context, txHash string) (EtlStorageProofVerification, error) { + row := q.db.QueryRow(ctx, getStorageProofVerificationByTxHash, txHash) + var i EtlStorageProofVerification + err := row.Scan( + &i.ID, + &i.Height, + &i.Proof, + &i.BlockHeight, + &i.TxHash, + &i.CreatedAt, + ) + return i, err +} + +const getStorageProofVerificationsByBlockHeightCursor = `-- name: GetStorageProofVerificationsByBlockHeightCursor :many +select id, height, proof, block_height, tx_hash, created_at from etl_storage_proof_verifications +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3 +` + +type GetStorageProofVerificationsByBlockHeightCursorParams struct { + BlockHeight int64 `json:"block_height"` + ID int32 `json:"id"` + Limit int32 `json:"limit"` +} + +func (q *Queries) GetStorageProofVerificationsByBlockHeightCursor(ctx context.Context, arg GetStorageProofVerificationsByBlockHeightCursorParams) ([]EtlStorageProofVerification, error) { + rows, err := q.db.Query(ctx, getStorageProofVerificationsByBlockHeightCursor, arg.BlockHeight, arg.ID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlStorageProofVerification + for rows.Next() { + var i EtlStorageProofVerification + if err := rows.Scan( + &i.ID, + &i.Height, + &i.Proof, + &i.BlockHeight, + &i.TxHash, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getStorageProofsByBlockHeightCursor = `-- name: GetStorageProofsByBlockHeightCursor :many +select id, height, address, prover_addresses, cid, proof_signature, proof, status, block_height, tx_hash, created_at from etl_storage_proofs +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3 +` + +type GetStorageProofsByBlockHeightCursorParams struct { + BlockHeight int64 `json:"block_height"` + ID int32 `json:"id"` + Limit int32 `json:"limit"` +} + +func (q *Queries) GetStorageProofsByBlockHeightCursor(ctx context.Context, arg GetStorageProofsByBlockHeightCursorParams) ([]EtlStorageProof, error) { + rows, err := q.db.Query(ctx, getStorageProofsByBlockHeightCursor, arg.BlockHeight, arg.ID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlStorageProof + for rows.Next() { + var i EtlStorageProof + if err := rows.Scan( + &i.ID, + &i.Height, + &i.Address, + &i.ProverAddresses, + &i.Cid, + &i.ProofSignature, + &i.Proof, + &i.Status, + &i.BlockHeight, + &i.TxHash, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getStorageProofsForHeight = `-- name: GetStorageProofsForHeight :many +select id, height, address, prover_addresses, cid, proof_signature, proof, status, block_height, tx_hash, created_at from etl_storage_proofs +where height = $1 +` + +// Storage proof consensus queries +func (q *Queries) GetStorageProofsForHeight(ctx context.Context, height int64) ([]EtlStorageProof, error) { + rows, err := q.db.Query(ctx, getStorageProofsForHeight, height) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlStorageProof + for rows.Next() { + var i EtlStorageProof + if err := rows.Scan( + &i.ID, + &i.Height, + &i.Address, + &i.ProverAddresses, + &i.Cid, + &i.ProofSignature, + &i.Proof, + &i.Status, + &i.BlockHeight, + &i.TxHash, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getTotalTransactions = `-- name: GetTotalTransactions :one +select id from etl_transactions order by id desc limit 1 +` + +func (q *Queries) GetTotalTransactions(ctx context.Context) (int32, error) { + row := q.db.QueryRow(ctx, getTotalTransactions) + var id int32 + err := row.Scan(&id) + return id, err +} + +const getTransactionByHash = `-- name: GetTransactionByHash :one +select id, tx_hash, block_height, tx_index, tx_type, address, created_at from etl_transactions +where tx_hash = $1 +` + +func (q *Queries) GetTransactionByHash(ctx context.Context, txHash string) (EtlTransaction, error) { + row := q.db.QueryRow(ctx, getTransactionByHash, txHash) + var i EtlTransaction + err := row.Scan( + &i.ID, + &i.TxHash, + &i.BlockHeight, + &i.TxIndex, + &i.TxType, + &i.Address, + &i.CreatedAt, + ) + return i, err +} + +const getTransactionCountByAddress = `-- name: GetTransactionCountByAddress :one +select count(*) +from etl_transactions t +left join etl_manage_entities me on t.tx_hash = me.tx_hash and t.tx_type = 'manage_entity' +where lower(t.address) = lower($1) + and ($2 = '' or + case + when t.tx_type = 'manage_entity' then coalesce(me.action || me.entity_type, t.tx_type) = $2 + else t.tx_type = $2 + end) + and ($3::timestamp is null or t.created_at >= $3) + and ($4::timestamp is null or t.created_at <= $4) +` + +type GetTransactionCountByAddressParams struct { + Lower string `json:"lower"` + Column2 interface{} `json:"column_2"` + Column3 pgtype.Timestamp `json:"column_3"` + Column4 pgtype.Timestamp `json:"column_4"` +} + +func (q *Queries) GetTransactionCountByAddress(ctx context.Context, arg GetTransactionCountByAddressParams) (int64, error) { + row := q.db.QueryRow(ctx, getTransactionCountByAddress, + arg.Lower, + arg.Column2, + arg.Column3, + arg.Column4, + ) + var count int64 + err := row.Scan(&count) + return count, err +} + +const getTransactionsByAddress = `-- name: GetTransactionsByAddress :many +select t.id, t.tx_hash, t.block_height, t.tx_index, t.tx_type, t.address, t.created_at, + case + when t.tx_type = 'manage_entity' then coalesce(me.action || me.entity_type, t.tx_type) + else t.tx_type + end as relation +from etl_transactions t +left join etl_manage_entities me on t.tx_hash = me.tx_hash and t.tx_type = 'manage_entity' +where lower(t.address) = lower($1) + and ($2 = '' or + case + when t.tx_type = 'manage_entity' then coalesce(me.action || me.entity_type, t.tx_type) = $2 + else t.tx_type = $2 + end) + and ($3::timestamp is null or t.created_at >= $3) + and ($4::timestamp is null or t.created_at <= $4) +order by t.block_height desc, t.tx_index desc +limit $5 offset $6 +` + +type GetTransactionsByAddressParams struct { + Lower string `json:"lower"` + Column2 interface{} `json:"column_2"` + Column3 pgtype.Timestamp `json:"column_3"` + Column4 pgtype.Timestamp `json:"column_4"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +type GetTransactionsByAddressRow struct { + ID int32 `json:"id"` + TxHash string `json:"tx_hash"` + BlockHeight int64 `json:"block_height"` + TxIndex int32 `json:"tx_index"` + TxType string `json:"tx_type"` + Address pgtype.Text `json:"address"` + CreatedAt pgtype.Timestamp `json:"created_at"` + Relation interface{} `json:"relation"` +} + +// Account transaction queries +func (q *Queries) GetTransactionsByAddress(ctx context.Context, arg GetTransactionsByAddressParams) ([]GetTransactionsByAddressRow, error) { + rows, err := q.db.Query(ctx, getTransactionsByAddress, + arg.Lower, + arg.Column2, + arg.Column3, + arg.Column4, + arg.Limit, + arg.Offset, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetTransactionsByAddressRow + for rows.Next() { + var i GetTransactionsByAddressRow + if err := rows.Scan( + &i.ID, + &i.TxHash, + &i.BlockHeight, + &i.TxIndex, + &i.TxType, + &i.Address, + &i.CreatedAt, + &i.Relation, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getTransactionsByBlockHeightCursor = `-- name: GetTransactionsByBlockHeightCursor :many +select id, tx_hash, block_height, tx_index, tx_type, address, created_at from etl_transactions +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3 +` + +type GetTransactionsByBlockHeightCursorParams struct { + BlockHeight int64 `json:"block_height"` + ID int32 `json:"id"` + Limit int32 `json:"limit"` +} + +func (q *Queries) GetTransactionsByBlockHeightCursor(ctx context.Context, arg GetTransactionsByBlockHeightCursorParams) ([]EtlTransaction, error) { + rows, err := q.db.Query(ctx, getTransactionsByBlockHeightCursor, arg.BlockHeight, arg.ID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlTransaction + for rows.Next() { + var i EtlTransaction + if err := rows.Scan( + &i.ID, + &i.TxHash, + &i.BlockHeight, + &i.TxIndex, + &i.TxType, + &i.Address, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getTransactionsByPage = `-- name: GetTransactionsByPage :many +select id, tx_hash, block_height, tx_index, tx_type, address, created_at from etl_transactions +order by block_height desc, tx_index desc +limit $1 offset $2 +` + +type GetTransactionsByPageParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +func (q *Queries) GetTransactionsByPage(ctx context.Context, arg GetTransactionsByPageParams) ([]EtlTransaction, error) { + rows, err := q.db.Query(ctx, getTransactionsByPage, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlTransaction + for rows.Next() { + var i EtlTransaction + if err := rows.Scan( + &i.ID, + &i.TxHash, + &i.BlockHeight, + &i.TxIndex, + &i.TxType, + &i.Address, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getValidatorByAddress = `-- name: GetValidatorByAddress :one +select id, address, endpoint, comet_address, node_type, spid, voting_power, status, registered_at, deregistered_at, created_at, updated_at from etl_validators +where lower(address) = lower($1) or lower(comet_address) = lower($1) +` + +func (q *Queries) GetValidatorByAddress(ctx context.Context, lower string) (EtlValidator, error) { + row := q.db.QueryRow(ctx, getValidatorByAddress, lower) + var i EtlValidator + err := row.Scan( + &i.ID, + &i.Address, + &i.Endpoint, + &i.CometAddress, + &i.NodeType, + &i.Spid, + &i.VotingPower, + &i.Status, + &i.RegisteredAt, + &i.DeregisteredAt, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getValidatorDeregistrationByTxHash = `-- name: GetValidatorDeregistrationByTxHash :one +select id, comet_address, comet_pubkey, block_height, tx_hash from etl_validator_deregistrations +where tx_hash = $1 +` + +func (q *Queries) GetValidatorDeregistrationByTxHash(ctx context.Context, txHash string) (EtlValidatorDeregistration, error) { + row := q.db.QueryRow(ctx, getValidatorDeregistrationByTxHash, txHash) + var i EtlValidatorDeregistration + err := row.Scan( + &i.ID, + &i.CometAddress, + &i.CometPubkey, + &i.BlockHeight, + &i.TxHash, + ) + return i, err +} + +const getValidatorDeregistrations = `-- name: GetValidatorDeregistrations :many +select vd.id, vd.comet_address, vd.comet_pubkey, vd.block_height, vd.tx_hash, v.endpoint, v.node_type, v.spid, v.voting_power, v.status +from etl_validator_deregistrations vd +left join etl_validators v on v.comet_address = vd.comet_address +order by vd.block_height desc +limit $1 offset $2 +` + +type GetValidatorDeregistrationsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +type GetValidatorDeregistrationsRow struct { + ID int32 `json:"id"` + CometAddress string `json:"comet_address"` + CometPubkey []byte `json:"comet_pubkey"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + Endpoint pgtype.Text `json:"endpoint"` + NodeType pgtype.Text `json:"node_type"` + Spid pgtype.Text `json:"spid"` + VotingPower pgtype.Int8 `json:"voting_power"` + Status pgtype.Text `json:"status"` +} + +func (q *Queries) GetValidatorDeregistrations(ctx context.Context, arg GetValidatorDeregistrationsParams) ([]GetValidatorDeregistrationsRow, error) { + rows, err := q.db.Query(ctx, getValidatorDeregistrations, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetValidatorDeregistrationsRow + for rows.Next() { + var i GetValidatorDeregistrationsRow + if err := rows.Scan( + &i.ID, + &i.CometAddress, + &i.CometPubkey, + &i.BlockHeight, + &i.TxHash, + &i.Endpoint, + &i.NodeType, + &i.Spid, + &i.VotingPower, + &i.Status, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getValidatorDeregistrationsByBlockHeightCursor = `-- name: GetValidatorDeregistrationsByBlockHeightCursor :many +select id, comet_address, comet_pubkey, block_height, tx_hash from etl_validator_deregistrations +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3 +` + +type GetValidatorDeregistrationsByBlockHeightCursorParams struct { + BlockHeight int64 `json:"block_height"` + ID int32 `json:"id"` + Limit int32 `json:"limit"` +} + +func (q *Queries) GetValidatorDeregistrationsByBlockHeightCursor(ctx context.Context, arg GetValidatorDeregistrationsByBlockHeightCursorParams) ([]EtlValidatorDeregistration, error) { + rows, err := q.db.Query(ctx, getValidatorDeregistrationsByBlockHeightCursor, arg.BlockHeight, arg.ID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlValidatorDeregistration + for rows.Next() { + var i EtlValidatorDeregistration + if err := rows.Scan( + &i.ID, + &i.CometAddress, + &i.CometPubkey, + &i.BlockHeight, + &i.TxHash, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getValidatorMisbehaviorDeregistrationsByBlockHeightCursor = `-- name: GetValidatorMisbehaviorDeregistrationsByBlockHeightCursor :many +select id, comet_address, pub_key, block_height, tx_hash, created_at from etl_validator_misbehavior_deregistrations +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3 +` + +type GetValidatorMisbehaviorDeregistrationsByBlockHeightCursorParams struct { + BlockHeight int64 `json:"block_height"` + ID int32 `json:"id"` + Limit int32 `json:"limit"` +} + +func (q *Queries) GetValidatorMisbehaviorDeregistrationsByBlockHeightCursor(ctx context.Context, arg GetValidatorMisbehaviorDeregistrationsByBlockHeightCursorParams) ([]EtlValidatorMisbehaviorDeregistration, error) { + rows, err := q.db.Query(ctx, getValidatorMisbehaviorDeregistrationsByBlockHeightCursor, arg.BlockHeight, arg.ID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlValidatorMisbehaviorDeregistration + for rows.Next() { + var i EtlValidatorMisbehaviorDeregistration + if err := rows.Scan( + &i.ID, + &i.CometAddress, + &i.PubKey, + &i.BlockHeight, + &i.TxHash, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getValidatorRegistrationByTxHash = `-- name: GetValidatorRegistrationByTxHash :one +select id, address, endpoint, comet_address, eth_block, node_type, spid, comet_pubkey, voting_power, block_height, tx_hash from etl_validator_registrations +where tx_hash = $1 +` + +func (q *Queries) GetValidatorRegistrationByTxHash(ctx context.Context, txHash string) (EtlValidatorRegistration, error) { + row := q.db.QueryRow(ctx, getValidatorRegistrationByTxHash, txHash) + var i EtlValidatorRegistration + err := row.Scan( + &i.ID, + &i.Address, + &i.Endpoint, + &i.CometAddress, + &i.EthBlock, + &i.NodeType, + &i.Spid, + &i.CometPubkey, + &i.VotingPower, + &i.BlockHeight, + &i.TxHash, + ) + return i, err +} + +const getValidatorRegistrations = `-- name: GetValidatorRegistrations :many +select vr.id, vr.address, vr.endpoint, vr.comet_address, vr.eth_block, vr.node_type, vr.spid, vr.comet_pubkey, vr.voting_power, vr.block_height, vr.tx_hash, v.endpoint, v.node_type, v.spid, v.voting_power, v.status +from etl_validator_registrations vr +left join etl_validators v on v.comet_address = vr.comet_address +order by vr.block_height desc +limit $1 offset $2 +` + +type GetValidatorRegistrationsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + +type GetValidatorRegistrationsRow struct { + ID int32 `json:"id"` + Address string `json:"address"` + Endpoint string `json:"endpoint"` + CometAddress string `json:"comet_address"` + EthBlock string `json:"eth_block"` + NodeType string `json:"node_type"` + Spid string `json:"spid"` + CometPubkey []byte `json:"comet_pubkey"` + VotingPower int64 `json:"voting_power"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + Endpoint_2 pgtype.Text `json:"endpoint_2"` + NodeType_2 pgtype.Text `json:"node_type_2"` + Spid_2 pgtype.Text `json:"spid_2"` + VotingPower_2 pgtype.Int8 `json:"voting_power_2"` + Status pgtype.Text `json:"status"` +} + +func (q *Queries) GetValidatorRegistrations(ctx context.Context, arg GetValidatorRegistrationsParams) ([]GetValidatorRegistrationsRow, error) { + rows, err := q.db.Query(ctx, getValidatorRegistrations, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetValidatorRegistrationsRow + for rows.Next() { + var i GetValidatorRegistrationsRow + if err := rows.Scan( + &i.ID, + &i.Address, + &i.Endpoint, + &i.CometAddress, + &i.EthBlock, + &i.NodeType, + &i.Spid, + &i.CometPubkey, + &i.VotingPower, + &i.BlockHeight, + &i.TxHash, + &i.Endpoint_2, + &i.NodeType_2, + &i.Spid_2, + &i.VotingPower_2, + &i.Status, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getValidatorRegistrationsByBlockHeightCursor = `-- name: GetValidatorRegistrationsByBlockHeightCursor :many +select id, address, endpoint, comet_address, eth_block, node_type, spid, comet_pubkey, voting_power, block_height, tx_hash from etl_validator_registrations +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3 +` + +type GetValidatorRegistrationsByBlockHeightCursorParams struct { + BlockHeight int64 `json:"block_height"` + ID int32 `json:"id"` + Limit int32 `json:"limit"` +} + +func (q *Queries) GetValidatorRegistrationsByBlockHeightCursor(ctx context.Context, arg GetValidatorRegistrationsByBlockHeightCursorParams) ([]EtlValidatorRegistration, error) { + rows, err := q.db.Query(ctx, getValidatorRegistrationsByBlockHeightCursor, arg.BlockHeight, arg.ID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EtlValidatorRegistration + for rows.Next() { + var i EtlValidatorRegistration + if err := rows.Scan( + &i.ID, + &i.Address, + &i.Endpoint, + &i.CometAddress, + &i.EthBlock, + &i.NodeType, + &i.Spid, + &i.CometPubkey, + &i.VotingPower, + &i.BlockHeight, + &i.TxHash, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getValidatorsForSlaRollup = `-- name: GetValidatorsForSlaRollup :many +select distinct v.id, v.address, v.endpoint, v.comet_address, v.node_type, v.spid, v.voting_power, v.status, v.registered_at, v.deregistered_at, v.created_at, v.updated_at, snr.num_blocks_proposed, snr.challenges_received, snr.challenges_failed +from etl_validators v +left join etl_sla_node_reports snr on v.comet_address = snr.address and snr.sla_rollup_id = $1 +where v.status = 'active' +order by v.comet_address, v.id +` + +type GetValidatorsForSlaRollupRow struct { + ID int32 `json:"id"` + Address string `json:"address"` + Endpoint string `json:"endpoint"` + CometAddress string `json:"comet_address"` + NodeType string `json:"node_type"` + Spid string `json:"spid"` + VotingPower int64 `json:"voting_power"` + Status string `json:"status"` + RegisteredAt int64 `json:"registered_at"` + DeregisteredAt pgtype.Int8 `json:"deregistered_at"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + NumBlocksProposed pgtype.Int4 `json:"num_blocks_proposed"` + ChallengesReceived pgtype.Int4 `json:"challenges_received"` + ChallengesFailed pgtype.Int4 `json:"challenges_failed"` +} + +func (q *Queries) GetValidatorsForSlaRollup(ctx context.Context, slaRollupID int32) ([]GetValidatorsForSlaRollupRow, error) { + rows, err := q.db.Query(ctx, getValidatorsForSlaRollup, slaRollupID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetValidatorsForSlaRollupRow + for rows.Next() { + var i GetValidatorsForSlaRollupRow + if err := rows.Scan( + &i.ID, + &i.Address, + &i.Endpoint, + &i.CometAddress, + &i.NodeType, + &i.Spid, + &i.VotingPower, + &i.Status, + &i.RegisteredAt, + &i.DeregisteredAt, + &i.CreatedAt, + &i.UpdatedAt, + &i.NumBlocksProposed, + &i.ChallengesReceived, + &i.ChallengesFailed, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const insertFailedStorageProof = `-- name: InsertFailedStorageProof :exec +insert into etl_storage_proofs ( + height, address, prover_addresses, cid, proof_signature, proof, status, block_height, tx_hash, created_at +) values ( + $1, $2, '{}', '', null, null, 'fail', $3, $4, $5 +) +` + +type InsertFailedStorageProofParams struct { + Height int64 `json:"height"` + Address string `json:"address"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +func (q *Queries) InsertFailedStorageProof(ctx context.Context, arg InsertFailedStorageProofParams) error { + _, err := q.db.Exec(ctx, insertFailedStorageProof, + arg.Height, + arg.Address, + arg.BlockHeight, + arg.TxHash, + arg.CreatedAt, + ) + return err +} + +const updateStorageProofStatus = `-- name: UpdateStorageProofStatus :exec +update etl_storage_proofs +set status = $1, proof = $2 +where height = $3 and address = $4 +` + +type UpdateStorageProofStatusParams struct { + Status EtlProofStatus `json:"status"` + Proof []byte `json:"proof"` + Height int64 `json:"height"` + Address string `json:"address"` +} + +func (q *Queries) UpdateStorageProofStatus(ctx context.Context, arg UpdateStorageProofStatusParams) error { + _, err := q.db.Exec(ctx, updateStorageProofStatus, + arg.Status, + arg.Proof, + arg.Height, + arg.Address, + ) + return err +} diff --git a/pkg/etl/db/sql/migrations/0001_etl_tables.down.sql b/pkg/etl/db/sql/migrations/0001_etl_tables.down.sql new file mode 100644 index 00000000..7ab02817 --- /dev/null +++ b/pkg/etl/db/sql/migrations/0001_etl_tables.down.sql @@ -0,0 +1,26 @@ +-- Drop materialized views first +drop materialized view if exists mv_dashboard_transaction_types; +drop materialized view if exists mv_dashboard_transaction_stats; + +-- Drop triggers +drop trigger if exists trigger_notify_new_plays on etl_plays; +drop trigger if exists trigger_notify_new_block on etl_blocks; + +-- Drop functions +drop function if exists notify_new_plays(); +drop function if exists notify_new_block(); + +-- Drop tables (in reverse order of dependencies) +drop table if exists etl_sla_node_reports; +drop table if exists etl_storage_proof_verifications; +drop table if exists etl_storage_proofs; +drop table if exists etl_validator_misbehavior_deregistrations; +drop table if exists etl_sla_rollups; +drop table if exists etl_validator_deregistrations; +drop table if exists etl_validator_registrations; +drop table if exists etl_validators; +drop table if exists etl_manage_entities; +drop table if exists etl_plays; +drop table if exists etl_blocks; +drop table if exists etl_transactions; +drop table if exists etl_addresses; diff --git a/pkg/etl/db/sql/migrations/0001_etl_tables.up.sql b/pkg/etl/db/sql/migrations/0001_etl_tables.up.sql new file mode 100644 index 00000000..f86666cc --- /dev/null +++ b/pkg/etl/db/sql/migrations/0001_etl_tables.up.sql @@ -0,0 +1,326 @@ +-- Tables for ETL service + +-- Storage proof status enum +create type etl_proof_status as enum ('unresolved', 'pass', 'fail'); + +create table if not exists etl_addresses( + id serial primary key, + address text not null, + pub_key bytea, + first_seen_block_height bigint, + created_at timestamp not null +); + + +create table if not exists etl_transactions( + id serial primary key, + tx_hash text not null, + block_height bigint not null, + tx_index integer not null, + tx_type text not null, + address text, + created_at timestamp not null +); + +create table if not exists etl_blocks( + id serial primary key, + proposer_address text not null, + block_height bigint not null, + block_time timestamp not null +); + +create table if not exists etl_plays( + id serial primary key, + user_id text not null, + track_id text not null, + city text not null, + region text not null, + country text not null, + played_at timestamp not null, + block_height bigint not null, + tx_hash text not null, + listened_at timestamp not null, + recorded_at timestamp not null +); + +create table if not exists etl_manage_entities( + id serial primary key, + address text not null, + entity_type text not null, + entity_id bigint not null, + action text not null, + metadata text, + signature text not null, + signer text not null, + nonce text not null, + block_height bigint not null, + tx_hash text not null, + created_at timestamp not null +); + +create table if not exists etl_validator_registrations( + id serial primary key, + address text not null, + endpoint text not null, + comet_address text not null, + eth_block text not null, + node_type text not null, + spid text not null, + comet_pubkey bytea not null, + voting_power bigint not null, + block_height bigint not null, + tx_hash text not null +); + +create table if not exists etl_validator_deregistrations( + id serial primary key, + comet_address text not null, + comet_pubkey bytea not null, + block_height bigint not null, + tx_hash text not null +); + +create table if not exists etl_sla_rollups( + id serial primary key, + block_start bigint not null, + block_end bigint not null, + block_height bigint not null, + validator_count integer not null, + block_quota integer not null, + bps float not null, + tps float not null, + tx_hash text not null, + created_at timestamp not null +); + +create table if not exists etl_sla_node_reports( + id serial primary key, + sla_rollup_id integer not null references etl_sla_rollups(id), + address text not null, + num_blocks_proposed integer not null, + challenges_received integer not null, + challenges_failed integer not null, + block_height bigint not null, + tx_hash text not null, + created_at timestamp not null +); + +create table if not exists etl_validator_misbehavior_deregistrations( + id serial primary key, + comet_address text not null, + pub_key bytea not null, + block_height bigint not null, + tx_hash text not null, + created_at timestamp not null +); + +create table if not exists etl_storage_proofs( + id serial primary key, + height bigint not null, + address text not null, + prover_addresses text[] not null, + cid text not null, + proof_signature bytea, + proof bytea, + status etl_proof_status not null default 'unresolved', + block_height bigint not null, + tx_hash text not null, + created_at timestamp not null +); + +create table if not exists etl_storage_proof_verifications( + id serial primary key, + height bigint not null, + proof bytea not null, + block_height bigint not null, + tx_hash text not null, + created_at timestamp not null +); + +create table if not exists etl_validators( + id serial primary key, + address text not null, + endpoint text not null unique, + comet_address text not null, + node_type text not null, + spid text not null, + voting_power bigint not null, + status text not null, + registered_at bigint not null, + deregistered_at bigint, + created_at timestamp not null, + updated_at timestamp not null +); + +-- Indexes +create index if not exists etl_transactions_address_idx on etl_transactions(address); +create index if not exists etl_transactions_tx_type_idx on etl_transactions(tx_type); +create index if not exists etl_transactions_created_at_idx on etl_transactions(created_at); + +-- Additional comprehensive indexes for query optimization + +-- Primary key and ordering indexes +create index if not exists etl_blocks_id_desc_idx on etl_blocks(id desc); +create index if not exists etl_transactions_id_desc_idx on etl_transactions(id desc); +create index if not exists etl_blocks_block_height_desc_idx on etl_blocks(block_height desc); +create index if not exists etl_transactions_block_height_desc_idx on etl_transactions(block_height desc, tx_index desc); + +-- Cursor pagination composite indexes (block_height, id) for efficient pagination +create index if not exists etl_transactions_cursor_idx on etl_transactions(block_height, id); +create index if not exists etl_plays_cursor_idx on etl_plays(block_height, id); +create index if not exists etl_manage_entities_cursor_idx on etl_manage_entities(block_height, id); +create index if not exists etl_validator_registrations_cursor_idx on etl_validator_registrations(block_height, id); +create index if not exists etl_validator_deregistrations_cursor_idx on etl_validator_deregistrations(block_height, id); +create index if not exists etl_sla_rollups_cursor_idx on etl_sla_rollups(block_height, id); +create index if not exists etl_sla_node_reports_cursor_idx on etl_sla_node_reports(block_height, id); +create index if not exists etl_validator_misbehavior_deregistrations_cursor_idx on etl_validator_misbehavior_deregistrations(block_height, id); +create index if not exists etl_storage_proofs_cursor_idx on etl_storage_proofs(block_height, id); +create index if not exists etl_storage_proof_verifications_cursor_idx on etl_storage_proof_verifications(block_height, id); + +-- Hash lookup indexes for O(1) access +create unique index if not exists etl_transactions_tx_hash_idx on etl_transactions(tx_hash); +create index if not exists etl_plays_tx_hash_idx on etl_plays(tx_hash); +create index if not exists etl_manage_entities_tx_hash_idx on etl_manage_entities(tx_hash); +create index if not exists etl_validator_registrations_tx_hash_idx on etl_validator_registrations(tx_hash); +create index if not exists etl_validator_deregistrations_tx_hash_idx on etl_validator_deregistrations(tx_hash); +create index if not exists etl_sla_rollups_tx_hash_idx on etl_sla_rollups(tx_hash); +create index if not exists etl_storage_proofs_tx_hash_idx on etl_storage_proofs(tx_hash); +create index if not exists etl_storage_proof_verifications_tx_hash_idx on etl_storage_proof_verifications(tx_hash); + +-- Block queries +create index if not exists etl_blocks_block_height_idx on etl_blocks(block_height); +create index if not exists etl_blocks_block_time_idx on etl_blocks(block_time); +create index if not exists etl_blocks_block_time_range_idx on etl_blocks(block_time, block_height); + +-- Validator-specific indexes +create index if not exists etl_validators_status_idx on etl_validators(status); +create index if not exists etl_validators_comet_address_idx on etl_validators(comet_address); +create index if not exists etl_validators_address_lower_idx on etl_validators(lower(address)); +create index if not exists etl_validators_comet_address_lower_idx on etl_validators(lower(comet_address)); +create index if not exists etl_validator_registrations_comet_address_idx on etl_validator_registrations(comet_address); +create index if not exists etl_validator_deregistrations_comet_address_idx on etl_validator_deregistrations(comet_address); + +-- Address-based queries with case-insensitive matching +create index if not exists etl_transactions_address_lower_idx on etl_transactions(lower(address)); +create index if not exists etl_sla_node_reports_address_lower_idx on etl_sla_node_reports(lower(address)); + +-- Storage proof queries +create index if not exists etl_storage_proofs_height_idx on etl_storage_proofs(height); +create index if not exists etl_storage_proofs_height_address_idx on etl_storage_proofs(height, address); +create index if not exists etl_storage_proofs_height_range_idx on etl_storage_proofs(height, address) where height >= 0; + +-- SLA rollup and node report relationships +create index if not exists etl_sla_node_reports_sla_rollup_id_idx on etl_sla_node_reports(sla_rollup_id); +create index if not exists etl_sla_node_reports_address_sla_rollup_idx on etl_sla_node_reports(address, sla_rollup_id); +create index if not exists etl_sla_rollups_block_height_desc_idx on etl_sla_rollups(block_height desc, id desc); + +-- Complex query optimizations for GetTransactionsByAddress and GetAllActiveValidatorsWithRecentRollups +create index if not exists etl_transactions_address_filter_idx on etl_transactions(lower(address), tx_type, created_at, block_height desc, tx_index desc); +create index if not exists etl_validators_active_reports_idx on etl_validators(status, comet_address) where status = 'active'; + +-- Manage entities for transaction relation queries +create index if not exists etl_manage_entities_tx_hash_action_entity_idx on etl_manage_entities(tx_hash, action, entity_type); + +-- Time-based analysis indexes for dashboard views +create index if not exists etl_transactions_created_at_type_idx on etl_transactions(created_at, tx_type); +create index if not exists etl_blocks_block_time_height_idx on etl_blocks(block_time desc, block_height desc); + +-- Covering indexes for frequently accessed columns to avoid table lookups +create index if not exists etl_validators_status_covering_idx on etl_validators(status, comet_address, endpoint, node_type, spid, voting_power) where status = 'active'; +create index if not exists etl_sla_rollups_latest_covering_idx on etl_sla_rollups(block_height desc, id desc, block_start, block_end, validator_count, block_quota, bps, tps); + +-- Partial indexes for specific query patterns +create index if not exists etl_storage_proofs_status_unresolved_idx on etl_storage_proofs(height, address) where status = 'unresolved'; +create index if not exists etl_storage_proofs_status_fail_idx on etl_storage_proofs(height, address) where status = 'fail'; + +-- Pgnotify triggers + +-- Function to notify when a new block is inserted +create or replace function notify_new_block() +returns trigger as $$ +begin + perform pg_notify('new_block', json_build_object( + 'block_height', new.block_height, + 'proposer_address', new.proposer_address + )::text); + return new; +end; +$$ language plpgsql; + +-- Function to notify when new plays are inserted +create or replace function notify_new_plays() +returns trigger as $$ +begin + perform pg_notify('new_plays', json_build_object( + 'user_id', new.user_id, + 'track_id', new.track_id, + 'city', new.city, + 'region', new.region, + 'country', new.country, + 'block_height', new.block_height + )::text); + return new; +end; +$$ language plpgsql; + +-- Trigger for new blocks +create trigger trigger_notify_new_block + after insert on etl_blocks + for each row + execute function notify_new_block(); + +-- Trigger for new plays +create trigger trigger_notify_new_plays + after insert on etl_plays + for each row + execute function notify_new_plays(); + +-- Materialized views for dashboard stats +-- These use the latest indexed block timestamp as "now" so syncing nodes have updating data + +-- Transaction time-based statistics +create materialized view mv_dashboard_transaction_stats as +with latest_block_time as ( + select block_time from etl_blocks order by block_height desc limit 1 +), +time_periods as ( + select + lbt.block_time as now_time, + lbt.block_time - interval '24 hours' as h24_ago, + lbt.block_time - interval '48 hours' as h48_ago, + lbt.block_time - interval '7 days' as d7_ago, + lbt.block_time - interval '30 days' as d30_ago + from latest_block_time lbt +) +select + -- Current 24h count + count(*) filter (where t.created_at >= tp.h24_ago) as transactions_24h, + -- Previous 24h count (for percentage change calculation) + count(*) filter (where t.created_at >= tp.h48_ago and t.created_at < tp.h24_ago) as transactions_previous_24h, + -- 7 day count + count(*) filter (where t.created_at >= tp.d7_ago) as transactions_7d, + -- 30 day count + count(*) filter (where t.created_at >= tp.d30_ago) as transactions_30d, + -- Total transactions + count(*) as total_transactions +from time_periods tp +cross join etl_transactions t +where t.created_at <= tp.now_time; + +-- Transaction type breakdown +create materialized view mv_dashboard_transaction_types as +with latest_block_time as ( + select block_time from etl_blocks order by block_height desc limit 1 +) +select + t.tx_type, + count(*) as transaction_count +from etl_transactions t +cross join latest_block_time lbt +where t.created_at <= lbt.block_time +group by t.tx_type +order by transaction_count desc; + +-- Indexes for better performance +create index on mv_dashboard_transaction_stats using btree (transactions_24h); +create index on mv_dashboard_transaction_types using btree (tx_type, transaction_count); diff --git a/pkg/etl/db/sql/reads.sql b/pkg/etl/db/sql/reads.sql new file mode 100644 index 00000000..5aa93243 --- /dev/null +++ b/pkg/etl/db/sql/reads.sql @@ -0,0 +1,283 @@ +-- get latest indexed block height +-- name: GetLatestIndexedBlock :one +SELECT block_height +FROM etl_blocks +ORDER BY id DESC +LIMIT 1; + +-- name: GetTotalTransactions :one +select id from etl_transactions order by id desc limit 1; + +-- name: GetTransactionsByBlockHeightCursor :many +select * from etl_transactions +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3; + +-- name: GetPlaysByBlockHeightCursor :many +select * from etl_plays +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3; + +-- name: GetManageEntitiesByBlockHeightCursor :many +select * from etl_manage_entities +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3; + +-- name: GetValidatorRegistrationsByBlockHeightCursor :many +select * from etl_validator_registrations +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3; + +-- name: GetValidatorDeregistrationsByBlockHeightCursor :many +select * from etl_validator_deregistrations +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3; + +-- name: GetSlaRollupsByBlockHeightCursor :many +select * from etl_sla_rollups +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3; + +-- name: GetSlaNodeReportsByBlockHeightCursor :many +select * from etl_sla_node_reports +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3; + +-- name: GetValidatorMisbehaviorDeregistrationsByBlockHeightCursor :many +select * from etl_validator_misbehavior_deregistrations +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3; + +-- name: GetStorageProofsByBlockHeightCursor :many +select * from etl_storage_proofs +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3; + +-- name: GetStorageProofVerificationsByBlockHeightCursor :many +select * from etl_storage_proof_verifications +where block_height > $1 or (block_height = $1 and id > $2) +order by block_height, id +limit $3; + +-- Transaction content queries by hash +-- name: GetPlaysByTxHash :many +select * from etl_plays +where tx_hash = $1; + +-- name: GetManageEntityByTxHash :one +select * from etl_manage_entities +where tx_hash = $1; + +-- name: GetValidatorRegistrationByTxHash :one +select * from etl_validator_registrations +where tx_hash = $1; + +-- name: GetValidatorDeregistrationByTxHash :one +select * from etl_validator_deregistrations +where tx_hash = $1; + +-- name: GetSlaRollupByTxHash :one +select * from etl_sla_rollups +where tx_hash = $1; + +-- name: GetStorageProofByTxHash :one +select * from etl_storage_proofs +where tx_hash = $1; + +-- name: GetStorageProofVerificationByTxHash :one +select * from etl_storage_proof_verifications +where tx_hash = $1; + +-- name: GetBlockRangeFirst :one +select id, proposer_address, block_height, block_time +from etl_blocks +where block_time >= $1 and block_time <= $2 +order by block_time +limit 1; + +-- name: GetBlockRangeLast :one +select id, proposer_address, block_height, block_time +from etl_blocks +where block_time >= $1 and block_time <= $2 +order by block_time desc +limit 1; + +-- name: GetBlocksByPage :many +select * from etl_blocks +order by block_height desc +limit $1 offset $2; + +-- name: GetBlockByHeight :one +select * from etl_blocks +where block_height = $1; + +-- name: GetTransactionByHash :one +select * from etl_transactions +where tx_hash = $1; + +-- name: GetTransactionsByPage :many +select * from etl_transactions +order by block_height desc, tx_index desc +limit $1 offset $2; + +-- name: GetActiveValidators :many +select * from etl_validators +where status = 'active' +order by comet_address +limit $1 offset $2; + +-- name: GetValidatorRegistrations :many +select vr.*, v.endpoint, v.node_type, v.spid, v.voting_power, v.status +from etl_validator_registrations vr +left join etl_validators v on v.comet_address = vr.comet_address +order by vr.block_height desc +limit $1 offset $2; + +-- name: GetValidatorDeregistrations :many +select vd.*, v.endpoint, v.node_type, v.spid, v.voting_power, v.status +from etl_validator_deregistrations vd +left join etl_validators v on v.comet_address = vd.comet_address +order by vd.block_height desc +limit $1 offset $2; + +-- name: GetValidatorByAddress :one +select * from etl_validators +where lower(address) = lower($1) or lower(comet_address) = lower($1); + +-- name: GetSlaNodeReportsByAddress :many +select * from etl_sla_node_reports +where lower(address) = lower($1) +order by block_height desc +limit $2; + +-- name: GetValidatorsForSlaRollup :many +select distinct v.*, snr.num_blocks_proposed, snr.challenges_received, snr.challenges_failed +from etl_validators v +left join etl_sla_node_reports snr on v.comet_address = snr.address and snr.sla_rollup_id = $1 +where v.status = 'active' +order by v.comet_address, v.id; + +-- name: GetAllActiveValidatorsWithRecentRollups :many +select v.*, snr.sla_rollup_id, snr.num_blocks_proposed, snr.challenges_received, snr.challenges_failed, snr.block_height as report_block_height, snr.created_at as report_created_at +from etl_validators v +left join etl_sla_node_reports snr on v.comet_address = snr.address +where v.status = 'active' +order by v.comet_address, v.id, snr.sla_rollup_id desc; + +-- name: GetSlaRollupsWithPagination :many +select * from etl_sla_rollups +order by id desc +limit $1 offset $2; + +-- name: GetSlaRollupById :one +select * from etl_sla_rollups +where id = $1; + +-- name: GetLatestSlaRollup :one +select * from etl_sla_rollups +order by block_height desc, id desc +limit 1; + +-- name: GetBlockTransactionCount :one +select count(*) from etl_transactions +where block_height = $1; + +-- name: GetActiveValidatorCount :one +select count(*) from etl_validators +where status = 'active'; + +-- Storage proof consensus queries +-- name: GetStorageProofsForHeight :many +select * from etl_storage_proofs +where height = $1; + +-- name: UpdateStorageProofStatus :exec +update etl_storage_proofs +set status = $1, proof = $2 +where height = $3 and address = $4; + +-- name: InsertFailedStorageProof :exec +insert into etl_storage_proofs ( + height, address, prover_addresses, cid, proof_signature, proof, status, block_height, tx_hash, created_at +) values ( + $1, $2, '{}', '', null, null, 'fail', $3, $4, $5 +); + +-- name: GetChallengeStatisticsForBlockRange :many +select + sp.address, + count(*) as challenges_received, + count(*) filter (where sp.status = 'fail') as challenges_failed +from etl_storage_proofs sp +where sp.height >= $1 and sp.height <= $2 +group by sp.address; + +-- Account transaction queries +-- name: GetTransactionsByAddress :many +select t.*, + case + when t.tx_type = 'manage_entity' then coalesce(me.action || me.entity_type, t.tx_type) + else t.tx_type + end as relation +from etl_transactions t +left join etl_manage_entities me on t.tx_hash = me.tx_hash and t.tx_type = 'manage_entity' +where lower(t.address) = lower($1) + and ($2 = '' or + case + when t.tx_type = 'manage_entity' then coalesce(me.action || me.entity_type, t.tx_type) = $2 + else t.tx_type = $2 + end) + and ($3::timestamp is null or t.created_at >= $3) + and ($4::timestamp is null or t.created_at <= $4) +order by t.block_height desc, t.tx_index desc +limit $5 offset $6; + +-- name: GetTransactionCountByAddress :one +select count(*) +from etl_transactions t +left join etl_manage_entities me on t.tx_hash = me.tx_hash and t.tx_type = 'manage_entity' +where lower(t.address) = lower($1) + and ($2 = '' or + case + when t.tx_type = 'manage_entity' then coalesce(me.action || me.entity_type, t.tx_type) = $2 + else t.tx_type = $2 + end) + and ($3::timestamp is null or t.created_at >= $3) + and ($4::timestamp is null or t.created_at <= $4); + +-- name: GetRelationTypesByAddress :many +select distinct + case + when t.tx_type = 'manage_entity' then coalesce(me.action || me.entity_type, t.tx_type) + else t.tx_type + end as relation_type +from etl_transactions t +left join etl_manage_entities me on t.tx_hash = me.tx_hash and t.tx_type = 'manage_entity' +where lower(t.address) = lower($1) +order by relation_type; + +-- Dashboard materialized view queries +-- name: GetDashboardTransactionStats :one +select * from mv_dashboard_transaction_stats limit 1; + +-- name: GetDashboardTransactionTypes :many +select * from mv_dashboard_transaction_types; + +-- name: GetHealthyValidatorCountsForRollups :many +SELECT + sr.id as rollup_id, + COALESCE(COUNT(*) FILTER (WHERE snr.challenges_failed = 0 OR snr.challenges_received = 0), 0) as healthy_validators +FROM etl_sla_rollups sr +LEFT JOIN etl_sla_node_reports snr ON sr.id = snr.sla_rollup_id +WHERE sr.id = ANY($1::int[]) +GROUP BY sr.id +ORDER BY sr.id; diff --git a/pkg/etl/db/sql/writes.sql b/pkg/etl/db/sql/writes.sql new file mode 100644 index 00000000..07f4db52 --- /dev/null +++ b/pkg/etl/db/sql/writes.sql @@ -0,0 +1,65 @@ +-- name: InsertAddress :exec +insert into etl_addresses (address, pub_key, first_seen_block_height, created_at) +values ($1, $2, $3, $4) +on conflict do nothing; + +-- name: InsertTransaction :exec +insert into etl_transactions (tx_hash, block_height, tx_index, tx_type, address, created_at) +values ($1, $2, $3, $4, $5, $6); + +-- name: InsertBlock :exec +insert into etl_blocks (proposer_address, block_height, block_time) +values ($1, $2, $3); + +-- name: InsertPlay :exec +insert into etl_plays (user_id, track_id, city, region, country, played_at, block_height, tx_hash, listened_at, recorded_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10); + +-- name: InsertPlays :exec +insert into etl_plays (user_id, track_id, city, region, country, played_at, block_height, tx_hash, listened_at, recorded_at) +select unnest($1::text[]), unnest($2::text[]), unnest($3::text[]), unnest($4::text[]), unnest($5::text[]), unnest($6::timestamp[]), unnest($7::bigint[]), unnest($8::text[]), unnest($9::timestamp[]), unnest($10::timestamp[]); + +-- name: InsertManageEntity :exec +insert into etl_manage_entities (address, entity_type, entity_id, action, metadata, signature, signer, nonce, block_height, tx_hash, created_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11); + +-- name: InsertValidatorRegistration :exec +insert into etl_validator_registrations (address, endpoint, comet_address, eth_block, node_type, spid, comet_pubkey, voting_power, block_height, tx_hash) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10); + +-- name: InsertValidatorDeregistration :exec +insert into etl_validator_deregistrations (comet_address, comet_pubkey, block_height, tx_hash) +values ($1, $2, $3, $4); + +-- name: InsertSlaRollup :exec +insert into etl_sla_rollups (block_start, block_end, block_height, validator_count, block_quota, bps, tps, tx_hash, created_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9); + +-- name: InsertSlaRollupReturningId :one +insert into etl_sla_rollups (block_start, block_end, block_height, validator_count, block_quota, bps, tps, tx_hash, created_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9) +returning id; + +-- name: InsertSlaNodeReport :exec +insert into etl_sla_node_reports (sla_rollup_id, address, num_blocks_proposed, challenges_received, challenges_failed, block_height, tx_hash, created_at) +values ($1, $2, $3, $4, $5, $6, $7, $8); + +-- name: InsertValidatorMisbehaviorDeregistration :exec +insert into etl_validator_misbehavior_deregistrations (comet_address, pub_key, block_height, tx_hash, created_at) +values ($1, $2, $3, $4, $5); + +-- name: InsertStorageProof :exec +insert into etl_storage_proofs (height, address, prover_addresses, cid, proof_signature, proof, status, block_height, tx_hash, created_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10); + +-- name: InsertStorageProofVerification :exec +insert into etl_storage_proof_verifications (height, proof, block_height, tx_hash, created_at) +values ($1, $2, $3, $4, $5); + +-- name: RegisterValidator :exec +insert into etl_validators (address, endpoint, comet_address, node_type, spid, voting_power, status, registered_at, deregistered_at, created_at, updated_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +on conflict (endpoint) do nothing; + +-- name: DeregisterValidator :exec +update etl_validators set deregistered_at = $1, updated_at = $2, status = $3 where comet_address = $4; diff --git a/pkg/etl/db/sqlc.yaml b/pkg/etl/db/sqlc.yaml new file mode 100644 index 00000000..7db467d1 --- /dev/null +++ b/pkg/etl/db/sqlc.yaml @@ -0,0 +1,31 @@ +version: "2" +sql: + - engine: "postgresql" + queries: "sql/reads.sql" + schema: "sql/migrations" + gen: + go: + package: "db" + out: "." + sql_package: "pgx/v5" + emit_json_tags: true + overrides: + - db_type: "timestamp" + go_type: + import: "time" + type: "time.Time" + + - engine: "postgresql" + queries: "sql/writes.sql" + schema: "sql/migrations" + gen: + go: + package: "db" + out: "." + sql_package: "pgx/v5" + emit_json_tags: true + overrides: + - db_type: "timestamp" + go_type: + import: "time" + type: "time.Time" diff --git a/pkg/etl/db/writes.sql.go b/pkg/etl/db/writes.sql.go new file mode 100644 index 00000000..55027023 --- /dev/null +++ b/pkg/etl/db/writes.sql.go @@ -0,0 +1,474 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: writes.sql + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const deregisterValidator = `-- name: DeregisterValidator :exec +update etl_validators set deregistered_at = $1, updated_at = $2, status = $3 where comet_address = $4 +` + +type DeregisterValidatorParams struct { + DeregisteredAt pgtype.Int8 `json:"deregistered_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + Status string `json:"status"` + CometAddress string `json:"comet_address"` +} + +func (q *Queries) DeregisterValidator(ctx context.Context, arg DeregisterValidatorParams) error { + _, err := q.db.Exec(ctx, deregisterValidator, + arg.DeregisteredAt, + arg.UpdatedAt, + arg.Status, + arg.CometAddress, + ) + return err +} + +const insertAddress = `-- name: InsertAddress :exec +insert into etl_addresses (address, pub_key, first_seen_block_height, created_at) +values ($1, $2, $3, $4) +on conflict do nothing +` + +type InsertAddressParams struct { + Address string `json:"address"` + PubKey []byte `json:"pub_key"` + FirstSeenBlockHeight pgtype.Int8 `json:"first_seen_block_height"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +func (q *Queries) InsertAddress(ctx context.Context, arg InsertAddressParams) error { + _, err := q.db.Exec(ctx, insertAddress, + arg.Address, + arg.PubKey, + arg.FirstSeenBlockHeight, + arg.CreatedAt, + ) + return err +} + +const insertBlock = `-- name: InsertBlock :exec +insert into etl_blocks (proposer_address, block_height, block_time) +values ($1, $2, $3) +` + +type InsertBlockParams struct { + ProposerAddress string `json:"proposer_address"` + BlockHeight int64 `json:"block_height"` + BlockTime pgtype.Timestamp `json:"block_time"` +} + +func (q *Queries) InsertBlock(ctx context.Context, arg InsertBlockParams) error { + _, err := q.db.Exec(ctx, insertBlock, arg.ProposerAddress, arg.BlockHeight, arg.BlockTime) + return err +} + +const insertManageEntity = `-- name: InsertManageEntity :exec +insert into etl_manage_entities (address, entity_type, entity_id, action, metadata, signature, signer, nonce, block_height, tx_hash, created_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +` + +type InsertManageEntityParams struct { + Address string `json:"address"` + EntityType string `json:"entity_type"` + EntityID int64 `json:"entity_id"` + Action string `json:"action"` + Metadata pgtype.Text `json:"metadata"` + Signature string `json:"signature"` + Signer string `json:"signer"` + Nonce string `json:"nonce"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +func (q *Queries) InsertManageEntity(ctx context.Context, arg InsertManageEntityParams) error { + _, err := q.db.Exec(ctx, insertManageEntity, + arg.Address, + arg.EntityType, + arg.EntityID, + arg.Action, + arg.Metadata, + arg.Signature, + arg.Signer, + arg.Nonce, + arg.BlockHeight, + arg.TxHash, + arg.CreatedAt, + ) + return err +} + +const insertPlay = `-- name: InsertPlay :exec +insert into etl_plays (user_id, track_id, city, region, country, played_at, block_height, tx_hash, listened_at, recorded_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) +` + +type InsertPlayParams struct { + UserID string `json:"user_id"` + TrackID string `json:"track_id"` + City string `json:"city"` + Region string `json:"region"` + Country string `json:"country"` + PlayedAt pgtype.Timestamp `json:"played_at"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + ListenedAt pgtype.Timestamp `json:"listened_at"` + RecordedAt pgtype.Timestamp `json:"recorded_at"` +} + +func (q *Queries) InsertPlay(ctx context.Context, arg InsertPlayParams) error { + _, err := q.db.Exec(ctx, insertPlay, + arg.UserID, + arg.TrackID, + arg.City, + arg.Region, + arg.Country, + arg.PlayedAt, + arg.BlockHeight, + arg.TxHash, + arg.ListenedAt, + arg.RecordedAt, + ) + return err +} + +const insertPlays = `-- name: InsertPlays :exec +insert into etl_plays (user_id, track_id, city, region, country, played_at, block_height, tx_hash, listened_at, recorded_at) +select unnest($1::text[]), unnest($2::text[]), unnest($3::text[]), unnest($4::text[]), unnest($5::text[]), unnest($6::timestamp[]), unnest($7::bigint[]), unnest($8::text[]), unnest($9::timestamp[]), unnest($10::timestamp[]) +` + +type InsertPlaysParams struct { + Column1 []string `json:"column_1"` + Column2 []string `json:"column_2"` + Column3 []string `json:"column_3"` + Column4 []string `json:"column_4"` + Column5 []string `json:"column_5"` + Column6 []pgtype.Timestamp `json:"column_6"` + Column7 []int64 `json:"column_7"` + Column8 []string `json:"column_8"` + Column9 []pgtype.Timestamp `json:"column_9"` + Column10 []pgtype.Timestamp `json:"column_10"` +} + +func (q *Queries) InsertPlays(ctx context.Context, arg InsertPlaysParams) error { + _, err := q.db.Exec(ctx, insertPlays, + arg.Column1, + arg.Column2, + arg.Column3, + arg.Column4, + arg.Column5, + arg.Column6, + arg.Column7, + arg.Column8, + arg.Column9, + arg.Column10, + ) + return err +} + +const insertSlaNodeReport = `-- name: InsertSlaNodeReport :exec +insert into etl_sla_node_reports (sla_rollup_id, address, num_blocks_proposed, challenges_received, challenges_failed, block_height, tx_hash, created_at) +values ($1, $2, $3, $4, $5, $6, $7, $8) +` + +type InsertSlaNodeReportParams struct { + SlaRollupID int32 `json:"sla_rollup_id"` + Address string `json:"address"` + NumBlocksProposed int32 `json:"num_blocks_proposed"` + ChallengesReceived int32 `json:"challenges_received"` + ChallengesFailed int32 `json:"challenges_failed"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +func (q *Queries) InsertSlaNodeReport(ctx context.Context, arg InsertSlaNodeReportParams) error { + _, err := q.db.Exec(ctx, insertSlaNodeReport, + arg.SlaRollupID, + arg.Address, + arg.NumBlocksProposed, + arg.ChallengesReceived, + arg.ChallengesFailed, + arg.BlockHeight, + arg.TxHash, + arg.CreatedAt, + ) + return err +} + +const insertSlaRollup = `-- name: InsertSlaRollup :exec +insert into etl_sla_rollups (block_start, block_end, block_height, validator_count, block_quota, bps, tps, tx_hash, created_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9) +` + +type InsertSlaRollupParams struct { + BlockStart int64 `json:"block_start"` + BlockEnd int64 `json:"block_end"` + BlockHeight int64 `json:"block_height"` + ValidatorCount int32 `json:"validator_count"` + BlockQuota int32 `json:"block_quota"` + Bps float64 `json:"bps"` + Tps float64 `json:"tps"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +func (q *Queries) InsertSlaRollup(ctx context.Context, arg InsertSlaRollupParams) error { + _, err := q.db.Exec(ctx, insertSlaRollup, + arg.BlockStart, + arg.BlockEnd, + arg.BlockHeight, + arg.ValidatorCount, + arg.BlockQuota, + arg.Bps, + arg.Tps, + arg.TxHash, + arg.CreatedAt, + ) + return err +} + +const insertSlaRollupReturningId = `-- name: InsertSlaRollupReturningId :one +insert into etl_sla_rollups (block_start, block_end, block_height, validator_count, block_quota, bps, tps, tx_hash, created_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9) +returning id +` + +type InsertSlaRollupReturningIdParams struct { + BlockStart int64 `json:"block_start"` + BlockEnd int64 `json:"block_end"` + BlockHeight int64 `json:"block_height"` + ValidatorCount int32 `json:"validator_count"` + BlockQuota int32 `json:"block_quota"` + Bps float64 `json:"bps"` + Tps float64 `json:"tps"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +func (q *Queries) InsertSlaRollupReturningId(ctx context.Context, arg InsertSlaRollupReturningIdParams) (int32, error) { + row := q.db.QueryRow(ctx, insertSlaRollupReturningId, + arg.BlockStart, + arg.BlockEnd, + arg.BlockHeight, + arg.ValidatorCount, + arg.BlockQuota, + arg.Bps, + arg.Tps, + arg.TxHash, + arg.CreatedAt, + ) + var id int32 + err := row.Scan(&id) + return id, err +} + +const insertStorageProof = `-- name: InsertStorageProof :exec +insert into etl_storage_proofs (height, address, prover_addresses, cid, proof_signature, proof, status, block_height, tx_hash, created_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) +` + +type InsertStorageProofParams struct { + Height int64 `json:"height"` + Address string `json:"address"` + ProverAddresses []string `json:"prover_addresses"` + Cid string `json:"cid"` + ProofSignature []byte `json:"proof_signature"` + Proof []byte `json:"proof"` + Status EtlProofStatus `json:"status"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +func (q *Queries) InsertStorageProof(ctx context.Context, arg InsertStorageProofParams) error { + _, err := q.db.Exec(ctx, insertStorageProof, + arg.Height, + arg.Address, + arg.ProverAddresses, + arg.Cid, + arg.ProofSignature, + arg.Proof, + arg.Status, + arg.BlockHeight, + arg.TxHash, + arg.CreatedAt, + ) + return err +} + +const insertStorageProofVerification = `-- name: InsertStorageProofVerification :exec +insert into etl_storage_proof_verifications (height, proof, block_height, tx_hash, created_at) +values ($1, $2, $3, $4, $5) +` + +type InsertStorageProofVerificationParams struct { + Height int64 `json:"height"` + Proof []byte `json:"proof"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +func (q *Queries) InsertStorageProofVerification(ctx context.Context, arg InsertStorageProofVerificationParams) error { + _, err := q.db.Exec(ctx, insertStorageProofVerification, + arg.Height, + arg.Proof, + arg.BlockHeight, + arg.TxHash, + arg.CreatedAt, + ) + return err +} + +const insertTransaction = `-- name: InsertTransaction :exec +insert into etl_transactions (tx_hash, block_height, tx_index, tx_type, address, created_at) +values ($1, $2, $3, $4, $5, $6) +` + +type InsertTransactionParams struct { + TxHash string `json:"tx_hash"` + BlockHeight int64 `json:"block_height"` + TxIndex int32 `json:"tx_index"` + TxType string `json:"tx_type"` + Address pgtype.Text `json:"address"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +func (q *Queries) InsertTransaction(ctx context.Context, arg InsertTransactionParams) error { + _, err := q.db.Exec(ctx, insertTransaction, + arg.TxHash, + arg.BlockHeight, + arg.TxIndex, + arg.TxType, + arg.Address, + arg.CreatedAt, + ) + return err +} + +const insertValidatorDeregistration = `-- name: InsertValidatorDeregistration :exec +insert into etl_validator_deregistrations (comet_address, comet_pubkey, block_height, tx_hash) +values ($1, $2, $3, $4) +` + +type InsertValidatorDeregistrationParams struct { + CometAddress string `json:"comet_address"` + CometPubkey []byte `json:"comet_pubkey"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` +} + +func (q *Queries) InsertValidatorDeregistration(ctx context.Context, arg InsertValidatorDeregistrationParams) error { + _, err := q.db.Exec(ctx, insertValidatorDeregistration, + arg.CometAddress, + arg.CometPubkey, + arg.BlockHeight, + arg.TxHash, + ) + return err +} + +const insertValidatorMisbehaviorDeregistration = `-- name: InsertValidatorMisbehaviorDeregistration :exec +insert into etl_validator_misbehavior_deregistrations (comet_address, pub_key, block_height, tx_hash, created_at) +values ($1, $2, $3, $4, $5) +` + +type InsertValidatorMisbehaviorDeregistrationParams struct { + CometAddress string `json:"comet_address"` + PubKey []byte `json:"pub_key"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +func (q *Queries) InsertValidatorMisbehaviorDeregistration(ctx context.Context, arg InsertValidatorMisbehaviorDeregistrationParams) error { + _, err := q.db.Exec(ctx, insertValidatorMisbehaviorDeregistration, + arg.CometAddress, + arg.PubKey, + arg.BlockHeight, + arg.TxHash, + arg.CreatedAt, + ) + return err +} + +const insertValidatorRegistration = `-- name: InsertValidatorRegistration :exec +insert into etl_validator_registrations (address, endpoint, comet_address, eth_block, node_type, spid, comet_pubkey, voting_power, block_height, tx_hash) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) +` + +type InsertValidatorRegistrationParams struct { + Address string `json:"address"` + Endpoint string `json:"endpoint"` + CometAddress string `json:"comet_address"` + EthBlock string `json:"eth_block"` + NodeType string `json:"node_type"` + Spid string `json:"spid"` + CometPubkey []byte `json:"comet_pubkey"` + VotingPower int64 `json:"voting_power"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` +} + +func (q *Queries) InsertValidatorRegistration(ctx context.Context, arg InsertValidatorRegistrationParams) error { + _, err := q.db.Exec(ctx, insertValidatorRegistration, + arg.Address, + arg.Endpoint, + arg.CometAddress, + arg.EthBlock, + arg.NodeType, + arg.Spid, + arg.CometPubkey, + arg.VotingPower, + arg.BlockHeight, + arg.TxHash, + ) + return err +} + +const registerValidator = `-- name: RegisterValidator :exec +insert into etl_validators (address, endpoint, comet_address, node_type, spid, voting_power, status, registered_at, deregistered_at, created_at, updated_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +on conflict (endpoint) do nothing +` + +type RegisterValidatorParams struct { + Address string `json:"address"` + Endpoint string `json:"endpoint"` + CometAddress string `json:"comet_address"` + NodeType string `json:"node_type"` + Spid string `json:"spid"` + VotingPower int64 `json:"voting_power"` + Status string `json:"status"` + RegisteredAt int64 `json:"registered_at"` + DeregisteredAt pgtype.Int8 `json:"deregistered_at"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + +func (q *Queries) RegisterValidator(ctx context.Context, arg RegisterValidatorParams) error { + _, err := q.db.Exec(ctx, registerValidator, + arg.Address, + arg.Endpoint, + arg.CometAddress, + arg.NodeType, + arg.Spid, + arg.VotingPower, + arg.Status, + arg.RegisteredAt, + arg.DeregisteredAt, + arg.CreatedAt, + arg.UpdatedAt, + ) + return err +} diff --git a/pkg/etl/etl.go b/pkg/etl/etl.go new file mode 100644 index 00000000..92e7fad9 --- /dev/null +++ b/pkg/etl/etl.go @@ -0,0 +1,141 @@ +package etl + +import ( + "context" + + "connectrpc.com/connect" + corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" + corev1connect "github.com/OpenAudio/go-openaudio/pkg/api/core/v1/v1connect" + v1 "github.com/OpenAudio/go-openaudio/pkg/api/etl/v1" + "github.com/OpenAudio/go-openaudio/pkg/api/etl/v1/v1connect" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "github.com/OpenAudio/go-openaudio/pkg/etl/location" + "github.com/jackc/pgx/v5/pgxpool" + "go.uber.org/zap" +) + +var _ v1connect.ETLServiceHandler = (*ETLService)(nil) + +type ETLService struct { + dbURL string + runDownMigrations bool + startingBlockHeight int64 + endingBlockHeight int64 + checkReadiness bool + ChainID string + + core corev1connect.CoreServiceClient + pool *pgxpool.Pool + db *db.Queries + logger *zap.Logger + + locationDB *location.LocationService + + blockPubsub *BlockPubsub + playPubsub *PlayPubsub + + mvRefresher *MaterializedViewRefresher +} + +func NewETLService(core corev1connect.CoreServiceClient, logger *zap.Logger) *ETLService { + etl := &ETLService{ + logger: logger.With(zap.String("service", "etl")), + core: core, + } + + return etl +} + +func (e *ETLService) SetDBURL(dbURL string) { + e.dbURL = dbURL +} + +func (e *ETLService) SetStartingBlockHeight(startingBlockHeight int64) { + e.startingBlockHeight = startingBlockHeight +} + +func (e *ETLService) SetEndingBlockHeight(endingBlockHeight int64) { + e.endingBlockHeight = endingBlockHeight +} + +func (e *ETLService) SetRunDownMigrations(runDownMigrations bool) { + e.runDownMigrations = runDownMigrations +} + +func (e *ETLService) SetCheckReadiness(checkReadiness bool) { + e.checkReadiness = checkReadiness +} + +func (e *ETLService) GetDB() *db.Queries { + return e.db +} + +// GetBlockPubsub returns the block pubsub instance +func (e *ETLService) GetBlockPubsub() *BlockPubsub { + return e.blockPubsub +} + +// GetPlayPubsub returns the play pubsub instance +func (e *ETLService) GetPlayPubsub() *PlayPubsub { + return e.playPubsub +} + +// GetLocationDB returns the location service instance +func (e *ETLService) GetLocationDB() *location.LocationService { + return e.locationDB +} + +// InitializeChainID fetches and caches the chain ID from the core service +func (e *ETLService) InitializeChainID(ctx context.Context) error { + nodeInfoResp, err := e.core.GetNodeInfo(ctx, connect.NewRequest(&corev1.GetNodeInfoRequest{})) + if err != nil { + // Use fallback chain ID if core service is not available + e.ChainID = "--" + e.logger.Warn("Failed to get chain ID from core service, using fallback", zap.Error(err), zap.String("chainID", e.ChainID)) + return nil + } + + e.ChainID = nodeInfoResp.Msg.Chainid + e.logger.Info("Initialized chain ID", zap.String("chainID", e.ChainID)) + return nil +} + +// GetHealth implements v1connect.ETLServiceHandler. +func (e *ETLService) GetHealth(context.Context, *connect.Request[v1.GetHealthRequest]) (*connect.Response[v1.GetHealthResponse], error) { + return connect.NewResponse(&v1.GetHealthResponse{}), nil +} + +// GetBlocks implements v1connect.ETLServiceHandler. +func (e *ETLService) GetBlocks(context.Context, *connect.Request[v1.GetBlocksRequest]) (*connect.Response[v1.GetBlocksResponse], error) { + return connect.NewResponse(&v1.GetBlocksResponse{}), nil +} + +// GetLocation implements v1connect.ETLServiceHandler. +func (e *ETLService) GetLocation(context.Context, *connect.Request[v1.GetLocationRequest]) (*connect.Response[v1.GetLocationResponse], error) { + return connect.NewResponse(&v1.GetLocationResponse{}), nil +} + +// GetManageEntities implements v1connect.ETLServiceHandler. +func (e *ETLService) GetManageEntities(context.Context, *connect.Request[v1.GetManageEntitiesRequest]) (*connect.Response[v1.GetManageEntitiesResponse], error) { + return connect.NewResponse(&v1.GetManageEntitiesResponse{}), nil +} + +// GetPlays implements v1connect.ETLServiceHandler. +func (e *ETLService) GetPlays(context.Context, *connect.Request[v1.GetPlaysRequest]) (*connect.Response[v1.GetPlaysResponse], error) { + return connect.NewResponse(&v1.GetPlaysResponse{}), nil +} + +// GetTransactions implements v1connect.ETLServiceHandler. +func (e *ETLService) GetTransactions(context.Context, *connect.Request[v1.GetTransactionsRequest]) (*connect.Response[v1.GetTransactionsResponse], error) { + return connect.NewResponse(&v1.GetTransactionsResponse{}), nil +} + +// GetValidators implements v1connect.ETLServiceHandler. +func (e *ETLService) GetValidators(context.Context, *connect.Request[v1.GetValidatorsRequest]) (*connect.Response[v1.GetValidatorsResponse], error) { + return connect.NewResponse(&v1.GetValidatorsResponse{}), nil +} + +// Ping implements v1connect.ETLServiceHandler. +func (e *ETLService) Ping(context.Context, *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) { + return connect.NewResponse(&v1.PingResponse{}), nil +} diff --git a/pkg/etl/indexer.go b/pkg/etl/indexer.go new file mode 100644 index 00000000..4c62d483 --- /dev/null +++ b/pkg/etl/indexer.go @@ -0,0 +1,697 @@ +package etl + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "sync" + "time" + + "connectrpc.com/connect" + corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "github.com/OpenAudio/go-openaudio/pkg/etl/location" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" + "github.com/jackc/pgx/v5/pgxpool" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" +) + +const ( + TxTypePlay = "play" + TxTypeManageEntity = "manage_entity" + TxTypeValidatorRegistration = "validator_registration" + TxTypeValidatorDeregistration = "validator_deregistration" + TxTypeValidatorRegistrationLegacy = "validator_registration_legacy" + TxTypeSlaRollup = "sla_rollup" + TxTypeValidatorMisbehaviorDeregistration = "validator_misbehavior_deregistration" + TxTypeStorageProof = "storage_proof" + TxTypeStorageProofVerification = "storage_proof_verification" + TxTypeRelease = "release" +) + +// ChallengeStats represents storage proof challenge statistics for a validator +type ChallengeStats struct { + ChallengesReceived int32 + ChallengesFailed int32 +} + +// StorageProofState tracks storage proof challenges and their resolution +type StorageProofState struct { + Height int64 + Proofs map[string]*StorageProofEntry // address -> proof entry + ProverAddresses map[string]int // address -> vote count for who should be provers + Resolved bool +} + +type StorageProofEntry struct { + Address string + ProverAddresses []string + ProofSignature []byte + Cid string + SignatureValid bool // determined during verification +} + +func (etl *ETLService) Run() error { + dbUrl := etl.dbURL + if dbUrl == "" { + return fmt.Errorf("dbUrl environment variable not set") + } + + err := db.RunMigrations(etl.logger, dbUrl, etl.runDownMigrations) + if err != nil { + return fmt.Errorf("error running migrations: %v", err) + } + + pgConfig, err := pgxpool.ParseConfig(dbUrl) + if err != nil { + return fmt.Errorf("error parsing database config: %v", err) + } + + pool, err := pgxpool.NewWithConfig(context.Background(), pgConfig) + if err != nil { + return fmt.Errorf("error creating database pool: %v", err) + } + + etl.pool = pool + etl.db = db.New(pool) + + // Initialize pubsub instances + etl.blockPubsub = NewPubsub[*db.EtlBlock]() + etl.playPubsub = NewPubsub[*db.EtlPlay]() + + locationDB, err := location.NewLocationService() + if err != nil { + etl.logger.Error("error creating location service", zap.Error(err)) + return fmt.Errorf("error creating location service: %v", err) + } + etl.logger.Info("location service initialized successfully") + etl.locationDB = locationDB + + // Initialize materialized view refresher + etl.mvRefresher = NewMaterializedViewRefresher(etl.pool, etl.logger) + + // Initialize chain ID from core service + err = etl.InitializeChainID(context.Background()) + if err != nil { + etl.logger.Error("error initializing chain ID", zap.Error(err)) + } + + etl.logger.Info("starting etl service") + + if etl.checkReadiness { + err = etl.awaitReadiness() + if err != nil { + etl.logger.Error("error awaiting readiness", zap.Error(err)) + } + } + + ctx := context.Background() + g, gCtx := errgroup.WithContext(ctx) + + // Start materialized view refresher in errgroup + g.Go(func() error { + return etl.mvRefresher.Start(gCtx) + }) + + // Start PostgreSQL notification listener + g.Go(func() error { + return etl.startPgNotifyListener(gCtx) + }) + + g.Go(func() error { + if err := etl.indexBlocks(); err != nil { + return fmt.Errorf("error indexing blocks: %v", err) + } + + return nil + }) + + return g.Wait() +} + +func (etl *ETLService) awaitReadiness() error { + etl.logger.Info("awaiting readiness") + attempts := 0 + + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + for range ticker.C { + attempts++ + if attempts > 60 { + return fmt.Errorf("timed out waiting for readiness") + } + + res, err := etl.core.GetStatus(context.Background(), connect.NewRequest(&corev1.GetStatusRequest{})) + if err != nil { + continue + } + + if res.Msg.Ready { + return nil + } + } + + return nil +} + +func (etl *ETLService) indexBlocks() error { + for { + // Get the latest indexed block height + latestHeight, err := etl.db.GetLatestIndexedBlock(context.Background()) + if err != nil { + // If no records exist, start from block 1 + if errors.Is(err, pgx.ErrNoRows) { + if etl.startingBlockHeight > 0 { + // Start from block 1 (nextHeight will be 1) + latestHeight = etl.startingBlockHeight - 1 + } else { + // Start from block 1 (nextHeight will be 1) + latestHeight = 0 + } + } else { + etl.logger.Error("error getting latest indexed block", zap.Error(err)) + continue + } + } + + // Get the next block + nextHeight := latestHeight + 1 + block, err := etl.core.GetBlock(context.Background(), connect.NewRequest(&corev1.GetBlockRequest{ + Height: nextHeight, + })) + if err != nil { + etl.logger.Error("error getting block", zap.Int64("height", nextHeight), zap.Error(err)) + continue + } + + if block.Msg.Block.Height < 0 { + continue + } + + // Insert block first + err = etl.db.InsertBlock(context.Background(), db.InsertBlockParams{ + ProposerAddress: block.Msg.Block.Proposer, + BlockHeight: block.Msg.Block.Height, + BlockTime: pgtype.Timestamp{Time: block.Msg.Block.Timestamp.AsTime(), Valid: true}, + }) + if err != nil { + etl.logger.Error("error inserting block", zap.Int64("height", nextHeight), zap.Error(err)) + continue + } + + var wg sync.WaitGroup + wg.Add(len(block.Msg.Block.Transactions)) + + for index := range block.Msg.Block.Transactions { + go func(block *corev1.Block, index int) { + defer wg.Done() + + tx := block.Transactions[index] + insertTxParams := db.InsertTransactionParams{ + TxHash: tx.Hash, + BlockHeight: block.Height, + TxIndex: int32(index), + TxType: "", // We'll update this after determining the type + Address: pgtype.Text{Valid: false}, // We'll update this after determining the address + CreatedAt: pgtype.Timestamp{Time: block.Timestamp.AsTime(), Valid: true}, + } + + switch signedTx := tx.Transaction.Transaction.(type) { + case *corev1.SignedTransaction_Plays: + insertTxParams.TxType = TxTypePlay + // Use the first play's user_id as the transaction address + if len(signedTx.Plays.GetPlays()) > 0 { + insertTxParams.Address = pgtype.Text{String: signedTx.Plays.GetPlays()[0].UserId, Valid: true} + } + // Process plays with batch insert + plays := signedTx.Plays.GetPlays() + if len(plays) > 0 { + // Prepare batch insert parameters + userIDs := make([]string, len(plays)) + trackIDs := make([]string, len(plays)) + cities := make([]string, len(plays)) + regions := make([]string, len(plays)) + countries := make([]string, len(plays)) + playedAts := make([]pgtype.Timestamp, len(plays)) + blockHeights := make([]int64, len(plays)) + txHashes := make([]string, len(plays)) + listenedAts := make([]pgtype.Timestamp, len(plays)) + recordedAts := make([]pgtype.Timestamp, len(plays)) + + for i, play := range plays { + userIDs[i] = play.UserId + trackIDs[i] = play.TrackId + cities[i] = play.City + regions[i] = play.Region + countries[i] = play.Country + playedAts[i] = pgtype.Timestamp{Time: play.Timestamp.AsTime(), Valid: true} + blockHeights[i] = block.Height + txHashes[i] = tx.Hash + listenedAts[i] = pgtype.Timestamp{Time: play.Timestamp.AsTime(), Valid: true} + recordedAts[i] = pgtype.Timestamp{Time: block.Timestamp.AsTime(), Valid: true} + } + + // Batch insert all plays + err = etl.db.InsertPlays(context.Background(), db.InsertPlaysParams{ + Column1: userIDs, + Column2: trackIDs, + Column3: cities, + Column4: regions, + Column5: countries, + Column6: playedAts, + Column7: blockHeights, + Column8: txHashes, + Column9: listenedAts, + Column10: recordedAts, + }) + if err != nil { + etl.logger.Error("error batch inserting plays", zap.Error(err)) + } + } + + case *corev1.SignedTransaction_ManageEntity: + insertTxParams.TxType = TxTypeManageEntity + me := signedTx.ManageEntity + insertTxParams.Address = pgtype.Text{String: me.GetSigner(), Valid: true} + + // Insert address first + err := etl.db.InsertAddress(context.Background(), db.InsertAddressParams{ + Address: me.GetSigner(), + PubKey: nil, + FirstSeenBlockHeight: pgtype.Int8{Int64: block.Height, Valid: true}, + CreatedAt: pgtype.Timestamp{Time: block.Timestamp.AsTime(), Valid: true}, + }) + if err != nil { + etl.logger.Error("error inserting address", zap.String("signer", me.GetSigner()), zap.Error(err)) + } + + err = etl.db.InsertManageEntity(context.Background(), db.InsertManageEntityParams{ + Address: me.GetSigner(), + EntityType: me.GetEntityType(), + EntityID: me.GetEntityId(), + Action: me.GetAction(), + Metadata: pgtype.Text{String: me.GetMetadata(), Valid: me.GetMetadata() != ""}, + Signature: me.GetSignature(), + Signer: me.GetSigner(), + Nonce: me.GetNonce(), + BlockHeight: block.Height, + TxHash: tx.Hash, + CreatedAt: pgtype.Timestamp{Time: block.Timestamp.AsTime(), Valid: true}, + }) + if err != nil { + etl.logger.Error("error inserting manage entity", zap.String("signer", me.GetSigner()), zap.Error(err)) + } + + case *corev1.SignedTransaction_ValidatorRegistration: + insertTxParams.TxType = TxTypeValidatorRegistrationLegacy + // Legacy validator registration - no specific table insert needed + case *corev1.SignedTransaction_ValidatorDeregistration: + insertTxParams.TxType = TxTypeValidatorMisbehaviorDeregistration + vd := signedTx.ValidatorDeregistration + // For deregistration we only have comet address, we'll need to look up eth address + // For now use comet address, can be improved later + insertTxParams.Address = pgtype.Text{String: vd.CometAddress, Valid: true} + err = etl.db.InsertValidatorMisbehaviorDeregistration(context.Background(), db.InsertValidatorMisbehaviorDeregistrationParams{ + CometAddress: vd.CometAddress, + PubKey: vd.PubKey, + BlockHeight: block.Height, + TxHash: tx.Hash, + CreatedAt: pgtype.Timestamp{Time: block.Timestamp.AsTime(), Valid: true}, + }) + if err != nil { + etl.logger.Error("error inserting validator misbehavior deregistration", zap.Error(err)) + } + case *corev1.SignedTransaction_SlaRollup: + insertTxParams.TxType = TxTypeSlaRollup + sr := signedTx.SlaRollup + // SLA rollups affect multiple validators, so we leave address as null + + // Use the number of reports in the rollup as the validator count + // This matches what the original core system does + validatorCount := int32(len(sr.Reports)) + + // Calculate block quota (total blocks divided by number of validators) + var blockQuota int32 = 0 + if sr.BlockEnd > sr.BlockStart && validatorCount > 0 { + blockQuota = int32(sr.BlockEnd-sr.BlockStart) / validatorCount + } + + // Calculate BPS and TPS for this rollup period + blockRange := sr.BlockEnd - sr.BlockStart + var bps, tps float64 = 0.0, 0.0 + + if blockRange > 0 { + // Get transaction count for this block range + txCount := int64(0) + for blockHeight := sr.BlockStart; blockHeight <= sr.BlockEnd; blockHeight++ { + blockTxCount, err := etl.db.GetBlockTransactionCount(context.Background(), blockHeight) + if err != nil { + etl.logger.Debug("failed to get transaction count for block", zap.Int64("height", blockHeight), zap.Error(err)) + continue + } + txCount += blockTxCount + } + + // Calculate time duration from the rollup timestamp and previous rollup + rollupTime := sr.Timestamp.AsTime() + var duration float64 = 0 + + // Try to get the previous rollup to calculate time difference + if latestRollup, err := etl.db.GetLatestSlaRollup(context.Background()); err == nil { + if latestRollup.CreatedAt.Valid { + duration = rollupTime.Sub(latestRollup.CreatedAt.Time).Seconds() + } + } + + // If we couldn't get duration from previous rollup, estimate from block count + // Assuming average block time of 2 seconds + if duration <= 0 { + duration = float64(blockRange) * 2.0 + } + + // Calculate BPS and TPS + if duration > 0 { + bps = float64(blockRange) / duration + tps = float64(txCount) / duration + } + } + + // Insert SLA rollup and get the ID + rollupId, err := etl.db.InsertSlaRollupReturningId(context.Background(), db.InsertSlaRollupReturningIdParams{ + BlockStart: sr.BlockStart, + BlockEnd: sr.BlockEnd, + BlockHeight: block.Height, + ValidatorCount: validatorCount, + BlockQuota: blockQuota, + Bps: bps, + Tps: tps, + TxHash: tx.Hash, + CreatedAt: pgtype.Timestamp{Time: sr.Timestamp.AsTime(), Valid: true}, // Use rollup timestamp, not block timestamp + }) + if err != nil { + etl.logger.Error("error inserting SLA rollup", zap.Error(err)) + } else { + // Get storage proof challenge statistics for this SLA period + challengeStats, err := etl.calculateChallengeStatistics(sr.BlockStart, sr.BlockEnd) + if err != nil { + etl.logger.Error("error calculating challenge statistics", zap.Error(err)) + challengeStats = make(map[string]ChallengeStats) // fallback to empty map + } + + // Insert SLA node reports with the actual rollup ID and challenge data + for _, report := range sr.Reports { + stats := challengeStats[report.Address] // Get challenge stats for this validator + + err = etl.db.InsertSlaNodeReport(context.Background(), db.InsertSlaNodeReportParams{ + SlaRollupID: rollupId, // Use the actual rollup ID + Address: report.Address, + NumBlocksProposed: report.NumBlocksProposed, + ChallengesReceived: stats.ChallengesReceived, + ChallengesFailed: stats.ChallengesFailed, + BlockHeight: block.Height, + TxHash: tx.Hash, + CreatedAt: pgtype.Timestamp{Time: sr.Timestamp.AsTime(), Valid: true}, // Use rollup timestamp + }) + if err != nil { + etl.logger.Error("error inserting SLA node report", zap.Error(err)) + } + } + } + case *corev1.SignedTransaction_StorageProof: + insertTxParams.TxType = TxTypeStorageProof + sp := signedTx.StorageProof + insertTxParams.Address = pgtype.Text{String: sp.Address, Valid: true} + err = etl.db.InsertStorageProof(context.Background(), db.InsertStorageProofParams{ + Height: sp.Height, + Address: sp.Address, + ProverAddresses: sp.ProverAddresses, + Cid: sp.Cid, + ProofSignature: sp.ProofSignature, + Proof: nil, // Will be set during verification + Status: "unresolved", + BlockHeight: block.Height, + TxHash: tx.Hash, + CreatedAt: pgtype.Timestamp{Time: block.Timestamp.AsTime(), Valid: true}, + }) + if err != nil { + etl.logger.Error("error inserting storage proof", zap.Error(err)) + } + case *corev1.SignedTransaction_StorageProofVerification: + insertTxParams.TxType = TxTypeStorageProofVerification + spv := signedTx.StorageProofVerification + // Storage proof verification doesn't have a specific address, leave as null + err = etl.db.InsertStorageProofVerification(context.Background(), db.InsertStorageProofVerificationParams{ + Height: spv.Height, + Proof: spv.Proof, + BlockHeight: block.Height, + TxHash: tx.Hash, + CreatedAt: pgtype.Timestamp{Time: block.Timestamp.AsTime(), Valid: true}, + }) + if err != nil { + etl.logger.Error("error inserting storage proof verification", zap.Error(err)) + } else { + // Process consensus for this storage proof challenge + err = etl.processStorageProofConsensus(spv.Height, spv.Proof, block.Height, tx.Hash, block.Timestamp.AsTime()) + if err != nil { + etl.logger.Error("error processing storage proof consensus", zap.Error(err)) + } + } + case *corev1.SignedTransaction_Attestation: + at := signedTx.Attestation + if vr := at.GetValidatorRegistration(); vr != nil { + insertTxParams.TxType = TxTypeValidatorRegistration + insertTxParams.Address = pgtype.Text{String: vr.DelegateWallet, Valid: true} + err = etl.db.InsertValidatorRegistration(context.Background(), db.InsertValidatorRegistrationParams{ + Address: vr.DelegateWallet, + Endpoint: vr.Endpoint, + CometAddress: vr.CometAddress, + EthBlock: fmt.Sprintf("%d", vr.EthBlock), + NodeType: vr.NodeType, + Spid: vr.SpId, + CometPubkey: vr.PubKey, + VotingPower: vr.Power, + BlockHeight: block.Height, + TxHash: tx.Hash, + }) + if err != nil { + etl.logger.Error("error inserting validator registration", zap.Error(err)) + } + // insert RegisteredValidator record + err = etl.db.RegisterValidator(context.Background(), db.RegisterValidatorParams{ + Address: vr.DelegateWallet, + Endpoint: vr.Endpoint, + CometAddress: vr.CometAddress, + NodeType: vr.NodeType, + Spid: vr.SpId, + VotingPower: vr.Power, + Status: "active", + RegisteredAt: block.Height, + DeregisteredAt: pgtype.Int8{Valid: false}, + CreatedAt: pgtype.Timestamp{Time: block.Timestamp.AsTime(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: block.Timestamp.AsTime(), Valid: true}, + }) + if err != nil { + etl.logger.Error("error registering validator", zap.Error(err)) + } + } + if vd := at.GetValidatorDeregistration(); vd != nil { + insertTxParams.TxType = TxTypeValidatorDeregistration + // For attestation deregistration we only have comet address, need to look up eth address + // For now use comet address, can be improved later + insertTxParams.Address = pgtype.Text{String: vd.CometAddress, Valid: true} + err = etl.db.InsertValidatorDeregistration(context.Background(), db.InsertValidatorDeregistrationParams{ + CometAddress: vd.CometAddress, + CometPubkey: vd.PubKey, + BlockHeight: block.Height, + TxHash: tx.Hash, + }) + if err != nil { + etl.logger.Error("error inserting validator deregistration", zap.Error(err)) + } + // insert DeregisteredValidator record + err = etl.db.DeregisterValidator(context.Background(), db.DeregisterValidatorParams{ + DeregisteredAt: pgtype.Int8{Int64: block.Height, Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: block.Timestamp.AsTime(), Valid: true}, + Status: "deregistered", + CometAddress: vd.CometAddress, + }) + if err != nil { + etl.logger.Error("error deregistering validator", zap.Error(err)) + } + } + } + + err = etl.db.InsertTransaction(context.Background(), insertTxParams) + if err != nil { + etl.logger.Error("error inserting transaction", zap.String("tx", tx.Hash), zap.Error(err)) + return + } + + }(block.Msg.Block, index) + } + + wg.Wait() + + // TODO: use pgnotify to publish block and play events to pubsub + + if etl.endingBlockHeight > 0 && block.Msg.Block.Height >= etl.endingBlockHeight { + etl.logger.Info("ending block height reached, stopping etl service") + return nil + } + } +} + +func (etl *ETLService) startPgNotifyListener(ctx context.Context) error { + conn, err := pgx.Connect(ctx, etl.dbURL) + if err != nil { + return fmt.Errorf("failed to connect for notifications: %w", err) + } + defer conn.Close(ctx) + + // Listen to both channels + _, err = conn.Exec(ctx, "LISTEN new_block") + if err != nil { + return fmt.Errorf("failed to listen to new_block: %w", err) + } + + _, err = conn.Exec(ctx, "LISTEN new_plays") + if err != nil { + return fmt.Errorf("failed to listen to new_plays: %w", err) + } + + for { + notification, err := conn.WaitForNotification(ctx) + if err != nil { + return fmt.Errorf("error waiting for notification: %w", err) + } + + switch notification.Channel { + case "new_block": + block := &db.EtlBlock{} + err = json.Unmarshal([]byte(notification.Payload), block) + if err != nil { + etl.logger.Error("error unmarshalling block", zap.Error(err)) + continue + } + if etl.blockPubsub.HasSubscribers(BlockTopic) { + etl.blockPubsub.Publish(context.Background(), BlockTopic, block) + } + case "new_plays": + play := &db.EtlPlay{} + err = json.Unmarshal([]byte(notification.Payload), play) + if err != nil { + etl.logger.Error("error unmarshalling play", zap.Error(err)) + continue + } + if etl.playPubsub.HasSubscribers(PlayTopic) { + etl.playPubsub.Publish(context.Background(), PlayTopic, play) + } + } + } +} + +// calculateChallengeStatistics aggregates storage proof challenge data for validators within a block range +// NOTE: This function may be called before all storage proof data for the block range is available, +// leading to potentially inaccurate pre-calculated statistics. Consider calculating these dynamically +// in the UI instead of storing them in the database. +func (etl *ETLService) calculateChallengeStatistics(blockStart, blockEnd int64) (map[string]ChallengeStats, error) { + ctx := context.Background() + stats := make(map[string]ChallengeStats) + + // Use the ETL database method to get challenge statistics with proper status tracking + results, err := etl.db.GetChallengeStatisticsForBlockRange(ctx, db.GetChallengeStatisticsForBlockRangeParams{ + Height: blockStart, + Height_2: blockEnd, + }) + if err != nil { + return stats, fmt.Errorf("error querying challenge statistics: %v", err) + } + + // Convert results to our ChallengeStats map + for _, result := range results { + stats[result.Address] = ChallengeStats{ + ChallengesReceived: int32(result.ChallengesReceived), + ChallengesFailed: int32(result.ChallengesFailed), + } + } + + return stats, nil +} + +func (etl *ETLService) processStorageProofConsensus(height int64, proof []byte, blockHeight int64, txHash string, blockTime time.Time) error { + ctx := context.Background() + + // Get all storage proofs for this height + storageProofs, err := etl.db.GetStorageProofsForHeight(ctx, height) + if err != nil { + return fmt.Errorf("error getting storage proofs for height %d: %v", height, err) + } + + if len(storageProofs) == 0 { + // No storage proofs submitted for this height + return nil + } + + // In the ETL context, we can't do cryptographic verification like the core system does, + // but we can implement simplified consensus logic based on majority agreement. + + // Count consensus on who the expected provers were + expectedProvers := make(map[string]int) + for _, sp := range storageProofs { + for _, proverAddr := range sp.ProverAddresses { + expectedProvers[proverAddr]++ + } + } + + // Determine majority threshold (more than half of submitted proofs) + majorityThreshold := len(storageProofs) / 2 + + // Mark proofs as 'pass' if they submitted and were part of majority consensus + passedProvers := make(map[string]bool) + for _, sp := range storageProofs { + if sp.Address != "" && sp.ProofSignature != nil { + // This prover submitted a proof - mark as passed + err = etl.db.UpdateStorageProofStatus(ctx, db.UpdateStorageProofStatusParams{ + Status: "pass", + Proof: proof, + Height: height, + Address: sp.Address, + }) + if err != nil { + etl.logger.Error("error updating storage proof status to pass", zap.Error(err)) + } else { + passedProvers[sp.Address] = true + } + } + } + + // Insert failed storage proofs for validators who were expected by majority but didn't submit + for expectedProver, voteCount := range expectedProvers { + if voteCount > majorityThreshold && !passedProvers[expectedProver] { + // This validator was expected by majority consensus but didn't submit a proof + err = etl.db.InsertFailedStorageProof(ctx, db.InsertFailedStorageProofParams{ + Height: height, + Address: expectedProver, + BlockHeight: blockHeight, + TxHash: txHash, + CreatedAt: pgtype.Timestamp{Time: blockTime, Valid: true}, + }) + if err != nil { + etl.logger.Error("error inserting failed storage proof for", zap.String("address", expectedProver), zap.Error(err)) + } + } + } + + etl.logger.Debug( + "Processed storage proof consensus", + zap.Int64("height", height), + zap.Int("passed", len(passedProvers)), + zap.Int("expected", len(expectedProvers)), + ) + + return nil +} diff --git a/pkg/etl/location/cities.sqlite3 b/pkg/etl/location/cities.sqlite3 new file mode 100644 index 00000000..8a231fcf Binary files /dev/null and b/pkg/etl/location/cities.sqlite3 differ diff --git a/pkg/etl/location/countries.sqlite3 b/pkg/etl/location/countries.sqlite3 new file mode 100644 index 00000000..9b11b184 Binary files /dev/null and b/pkg/etl/location/countries.sqlite3 differ diff --git a/pkg/etl/location/db.go b/pkg/etl/location/db.go new file mode 100644 index 00000000..ab98125d --- /dev/null +++ b/pkg/etl/location/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package location + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/pkg/etl/location/location.go b/pkg/etl/location/location.go new file mode 100644 index 00000000..d6439953 --- /dev/null +++ b/pkg/etl/location/location.go @@ -0,0 +1,184 @@ +package location + +import ( + "context" + "database/sql" + "embed" + "fmt" + "os" + "path/filepath" + + _ "modernc.org/sqlite" +) + +//go:embed *.sqlite3 +var databases embed.FS + +// LocationService provides location lookup functionality +type LocationService struct { + citiesDB *sql.DB + statesDB *sql.DB + regionsDB *sql.DB + countriesDB *sql.DB + citiesQuery *Queries + statesQuery *Queries + regionsQuery *Queries + countriesQuery *Queries +} + +// LatLong represents latitude and longitude coordinates +type LatLong struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` +} + +// NewLocationService creates a new location service with embedded databases +func NewLocationService() (*LocationService, error) { + service := &LocationService{} + + // Extract and open each database + var err error + + service.citiesDB, err = openEmbeddedDB("cities.sqlite3") + if err != nil { + return nil, fmt.Errorf("failed to open cities database: %w", err) + } + service.citiesQuery = New(service.citiesDB) + + service.statesDB, err = openEmbeddedDB("states.sqlite3") + if err != nil { + service.citiesDB.Close() + return nil, fmt.Errorf("failed to open states database: %w", err) + } + service.statesQuery = New(service.statesDB) + + service.regionsDB, err = openEmbeddedDB("regions.sqlite3") + if err != nil { + service.citiesDB.Close() + service.statesDB.Close() + return nil, fmt.Errorf("failed to open regions database: %w", err) + } + service.regionsQuery = New(service.regionsDB) + + service.countriesDB, err = openEmbeddedDB("countries.sqlite3") + if err != nil { + service.citiesDB.Close() + service.statesDB.Close() + service.regionsDB.Close() + return nil, fmt.Errorf("failed to open countries database: %w", err) + } + service.countriesQuery = New(service.countriesDB) + + return service, nil +} + +// openEmbeddedDB extracts an embedded database to a temporary file and opens it +func openEmbeddedDB(filename string) (*sql.DB, error) { + // Read the embedded database + data, err := databases.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to read embedded database %s: %w", filename, err) + } + + // Create a temporary file + tmpDir := os.TempDir() + tmpFile := filepath.Join(tmpDir, fmt.Sprintf("openaudio_%s", filename)) + + // Write the database to the temporary file + if err := os.WriteFile(tmpFile, data, 0644); err != nil { + return nil, fmt.Errorf("failed to write temporary database file: %w", err) + } + + // Open the database + db, err := sql.Open("sqlite", tmpFile) + if err != nil { + os.Remove(tmpFile) + return nil, fmt.Errorf("failed to open database: %w", err) + } + + // Test the database connection + if err := db.Ping(); err != nil { + db.Close() + os.Remove(tmpFile) + return nil, fmt.Errorf("failed to ping database %s: %w", filename, err) + } + + return db, nil +} + +// GetLatLong retrieves latitude and longitude for a given city, state, and country +func (ls *LocationService) GetLatLong(ctx context.Context, city, state, country string) (*LatLong, error) { + // Step 1: Get country code from country name + countryCode, err := ls.countriesQuery.GetCountryCode(ctx, country) + if err != nil { + return nil, fmt.Errorf("failed to get country code for %s: %w", country, err) + } + if !countryCode.Valid { + return nil, fmt.Errorf("country not found: %s", country) + } + + // Step 2: Get state code from state name and country code + stateCode, err := ls.statesQuery.GetStateCode(ctx, GetStateCodeParams{ + Name: state, + CountryCode: countryCode.String, + }) + if err != nil { + return nil, fmt.Errorf("failed to get state code for %s in %s: %w", state, country, err) + } + if !stateCode.Valid { + return nil, fmt.Errorf("state not found: %s in %s", state, country) + } + + // Step 3: Get latitude and longitude from city name, state code, and country code + cityResult, err := ls.citiesQuery.GetCityLatLong(ctx, GetCityLatLongParams{ + Name: city, + StateCode: stateCode.String, + CountryCode: countryCode.String, + }) + if err != nil { + if err == sql.ErrNoRows { + return nil, fmt.Errorf("city not found") + } + return nil, fmt.Errorf("failed to get coordinates for %s, %s, %s: %w", city, state, country, err) + } + + return &LatLong{ + Latitude: cityResult.Latitude, + Longitude: cityResult.Longitude, + }, nil +} + +// Close closes all database connections and cleans up temporary files +func (ls *LocationService) Close() error { + var errs []error + + if ls.citiesDB != nil { + if err := ls.citiesDB.Close(); err != nil { + errs = append(errs, err) + } + } + + if ls.statesDB != nil { + if err := ls.statesDB.Close(); err != nil { + errs = append(errs, err) + } + } + + if ls.regionsDB != nil { + if err := ls.regionsDB.Close(); err != nil { + errs = append(errs, err) + } + } + + if ls.countriesDB != nil { + if err := ls.countriesDB.Close(); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return fmt.Errorf("errors closing databases: %v", errs) + } + + return nil +} diff --git a/pkg/etl/location/models.go b/pkg/etl/location/models.go new file mode 100644 index 00000000..f4c4a4cd --- /dev/null +++ b/pkg/etl/location/models.go @@ -0,0 +1,84 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package location + +import ( + "database/sql" + "time" +) + +type City struct { + ID int64 `json:"id"` + Name string `json:"name"` + StateID int64 `json:"state_id"` + StateCode string `json:"state_code"` + CountryID int64 `json:"country_id"` + CountryCode string `json:"country_code"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Flag int64 `json:"flag"` + WikiDataId sql.NullString `json:"wikiDataId"` +} + +type Country struct { + ID int64 `json:"id"` + Name string `json:"name"` + Iso3 sql.NullString `json:"iso3"` + NumericCode sql.NullString `json:"numeric_code"` + Iso2 sql.NullString `json:"iso2"` + Phonecode sql.NullString `json:"phonecode"` + Capital sql.NullString `json:"capital"` + Currency sql.NullString `json:"currency"` + CurrencyName sql.NullString `json:"currency_name"` + CurrencySymbol sql.NullString `json:"currency_symbol"` + Tld sql.NullString `json:"tld"` + Native sql.NullString `json:"native"` + Region sql.NullString `json:"region"` + RegionID sql.NullInt64 `json:"region_id"` + Subregion sql.NullString `json:"subregion"` + SubregionID sql.NullInt64 `json:"subregion_id"` + Nationality sql.NullString `json:"nationality"` + Timezones sql.NullString `json:"timezones"` + Translations sql.NullString `json:"translations"` + Latitude sql.NullFloat64 `json:"latitude"` + Longitude sql.NullFloat64 `json:"longitude"` + Emoji sql.NullString `json:"emoji"` + EmojiU sql.NullString `json:"emojiU"` + CreatedAt sql.NullTime `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Flag int64 `json:"flag"` + WikiDataId sql.NullString `json:"wikiDataId"` +} + +type Region struct { + ID int64 `json:"id"` + Name string `json:"name"` + Translations sql.NullString `json:"translations"` + CreatedAt sql.NullTime `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Flag int64 `json:"flag"` + WikiDataId sql.NullString `json:"wikiDataId"` +} + +type State struct { + ID int64 `json:"id"` + Name string `json:"name"` + CountryID int64 `json:"country_id"` + CountryCode string `json:"country_code"` + FipsCode sql.NullString `json:"fips_code"` + Iso2 sql.NullString `json:"iso2"` + Type sql.NullString `json:"type"` + Level sql.NullInt64 `json:"level"` + ParentID sql.NullInt64 `json:"parent_id"` + Native sql.NullString `json:"native"` + Latitude sql.NullFloat64 `json:"latitude"` + Longitude sql.NullFloat64 `json:"longitude"` + CreatedAt sql.NullTime `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Flag int64 `json:"flag"` + WikiDataId sql.NullString `json:"wikiDataId"` +} diff --git a/pkg/etl/location/queries.sql b/pkg/etl/location/queries.sql new file mode 100644 index 00000000..f3d1b010 --- /dev/null +++ b/pkg/etl/location/queries.sql @@ -0,0 +1,8 @@ +-- name: GetCountryCode :one +select iso2 from countries where name = ? limit 1; + +-- name: GetStateCode :one +select iso2 from states where name = ? and country_code = ? limit 1; + +-- name: GetCityLatLong :one +select latitude, longitude from cities where name = ? and state_code = ? and country_code = ? limit 1; diff --git a/pkg/etl/location/queries.sql.go b/pkg/etl/location/queries.sql.go new file mode 100644 index 00000000..9969dcff --- /dev/null +++ b/pkg/etl/location/queries.sql.go @@ -0,0 +1,60 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: queries.sql + +package location + +import ( + "context" + "database/sql" +) + +const getCityLatLong = `-- name: GetCityLatLong :one +select latitude, longitude from cities where name = ? and state_code = ? and country_code = ? limit 1 +` + +type GetCityLatLongParams struct { + Name string `json:"name"` + StateCode string `json:"state_code"` + CountryCode string `json:"country_code"` +} + +type GetCityLatLongRow struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` +} + +func (q *Queries) GetCityLatLong(ctx context.Context, arg GetCityLatLongParams) (GetCityLatLongRow, error) { + row := q.db.QueryRowContext(ctx, getCityLatLong, arg.Name, arg.StateCode, arg.CountryCode) + var i GetCityLatLongRow + err := row.Scan(&i.Latitude, &i.Longitude) + return i, err +} + +const getCountryCode = `-- name: GetCountryCode :one +select iso2 from countries where name = ? limit 1 +` + +func (q *Queries) GetCountryCode(ctx context.Context, name string) (sql.NullString, error) { + row := q.db.QueryRowContext(ctx, getCountryCode, name) + var iso2 sql.NullString + err := row.Scan(&iso2) + return iso2, err +} + +const getStateCode = `-- name: GetStateCode :one +select iso2 from states where name = ? and country_code = ? limit 1 +` + +type GetStateCodeParams struct { + Name string `json:"name"` + CountryCode string `json:"country_code"` +} + +func (q *Queries) GetStateCode(ctx context.Context, arg GetStateCodeParams) (sql.NullString, error) { + row := q.db.QueryRowContext(ctx, getStateCode, arg.Name, arg.CountryCode) + var iso2 sql.NullString + err := row.Scan(&iso2) + return iso2, err +} diff --git a/pkg/etl/location/regions.sqlite3 b/pkg/etl/location/regions.sqlite3 new file mode 100644 index 00000000..87e6047b Binary files /dev/null and b/pkg/etl/location/regions.sqlite3 differ diff --git a/pkg/etl/location/schema.sql b/pkg/etl/location/schema.sql new file mode 100644 index 00000000..a4388f0b --- /dev/null +++ b/pkg/etl/location/schema.sql @@ -0,0 +1,73 @@ +CREATE TABLE "cities" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "name" VARCHAR(255) NOT NULL, + "state_id" MEDIUMINT NOT NULL, + "state_code" VARCHAR(255) NOT NULL, + "country_id" MEDIUMINT NOT NULL, + "country_code" CHARACTER(2) NOT NULL, + "latitude" DECIMAL NOT NULL, + "longitude" DECIMAL NOT NULL, + "created_at" DATETIME NOT NULL DEFAULT '2014-01-01 12:01:01', + "updated_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', + "flag" TINYINT NOT NULL DEFAULT '1', + "wikiDataId" VARCHAR(255) +); + +CREATE TABLE "states" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "name" VARCHAR(255) NOT NULL, + "country_id" MEDIUMINT NOT NULL, + "country_code" CHARACTER(2) NOT NULL, + "fips_code" VARCHAR(255), + "iso2" VARCHAR(255), + "type" VARCHAR(191), + "level" INTEGER, + "parent_id" INTEGER, + "native" VARCHAR(255), + "latitude" DECIMAL, + "longitude" DECIMAL, + "created_at" DATETIME, + "updated_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', + "flag" TINYINT NOT NULL DEFAULT '1', + "wikiDataId" VARCHAR(255) +); + +CREATE TABLE "regions" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "name" VARCHAR(100) NOT NULL, + "translations" TEXT, + "created_at" DATETIME, + "updated_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', + "flag" TINYINT NOT NULL DEFAULT '1', + "wikiDataId" VARCHAR(255) +); + +CREATE TABLE "countries" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "name" VARCHAR(100) NOT NULL, + "iso3" CHARACTER(3), + "numeric_code" CHARACTER(3), + "iso2" CHARACTER(2), + "phonecode" VARCHAR(255), + "capital" VARCHAR(255), + "currency" VARCHAR(255), + "currency_name" VARCHAR(255), + "currency_symbol" VARCHAR(255), + "tld" VARCHAR(255), + "native" VARCHAR(255), + "region" VARCHAR(255), + "region_id" MEDIUMINT, + "subregion" VARCHAR(255), + "subregion_id" MEDIUMINT, + "nationality" VARCHAR(255), + "timezones" TEXT, + "translations" TEXT, + "latitude" DECIMAL, + "longitude" DECIMAL, + "emoji" VARCHAR(191), + "emojiU" VARCHAR(191), + "created_at" DATETIME, + "updated_at" DATETIME NOT NULL DEFAULT 'CURRENT_TIMESTAMP', + "flag" TINYINT NOT NULL DEFAULT '1', + "wikiDataId" VARCHAR(255) +); diff --git a/pkg/etl/location/sqlc.yaml b/pkg/etl/location/sqlc.yaml new file mode 100644 index 00000000..cc5a1ef6 --- /dev/null +++ b/pkg/etl/location/sqlc.yaml @@ -0,0 +1,11 @@ +version: "2" +sql: + - engine: "sqlite" + queries: "queries.sql" + schema: "schema.sql" + gen: + go: + package: "location" + out: "." + sql_package: "database/sql" + emit_json_tags: true diff --git a/pkg/etl/location/states.sqlite3 b/pkg/etl/location/states.sqlite3 new file mode 100644 index 00000000..353aadb6 Binary files /dev/null and b/pkg/etl/location/states.sqlite3 differ diff --git a/pkg/etl/materialized_view_refresher.go b/pkg/etl/materialized_view_refresher.go new file mode 100644 index 00000000..1b90ca95 --- /dev/null +++ b/pkg/etl/materialized_view_refresher.go @@ -0,0 +1,122 @@ +package etl + +import ( + "context" + "fmt" + "time" + + "github.com/OpenAudio/go-openaudio/pkg/etl/db" + "go.uber.org/zap" +) + +// MaterializedViewRefresher refreshes dashboard materialized views periodically +type MaterializedViewRefresher struct { + db db.DBTX + logger *zap.Logger + ticker *time.Ticker + done chan bool +} + +// NewMaterializedViewRefresher creates a new refresher service +func NewMaterializedViewRefresher(database db.DBTX, logger *zap.Logger) *MaterializedViewRefresher { + return &MaterializedViewRefresher{ + db: database, + logger: logger.With(zap.String("service", "mv_refresher")), + done: make(chan bool), + } +} + +// Start begins the periodic refresh cycle (every 2 minutes) +// This method blocks and should be run in a goroutine (e.g., via errgroup) +func (r *MaterializedViewRefresher) Start(ctx context.Context) error { + r.ticker = time.NewTicker(2 * time.Minute) + defer r.ticker.Stop() + + r.logger.Info("Starting materialized view refresher", zap.String("interval", "2m")) + + // Initial refresh on startup + r.refreshViews(ctx) + + for { + select { + case <-r.ticker.C: + r.refreshViews(ctx) + case <-r.done: + r.logger.Info("Materialized view refresher stopped via done channel") + return nil + case <-ctx.Done(): + r.logger.Info("Materialized view refresher stopped via context cancellation") + return ctx.Err() + } + } +} + +// Stop stops the refresher +func (r *MaterializedViewRefresher) Stop() { + if r.ticker != nil { + r.ticker.Stop() + } + close(r.done) + r.logger.Info("Stopped materialized view refresher") +} + +// refreshViews calls the database function to refresh all dashboard materialized views +// It attempts to refresh independent views in parallel for better performance +func (r *MaterializedViewRefresher) refreshViews(ctx context.Context) { + start := time.Now() + + // List of materialized views to refresh + views := []string{ + "mv_dashboard_transaction_stats", + "mv_dashboard_transaction_types", + } + + // Refresh views in parallel for better performance + type result struct { + view string + err error + duration time.Duration + } + + results := make(chan result, len(views)) + + for _, view := range views { + go func(viewName string) { + viewStart := time.Now() + _, err := r.db.Exec(ctx, "REFRESH MATERIALIZED VIEW "+viewName) + results <- result{ + view: viewName, + err: err, + duration: time.Since(viewStart), + } + }(view) + } + + // Collect results + var errors []string + for i := 0; i < len(views); i++ { + res := <-results + if res.err != nil { + r.logger.Error("Failed to refresh materialized view", + zap.String("view", res.view), + zap.Error(res.err), + zap.Duration("duration", res.duration)) + errors = append(errors, fmt.Sprintf("%s: %v", res.view, res.err)) + } else { + r.logger.Debug("Refreshed materialized view", + zap.String("view", res.view), + zap.Duration("duration", res.duration)) + } + } + + totalDuration := time.Since(start) + if len(errors) > 0 { + r.logger.Warn("Some materialized views failed to refresh", + zap.Int("errors", len(errors)), + zap.Duration("total_duration", totalDuration)) + } else { + r.logger.Info("Successfully refreshed all materialized views", + zap.Int("views", len(views)), + zap.Duration("total_duration", totalDuration)) + } +} diff --git a/pkg/etl/pubsub.go b/pkg/etl/pubsub.go new file mode 100644 index 00000000..7e409322 --- /dev/null +++ b/pkg/etl/pubsub.go @@ -0,0 +1,95 @@ +package etl + +import ( + "context" + "sync" + + "github.com/OpenAudio/go-openaudio/pkg/etl/db" +) + +const ( + BlockTopic = "block-subscriber" + PlayTopic = "play-subscriber" +) + +type BlockPubsub = Pubsub[*db.EtlBlock] +type PlayPubsub = Pubsub[*db.EtlPlay] + +type Pubsub[Message any] struct { + subscribers map[string]map[chan Message]struct{} // Map of topic to channels + mu sync.RWMutex +} + +func NewPubsub[Message any]() *Pubsub[Message] { + return &Pubsub[Message]{ + subscribers: make(map[string]map[chan Message]struct{}), + } +} + +// Subscribe subscribes to a specific topic and returns a channel to receive messages. +func (ps *Pubsub[Message]) Subscribe(topic string, bufferSizes ...int) chan Message { + ps.mu.Lock() + defer ps.mu.Unlock() + + bufferSize := 1 + if len(bufferSizes) > 0 { + bufferSize = bufferSizes[0] + } + + ch := make(chan Message, bufferSize) + if ps.subscribers[topic] == nil { + ps.subscribers[topic] = make(map[chan Message]struct{}) + } + ps.subscribers[topic][ch] = struct{}{} + return ch +} + +// Unsubscribe removes a subscriber from a topic and closes the channel. +func (ps *Pubsub[Message]) Unsubscribe(topic string, ch chan Message) { + ps.mu.Lock() + defer ps.mu.Unlock() + + if subs, exists := ps.subscribers[topic]; exists { + if _, ok := subs[ch]; ok { + delete(subs, ch) + close(ch) + } + + // Clean up the topic if no subscribers remain + if len(subs) == 0 { + delete(ps.subscribers, topic) + } + } +} + +// HasSubscribers checks if there are any active subscribers for a topic +func (ps *Pubsub[Message]) HasSubscribers(topic string) bool { + ps.mu.RLock() + defer ps.mu.RUnlock() + + subs, exists := ps.subscribers[topic] + return exists && len(subs) > 0 +} + +// Publish sends a message to all subscribers of the specified topic +func (ps *Pubsub[Message]) Publish(ctx context.Context, topic string, msg Message) { + ps.mu.RLock() + defer ps.mu.RUnlock() + + // Helper function to send messages to a topic + publishToTopic := func(topic string) { + if subs, exists := ps.subscribers[topic]; exists { + for ch := range subs { + go func(ch chan Message) { + select { + case ch <- msg: + // Message sent successfully + default: + // Subscriber is not ready, drop the message + } + }(ch) + } + } + } + publishToTopic(topic) +} diff --git a/pkg/sdk/sdk.go b/pkg/sdk/sdk.go index 62538e70..62068a6a 100644 --- a/pkg/sdk/sdk.go +++ b/pkg/sdk/sdk.go @@ -11,6 +11,7 @@ import ( "connectrpc.com/connect" corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" corev1connect "github.com/OpenAudio/go-openaudio/pkg/api/core/v1/v1connect" + etlv1connect "github.com/OpenAudio/go-openaudio/pkg/api/etl/v1/v1connect" ethv1connect "github.com/OpenAudio/go-openaudio/pkg/api/eth/v1/v1connect" storagev1connect "github.com/OpenAudio/go-openaudio/pkg/api/storage/v1/v1connect" systemv1connect "github.com/OpenAudio/go-openaudio/pkg/api/system/v1/v1connect" @@ -26,6 +27,7 @@ type OpenAudioSDK struct { Core corev1connect.CoreServiceClient Storage *StorageServiceClientWithTUS + ETL etlv1connect.ETLServiceClient System systemv1connect.SystemServiceClient Eth ethv1connect.EthServiceClient @@ -53,6 +55,7 @@ func NewOpenAudioSDKWithClient(nodeURL string, httpClient *http.Client) *OpenAud coreClient := corev1connect.NewCoreServiceClient(httpClient, baseURL) storageClientBase := storagev1connect.NewStorageServiceClient(httpClient, baseURL) + etlClient := etlv1connect.NewETLServiceClient(httpClient, baseURL) systemClient := systemv1connect.NewSystemServiceClient(httpClient, baseURL) ethClient := ethv1connect.NewEthServiceClient(httpClient, baseURL) mediorumClient := mediorum.New(baseURL, mediorum.WithCoreClient(coreClient), mediorum.WithHTTPClient(httpClient)) @@ -76,6 +79,7 @@ func NewOpenAudioSDKWithClient(nodeURL string, httpClient *http.Client) *OpenAud StorageServiceClient: storageClientBase, tusClient: tusClient, }, + ETL: etlClient, System: systemClient, Eth: ethClient, Mediorum: mediorumClient, diff --git a/proto/etl/v1/service.proto b/proto/etl/v1/service.proto new file mode 100644 index 00000000..3f208244 --- /dev/null +++ b/proto/etl/v1/service.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package etl.v1; + +import "etl/v1/types.proto"; + +option go_package = "github.com/OpenAudio/go-openaudio/pkg/api/etl/v1"; + +service ETLService { + rpc Ping(PingRequest) returns (PingResponse) {} + rpc GetHealth(GetHealthRequest) returns (GetHealthResponse) {} + rpc GetBlocks(GetBlocksRequest) returns (GetBlocksResponse) {} + rpc GetTransactions(GetTransactionsRequest) returns (GetTransactionsResponse) {} + rpc GetPlays(GetPlaysRequest) returns (GetPlaysResponse) {} + rpc GetManageEntities(GetManageEntitiesRequest) returns (GetManageEntitiesResponse) {} + rpc GetValidators(GetValidatorsRequest) returns (GetValidatorsResponse) {} + rpc GetLocation(GetLocationRequest) returns (GetLocationResponse) {} +} diff --git a/proto/etl/v1/types.proto b/proto/etl/v1/types.proto new file mode 100644 index 00000000..1a96f260 --- /dev/null +++ b/proto/etl/v1/types.proto @@ -0,0 +1,119 @@ +syntax = "proto3"; + +package etl.v1; + +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/OpenAudio/go-openaudio/pkg/api/etl/v1"; + +message PingRequest {} + +message PingResponse { + string message = 1; +} + +message GetHealthRequest {} + +message GetHealthResponse {} + +message GetBlocksRequest {} + +message GetBlocksResponse {} + +message GetTransactionsRequest {} + +message GetTransactionsResponse {} + +message GetPlaysRequest { + oneof query { + GetPlays get_plays = 1; + GetPlaysByAddress get_plays_by_address = 2; + GetPlaysByUser get_plays_by_user = 3; + GetPlaysByTimeRange get_plays_by_time_range = 4; + GetPlaysByLocation get_plays_by_location = 5; + } +} + +message GetPlays {} + +message GetPlaysByAddress {} + +message GetPlaysByUser {} + +message GetPlaysByTrack {} + +message GetPlaysByTimeRange {} + +message GetPlaysByLocation {} + +message GetPlaysResponse { + repeated GetPlayResponse plays = 1; +} + +message GetPlayResponse { + string address = 1; + string track_id = 2; + int64 timestamp = 3; + string city = 4; + string country = 5; + string region = 6; + int64 block_height = 7; + string tx_hash = 8; +} + +message GetManageEntitiesRequest { +} + +message GetManageEntitiesResponse { + repeated GetManageEntityResponse manage_entities = 1; +} + +message GetManageEntityResponse { + string address = 1; + string entity_type = 2; + int64 entity_id = 3; + string action = 4; + string metadata = 5; + string signature = 6; + string signer = 7; + string nonce = 8; + int64 block = 9; + string tx_hash = 10; +} + +message GetValidatorsRequest { + oneof query { + GetRegisteredValidators get_registered_validators = 1; + GetValidatorRegistrations get_validator_registrations = 2; + GetValidatorDeregistrations get_validator_deregistrations = 3; + } +} + +message GetRegisteredValidators {} +message GetValidatorRegistrations {} +message GetValidatorDeregistrations {} + +message GetValidatorsResponse {} + +message GetValidatorResponse { + string address = 1; + string validator_address = 2; + int64 block_height = 3; + string tx_hash = 4; + google.protobuf.Timestamp timestamp = 5; +} + +message GetLocationRequest { + oneof query { + GetAvailableCities get_available_cities = 1; + GetAvailableRegions get_available_regions = 2; + GetAvailableCountries get_available_countries = 3; + } +} + +message GetAvailableCities {} +message GetAvailableRegions {} +message GetAvailableCountries {} + +message GetLocationResponse { +}