diff --git a/plugin/Taskfile.yml b/plugin/Taskfile.yml index 0d0f1cc2..5d74075b 100644 --- a/plugin/Taskfile.yml +++ b/plugin/Taskfile.yml @@ -22,13 +22,23 @@ tasks: cmds: - cmd: ./build/deweb-plugin - build: + build-frontend: + dir: home + cmds: + - cmd: npm run build + + build-backend: cmds: - task: build:internal vars: APP_NAME: plugin BIN_DIR: build + build: + cmds: + - task: build-frontend + - task: build-backend + build:internal: build: desc: Internal build task diff --git a/plugin/api/models/network_info_item.go b/plugin/api/models/network_info_item.go new file mode 100644 index 00000000..bea7f502 --- /dev/null +++ b/plugin/api/models/network_info_item.go @@ -0,0 +1,117 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// NetworkInfoItem Detailed network information +// +// swagger:model NetworkInfoItem +type NetworkInfoItem struct { + + // chain Id + ChainID int64 `json:"chainId,omitempty"` + + // name + Name string `json:"name,omitempty"` + + // status + // Enum: ["up","down"] + Status string `json:"status,omitempty"` + + // url + URL string `json:"url,omitempty"` + + // version + Version string `json:"version,omitempty"` +} + +// Validate validates this network info item +func (m *NetworkInfoItem) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var networkInfoItemTypeStatusPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["up","down"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + networkInfoItemTypeStatusPropEnum = append(networkInfoItemTypeStatusPropEnum, v) + } +} + +const ( + + // NetworkInfoItemStatusUp captures enum value "up" + NetworkInfoItemStatusUp string = "up" + + // NetworkInfoItemStatusDown captures enum value "down" + NetworkInfoItemStatusDown string = "down" +) + +// prop value enum +func (m *NetworkInfoItem) validateStatusEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, networkInfoItemTypeStatusPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *NetworkInfoItem) validateStatus(formats strfmt.Registry) error { + if swag.IsZero(m.Status) { // not required + return nil + } + + // value enum + if err := m.validateStatusEnum("status", "body", m.Status); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this network info item based on context it is used +func (m *NetworkInfoItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *NetworkInfoItem) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *NetworkInfoItem) UnmarshalBinary(b []byte) error { + var res NetworkInfoItem + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/plugin/api/models/server_status.go b/plugin/api/models/server_status.go index 821bf239..e699fd74 100644 --- a/plugin/api/models/server_status.go +++ b/plugin/api/models/server_status.go @@ -8,6 +8,7 @@ package models import ( "context" "encoding/json" + stderrors "errors" "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" @@ -24,7 +25,7 @@ type ServerStatus struct { ErrorMessage string `json:"errorMessage,omitempty"` // network - Network *ServerStatusNetwork `json:"network,omitempty"` + Network *NetworkInfoItem `json:"network,omitempty"` // The port the server is running on ServerPort int32 `json:"serverPort,omitempty"` @@ -59,11 +60,15 @@ func (m *ServerStatus) validateNetwork(formats strfmt.Registry) error { if m.Network != nil { if err := m.Network.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { return ve.ValidateName("network") - } else if ce, ok := err.(*errors.CompositeError); ok { + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { return ce.ValidateName("network") } + return err } } @@ -71,7 +76,7 @@ func (m *ServerStatus) validateNetwork(formats strfmt.Registry) error { return nil } -var serverStatusTypeStatusPropEnum []interface{} +var serverStatusTypeStatusPropEnum []any func init() { var res []string @@ -145,11 +150,15 @@ func (m *ServerStatus) contextValidateNetwork(ctx context.Context, formats strfm } if err := m.Network.ContextValidate(ctx, formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { return ve.ValidateName("network") - } else if ce, ok := err.(*errors.CompositeError); ok { + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { return ce.ValidateName("network") } + return err } } @@ -174,46 +183,3 @@ func (m *ServerStatus) UnmarshalBinary(b []byte) error { *m = res return nil } - -// ServerStatusNetwork server status network -// -// swagger:model ServerStatusNetwork -type ServerStatusNetwork struct { - - // chain ID - ChainID uint64 `json:"chainID,omitempty"` - - // network - Network string `json:"network,omitempty"` - - // version - Version string `json:"version,omitempty"` -} - -// Validate validates this server status network -func (m *ServerStatusNetwork) Validate(formats strfmt.Registry) error { - return nil -} - -// ContextValidate validates this server status network based on context it is used -func (m *ServerStatusNetwork) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - return nil -} - -// MarshalBinary interface implementation -func (m *ServerStatusNetwork) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *ServerStatusNetwork) UnmarshalBinary(b []byte) error { - var res ServerStatusNetwork - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/plugin/api/models/settings.go b/plugin/api/models/settings.go index b86860f4..81c6fd9e 100644 --- a/plugin/api/models/settings.go +++ b/plugin/api/models/settings.go @@ -7,6 +7,7 @@ package models import ( "context" + stderrors "errors" "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" @@ -55,11 +56,15 @@ func (m *Settings) validateCache(formats strfmt.Registry) error { if m.Cache != nil { if err := m.Cache.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { return ve.ValidateName("cache") - } else if ce, ok := err.(*errors.CompositeError); ok { + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { return ce.ValidateName("cache") } + return err } } @@ -99,11 +104,15 @@ func (m *Settings) contextValidateCache(ctx context.Context, formats strfmt.Regi } if err := m.Cache.ContextValidate(ctx, formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { return ve.ValidateName("cache") - } else if ce, ok := err.(*errors.CompositeError); ok { + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { return ce.ValidateName("cache") } + return err } } diff --git a/plugin/api/pluginAPI-V0.yml b/plugin/api/pluginAPI-V0.yml index ac206fd0..84b6d21a 100644 --- a/plugin/api/pluginAPI-V0.yml +++ b/plugin/api/pluginAPI-V0.yml @@ -98,6 +98,22 @@ definitions: required: - code - message + + NetworkInfoItem: + type: object + description: Detailed network information + properties: + name: + type: string + url: + type: string + chainId: + type: integer + version: + type: string + status: + type: string + enum: [up, down] ServerStatus: type: object @@ -114,14 +130,7 @@ definitions: description: Error message if server failed to start or is in error state network: type: object - properties: - network: - type: string - version: - type: string - chainID: - type: integer - format: uint64 + $ref: "#/definitions/NetworkInfoItem" Settings: type: object diff --git a/plugin/api/restapi/configure_deweb_plugin.go b/plugin/api/restapi/configure_deweb_plugin.go index 130c5316..3a0a8ead 100644 --- a/plugin/api/restapi/configure_deweb_plugin.go +++ b/plugin/api/restapi/configure_deweb_plugin.go @@ -11,6 +11,8 @@ import ( "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" "github.com/massalabs/deweb-plugin/api/restapi/operations" + dewebmiddleware "github.com/massalabs/deweb-plugin/int/api/middleware" + "github.com/rs/cors" ) //go:generate swagger generate server --target ../../api --name DewebPlugin --spec ../pluginAPI-V0.yml --principal interface{} --exclude-main @@ -104,5 +106,11 @@ func setupMiddlewares(handler http.Handler) http.Handler { // The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document. // So this is a good place to plug in a panic handling middleware, logging and metrics. func setupGlobalMiddleware(handler http.Handler) http.Handler { - return handler + handleCORS := cors.New(cors.Options{ + AllowedMethods: []string{http.MethodGet, http.MethodPut}, + AllowedOrigins: dewebmiddleware.AllowedDomainsList(), + }).Handler + + // Middleware chain: CORS → WebAppMiddleware → DomainRestrictionMiddleware → API handlers + return handleCORS(dewebmiddleware.WebAppMiddleware(dewebmiddleware.DomainRestrictionMiddleware(handler))) } diff --git a/plugin/api/restapi/embedded_spec.go b/plugin/api/restapi/embedded_spec.go index 72984cae..e2e41aaf 100644 --- a/plugin/api/restapi/embedded_spec.go +++ b/plugin/api/restapi/embedded_spec.go @@ -188,6 +188,31 @@ func init() { } } }, + "NetworkInfoItem": { + "description": "Detailed network information", + "type": "object", + "properties": { + "chainId": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "up", + "down" + ] + }, + "url": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, "ServerStatus": { "type": "object", "properties": { @@ -197,18 +222,7 @@ func init() { }, "network": { "type": "object", - "properties": { - "chainID": { - "type": "integer", - "format": "uint64" - }, - "network": { - "type": "string" - }, - "version": { - "type": "string" - } - } + "$ref": "#/definitions/NetworkInfoItem" }, "serverPort": { "description": "The port the server is running on", @@ -420,6 +434,31 @@ func init() { } } }, + "NetworkInfoItem": { + "description": "Detailed network information", + "type": "object", + "properties": { + "chainId": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "up", + "down" + ] + }, + "url": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, "ServerStatus": { "type": "object", "properties": { @@ -429,18 +468,7 @@ func init() { }, "network": { "type": "object", - "properties": { - "chainID": { - "type": "integer", - "format": "uint64" - }, - "network": { - "type": "string" - }, - "version": { - "type": "string" - } - } + "$ref": "#/definitions/NetworkInfoItem" }, "serverPort": { "description": "The port the server is running on", @@ -459,21 +487,6 @@ func init() { } } }, - "ServerStatusNetwork": { - "type": "object", - "properties": { - "chainID": { - "type": "integer", - "format": "uint64" - }, - "network": { - "type": "string" - }, - "version": { - "type": "string" - } - } - }, "Settings": { "type": "object", "required": [ diff --git a/plugin/api/restapi/operations/default_page.go b/plugin/api/restapi/operations/default_page.go index 3875419b..6d354c44 100644 --- a/plugin/api/restapi/operations/default_page.go +++ b/plugin/api/restapi/operations/default_page.go @@ -51,6 +51,7 @@ func (o *DefaultPage) ServeHTTP(rw http.ResponseWriter, r *http.Request) { } res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) } diff --git a/plugin/api/restapi/operations/default_page_parameters.go b/plugin/api/restapi/operations/default_page_parameters.go index fa657be3..ea153e0b 100644 --- a/plugin/api/restapi/operations/default_page_parameters.go +++ b/plugin/api/restapi/operations/default_page_parameters.go @@ -25,7 +25,6 @@ func NewDefaultPageParams() DefaultPageParams { // // swagger:parameters DefaultPage type DefaultPageParams struct { - // HTTP Request Object HTTPRequest *http.Request `json:"-"` } diff --git a/plugin/api/restapi/operations/default_page_responses.go b/plugin/api/restapi/operations/default_page_responses.go index d44d4ee7..121d13c5 100644 --- a/plugin/api/restapi/operations/default_page_responses.go +++ b/plugin/api/restapi/operations/default_page_responses.go @@ -31,7 +31,7 @@ func NewDefaultPageFound() *DefaultPageFound { // WriteResponse to the client func (o *DefaultPageFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + rw.Header().Del(runtime.HeaderContentType) // Remove Content-Type on empty responses rw.WriteHeader(302) } diff --git a/plugin/api/restapi/operations/deweb_plugin_api.go b/plugin/api/restapi/operations/deweb_plugin_api.go index 2b120457..7df8f3b1 100644 --- a/plugin/api/restapi/operations/deweb_plugin_api.go +++ b/plugin/api/restapi/operations/deweb_plugin_api.go @@ -42,33 +42,59 @@ func NewDewebPluginAPI(spec *loads.Document) *DewebPluginAPI { JSONConsumer: runtime.JSONConsumer(), BinProducer: runtime.ByteStreamProducer(), - CSSProducer: runtime.ProducerFunc(func(w io.Writer, data interface{}) error { + CSSProducer: runtime.ProducerFunc(func(w io.Writer, data any) error { + _ = w + _ = data + return errors.NotImplemented("css producer has not yet been implemented") }), - HTMLProducer: runtime.ProducerFunc(func(w io.Writer, data interface{}) error { + HTMLProducer: runtime.ProducerFunc(func(w io.Writer, data any) error { + _ = w + _ = data + return errors.NotImplemented("html producer has not yet been implemented") }), - JsProducer: runtime.ProducerFunc(func(w io.Writer, data interface{}) error { + JsProducer: runtime.ProducerFunc(func(w io.Writer, data any) error { + _ = w + _ = data + return errors.NotImplemented("js producer has not yet been implemented") }), JSONProducer: runtime.JSONProducer(), - TextWebpProducer: runtime.ProducerFunc(func(w io.Writer, data interface{}) error { + TextWebpProducer: runtime.ProducerFunc(func(w io.Writer, data any) error { + _ = w + _ = data + return errors.NotImplemented("textWebp producer has not yet been implemented") }), DefaultPageHandler: DefaultPageHandlerFunc(func(params DefaultPageParams) middleware.Responder { + _ = params + return middleware.NotImplemented("operation DefaultPage has not yet been implemented") }), + GetServerStatusHandler: GetServerStatusHandlerFunc(func(params GetServerStatusParams) middleware.Responder { + _ = params + return middleware.NotImplemented("operation GetServerStatus has not yet been implemented") }), + GetSettingsHandler: GetSettingsHandlerFunc(func(params GetSettingsParams) middleware.Responder { + _ = params + return middleware.NotImplemented("operation GetSettings has not yet been implemented") }), + PluginWebAppHandler: PluginWebAppHandlerFunc(func(params PluginWebAppParams) middleware.Responder { + _ = params + return middleware.NotImplemented("operation PluginWebApp has not yet been implemented") }), + UpdateSettingsHandler: UpdateSettingsHandlerFunc(func(params UpdateSettingsParams) middleware.Responder { + _ = params + return middleware.NotImplemented("operation UpdateSettings has not yet been implemented") }), } @@ -149,7 +175,7 @@ type DewebPluginAPI struct { CommandLineOptionsGroups []swag.CommandLineOptionsGroup // User defined logger function. - Logger func(string, ...interface{}) + Logger func(string, ...any) } // UseRedoc for documentation at /docs @@ -263,12 +289,12 @@ func (o *DewebPluginAPI) Authorizer() runtime.Authorizer { } // ConsumersFor gets the consumers for the specified media types. +// // MIME type parameters are ignored here. func (o *DewebPluginAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer { result := make(map[string]runtime.Consumer, len(mediaTypes)) for _, mt := range mediaTypes { - switch mt { - case "application/json": + if mt == "application/json" { result["application/json"] = o.JSONConsumer } @@ -276,10 +302,12 @@ func (o *DewebPluginAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Co result[mt] = c } } + return result } // ProducersFor gets the producers for the specified media types. +// // MIME type parameters are ignored here. func (o *DewebPluginAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer { result := make(map[string]runtime.Producer, len(mediaTypes)) @@ -303,6 +331,7 @@ func (o *DewebPluginAPI) ProducersFor(mediaTypes []string) map[string]runtime.Pr result[mt] = p } } + return result } diff --git a/plugin/api/restapi/operations/get_server_status.go b/plugin/api/restapi/operations/get_server_status.go index 51c054a8..bf8f713b 100644 --- a/plugin/api/restapi/operations/get_server_status.go +++ b/plugin/api/restapi/operations/get_server_status.go @@ -51,6 +51,7 @@ func (o *GetServerStatus) ServeHTTP(rw http.ResponseWriter, r *http.Request) { } res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) } diff --git a/plugin/api/restapi/operations/get_server_status_parameters.go b/plugin/api/restapi/operations/get_server_status_parameters.go index 24499c0f..ff5a61dc 100644 --- a/plugin/api/restapi/operations/get_server_status_parameters.go +++ b/plugin/api/restapi/operations/get_server_status_parameters.go @@ -25,7 +25,6 @@ func NewGetServerStatusParams() GetServerStatusParams { // // swagger:parameters GetServerStatus type GetServerStatusParams struct { - // HTTP Request Object HTTPRequest *http.Request `json:"-"` } diff --git a/plugin/api/restapi/operations/get_settings.go b/plugin/api/restapi/operations/get_settings.go index 813e767d..411e3da1 100644 --- a/plugin/api/restapi/operations/get_settings.go +++ b/plugin/api/restapi/operations/get_settings.go @@ -51,6 +51,7 @@ func (o *GetSettings) ServeHTTP(rw http.ResponseWriter, r *http.Request) { } res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) } diff --git a/plugin/api/restapi/operations/get_settings_parameters.go b/plugin/api/restapi/operations/get_settings_parameters.go index 8b8fe5ba..47c01976 100644 --- a/plugin/api/restapi/operations/get_settings_parameters.go +++ b/plugin/api/restapi/operations/get_settings_parameters.go @@ -25,7 +25,6 @@ func NewGetSettingsParams() GetSettingsParams { // // swagger:parameters GetSettings type GetSettingsParams struct { - // HTTP Request Object HTTPRequest *http.Request `json:"-"` } diff --git a/plugin/api/restapi/operations/plugin_web_app.go b/plugin/api/restapi/operations/plugin_web_app.go index 8d120f26..50f5be0d 100644 --- a/plugin/api/restapi/operations/plugin_web_app.go +++ b/plugin/api/restapi/operations/plugin_web_app.go @@ -51,6 +51,7 @@ func (o *PluginWebApp) ServeHTTP(rw http.ResponseWriter, r *http.Request) { } res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) } diff --git a/plugin/api/restapi/operations/plugin_web_app_parameters.go b/plugin/api/restapi/operations/plugin_web_app_parameters.go index 747bae53..cc0ba045 100644 --- a/plugin/api/restapi/operations/plugin_web_app_parameters.go +++ b/plugin/api/restapi/operations/plugin_web_app_parameters.go @@ -26,7 +26,6 @@ func NewPluginWebAppParams() PluginWebAppParams { // // swagger:parameters PluginWebApp type PluginWebAppParams struct { - // HTTP Request Object HTTPRequest *http.Request `json:"-"` diff --git a/plugin/api/restapi/operations/plugin_web_app_responses.go b/plugin/api/restapi/operations/plugin_web_app_responses.go index 1a282b02..e52e4f02 100644 --- a/plugin/api/restapi/operations/plugin_web_app_responses.go +++ b/plugin/api/restapi/operations/plugin_web_app_responses.go @@ -33,7 +33,7 @@ func NewPluginWebAppOK() *PluginWebAppOK { // WriteResponse to the client func (o *PluginWebAppOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + rw.Header().Del(runtime.HeaderContentType) // Remove Content-Type on empty responses rw.WriteHeader(200) } diff --git a/plugin/api/restapi/operations/plugin_web_app_urlbuilder.go b/plugin/api/restapi/operations/plugin_web_app_urlbuilder.go index a642bde5..67dfc7e7 100644 --- a/plugin/api/restapi/operations/plugin_web_app_urlbuilder.go +++ b/plugin/api/restapi/operations/plugin_web_app_urlbuilder.go @@ -44,7 +44,7 @@ func (o *PluginWebAppURL) Build() (*url.URL, error) { resource := o.Resource if resource != "" { - _path = strings.Replace(_path, "{resource}", resource, -1) + _path = strings.ReplaceAll(_path, "{resource}", resource) } else { return nil, errors.New("resource is required on PluginWebAppURL") } diff --git a/plugin/api/restapi/operations/update_settings.go b/plugin/api/restapi/operations/update_settings.go index 23ffc21b..cccd4df0 100644 --- a/plugin/api/restapi/operations/update_settings.go +++ b/plugin/api/restapi/operations/update_settings.go @@ -51,6 +51,7 @@ func (o *UpdateSettings) ServeHTTP(rw http.ResponseWriter, r *http.Request) { } res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) } diff --git a/plugin/api/restapi/operations/update_settings_parameters.go b/plugin/api/restapi/operations/update_settings_parameters.go index 28c21f4b..a4d05399 100644 --- a/plugin/api/restapi/operations/update_settings_parameters.go +++ b/plugin/api/restapi/operations/update_settings_parameters.go @@ -6,6 +6,7 @@ package operations // Editing this file might prove futile when you re-run the swagger generate command import ( + stderrors "errors" "io" "net/http" @@ -30,7 +31,6 @@ func NewUpdateSettingsParams() UpdateSettingsParams { // // swagger:parameters UpdateSettings type UpdateSettingsParams struct { - // HTTP Request Object HTTPRequest *http.Request `json:"-"` @@ -51,10 +51,12 @@ func (o *UpdateSettingsParams) BindRequest(r *http.Request, route *middleware.Ma o.HTTPRequest = r if runtime.HasBody(r) { - defer r.Body.Close() + defer func() { + _ = r.Body.Close() + }() var body models.Settings if err := route.Consumer.Consume(r.Body, &body); err != nil { - if err == io.EOF { + if stderrors.Is(err, io.EOF) { res = append(res, errors.Required("settings", "body", "")) } else { res = append(res, errors.NewParseError("settings", "body", "", err)) diff --git a/plugin/api/restapi/operations/update_settings_responses.go b/plugin/api/restapi/operations/update_settings_responses.go index 7a6aa6eb..bc14c6fd 100644 --- a/plugin/api/restapi/operations/update_settings_responses.go +++ b/plugin/api/restapi/operations/update_settings_responses.go @@ -33,7 +33,7 @@ func NewUpdateSettingsOK() *UpdateSettingsOK { // WriteResponse to the client func (o *UpdateSettingsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + rw.Header().Del(runtime.HeaderContentType) // Remove Content-Type on empty responses rw.WriteHeader(200) } diff --git a/plugin/api/restapi/server.go b/plugin/api/restapi/server.go index e25c6891..292ec918 100644 --- a/plugin/api/restapi/server.go +++ b/plugin/api/restapi/server.go @@ -7,7 +7,6 @@ import ( "crypto/tls" "crypto/x509" "errors" - "fmt" "log" "net" "net/http" @@ -19,11 +18,12 @@ import ( "syscall" "time" - "github.com/go-openapi/runtime/flagext" - "github.com/go-openapi/swag" flags "github.com/jessevdk/go-flags" "golang.org/x/net/netutil" + "github.com/go-openapi/runtime/flagext" + "github.com/go-openapi/swag" + "github.com/massalabs/deweb-plugin/api/restapi/operations" ) @@ -104,7 +104,7 @@ type Server struct { } // Logf logs message either via defined user logger or via system one if no user logger is defined. -func (s *Server) Logf(f string, args ...interface{}) { +func (s *Server) Logf(f string, args ...any) { if s.api != nil && s.api.Logger != nil { s.api.Logger(f, args...) } else { @@ -114,7 +114,7 @@ func (s *Server) Logf(f string, args ...interface{}) { // Fatalf logs message either via defined user logger or via system one if no user logger is defined. // Exits with non-zero status after printing -func (s *Server) Fatalf(f string, args ...interface{}) { +func (s *Server) Fatalf(f string, args ...any) { if s.api != nil && s.api.Logger != nil { s.api.Logger(f, args...) os.Exit(1) @@ -188,8 +188,8 @@ func (s *Server) Serve() (err error) { s.Logf("Serving deweb plugin at unix://%s", s.SocketPath) go func(l net.Listener) { defer wg.Done() - if err := domainSocket.Serve(l); err != nil && err != http.ErrServerClosed { - s.Fatalf("%v", err) + if errServe := domainSocket.Serve(l); errServe != nil && !errors.Is(errServe, http.ErrServerClosed) { + s.Fatalf("%v", errServe) } s.Logf("Stopped serving deweb plugin at unix://%s", s.SocketPath) }(s.domainSocketL) @@ -218,8 +218,8 @@ func (s *Server) Serve() (err error) { s.Logf("Serving deweb plugin at http://%s", s.httpServerL.Addr()) go func(l net.Listener) { defer wg.Done() - if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed { - s.Fatalf("%v", err) + if errServe := httpServer.Serve(l); errServe != nil && !errors.Is(errServe, http.ErrServerClosed) { + s.Fatalf("%v", errServe) } s.Logf("Stopped serving deweb plugin at http://%s", l.Addr()) }(s.httpServerL) @@ -280,7 +280,7 @@ func (s *Server) Serve() (err error) { caCertPool := x509.NewCertPool() ok := caCertPool.AppendCertsFromPEM(caCert) if !ok { - return fmt.Errorf("cannot parse CA certificate") + return errors.New("cannot parse CA certificate") } httpsServer.TLSConfig.ClientCAs = caCertPool httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert @@ -311,8 +311,8 @@ func (s *Server) Serve() (err error) { s.Logf("Serving deweb plugin at https://%s", s.httpsServerL.Addr()) go func(l net.Listener) { defer wg.Done() - if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed { - s.Fatalf("%v", err) + if errServe := httpsServer.Serve(l); errServe != nil && !errors.Is(errServe, http.ErrServerClosed) { + s.Fatalf("%v", errServe) } s.Logf("Stopped serving deweb plugin at https://%s", l.Addr()) }(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig)) diff --git a/plugin/go.mod b/plugin/go.mod index ebbf4689..3f5f5182 100644 --- a/plugin/go.mod +++ b/plugin/go.mod @@ -14,9 +14,10 @@ require ( github.com/go-openapi/validate v0.24.0 github.com/jessevdk/go-flags v1.6.1 github.com/massalabs/deweb-server v0.0.0-00010101000000-000000000000 - github.com/massalabs/station v0.6.9 + github.com/massalabs/station v0.8.3 github.com/massalabs/station-massa-wallet v0.4.5 github.com/massalabs/station/plugin-kit v0.1.1 + github.com/rs/cors v1.8.3 github.com/shirou/gopsutil/v4 v4.25.3 golang.org/x/net v0.39.0 gopkg.in/yaml.v2 v2.4.0 @@ -40,7 +41,6 @@ require ( github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/shopspring/decimal v1.3.1 // indirect - github.com/stretchr/objx v0.5.2 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/ybbus/jsonrpc/v3 v3.1.4 // indirect @@ -53,5 +53,4 @@ require ( golang.org/x/sys v0.32.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - howett.net/plist v1.0.0 // indirect ) diff --git a/plugin/go.sum b/plugin/go.sum index 22cc47af..2c8969e2 100644 --- a/plugin/go.sum +++ b/plugin/go.sum @@ -51,7 +51,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -66,8 +65,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/massalabs/station v0.6.9 h1:eampc2dndCq8BHUydRlcMSy1i1dBhxqI5tKWKGMMj+4= -github.com/massalabs/station v0.6.9/go.mod h1:fvIMuIS8v1/tu5e4FPvvqEJEcLsZN+wUD5OMSHD/rO8= +github.com/massalabs/station v0.8.3 h1:LuxBQlUekzb/oYDrXwYT5RXQnDBShc+L5FwOJ8mr/ME= +github.com/massalabs/station v0.8.3/go.mod h1:skBhCxU+An9l05EGmQbk3RqIDT2xNRMTRqs3v5jR3Us= github.com/massalabs/station-massa-wallet v0.4.5 h1:0rTHxGPlJ5cKjgB/yQclOBHbWiZO5rOwO0lT7ZjFuVQ= github.com/massalabs/station-massa-wallet v0.4.5/go.mod h1:Eu6Zlijs0uAuGM5CxEUOxFrcIlWtuZVAbiWPCUni9XY= github.com/massalabs/station/plugin-kit v0.1.1 h1:KIzEjQX1ukgSSmmnD3EVADN9N7h3kTbFzHI3lal9+Po= @@ -87,14 +86,14 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= +github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE= github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= @@ -141,11 +140,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= -howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= diff --git a/plugin/home/src/App.tsx b/plugin/home/src/App.tsx index 20b0f7cb..3ec8b02c 100644 --- a/plugin/home/src/App.tsx +++ b/plugin/home/src/App.tsx @@ -1,15 +1,16 @@ -import { FiSearch, FiInfo, FiCheckCircle, FiAlertTriangle, FiXCircle, FiClock } from "react-icons/fi"; +import { FiSearch } from "react-icons/fi"; import { useEffect, useState } from "react"; import { UseGenerateTheme } from "deweb-pages/src/hooks/UseGenerateTheme"; -import { ServerStatusResponse, NetworkInfo } from "./types/server"; +import { ServerStatusResponse, NetworkInfo, ServerStatus } from "./types/server"; import { QuickAccessItem } from "./QuickAccessItem"; +import { Status } from "./Status"; const POLLING_INTERVAL = 1000; export default function App() { const [searchQuery, setSearchQuery] = useState(""); const [port, setPort] = useState(null); - const [status, setStatus] = useState<"running" | "stopped" | "starting" | "stopping" | "error" | null>(null); + const [status, setStatus] = useState(null); const [network, setNetwork] = useState(undefined); const [loading, setLoading] = useState(true); const [errorMessage, setErrorMessage] = useState(null); @@ -24,14 +25,32 @@ export default function App() { fetch(`${urlWithoutWeb}/api/server/status`) .then((res) => res.json()) .then((data: ServerStatusResponse) => { - setPort(data.serverPort); - setStatus(data.status); - setNetwork(data.network); - if (data.errorMessage) { - setErrorMessage(data.errorMessage); - } else { - setErrorMessage(null); - } + // Only update state if values have changed to avoid unnecessary re-renders + setPort(prevPort => data.serverPort !== prevPort ? data.serverPort : prevPort); + setStatus(prevStatus => data.status !== prevStatus ? data.status : prevStatus); + + // Deep comparison for network object + setNetwork(prevNetwork => { + const newNetwork = data.network; + if (prevNetwork === newNetwork) return prevNetwork; + if (!prevNetwork && !newNetwork) return prevNetwork; + if (!prevNetwork || !newNetwork) return newNetwork; + if ( + prevNetwork.name === newNetwork.name && + prevNetwork.url === newNetwork.url && + prevNetwork.version === newNetwork.version && + prevNetwork.chainID === newNetwork.chainID + ) { + return prevNetwork; + } + return newNetwork; + }); + + // Only update errorMessage if it changed + setErrorMessage(prevError => { + const newError = data.errorMessage || null; + return prevError !== newError ? newError : prevError; + }); }) .catch(err => console.error("Failed to fetch port:", err)) .finally(() => { @@ -50,45 +69,6 @@ export default function App() { return () => clearInterval(intervalId); }, []); - const getStatusIcon = () => { - if (loading) return ; - - switch (status) { - case "running": - return ; - case "stopped": - return ; - case "starting": - case "stopping": - return ; - case "error": - return ; - default: - return ; - } - }; - - const getStatusText = () => { - if (loading) return "Connecting..."; - - if (status === "error" && errorMessage) { - return `Error: ${errorMessage}`; - } - - if (status === "running" && network) { - return `Connected to ${network.network} ${network.version || ''}`; - } - - const statusMessages = { - running: "Server running", - stopped: "Server stopped", - starting: "Server starting...", - stopping: "Server stopping...", - error: "Server error" - }; - - return statusMessages[status || "stopped"]; - }; const getStatusClass = () => { if (loading) return "bg-blue-100 text-blue-800"; @@ -148,8 +128,7 @@ export default function App() { > {/* Network Status Indicator */}
- {getStatusIcon()} - {getStatusText()} +

Search on DeWeb

diff --git a/plugin/home/src/Status.tsx b/plugin/home/src/Status.tsx new file mode 100644 index 00000000..50d8aedb --- /dev/null +++ b/plugin/home/src/Status.tsx @@ -0,0 +1,73 @@ +import { FiClock, FiCheckCircle, FiXCircle, FiAlertTriangle, FiInfo } from "react-icons/fi"; +import { NetworkInfo, ServerStatus } from "./types/server"; + +type StatusProps = { + network?: NetworkInfo; + loading: boolean; + status: ServerStatus | null; + errorMessage: string | null; +}; + +export const Status = ({ network, loading, status, errorMessage }: StatusProps) => { + + const getStatusIcon = () => { + if (loading) return ; + + switch (status) { + case "running": + return ; + case "stopped": + return ; + case "starting": + case "stopping": + return ; + case "error": + return ; + default: + return ; + } + }; + + const getStatusText = () => { + if (loading) return "Connecting..."; + + if (status === "error" && errorMessage) { + return `Error: ${errorMessage}`; + } + + + const networkTypePrecision = network?.name.toLowerCase().includes("mainnet") || network?.name.toLowerCase().includes("buildnet") ? + "" + : + network?.chainID === 77658377 ? + "(mainnet)" : + "(buildnet)"; + + if (status === "running" && network) { + return `Connected to '${network.name}' node ${networkTypePrecision} ; version: ${network.version || ''}`; + } + + const statusMessages = { + running: "Server running", + stopped: "Server stopped", + starting: "Server starting...", + stopping: "Server stopping...", + error: "Server error" + }; + + return statusMessages[status || "stopped"]; + }; + + return ( +
+ {getStatusIcon()} + {getStatusText()} + {network?.url && ( +
+ {network.url} +
+ )} +
+ ); +}; + diff --git a/plugin/home/src/types/server.ts b/plugin/home/src/types/server.ts index 3aa46c19..ffa6e17a 100644 --- a/plugin/home/src/types/server.ts +++ b/plugin/home/src/types/server.ts @@ -1,7 +1,8 @@ export type ServerStatus = 'running' | 'stopped' | 'starting' | 'stopping' | 'error'; export interface NetworkInfo { - network: string; + name: string; + url: string; version: string; chainID: number; } diff --git a/plugin/int/api/api.go b/plugin/int/api/api.go index 5b81a762..2deca871 100644 --- a/plugin/int/api/api.go +++ b/plugin/int/api/api.go @@ -10,15 +10,18 @@ import ( "github.com/massalabs/deweb-plugin/int/api/html" apiserver "github.com/massalabs/deweb-plugin/int/api/server" "github.com/massalabs/deweb-plugin/int/server" + "github.com/massalabs/deweb-plugin/int/station" "github.com/massalabs/station/pkg/logger" pluginkit "github.com/massalabs/station/plugin-kit" ) type API struct { - apiServer *restapi.Server - pluginAPI *operations.DewebPluginAPI - serverManager *server.ServerManager - configDir string + apiServer *restapi.Server + pluginAPI *operations.DewebPluginAPI + serverManager *server.ServerManager + networkManager *station.NetworkManager + configDir string + configManager *server.ServerConfigManager } // NewAPI creates a new API with the provided plugin directory @@ -36,11 +39,15 @@ func NewAPI(configDir string) *API { logger.Errorf("Failed to create server manager: %v", err) } + configManager := server.NewServerConfigManager(configDir) + return &API{ - apiServer: apiServer, - pluginAPI: dewebAPI, - configDir: configDir, - serverManager: manager, + apiServer: apiServer, + pluginAPI: dewebAPI, + configDir: configDir, + serverManager: manager, + configManager: configManager, + networkManager: station.NewNetworkManager(configManager, manager), } } @@ -51,6 +58,8 @@ func (a *API) Start() { log.Fatalln(err) } + a.networkManager.Stop() + // Shutdown the server manager if it is running if a.serverManager != nil { if err := a.serverManager.Stop(); err != nil { @@ -66,8 +75,6 @@ func (a *API) Start() { a.apiServer.ConfigureAPI() - a.apiServer.SetHandler(webAppMiddleware(a.pluginAPI.Serve(nil))) - listener, err := a.apiServer.HTTPListener() if err != nil { logger.Fatalf("Failed to get HTTP listener: %v", err) @@ -77,12 +84,18 @@ func (a *API) Start() { logger.Fatalf("Failed to register plugin: %v", err) } + // Sync the server network config with the station network config + if _, err = a.networkManager.SyncServerConfNetworkWithStation(); err != nil { + logger.Errorf("Failed to sync server network config with station: %v", err) + } + // Start the server if manager exists if a.serverManager != nil { go func() { if err := a.serverManager.Start(); err != nil && err != server.ErrServerAlreadyRunning { logger.Errorf("Failed to start DeWeb server: %v", err) } + a.networkManager.Start() }() } @@ -101,7 +114,7 @@ func (a *API) configureAPI() { html.AppendEndpoints(a.pluginAPI) if a.serverManager != nil { - apiserver.RegisterHandlers(a.pluginAPI, a.serverManager, a.configDir) + apiserver.RegisterHandlers(a.pluginAPI, a.serverManager, a.configManager) } else { logger.Errorf("Server manager not available for registering handlers") } diff --git a/plugin/int/api/middleware.go b/plugin/int/api/middleware.go deleted file mode 100644 index ebde0ba9..00000000 --- a/plugin/int/api/middleware.go +++ /dev/null @@ -1,29 +0,0 @@ -package api - -import ( - "net/http" - "strings" - - "github.com/go-openapi/runtime" - "github.com/massalabs/deweb-plugin/api/restapi/operations" - "github.com/massalabs/deweb-plugin/int/api/html" -) - -const frontendPrefix = "/web/" - -func webAppMiddleware(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.HasPrefix(r.URL.Path, frontendPrefix) { - params := operations.PluginWebAppParams{ - HTTPRequest: r, - Resource: strings.TrimPrefix(r.URL.Path, frontendPrefix), - } - responder := html.HandleWebApp(params) - responder.WriteResponse(w, runtime.JSONProducer()) - - return - } - - handler.ServeHTTP(w, r) - }) -} diff --git a/plugin/int/api/middleware/middleware.go b/plugin/int/api/middleware/middleware.go new file mode 100644 index 00000000..c14bea3e --- /dev/null +++ b/plugin/int/api/middleware/middleware.go @@ -0,0 +1,47 @@ +package middleware + +import ( + "net/http" + "slices" + "strings" + + "github.com/go-openapi/runtime" + "github.com/massalabs/deweb-plugin/api/restapi/operations" + "github.com/massalabs/deweb-plugin/int/api/html" + stationHttp "github.com/massalabs/station/pkg/http" +) + +const frontendPrefix = "/web/" + +func AllowedDomainsList() []string { + return []string{"station.massa", "localhost", "127.0.0.1"} +} + +func WebAppMiddleware(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, frontendPrefix) { + params := operations.PluginWebAppParams{ + HTTPRequest: r, + Resource: strings.TrimPrefix(r.URL.Path, frontendPrefix), + } + responder := html.HandleWebApp(params) + responder.WriteResponse(w, runtime.JSONProducer()) + + return + } + + handler.ServeHTTP(w, r) + }) +} + +func DomainRestrictionMiddleware(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin := stationHttp.GetRequestOrigin(r) + hostname := stationHttp.ExtractHostname(origin) + if !slices.Contains(AllowedDomainsList(), hostname) { + w.WriteHeader(http.StatusForbidden) + return + } + handler.ServeHTTP(w, r) + }) +} diff --git a/plugin/int/api/server/handlers.go b/plugin/int/api/server/handlers.go index 87c0e3d0..bb14846c 100644 --- a/plugin/int/api/server/handlers.go +++ b/plugin/int/api/server/handlers.go @@ -2,30 +2,24 @@ package server import ( "net/http" - "os" - "path/filepath" "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" "github.com/massalabs/deweb-plugin/api/models" "github.com/massalabs/deweb-plugin/api/restapi/operations" "github.com/massalabs/deweb-plugin/int/server" - "github.com/massalabs/deweb-server/int/api/config" "github.com/massalabs/station/pkg/logger" - "gopkg.in/yaml.v2" ) // RegisterHandlers registers the server-related API handlers -func RegisterHandlers(api *operations.DewebPluginAPI, manager *server.ServerManager, configDir string) { - configPath := filepath.Join(configDir, "deweb_server_config.yaml") - - api.GetServerStatusHandler = operations.GetServerStatusHandlerFunc(handleGetServerStatus(manager)) - api.GetSettingsHandler = operations.GetSettingsHandlerFunc(handleGetSettings(configPath)) - api.UpdateSettingsHandler = operations.UpdateSettingsHandlerFunc(handleUpdateSettings(manager, configPath)) +func RegisterHandlers(api *operations.DewebPluginAPI, manager *server.ServerManager, configManager *server.ServerConfigManager) { + api.GetServerStatusHandler = operations.GetServerStatusHandlerFunc(handleGetServerStatus(manager, configManager)) + api.GetSettingsHandler = operations.GetSettingsHandlerFunc(handleGetSettings(configManager)) + api.UpdateSettingsHandler = operations.UpdateSettingsHandlerFunc(handleUpdateSettings(manager, configManager)) } // handleGetServerStatus returns a handler function for the GET /api/server/status endpoint -func handleGetServerStatus(manager *server.ServerManager) func(operations.GetServerStatusParams) middleware.Responder { +func handleGetServerStatus(manager *server.ServerManager, configManager *server.ServerConfigManager) func(operations.GetServerStatusParams) middleware.Responder { return func(params operations.GetServerStatusParams) middleware.Responder { status := manager.GetStatus() @@ -39,15 +33,17 @@ func handleGetServerStatus(manager *server.ServerManager) func(operations.GetSer } if status == server.StatusRunning { - serverConfig, err := manager.GetConfig() + serverConfig, err := configManager.GetServerConfig() if err != nil { return createErrorResponse(http.StatusInternalServerError, "Failed to get server config") } + logger.Infof("Server config: %+v", serverConfig) - response.Network = &models.ServerStatusNetwork{ - ChainID: serverConfig.NetworkInfos.ChainID, - Network: serverConfig.NetworkInfos.Network, + response.Network = &models.NetworkInfoItem{ + URL: serverConfig.NetworkInfos.NodeURL, + Name: serverConfig.NetworkInfos.Name, Version: serverConfig.NetworkInfos.Version, + ChainID: int64(serverConfig.NetworkInfos.ChainID), } apiPort, err := manager.GetServerPort() @@ -63,9 +59,9 @@ func handleGetServerStatus(manager *server.ServerManager) func(operations.GetSer } // handleGetSettings returns a handler function for the GET /api/settings endpoint -func handleGetSettings(configPath string) func(operations.GetSettingsParams) middleware.Responder { +func handleGetSettings(configManager *server.ServerConfigManager) func(operations.GetSettingsParams) middleware.Responder { return func(params operations.GetSettingsParams) middleware.Responder { - serverConfig, err := config.LoadServerConfig(configPath) + serverConfig, err := configManager.GetServerConfig() if err != nil { return createErrorResponse(http.StatusInternalServerError, "Failed to load server configuration") } @@ -92,14 +88,14 @@ func handleGetSettings(configPath string) func(operations.GetSettingsParams) mid } // handleUpdateSettings returns a handler function for the PUT /api/settings endpoint -func handleUpdateSettings(manager *server.ServerManager, configPath string) func(operations.UpdateSettingsParams) middleware.Responder { +func handleUpdateSettings(manager *server.ServerManager, configManager *server.ServerConfigManager) func(operations.UpdateSettingsParams) middleware.Responder { return func(params operations.UpdateSettingsParams) middleware.Responder { if params.Settings == nil { return createErrorResponse(http.StatusBadRequest, "Settings data is required") } // Load current config - serverConfig, err := config.LoadServerConfig(configPath) + serverConfig, err := configManager.GetServerConfig() if err != nil { return createErrorResponse(http.StatusInternalServerError, "Failed to load server configuration") } @@ -136,26 +132,21 @@ func handleUpdateSettings(manager *server.ServerManager, configPath string) func } } - // Marshal config to YAML - yamlData, err := yaml.Marshal(serverConfig) - if err != nil { - return createErrorResponse(http.StatusInternalServerError, "Failed to marshal configuration") - } - // Stop the server if err := manager.Stop(); err != nil && err != server.ErrServerNotRunning { return createErrorResponse(http.StatusInternalServerError, "Failed to stop server") } - // Write config file - if err := os.WriteFile(configPath, yamlData, 0o644); err != nil { - return createErrorResponse(http.StatusInternalServerError, "Failed to write configuration file") + // Save the new server config + // Stopping the server can take a few seconds so use this time to save the new server config. + if err := configManager.SaveServerConfig(serverConfig); err != nil { + return createErrorResponse(http.StatusInternalServerError, "Failed to save server configuration") } - // Restart the server + // Start the server (it will read the new config on startup) go func() { if err := manager.Start(); err != nil { - logger.Errorf("Failed to restart server: %v", err) + logger.Errorf("Failed to start server: %v", err) } }() diff --git a/plugin/int/server/config.go b/plugin/int/server/config.go index 6f171390..86e6b6e5 100644 --- a/plugin/int/server/config.go +++ b/plugin/int/server/config.go @@ -16,6 +16,51 @@ const ( DefaultConfigFileName = "deweb_server_config.yaml" ) +type ServerConfigManager struct { + serverConfig *config.ServerConfig + configDir string +} + +// NewServerConfigManager creates a new ServerConfigManager +func NewServerConfigManager(configDir string) *ServerConfigManager { + return &ServerConfigManager{ + configDir: configDir, + } +} + +// SaveServerConfig saves a ServerConfig to the given path +func (c *ServerConfigManager) SaveServerConfig(serverConfig *config.ServerConfig) error { + if serverConfig == nil { + return fmt.Errorf("new server config is nil, cannot save it") + } + + yamlConfig := convertToYamlConfig(serverConfig) + logger.Infof("Saving server config: %+v", yamlConfig) + + if err := saveYamlConfig(yamlConfig, getConfigPath(c.configDir)); err != nil { + return fmt.Errorf("failed to save server config: %w", err) + } + + // Update the cached server config + c.serverConfig = serverConfig + + return nil +} + +// GetServerConfig returns the cached server config +func (c *ServerConfigManager) GetServerConfig() (*config.ServerConfig, error) { + if c.serverConfig == nil { + serverConfig, err := loadConfig(getConfigPath(c.configDir)) + if err != nil { + return nil, fmt.Errorf("failed to load server config: %w", err) + } + + c.serverConfig = serverConfig + } + + return c.serverConfig, nil +} + // ensureConfigFileExists makes sure the config file exists, creating it with defaults if needed func ensureConfigFileExists(configDir string) error { configPath := filepath.Join(configDir, DefaultConfigFileName) @@ -69,13 +114,6 @@ func createDefaultYamlConfig(configDir string) config.YamlServerConfig { return yamlConfig } -// SaveServerConfig saves a ServerConfig to the given path -func SaveServerConfig(serverConfig *config.ServerConfig, configPath string) error { - yamlConfig := convertToYamlConfig(serverConfig) - - return saveYamlConfig(yamlConfig, configPath) -} - // convertToYamlConfig converts a ServerConfig to YamlServerConfig func convertToYamlConfig(serverConfig *config.ServerConfig) config.YamlServerConfig { if serverConfig == nil { diff --git a/plugin/int/server/manager.go b/plugin/int/server/manager.go index 91276f9f..61a965cb 100644 --- a/plugin/int/server/manager.go +++ b/plugin/int/server/manager.go @@ -9,7 +9,6 @@ import ( "syscall" "time" - "github.com/massalabs/deweb-server/int/api/config" "github.com/massalabs/station/pkg/logger" "github.com/shirou/gopsutil/v4/process" ) @@ -170,13 +169,17 @@ func (m *ServerManager) Stop() error { // Wait for the process to exit timeout := time.Now().Add(5 * time.Second) + logger.Infof("Waiting for server to stop isRunning: %v", m.isRunning) for time.Now().Before(timeout) && m.isRunning { time.Sleep(100 * time.Millisecond) } + logger.Infof("Server stopped after timeout: %v", m.isRunning) + // If still running after timeout, force kill if m.isRunning { _ = m.kill() + logger.Infof("Server stopped after kill: %v", m.isRunning) } logger.Infof("Server stopped") @@ -186,10 +189,13 @@ func (m *ServerManager) Stop() error { // Restart restarts the server func (m *ServerManager) Restart() error { + logger.Infof("Restarting DeWeb server") if err := m.Stop(); err != nil && err != ErrServerNotRunning { return err } + time.Sleep(10 * time.Second) + return m.Start() } @@ -220,10 +226,6 @@ func (m *ServerManager) GetConfigPath() string { return getConfigPath(m.configDir) } -func (m *ServerManager) GetConfig() (*config.ServerConfig, error) { - return loadConfig(m.GetConfigPath()) -} - // GetLastError returns the last error message func (m *ServerManager) GetLastError() string { m.mu.Lock() diff --git a/plugin/int/station/networkManager.go b/plugin/int/station/networkManager.go new file mode 100644 index 00000000..c03db034 --- /dev/null +++ b/plugin/int/station/networkManager.go @@ -0,0 +1,220 @@ +package station + +import ( + "encoding/json" + "fmt" + "net/http" + "sync" + "time" + + "github.com/massalabs/deweb-plugin/api/models" + "github.com/massalabs/deweb-plugin/int/server" + msConfig "github.com/massalabs/deweb-server/pkg/config" + "github.com/massalabs/station/pkg/logger" +) + +const ( + // StationNetworkEndpoint is the URL for Station's network API + StationNetworkEndpoint = "http://station.massa/network" + + // PollingInterval is how often to poll Station for network information + PollingInterval = 3 * time.Second +) + +// StationNetworkResponse represents the response from Station's network endpoint +type StationNetworkResponse struct { + CurrentNetwork string `json:"currentNetwork"` + AvailableNetworkInfos []models.NetworkInfoItem `json:"availableNetworkInfos"` +} + +// NetworkPoller periodically polls Station for network information +type NetworkManager struct { + mu sync.RWMutex + configManager *server.ServerConfigManager + serverManager *server.ServerManager + stopChan chan struct{} + httpClient *http.Client + isRunning bool +} + +// NewNetworkPoller creates a new network poller instance +func NewNetworkManager(configManager *server.ServerConfigManager, serverManager *server.ServerManager) *NetworkManager { + return &NetworkManager{ + stopChan: make(chan struct{}), + configManager: configManager, + serverManager: serverManager, + httpClient: &http.Client{ + Timeout: 3 * time.Second, + }, + } +} + +// Start begins polling Station for network information +func (np *NetworkManager) Start() { + np.mu.Lock() + if np.isRunning { + np.mu.Unlock() + logger.Infof("Network manager already running") + return + } + np.isRunning = true + np.mu.Unlock() + + logger.Infof("Starting Station network polling (interval: %v)", PollingInterval) + + // Start periodic polling + go func() { + ticker := time.NewTicker(PollingInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + np.pollNetwork() + case <-np.stopChan: + logger.Infof("Station network poller stopped") + return + } + } + }() +} + +// Stop stops the network poller +func (np *NetworkManager) Stop() { + np.mu.Lock() + defer np.mu.Unlock() + + if !np.isRunning { + return + } + + close(np.stopChan) + np.isRunning = false +} + +/* +poll fetches the current network information from Station +If the network used by station has changed, the deweb server is stopped and restarted with the new network url. +*/ +func (np *NetworkManager) pollNetwork() { + response, err := np.fetchStationNetwork() + if err != nil { + logger.Errorf("Failed to fetch station network: %v", err) + return + } + + currentConfig, err := np.configManager.GetServerConfig() + if err != nil { + logger.Errorf("Failed to get server config: %v", err) + return + } + + // if the network on station has changed, update the server config + if response.URL != currentConfig.NetworkInfos.NodeURL || response.Name != currentConfig.NetworkInfos.Name { + logger.Infof( + "Changing massa node configuration from '%s' (%s) to '%s' (%s, the node currently used by station)", + currentConfig.NetworkInfos.Name, + currentConfig.NetworkInfos.NodeURL, + response.Name, + response.URL, + ) + + // stop the deweb server + if err := np.serverManager.Stop(); err != nil && err != server.ErrServerNotRunning { + logger.Errorf("Failed to stop server: %v", err) + return + } + + // update the server config with the new network url + // Stopping the server can take a few seconds so use this time to update the server config. + conf := *currentConfig + conf.NetworkInfos = msConfig.NetworkInfos{ + NodeURL: response.URL, + Name: response.Name, + Version: response.Version, + ChainID: uint64(response.ChainID), + } + if err := np.configManager.SaveServerConfig(&conf); err != nil { + logger.Errorf("Failed to save server config: %v", err) + return + } + + // start the server with the new network url + if err := np.serverManager.Start(); err != nil { + logger.Errorf("Failed to start server: %v", err) + return + } + } +} + +/* +Retrieve current network information from station and update the deweb +server config file if the network is not the same. +*/ +func (np *NetworkManager) SyncServerConfNetworkWithStation() (bool, error) { + response, err := np.fetchStationNetwork() + if err != nil { + return false, err + } + + currentConfig, err := np.configManager.GetServerConfig() + if err != nil { + return false, err + } + + // if the network on station has changed, update the server config + if response.URL != currentConfig.NetworkInfos.NodeURL || response.Name != currentConfig.NetworkInfos.Name { + logger.Infof( + "Changing massa node configuration from '%s' (%s) to '%s' (%s, the node currently used by station)", + currentConfig.NetworkInfos.Name, + currentConfig.NetworkInfos.NodeURL, + response.Name, + response.URL, + ) + + // update the server config with the new network url + conf := *currentConfig + conf.NetworkInfos = msConfig.NetworkInfos{ + NodeURL: response.URL, + Name: response.Name, + Version: response.Version, + ChainID: uint64(response.ChainID), + } + if err := np.configManager.SaveServerConfig(&conf); err != nil { + return false, err + } + return true, nil + } + + return false, nil +} + +// fetchStationNetwork fetches network information from Station +func (np *NetworkManager) fetchStationNetwork() (*models.NetworkInfoItem, error) { + resp, err := np.httpClient.Get(StationNetworkEndpoint) + if err != nil { + return nil, fmt.Errorf("failed to fetch Station network: %w", err) + } + defer func() { + if cerr := resp.Body.Close(); cerr != nil { + logger.Errorf("failed to close station network fetch response body: %v", cerr) + } + }() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("station API returned status %d", resp.StatusCode) + } + + var networkResponse StationNetworkResponse + if err := json.NewDecoder(resp.Body).Decode(&networkResponse); err != nil { + return nil, fmt.Errorf("failed to decode Station network response: %w", err) + } + + for _, network := range networkResponse.AvailableNetworkInfos { + if network.Name == networkResponse.CurrentNetwork { + return &network, nil + } + } + + return nil, fmt.Errorf("current network %s not found in available networks", networkResponse.CurrentNetwork) +} diff --git a/server/int/api/config/config.go b/server/int/api/config/config.go index 26a1e411..25d51781 100644 --- a/server/int/api/config/config.go +++ b/server/int/api/config/config.go @@ -6,7 +6,6 @@ import ( "github.com/massalabs/deweb-server/int/utils" pkgConfig "github.com/massalabs/deweb-server/pkg/config" - msConfig "github.com/massalabs/station/int/config" "github.com/massalabs/station/pkg/logger" yaml "gopkg.in/yaml.v2" ) @@ -20,7 +19,7 @@ const ( type ServerConfig struct { Domain string APIPort int - NetworkInfos msConfig.NetworkInfos + NetworkInfos pkgConfig.NetworkInfos AllowList []string BlockList []string MiscPublicInfoJson interface{} diff --git a/server/int/api/handlers.go b/server/int/api/handlers.go index c316d0c7..912e7620 100644 --- a/server/int/api/handlers.go +++ b/server/int/api/handlers.go @@ -52,7 +52,7 @@ func (dI *dewebInfo) Handle(params operations.GetDeWebInfoParams) middleware.Res Version: config.Version, Misc: dI.conf.MiscPublicInfoJson, Network: &models.DeWebInfoNetwork{ - Network: dI.conf.NetworkInfos.Network, + Network: dI.conf.NetworkInfos.Name, Version: dI.conf.NetworkInfos.Version, ChainID: int64(dI.conf.NetworkInfos.ChainID), }, diff --git a/server/int/api/middlewares.go b/server/int/api/middlewares.go index cc238111..d18ef2bc 100644 --- a/server/int/api/middlewares.go +++ b/server/int/api/middlewares.go @@ -9,11 +9,11 @@ import ( "github.com/massalabs/deweb-server/int/api/config" "github.com/massalabs/deweb-server/pkg/cache" + msConfig "github.com/massalabs/deweb-server/pkg/config" "github.com/massalabs/deweb-server/pkg/mns" mnscache "github.com/massalabs/deweb-server/pkg/mns/cache" "github.com/massalabs/deweb-server/pkg/webmanager" mwUtils "github.com/massalabs/station-massa-wallet/pkg/utils" - msConfig "github.com/massalabs/station/int/config" "github.com/massalabs/station/pkg/logger" ) diff --git a/server/pkg/config/network.go b/server/pkg/config/network.go index 68ab4d92..9bf255a7 100644 --- a/server/pkg/config/network.go +++ b/server/pkg/config/network.go @@ -3,7 +3,6 @@ package config import ( "fmt" - msConfig "github.com/massalabs/station/int/config" "github.com/massalabs/station/pkg/logger" "github.com/massalabs/station/pkg/node" ) @@ -16,18 +15,25 @@ const ( BuildnetChainID = 77658366 ) -func NewNetworkConfig(NodeURL string) (msConfig.NetworkInfos, error) { +type NetworkInfos struct { + Name string + NodeURL string + Version string + ChainID uint64 +} + +func NewNetworkConfig(NodeURL string) (NetworkInfos, error) { client := node.NewClient(NodeURL) status, err := node.Status(client) if err != nil { - return msConfig.NetworkInfos{}, fmt.Errorf("unable to get node status: %w", err) + return NetworkInfos{}, fmt.Errorf("unable to get node status: %w", err) } chainID, networkName := getChainIDAndNetworkName(status) nodeVersion := getNodeVersion(status) - return msConfig.NetworkInfos{ - Network: networkName, + return NetworkInfos{ + Name: networkName, NodeURL: NodeURL, Version: nodeVersion, ChainID: chainID, diff --git a/server/pkg/mns/mns.go b/server/pkg/mns/mns.go index 30050f68..009759d0 100644 --- a/server/pkg/mns/mns.go +++ b/server/pkg/mns/mns.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - msConfig "github.com/massalabs/station/int/config" + msConfig "github.com/massalabs/deweb-server/pkg/config" "github.com/massalabs/station/pkg/convert" "github.com/massalabs/station/pkg/logger" "github.com/massalabs/station/pkg/node" diff --git a/server/pkg/mns/mns_test.go b/server/pkg/mns/mns_test.go index c15731a2..f285b186 100644 --- a/server/pkg/mns/mns_test.go +++ b/server/pkg/mns/mns_test.go @@ -3,7 +3,7 @@ package mns import ( "testing" - msConfig "github.com/massalabs/station/int/config" + msConfig "github.com/massalabs/deweb-server/pkg/config" ) func TestGetSCAddress(t *testing.T) { diff --git a/server/pkg/webmanager/manager.go b/server/pkg/webmanager/manager.go index 818f1c3c..290e8f51 100644 --- a/server/pkg/webmanager/manager.go +++ b/server/pkg/webmanager/manager.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/massalabs/deweb-server/pkg/cache" + msConfig "github.com/massalabs/deweb-server/pkg/config" "github.com/massalabs/deweb-server/pkg/website" - msConfig "github.com/massalabs/station/int/config" "github.com/massalabs/station/pkg/logger" ) diff --git a/server/pkg/website/read.go b/server/pkg/website/read.go index 1b4a1b56..8cc1cc84 100644 --- a/server/pkg/website/read.go +++ b/server/pkg/website/read.go @@ -9,8 +9,8 @@ import ( "time" "github.com/massalabs/deweb-server/int/api/config" + msConfig "github.com/massalabs/deweb-server/pkg/config" "github.com/massalabs/deweb-server/pkg/website/storagekeys" - msConfig "github.com/massalabs/station/int/config" "github.com/massalabs/station/pkg/convert" "github.com/massalabs/station/pkg/logger" "github.com/massalabs/station/pkg/node"