diff --git a/dashboard/components/block.go b/dashboard/components/block.go new file mode 100644 index 0000000..0875e20 --- /dev/null +++ b/dashboard/components/block.go @@ -0,0 +1,58 @@ +package components + +import ( + "encoding/json" + "log" + + "github.com/jim380/Cendermint/rest" + "github.com/kyoto-framework/kyoto/v2" + "go.uber.org/zap" +) + +/* +Component + - Each component is a context receiver, which returns its state + - Each component becomes a part of the page or top-level component, + which executes component asynchronously and gets a state future object + - Context holds common objects like http.ResponseWriter, *http.Request, etc +*/ +func GetBlockInfo(ctx *kyoto.Context) (state rest.Blocks) { + route := "/cosmos/base/tendermint/v1beta1/blocks/latest" //TO-DO refactor this + fetchBlockInfo := func() rest.Blocks { + var state rest.Blocks + resp, err := rest.HttpQuery(rest.RESTAddr + route) + if err != nil { + zap.L().Fatal("Connection to REST failed", zap.Bool("Success", false), zap.String("err:", err.Error())) + return rest.Blocks{} + } + + err = json.Unmarshal(resp, &state) + if err != nil { + zap.L().Fatal("Failed to unmarshal response", zap.Bool("Success", false), zap.String("err:", err.Error())) + return rest.Blocks{} + } + + return state + } + + /* + Handle Actions + - To call an action of parent component, use $ prefix in action name + - To call an action of component by id, use as an action name + - To push multiple component UI updates during a single action call, + call kyoto.ActionFlush(ctx, state) to initiate an update + */ + handled := kyoto.Action(ctx, "Reload Block", func(args ...any) { + // add logic here + state = fetchBlockInfo() + log.Println("New block info fetched on block", state.Block.Header.Height) + }) + // Prevent further execution if action handled + if handled { + return + } + // Default loading behavior if not handled + state = fetchBlockInfo() + + return +} diff --git a/dashboard/main.go b/dashboard/main.go new file mode 100644 index 0000000..e76076b --- /dev/null +++ b/dashboard/main.go @@ -0,0 +1,21 @@ +package dashboard + +import ( + "os" + + "github.com/jim380/Cendermint/dashboard/components" + "github.com/jim380/Cendermint/dashboard/pages" + "github.com/kyoto-framework/kyoto/v2" +) + +func StartDashboard() { + go func() { + port := os.Getenv("DASHBOARD_PORT") + // Register page + kyoto.HandlePage("/", pages.PIndex) + // Client + kyoto.HandleAction(components.GetBlockInfo) + // Serve + kyoto.Serve(":" + port) + }() +} diff --git a/dashboard/pages/index.go b/dashboard/pages/index.go new file mode 100644 index 0000000..800fc36 --- /dev/null +++ b/dashboard/pages/index.go @@ -0,0 +1,26 @@ +package pages + +import ( + "github.com/jim380/Cendermint/dashboard/components" + "github.com/jim380/Cendermint/rest" + "github.com/kyoto-framework/kyoto/v2" +) + +type PIndexState struct { + Block *kyoto.ComponentF[rest.Blocks] +} + +/* +Page + - A page is a top-level component, which attaches components and + defines rendering +*/ +func PIndex(ctx *kyoto.Context) (state PIndexState) { + // Define rendering + kyoto.Template(ctx, "page.index.html") + + // Attach components + state.Block = kyoto.Use(ctx, components.GetBlockInfo) + + return +} diff --git a/main.go b/main.go index c57cb33..298c531 100644 --- a/main.go +++ b/main.go @@ -16,18 +16,17 @@ limitations under the License. package main import ( - "encoding/json" "fmt" "log" "os" "strings" "github.com/jim380/Cendermint/config" + "github.com/jim380/Cendermint/dashboard" "github.com/jim380/Cendermint/exporter" "github.com/jim380/Cendermint/logging" "github.com/jim380/Cendermint/rest" "github.com/joho/godotenv" - "github.com/kyoto-framework/kyoto/v2" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) @@ -38,73 +37,6 @@ var ( logger *zap.Logger ) -type PIndexState struct { - Block *kyoto.ComponentF[rest.Blocks] -} - -/* -Page - - A page is a top-level component, which attaches components and - defines rendering -*/ -func PIndex(ctx *kyoto.Context) (state PIndexState) { - // Define rendering - kyoto.Template(ctx, "page.index.html") - - // Attach components - state.Block = kyoto.Use(ctx, GetBlockInfo) - - return -} - -/* -Component - - Each component is a context receiver, which returns its state - - Each component becomes a part of the page or top-level component, - which executes component asynchronously and gets a state future object - - Context holds common objects like http.ResponseWriter, *http.Request, etc -*/ -func GetBlockInfo(ctx *kyoto.Context) (state rest.Blocks) { - route := "/cosmos/base/tendermint/v1beta1/blocks/latest" //TO-DO refactor this - fetchBlockInfo := func() rest.Blocks { - var state rest.Blocks - resp, err := rest.HttpQuery(rest.RESTAddr + route) - if err != nil { - zap.L().Fatal("Connection to REST failed", zap.Bool("Success", false), zap.String("err:", err.Error())) - return rest.Blocks{} - } - - err = json.Unmarshal(resp, &state) - if err != nil { - zap.L().Fatal("Failed to unmarshal response", zap.Bool("Success", false), zap.String("err:", err.Error())) - return rest.Blocks{} - } - - return state - } - - /* - Handle Actions - - To call an action of parent component, use $ prefix in action name - - To call an action of component by id, use as an action name - - To push multiple component UI updates during a single action call, - call kyoto.ActionFlush(ctx, state) to initiate an update - */ - handled := kyoto.Action(ctx, "Reload Block", func(args ...any) { - // add logic here - state = fetchBlockInfo() - log.Println("New block info fetched on block", state.Block.Header.Height) - }) - // Prevent further execution if action handled - if handled { - return - } - // Default loading behavior if not handled - state = fetchBlockInfo() - - return -} - func main() { err := godotenv.Load("config.env") if err != nil { @@ -163,15 +95,7 @@ func main() { // run dashboard in a separate thread in enabled if strings.ToLower(cfg.DashboardEnabled) == "true" { - go func() { - port := os.Getenv("DASHBOARD_PORT") - // Register page - kyoto.HandlePage("/", PIndex) - // Client - kyoto.HandleAction(GetBlockInfo) - // Serve - kyoto.Serve(":" + port) - }() + dashboard.StartDashboard() } exporter.Start(&cfg, listeningPort, logger)