From 7231b043ec406eb923048de0bfcf917abfbb4db9 Mon Sep 17 00:00:00 2001 From: Kschappacher <56745262+Kschappacher@users.noreply.github.com> Date: Wed, 23 Nov 2022 12:14:52 -0500 Subject: [PATCH 01/11] add option to allow partial start of robot with invalid configs --- config/config.go | 35 ++++-- config/config_test.go | 187 ++++++++++++++++++++++++++++++++- config/reader_ext_test.go | 2 +- robot/impl/local_robot_test.go | 34 ++++++ robot/impl/resource_manager.go | 15 +++ 5 files changed, 260 insertions(+), 13 deletions(-) diff --git a/config/config.go b/config/config.go index 57dc33ef7d7..ef023182b57 100644 --- a/config/config.go +++ b/config/config.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/edaniels/golog" "github.com/pkg/errors" "go.viam.com/utils" "go.viam.com/utils/pexec" @@ -27,8 +28,7 @@ type Config struct { Services []Service `json:"services,omitempty"` Network NetworkConfig `json:"network"` Auth AuthConfig `json:"auth"` - - Debug bool `json:"debug,omitempty"` + Debug bool `json:"debug,omitempty"` ConfigFilePath string `json:"-"` @@ -46,6 +46,10 @@ type Config struct { // If false, it's for creating a robot via the RDK library. This is helpful for // error messages that can indicate flags/config fields to use. FromCommand bool `json:"-"` + + // DisablepartialStart ensures that a robot will only start when all the components, + // services, and remotes pass config validation. This value is false by default + DisablePartialStart bool `json:"disablepartialstart"` } // Ensure ensures all parts of the config are valid. @@ -58,30 +62,45 @@ func (c *Config) Ensure(fromCloud bool) error { for idx := 0; idx < len(c.Remotes); idx++ { if err := c.Remotes[idx].Validate(fmt.Sprintf("%s.%d", "remotes", idx)); err != nil { - return err + if c.DisablePartialStart { + return err + } + golog.Global().Error(err) } } for idx := 0; idx < len(c.Components); idx++ { dependsOn, err := c.Components[idx].Validate(fmt.Sprintf("%s.%d", "components", idx)) if err != nil { - return errors.Errorf("error validating component %s: %s", c.Components[idx].Name, err) + fullErr := errors.Errorf("error validating component %s: %s", c.Components[idx].Name, err) + if c.DisablePartialStart { + return fullErr + } + golog.Global().Error(fullErr) + } else { + c.Components[idx].ImplicitDependsOn = dependsOn } - c.Components[idx].ImplicitDependsOn = dependsOn } for idx := 0; idx < len(c.Processes); idx++ { if err := c.Processes[idx].Validate(fmt.Sprintf("%s.%d", "processes", idx)); err != nil { - return err + if c.DisablePartialStart { + return err + } + golog.Global().Error(err) } } for idx := 0; idx < len(c.Services); idx++ { dependsOn, err := c.Services[idx].Validate(fmt.Sprintf("%s.%d", "services", idx)) if err != nil { - return err + if c.DisablePartialStart { + return err + } + golog.Global().Error(err) + } else { + c.Services[idx].ImplicitDependsOn = dependsOn } - c.Services[idx].ImplicitDependsOn = dependsOn } if err := c.Network.Validate("network"); err != nil { diff --git a/config/config_test.go b/config/config_test.go index 0ecee3e7025..c1e2dcc25d2 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -162,7 +162,8 @@ func TestConfigEnsure(t *testing.T) { test.That(t, invalidCloud.Ensure(true), test.ShouldBeNil) invalidRemotes := config.Config{ - Remotes: []config.Remote{{}}, + DisablePartialStart: true, + Remotes: []config.Remote{{}}, } err = invalidRemotes.Ensure(false) test.That(t, err, test.ShouldNotBeNil) @@ -176,7 +177,8 @@ func TestConfigEnsure(t *testing.T) { test.That(t, invalidRemotes.Ensure(false), test.ShouldBeNil) invalidComponents := config.Config{ - Components: []config.Component{{}}, + DisablePartialStart: true, + Components: []config.Component{{}}, } err = invalidComponents.Ensure(false) test.That(t, err, test.ShouldNotBeNil) @@ -193,13 +195,15 @@ func TestConfigEnsure(t *testing.T) { c6 := config.Component{Namespace: resource.ResourceNamespaceRDK, Name: "c6"} c7 := config.Component{Namespace: resource.ResourceNamespaceRDK, Name: "c7", DependsOn: []string{"c6", "c4"}} components := config.Config{ - Components: []config.Component{c7, c6, c5, c3, c4, c1, c2}, + DisablePartialStart: true, + Components: []config.Component{c7, c6, c5, c3, c4, c1, c2}, } err = components.Ensure(false) test.That(t, err, test.ShouldBeNil) invalidProcesses := config.Config{ - Processes: []pexec.ProcessConfig{{}}, + DisablePartialStart: true, + Processes: []pexec.ProcessConfig{{}}, } err = invalidProcesses.Ensure(false) test.That(t, err, test.ShouldNotBeNil) @@ -317,6 +321,181 @@ func TestConfigEnsure(t *testing.T) { test.That(t, invalidAuthConfig.Ensure(false), test.ShouldBeNil) } +func TestConfigEnsurePartialStart(t *testing.T) { + var emptyConfig config.Config + test.That(t, emptyConfig.Ensure(false), test.ShouldBeNil) + + invalidCloud := config.Config{ + Cloud: &config.Cloud{}, + } + err := invalidCloud.Ensure(false) + test.That(t, err, test.ShouldNotBeNil) + test.That(t, err.Error(), test.ShouldContainSubstring, `cloud`) + test.That(t, err.Error(), test.ShouldContainSubstring, `"id" is required`) + invalidCloud.Cloud.ID = "some_id" + err = invalidCloud.Ensure(false) + test.That(t, err, test.ShouldNotBeNil) + test.That(t, err.Error(), test.ShouldContainSubstring, `"secret" is required`) + err = invalidCloud.Ensure(true) + test.That(t, err, test.ShouldNotBeNil) + test.That(t, err.Error(), test.ShouldContainSubstring, `"fqdn" is required`) + invalidCloud.Cloud.Secret = "my_secret" + test.That(t, invalidCloud.Ensure(false), test.ShouldBeNil) + test.That(t, invalidCloud.Ensure(true), test.ShouldNotBeNil) + invalidCloud.Cloud.Secret = "" + invalidCloud.Cloud.FQDN = "wooself" + err = invalidCloud.Ensure(true) + test.That(t, err, test.ShouldNotBeNil) + test.That(t, err.Error(), test.ShouldContainSubstring, `"local_fqdn" is required`) + invalidCloud.Cloud.LocalFQDN = "yeeself" + test.That(t, invalidCloud.Ensure(true), test.ShouldBeNil) + + invalidRemotes := config.Config{ + Remotes: []config.Remote{{}}, + } + err = invalidRemotes.Ensure(false) + test.That(t, err, test.ShouldBeNil) + invalidRemotes.Remotes[0].Name = "foo" + err = invalidRemotes.Ensure(false) + test.That(t, err, test.ShouldBeNil) + invalidRemotes.Remotes[0].Address = "bar" + test.That(t, invalidRemotes.Ensure(false), test.ShouldBeNil) + + invalidComponents := config.Config{ + Components: []config.Component{{}}, + } + err = invalidComponents.Ensure(false) + test.That(t, err, test.ShouldBeNil) + invalidComponents.Components[0].Name = "foo" + test.That(t, invalidComponents.Ensure(false), test.ShouldBeNil) + + c1 := config.Component{Namespace: resource.ResourceNamespaceRDK, Name: "c1"} + c2 := config.Component{Namespace: resource.ResourceNamespaceRDK, Name: "c2", DependsOn: []string{"c1"}} + c3 := config.Component{Namespace: resource.ResourceNamespaceRDK, Name: "c3", DependsOn: []string{"c1", "c2"}} + c4 := config.Component{Namespace: resource.ResourceNamespaceRDK, Name: "c4", DependsOn: []string{"c1", "c3"}} + c5 := config.Component{Namespace: resource.ResourceNamespaceRDK, Name: "c5", DependsOn: []string{"c2", "c4"}} + c6 := config.Component{Namespace: resource.ResourceNamespaceRDK, Name: "c6"} + c7 := config.Component{Namespace: resource.ResourceNamespaceRDK, Name: "c7", DependsOn: []string{"c6", "c4"}} + components := config.Config{ + Components: []config.Component{c7, c6, c5, c3, c4, c1, c2}, + } + err = components.Ensure(false) + test.That(t, err, test.ShouldBeNil) + + invalidProcesses := config.Config{ + Processes: []pexec.ProcessConfig{{}}, + } + err = invalidProcesses.Ensure(false) + test.That(t, err, test.ShouldBeNil) + invalidProcesses.Processes[0].Name = "foo" + test.That(t, invalidProcesses.Ensure(false), test.ShouldBeNil) + + invalidNetwork := config.Config{ + Network: config.NetworkConfig{ + NetworkConfigData: config.NetworkConfigData{ + TLSCertFile: "hey", + }, + }, + } + err = invalidNetwork.Ensure(false) + test.That(t, err, test.ShouldNotBeNil) + test.That(t, err.Error(), test.ShouldContainSubstring, `network`) + test.That(t, err.Error(), test.ShouldContainSubstring, `both tls`) + + invalidNetwork.Network.TLSCertFile = "" + invalidNetwork.Network.TLSKeyFile = "hey" + err = invalidNetwork.Ensure(false) + test.That(t, err, test.ShouldNotBeNil) + test.That(t, err.Error(), test.ShouldContainSubstring, `network`) + test.That(t, err.Error(), test.ShouldContainSubstring, `both tls`) + + invalidNetwork.Network.TLSCertFile = "dude" + test.That(t, invalidNetwork.Ensure(false), test.ShouldBeNil) + + invalidNetwork.Network.TLSCertFile = "" + invalidNetwork.Network.TLSKeyFile = "" + test.That(t, invalidNetwork.Ensure(false), test.ShouldBeNil) + + invalidNetwork.Network.BindAddress = "woop" + err = invalidNetwork.Ensure(false) + test.That(t, err, test.ShouldNotBeNil) + test.That(t, err.Error(), test.ShouldContainSubstring, `bind_address`) + test.That(t, err.Error(), test.ShouldContainSubstring, `missing port`) + + invalidNetwork.Network.BindAddress = "woop" + invalidNetwork.Network.Listener = &net.TCPListener{} + err = invalidNetwork.Ensure(false) + test.That(t, err, test.ShouldNotBeNil) + test.That(t, err.Error(), test.ShouldContainSubstring, `only set one of`) + + invalidAuthConfig := config.Config{ + Auth: config.AuthConfig{}, + } + test.That(t, invalidAuthConfig.Ensure(false), test.ShouldBeNil) + + invalidAuthConfig.Auth.Handlers = []config.AuthHandlerConfig{ + {Type: rpc.CredentialsTypeAPIKey}, + } + err = invalidAuthConfig.Ensure(false) + test.That(t, err, test.ShouldNotBeNil) + test.That(t, err.Error(), test.ShouldContainSubstring, `auth.handlers.0`) + test.That(t, err.Error(), test.ShouldContainSubstring, `required`) + test.That(t, err.Error(), test.ShouldContainSubstring, `key`) + + validAPIKeyHandler := config.AuthHandlerConfig{ + Type: rpc.CredentialsTypeAPIKey, + Config: config.AttributeMap{ + "key": "foo", + }, + } + + invalidAuthConfig.Auth.Handlers = []config.AuthHandlerConfig{ + validAPIKeyHandler, + validAPIKeyHandler, + } + err = invalidAuthConfig.Ensure(false) + test.That(t, err, test.ShouldNotBeNil) + test.That(t, err.Error(), test.ShouldContainSubstring, `auth.handlers.1`) + test.That(t, err.Error(), test.ShouldContainSubstring, `duplicate`) + test.That(t, err.Error(), test.ShouldContainSubstring, `api-key`) + + invalidAuthConfig.Auth.Handlers = []config.AuthHandlerConfig{ + validAPIKeyHandler, + {Type: "unknown"}, + } + err = invalidAuthConfig.Ensure(false) + test.That(t, err, test.ShouldNotBeNil) + test.That(t, err.Error(), test.ShouldContainSubstring, `auth.handlers.1`) + test.That(t, err.Error(), test.ShouldContainSubstring, `do not know how`) + test.That(t, err.Error(), test.ShouldContainSubstring, `unknown`) + + invalidAuthConfig.Auth.Handlers = []config.AuthHandlerConfig{ + validAPIKeyHandler, + } + test.That(t, invalidAuthConfig.Ensure(false), test.ShouldBeNil) + + validAPIKeyHandler.Config = config.AttributeMap{ + "keys": []string{}, + } + invalidAuthConfig.Auth.Handlers = []config.AuthHandlerConfig{ + validAPIKeyHandler, + } + err = invalidAuthConfig.Ensure(false) + test.That(t, err, test.ShouldNotBeNil) + test.That(t, err.Error(), test.ShouldContainSubstring, `auth.handlers.0`) + test.That(t, err.Error(), test.ShouldContainSubstring, `required`) + test.That(t, err.Error(), test.ShouldContainSubstring, `key`) + + validAPIKeyHandler.Config = config.AttributeMap{ + "keys": []string{"one", "two"}, + } + invalidAuthConfig.Auth.Handlers = []config.AuthHandlerConfig{ + validAPIKeyHandler, + } + + test.That(t, invalidAuthConfig.Ensure(false), test.ShouldBeNil) +} + func TestCopyOnlyPublicFields(t *testing.T) { t.Run("copy sample config", func(t *testing.T) { content, err := os.ReadFile("data/robot.json") diff --git a/config/reader_ext_test.go b/config/reader_ext_test.go index e37e689cd24..0352e29c4af 100644 --- a/config/reader_ext_test.go +++ b/config/reader_ext_test.go @@ -36,7 +36,7 @@ func TestFromReaderValidate(t *testing.T) { test.That(t, err, test.ShouldNotBeNil) test.That(t, err.Error(), test.ShouldContainSubstring, `"id" is required`) - _, err = config.FromReader(context.Background(), "somepath", strings.NewReader(`{"components": [{}]}`), logger) + _, err = config.FromReader(context.Background(), "somepath", strings.NewReader(`{"disablepartialstart":true,"components": [{}]}`), logger) test.That(t, err, test.ShouldNotBeNil) test.That(t, err.Error(), test.ShouldContainSubstring, `components.0`) test.That(t, err.Error(), test.ShouldContainSubstring, `"name" is required`) diff --git a/robot/impl/local_robot_test.go b/robot/impl/local_robot_test.go index 80a6e38d70d..07a78842f84 100644 --- a/robot/impl/local_robot_test.go +++ b/robot/impl/local_robot_test.go @@ -18,6 +18,7 @@ import ( "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" + // registers all components. commonpb "go.viam.com/api/common/v1" armpb "go.viam.com/api/component/arm/v1" @@ -1393,6 +1394,39 @@ func TestGetRemoteResourceAndGrandFather(t *testing.T) { test.That(t, err, test.ShouldBeError, "more that one remote resources with name \"pieceArm\" exists") } +func TestValidationErrorOnReconfigure(t *testing.T) { + logger := golog.NewTestLogger(t) + ctx := context.Background() + + badConfig := &config.Config{ + Components: []config.Component{ + { + Namespace: resource.ResourceNamespaceRDK, + Name: "", + Type: base.SubtypeName, + Model: "random", + }, + }, + Cloud: &config.Cloud{}, + } + r, err := robotimpl.New(ctx, badConfig, logger) + defer func() { + test.That(t, r.Close(context.Background()), test.ShouldBeNil) + }() + test.That(t, err, test.ShouldBeNil) + test.That(t, r, test.ShouldNotBeNil) + + name := base.Named("") + noBase, err := r.ResourceByName(name) + test.That( + t, + err, + test.ShouldBeError, + errors.Wrapf(utils.NewConfigValidationFieldRequiredError("", "name"), "resource %q not available", name), + ) + test.That(t, noBase, test.ShouldBeNil) +} + func TestResourceStartsOnReconfigure(t *testing.T) { logger := golog.NewTestLogger(t) ctx := context.Background() diff --git a/robot/impl/resource_manager.go b/robot/impl/resource_manager.go index 200632c38ae..202a37e633a 100644 --- a/robot/impl/resource_manager.go +++ b/robot/impl/resource_manager.go @@ -377,6 +377,11 @@ func (manager *resourceManager) completeConfig( } manager.logger.Debugw("we are now handling the resource", "resource", r) if c, ok := wrap.config.(config.Component); ok { + _, err := c.Validate("") + if err != nil { + wrap.err = err + continue + } iface, err := manager.processComponent(ctx, r, c, wrap.real, robot) if err != nil { manager.logger.Errorw("error building component", "resource", c.ResourceName(), "model", c.Model, "error", err) @@ -385,6 +390,11 @@ func (manager *resourceManager) completeConfig( } manager.resources.AddNode(r, iface) } else if s, ok := wrap.config.(config.Service); ok { + _, err := s.Validate("") + if err != nil { + wrap.err = err + continue + } iface, err := manager.processService(ctx, s, wrap.real, robot) if err != nil { manager.logger.Errorw("error building service", "resource", s.ResourceName(), "model", s.Model, "error", err) @@ -393,6 +403,11 @@ func (manager *resourceManager) completeConfig( } manager.resources.AddNode(r, iface) } else if rc, ok := wrap.config.(config.Remote); ok { + err := rc.Validate("") + if err != nil { + wrap.err = err + continue + } rr, err := manager.processRemote(ctx, rc) if err != nil { manager.logger.Errorw("error connecting to remote", "remote", rc.Name, "error", err) From 7795e54522547a7b659ee67dbe87e573e8c7430c Mon Sep 17 00:00:00 2001 From: Kschappacher <56745262+Kschappacher@users.noreply.github.com> Date: Wed, 23 Nov 2022 12:40:47 -0500 Subject: [PATCH 02/11] lint --- robot/impl/local_robot_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/robot/impl/local_robot_test.go b/robot/impl/local_robot_test.go index 07a78842f84..fe326f73851 100644 --- a/robot/impl/local_robot_test.go +++ b/robot/impl/local_robot_test.go @@ -18,7 +18,6 @@ import ( "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" - // registers all components. commonpb "go.viam.com/api/common/v1" armpb "go.viam.com/api/component/arm/v1" From f0d92cc4c06ca143a6ad33b038c7da05efe8df47 Mon Sep 17 00:00:00 2001 From: Kschappacher <56745262+Kschappacher@users.noreply.github.com> Date: Tue, 29 Nov 2022 11:50:07 -0500 Subject: [PATCH 03/11] add tests and resolve comments --- config/config.go | 10 +++---- config/proto_conversions.go | 2 +- robot/impl/local_robot_test.go | 46 +++++++++++++++++++++++++----- robot/impl/resource_manager.go | 6 ++-- utils/errors.go | 2 +- web/runtime-shared/static/main.css | 2 +- 6 files changed, 50 insertions(+), 18 deletions(-) diff --git a/config/config.go b/config/config.go index ef023182b57..e8fdbcfad3f 100644 --- a/config/config.go +++ b/config/config.go @@ -49,7 +49,7 @@ type Config struct { // DisablepartialStart ensures that a robot will only start when all the components, // services, and remotes pass config validation. This value is false by default - DisablePartialStart bool `json:"disablepartialstart"` + DisablePartialStart bool `json:"disable_partial_start"` } // Ensure ensures all parts of the config are valid. @@ -65,7 +65,7 @@ func (c *Config) Ensure(fromCloud bool) error { if c.DisablePartialStart { return err } - golog.Global().Error(err) + golog.Global().Debug(errors.Wrap(err, "Remote config error, starting robot without remote: "+c.Remotes[idx].Name)) } } @@ -76,7 +76,7 @@ func (c *Config) Ensure(fromCloud bool) error { if c.DisablePartialStart { return fullErr } - golog.Global().Error(fullErr) + golog.Global().Debug(errors.Wrap(err, "Component config error, starting robot without component: "+c.Components[idx].Name)) } else { c.Components[idx].ImplicitDependsOn = dependsOn } @@ -87,7 +87,7 @@ func (c *Config) Ensure(fromCloud bool) error { if c.DisablePartialStart { return err } - golog.Global().Error(err) + golog.Global().Debug(errors.Wrap(err, "Process config error, starting robot without process: "+c.Processes[idx].Name)) } } @@ -97,7 +97,7 @@ func (c *Config) Ensure(fromCloud bool) error { if c.DisablePartialStart { return err } - golog.Global().Error(err) + golog.Global().Debug(errors.Wrap(err, "Service config error, starting robot without service: "+c.Services[idx].Name)) } else { c.Services[idx].ImplicitDependsOn = dependsOn } diff --git a/config/proto_conversions.go b/config/proto_conversions.go index 25db41c1a4d..a7e9693f33c 100644 --- a/config/proto_conversions.go +++ b/config/proto_conversions.go @@ -499,7 +499,7 @@ func CloudConfigFromProto(proto *pb.CloudConfig) (*Cloud, error) { return &Cloud{ ID: proto.GetId(), Secret: proto.GetSecret(), - //nolint:staticcheck + LocationSecret: proto.GetLocationSecret(), LocationSecrets: locationSecrets, ManagedBy: proto.GetManagedBy(), diff --git a/robot/impl/local_robot_test.go b/robot/impl/local_robot_test.go index fe326f73851..ab3ef94c8a0 100644 --- a/robot/impl/local_robot_test.go +++ b/robot/impl/local_robot_test.go @@ -52,6 +52,7 @@ import ( "go.viam.com/rdk/services/datamanager" "go.viam.com/rdk/services/datamanager/builtin" "go.viam.com/rdk/services/motion" + "go.viam.com/rdk/services/navigation" _ "go.viam.com/rdk/services/register" "go.viam.com/rdk/services/sensors" "go.viam.com/rdk/services/vision" @@ -1393,6 +1394,14 @@ func TestGetRemoteResourceAndGrandFather(t *testing.T) { test.That(t, err, test.ShouldBeError, "more that one remote resources with name \"pieceArm\" exists") } +type attrs struct { + Thing string +} + +func (attrs) Validate(path string) error { + return errors.New("fail") +} + func TestValidationErrorOnReconfigure(t *testing.T) { logger := golog.NewTestLogger(t) ctx := context.Background() @@ -1400,12 +1409,26 @@ func TestValidationErrorOnReconfigure(t *testing.T) { badConfig := &config.Config{ Components: []config.Component{ { - Namespace: resource.ResourceNamespaceRDK, - Name: "", - Type: base.SubtypeName, - Model: "random", + Namespace: resource.ResourceNamespaceRDK, + Name: "test", + Type: base.SubtypeName, + Model: "random", + ConvertedAttributes: attrs{}, }, }, + Services: []config.Service{ + { + Namespace: resource.ResourceNamespaceRDK, + Name: "fake1", + Type: "navigation", + ConvertedAttributes: attrs{}, + }, + }, + Remotes: []config.Remote{{ + Name: "remote", + Insecure: true, + Address: "", + }}, Cloud: &config.Cloud{}, } r, err := robotimpl.New(ctx, badConfig, logger) @@ -1414,16 +1437,25 @@ func TestValidationErrorOnReconfigure(t *testing.T) { }() test.That(t, err, test.ShouldBeNil) test.That(t, r, test.ShouldNotBeNil) - - name := base.Named("") + // Test Component Error + name := base.Named("test") noBase, err := r.ResourceByName(name) test.That( t, err, test.ShouldBeError, - errors.Wrapf(utils.NewConfigValidationFieldRequiredError("", "name"), "resource %q not available", name), + rutils.NewResourceNotAvailableError(name, errors.New("Config validation error found in component: test: fail")), ) test.That(t, noBase, test.ShouldBeNil) + // Test Service Error + s, err := r.ResourceByName(navigation.Named("fake1")) + test.That(t, s, test.ShouldBeNil) + errTmp := errors.New("resource \"fake1\" not available: Config validation error found in service: fake1: fail") + test.That(t, err, test.ShouldBeError, errTmp) + // Test Remote Error + rem, ok := r.RemoteByName("remote") + test.That(t, rem, test.ShouldBeNil) + test.That(t, ok, test.ShouldBeFalse) } func TestResourceStartsOnReconfigure(t *testing.T) { diff --git a/robot/impl/resource_manager.go b/robot/impl/resource_manager.go index 202a37e633a..8fbf0042eef 100644 --- a/robot/impl/resource_manager.go +++ b/robot/impl/resource_manager.go @@ -379,7 +379,7 @@ func (manager *resourceManager) completeConfig( if c, ok := wrap.config.(config.Component); ok { _, err := c.Validate("") if err != nil { - wrap.err = err + wrap.err = errors.Wrap(err, "Config validation error found in component: "+c.Name) continue } iface, err := manager.processComponent(ctx, r, c, wrap.real, robot) @@ -392,7 +392,7 @@ func (manager *resourceManager) completeConfig( } else if s, ok := wrap.config.(config.Service); ok { _, err := s.Validate("") if err != nil { - wrap.err = err + wrap.err = errors.Wrap(err, "Config validation error found in service: "+s.Name) continue } iface, err := manager.processService(ctx, s, wrap.real, robot) @@ -405,7 +405,7 @@ func (manager *resourceManager) completeConfig( } else if rc, ok := wrap.config.(config.Remote); ok { err := rc.Validate("") if err != nil { - wrap.err = err + wrap.err = errors.Wrap(err, "Config validation error found in remote: "+rc.Name) continue } rr, err := manager.processRemote(ctx, rc) diff --git a/utils/errors.go b/utils/errors.go index 430b4b0a4f4..e9cc7b5a40e 100644 --- a/utils/errors.go +++ b/utils/errors.go @@ -13,7 +13,7 @@ func NewResourceNotFoundError(name resource.Name) error { // NewResourceNotAvailableError is used when a resource is not available because of some error. func NewResourceNotAvailableError(name resource.Name, err error) error { - return errors.Wrapf(err, "resource %q not available", name) + return errors.Wrapf(err, "resource %q not available", name.Name) } // NewRemoteResourceClashError is used when you are more than one resource with the same name exist. diff --git a/web/runtime-shared/static/main.css b/web/runtime-shared/static/main.css index 2d50c1df213..6fdf1078771 100644 --- a/web/runtime-shared/static/main.css +++ b/web/runtime-shared/static/main.css @@ -1 +1 @@ -@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/space-mono-vietnamese-400-normal.woff2) format("woff2"),url(/space-mono-all-400-normal.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/space-mono-latin-ext-400-normal.woff2) format("woff2"),url(/space-mono-all-400-normal.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/space-mono-latin-400-normal.woff2) format("woff2"),url(/space-mono-all-400-normal.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:400;src:url(/space-mono-vietnamese-400-italic.woff2) format("woff2"),url(/space-mono-all-400-italic.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:400;src:url(/space-mono-latin-ext-400-italic.woff2) format("woff2"),url(/space-mono-all-400-italic.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:400;src:url(/space-mono-latin-400-italic.woff2) format("woff2"),url(/space-mono-all-400-italic.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:700;src:url(/space-mono-vietnamese-700-normal.woff2) format("woff2"),url(/space-mono-all-700-normal.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:700;src:url(/space-mono-latin-ext-700-normal.woff2) format("woff2"),url(/space-mono-all-700-normal.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:700;src:url(/space-mono-latin-700-normal.woff2) format("woff2"),url(/space-mono-all-700-normal.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:700;src:url(/space-mono-vietnamese-700-italic.woff2) format("woff2"),url(/space-mono-all-700-italic.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:700;src:url(/space-mono-latin-ext-700-italic.woff2) format("woff2"),url(/space-mono-all-700-italic.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:700;src:url(/space-mono-latin-700-italic.woff2) format("woff2"),url(/space-mono-all-700-italic.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0px;bottom:0px}.right-0{right:0px}.float-right{float:right}.clear-both{clear:both}.m-0{margin:0}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mt-2{margin-top:.5rem}.mr-4{margin-right:1rem}.mr-2{margin-right:.5rem}.mb-1{margin-bottom:.25rem}.ml-0{margin-left:0}.ml-2{margin-left:.5rem}.block{display:block}.flex{display:flex}.table{display:table}.grid{display:grid}.inline-grid{display:inline-grid}.hidden{display:none}.h-auto{height:auto}.h-fit{height:fit-content}.h-\[30px\]{height:30px}.h-4{height:1rem}.h-full{height:100%}.h-\[250px\]{height:250px}.h-\[400px\]{height:400px}.w-96{width:24rem}.w-full{width:100%}.w-72{width:18rem}.w-64{width:16rem}.w-4{width:1rem}.w-\[8ex\]{width:8ex}.w-48{width:12rem}.w-32{width:8rem}.w-80{width:20rem}.min-w-\[90px\]{min-width:90px}.max-w-full{max-width:100%}.max-w-xs{max-width:20rem}.flex-auto{flex:1 1 auto}.grow{flex-grow:1}.table-auto{table-layout:auto}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.list-disc{list-style-type:disc}.appearance-none{appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-2{gap:.5rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-12{gap:3rem}.gap-3{gap:.75rem}.place-self-end{place-self:end}.self-end{align-self:flex-end}.overflow-auto{overflow:auto}.whitespace-nowrap{white-space:nowrap}.rounded-full{border-radius:9999px}.border{border-width:1px}.border-x{border-left-width:1px;border-right-width:1px}.border-l-4{border-left-width:4px}.border-t-0{border-top-width:0px}.border-b{border-bottom-width:1px}.border-solid{border-style:solid}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity))}.border-black{--tw-border-opacity: 1;border-color:rgb(0 0 0 / var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity: 1;border-color:rgb(107 114 128 / var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.bg-clip-padding{background-clip:padding-box}.stroke-2{stroke-width:2}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-0{padding-top:0;padding-bottom:0}.pl-4{padding-left:1rem}.pb-1{padding-bottom:.25rem}.pr-2{padding-right:.5rem}.pl-2{padding-left:.5rem}.pt-4{padding-top:1rem}.pt-2{padding-top:.5rem}.pl-6{padding-left:1.5rem}.pb-4{padding-bottom:1rem}.pt-7{padding-top:1.75rem}.pr-4{padding-right:1rem}.pb-8{padding-bottom:2rem}.pl-8{padding-left:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.font-normal{font-weight:400}.font-bold{font-weight:700}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-\[\#045681\]{--tw-text-opacity: 1;color:rgb(4 86 129 / var(--tw-text-opacity))}.underline{text-decoration-line:underline}.opacity-50{opacity:.5}.outline-none{outline:2px solid transparent;outline-offset:2px}.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)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-150{transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}*{font-family:Space Mono,monospace}body,table{font-size:14px}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.v-toast--fade-out{animation-name:fadeOut}@keyframes fadeInDown{0%{opacity:0;transform:translate3d(0,-100%,0)}to{opacity:1;transform:none}}.v-toast--fade-in-down{animation-name:fadeInDown}@keyframes fadeInUp{0%{opacity:0;transform:translate3d(0,100%,0)}to{opacity:1;transform:none}}.v-toast--fade-in-up{animation-name:fadeInUp}.fade-enter-active,.fade-leave-active{transition:opacity .15s ease-out}.fade-enter,.fade-leave-to{opacity:0}.v-toast{position:fixed;display:flex;top:0;bottom:0;left:0;right:0;padding:2em;overflow:hidden;z-index:1052;pointer-events:none}.v-toast__item{display:inline-flex;align-items:center;animation-duration:.15s;margin:.5em 0;box-shadow:0 1px 4px #0000001f,0 0 6px #0000000a;pointer-events:auto;opacity:.92;color:#fff;min-height:3em;cursor:pointer;text-transform:uppercase}.v-toast__item--success{background-color:#25933c}.v-toast__item--info,.v-toast__item--warning{background-color:#000}.v-toast__item--error{background-color:#d82323}.v-toast__item--default{background-color:#000}.v-toast__item.v-toast__item--top,.v-toast__item.v-toast__item--bottom{align-self:center}.v-toast__item.v-toast__item--top-right,.v-toast__item.v-toast__item--bottom-right{align-self:flex-end}.v-toast__item.v-toast__item--top-left,.v-toast__item.v-toast__item--bottom-left{align-self:flex-start}.v-toast__text{margin:0;padding:.5em 1em;word-break:break-word}.v-toast.v-toast--top{flex-direction:column}.v-toast.v-toast--bottom{flex-direction:column-reverse}.v-toast.v-toast--custom-parent{position:absolute}@media screen and (max-width: 768px){.v-toast{padding:0;position:fixed!important}}.v-toast__item{opacity:1;min-height:4em}.v-toast__item .v-toast__text{padding:1.5em 1em}.v-toast__item .v-toast__icon{display:block;width:27px;min-width:27px;height:27px;margin-left:1em;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 45.999 45.999'%3e %3cpath fill='%23fff' d='M39.264 6.736c-8.982-8.981-23.545-8.982-32.528 0-8.982 8.982-8.981 23.545 0 32.528 8.982 8.98 23.545 8.981 32.528 0 8.981-8.983 8.98-23.545 0-32.528zM25.999 33a3 3 0 11-6 0V21a3 3 0 116 0v12zm-3.053-17.128c-1.728 0-2.88-1.224-2.844-2.735-.036-1.584 1.116-2.771 2.879-2.771 1.764 0 2.88 1.188 2.917 2.771-.001 1.511-1.152 2.735-2.952 2.735z'/%3e %3c/svg%3e") no-repeat}.v-toast__item.v-toast__item--success .v-toast__icon{background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 52 52'%3e %3cpath fill='%23fff' d='M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329l-16 18a1.997 1.997 0 01-2.745.233l-10-8a2 2 0 012.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 012.99 2.659z'/%3e %3c/svg%3e") no-repeat}.v-toast__item.v-toast__item--error .v-toast__icon{background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 51.976 51.976'%3e %3cpath fill='%23fff' d='M44.373 7.603c-10.137-10.137-26.632-10.138-36.77 0-10.138 10.138-10.137 26.632 0 36.77s26.632 10.138 36.77 0c10.137-10.138 10.137-26.633 0-36.77zm-8.132 28.638a2 2 0 01-2.828 0l-7.425-7.425-7.778 7.778a2 2 0 11-2.828-2.828l7.778-7.778-7.425-7.425a2 2 0 112.828-2.828l7.425 7.425 7.071-7.071a2 2 0 112.828 2.828l-7.071 7.071 7.425 7.425a2 2 0 010 2.828z'/%3e %3c/svg%3e") no-repeat}.v-toast__item.v-toast__item--warning .v-toast__icon{background:url("data:image/svg+xml,%3csvg viewBox='0 0 52 52' xmlns='http://www.w3.org/2000/svg'%3e %3cpath fill='%23fff' d='M49.466 41.26L29.216 6.85c-.69-1.16-1.89-1.85-3.22-1.85-1.32 0-2.53.69-3.21 1.85L2.536 41.26c-.71 1.2-.72 2.64-.03 3.85.68 1.18 1.89 1.89 3.24 1.89h40.51c1.35 0 2.56-.71 3.23-1.89.7-1.21.69-2.65-.02-3.85zm-25.53-21.405h3.381v3.187l-.724 8.92H24.66l-.725-8.92v-3.187zm2.97 17.344a1.712 1.712 0 01-1.267.543c-.491 0-.914-.181-1.268-.543a1.788 1.788 0 01-.531-1.297c0-.502.176-.935.53-1.297a1.712 1.712 0 011.269-.544c.49 0 .914.181 1.268.544s.53.795.53 1.297c0 .503-.176.934-.53 1.297z'/%3e %3c/svg%3e") no-repeat}.placeholder\:text-gray-400::placeholder{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}@media (min-width: 640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}:root{--popper-theme-background-color: #fff;--popper-theme-background-color-hover: #fff;--popper-theme-text-color: #000;--popper-theme-border-color: #000;--popper-theme-border-width: 1px;--popper-theme-border-style: solid;--popper-theme-border-radius: 0px;--popper-theme-padding: 4px;--popper-theme-box-shadow: 0 6px 30px -6px rgba(0, 0, 0, .25)}.subtitle[data-v-177d004e],.subtitle[data-v-dbe09de2]{color:var(--black-70)}#source{position:relative;width:50%;height:50%}h3{margin:.1em;margin-block-end:.1em} +@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/space-mono-vietnamese-400-normal.woff2) format("woff2"),url(/space-mono-all-400-normal.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/space-mono-latin-ext-400-normal.woff2) format("woff2"),url(/space-mono-all-400-normal.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/space-mono-latin-400-normal.woff2) format("woff2"),url(/space-mono-all-400-normal.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:400;src:url(/space-mono-vietnamese-400-italic.woff2) format("woff2"),url(/space-mono-all-400-italic.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:400;src:url(/space-mono-latin-ext-400-italic.woff2) format("woff2"),url(/space-mono-all-400-italic.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:400;src:url(/space-mono-latin-400-italic.woff2) format("woff2"),url(/space-mono-all-400-italic.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:700;src:url(/space-mono-vietnamese-700-normal.woff2) format("woff2"),url(/space-mono-all-700-normal.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:700;src:url(/space-mono-latin-ext-700-normal.woff2) format("woff2"),url(/space-mono-all-700-normal.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:700;src:url(/space-mono-latin-700-normal.woff2) format("woff2"),url(/space-mono-all-700-normal.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:700;src:url(/space-mono-vietnamese-700-italic.woff2) format("woff2"),url(/space-mono-all-700-italic.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:700;src:url(/space-mono-latin-ext-700-italic.woff2) format("woff2"),url(/space-mono-all-700-italic.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:700;src:url(/space-mono-latin-700-italic.woff2) format("woff2"),url(/space-mono-all-700-italic.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0px;bottom:0px}.right-0{right:0px}.float-right{float:right}.clear-both{clear:both}.m-0{margin:0}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mt-2{margin-top:.5rem}.mr-4{margin-right:1rem}.mr-2{margin-right:.5rem}.mb-1{margin-bottom:.25rem}.ml-0{margin-left:0}.ml-2{margin-left:.5rem}.block{display:block}.flex{display:flex}.table{display:table}.grid{display:grid}.inline-grid{display:inline-grid}.hidden{display:none}.h-auto{height:auto}.h-fit{height:fit-content}.h-\[30px\]{height:30px}.h-4{height:1rem}.h-full{height:100%}.h-\[250px\]{height:250px}.h-\[400px\]{height:400px}.w-96{width:24rem}.w-full{width:100%}.w-72{width:18rem}.w-64{width:16rem}.w-4{width:1rem}.w-\[8ex\]{width:8ex}.w-48{width:12rem}.w-32{width:8rem}.w-80{width:20rem}.min-w-\[90px\]{min-width:90px}.max-w-full{max-width:100%}.max-w-xs{max-width:20rem}.flex-auto{flex:1 1 auto}.grow{flex-grow:1}.table-auto{table-layout:auto}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.list-disc{list-style-type:disc}.appearance-none{appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-2{gap:.5rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-12{gap:3rem}.gap-3{gap:.75rem}.place-self-end{place-self:end}.self-end{align-self:flex-end}.overflow-auto{overflow:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.rounded-full{border-radius:9999px}.border{border-width:1px}.border-x{border-left-width:1px;border-right-width:1px}.border-l-4{border-left-width:4px}.border-t-0{border-top-width:0px}.border-b{border-bottom-width:1px}.border-solid{border-style:solid}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity))}.border-black{--tw-border-opacity: 1;border-color:rgb(0 0 0 / var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity: 1;border-color:rgb(107 114 128 / var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.bg-clip-padding{background-clip:padding-box}.stroke-2{stroke-width:2}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-0{padding-top:0;padding-bottom:0}.pl-4{padding-left:1rem}.pb-1{padding-bottom:.25rem}.pr-2{padding-right:.5rem}.pl-2{padding-left:.5rem}.pt-4{padding-top:1rem}.pt-2{padding-top:.5rem}.pl-6{padding-left:1.5rem}.pb-4{padding-bottom:1rem}.pt-7{padding-top:1.75rem}.pr-4{padding-right:1rem}.pb-8{padding-bottom:2rem}.pl-8{padding-left:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.font-normal{font-weight:400}.font-bold{font-weight:700}.capitalize{text-transform:capitalize}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-\[\#045681\]{--tw-text-opacity: 1;color:rgb(4 86 129 / var(--tw-text-opacity))}.underline{text-decoration-line:underline}.opacity-50{opacity:.5}.outline-none{outline:2px solid transparent;outline-offset:2px}.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)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-150{transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}*{font-family:Space Mono,monospace}body,table{font-size:14px}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.v-toast--fade-out{animation-name:fadeOut}@keyframes fadeInDown{0%{opacity:0;transform:translate3d(0,-100%,0)}to{opacity:1;transform:none}}.v-toast--fade-in-down{animation-name:fadeInDown}@keyframes fadeInUp{0%{opacity:0;transform:translate3d(0,100%,0)}to{opacity:1;transform:none}}.v-toast--fade-in-up{animation-name:fadeInUp}.fade-enter-active,.fade-leave-active{transition:opacity .15s ease-out}.fade-enter,.fade-leave-to{opacity:0}.v-toast{position:fixed;display:flex;top:0;bottom:0;left:0;right:0;padding:2em;overflow:hidden;z-index:1052;pointer-events:none}.v-toast__item{display:inline-flex;align-items:center;animation-duration:.15s;margin:.5em 0;box-shadow:0 1px 4px #0000001f,0 0 6px #0000000a;pointer-events:auto;opacity:.92;color:#fff;min-height:3em;cursor:pointer;text-transform:uppercase}.v-toast__item--success{background-color:#25933c}.v-toast__item--info,.v-toast__item--warning{background-color:#000}.v-toast__item--error{background-color:#d82323}.v-toast__item--default{background-color:#000}.v-toast__item.v-toast__item--top,.v-toast__item.v-toast__item--bottom{align-self:center}.v-toast__item.v-toast__item--top-right,.v-toast__item.v-toast__item--bottom-right{align-self:flex-end}.v-toast__item.v-toast__item--top-left,.v-toast__item.v-toast__item--bottom-left{align-self:flex-start}.v-toast__text{margin:0;padding:.5em 1em;word-break:break-word}.v-toast.v-toast--top{flex-direction:column}.v-toast.v-toast--bottom{flex-direction:column-reverse}.v-toast.v-toast--custom-parent{position:absolute}@media screen and (max-width: 768px){.v-toast{padding:0;position:fixed!important}}.v-toast__item{opacity:1;min-height:4em}.v-toast__item .v-toast__text{padding:1.5em 1em}.v-toast__item .v-toast__icon{display:block;width:27px;min-width:27px;height:27px;margin-left:1em;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 45.999 45.999'%3e %3cpath fill='%23fff' d='M39.264 6.736c-8.982-8.981-23.545-8.982-32.528 0-8.982 8.982-8.981 23.545 0 32.528 8.982 8.98 23.545 8.981 32.528 0 8.981-8.983 8.98-23.545 0-32.528zM25.999 33a3 3 0 11-6 0V21a3 3 0 116 0v12zm-3.053-17.128c-1.728 0-2.88-1.224-2.844-2.735-.036-1.584 1.116-2.771 2.879-2.771 1.764 0 2.88 1.188 2.917 2.771-.001 1.511-1.152 2.735-2.952 2.735z'/%3e %3c/svg%3e") no-repeat}.v-toast__item.v-toast__item--success .v-toast__icon{background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 52 52'%3e %3cpath fill='%23fff' d='M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329l-16 18a1.997 1.997 0 01-2.745.233l-10-8a2 2 0 012.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 012.99 2.659z'/%3e %3c/svg%3e") no-repeat}.v-toast__item.v-toast__item--error .v-toast__icon{background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 51.976 51.976'%3e %3cpath fill='%23fff' d='M44.373 7.603c-10.137-10.137-26.632-10.138-36.77 0-10.138 10.138-10.137 26.632 0 36.77s26.632 10.138 36.77 0c10.137-10.138 10.137-26.633 0-36.77zm-8.132 28.638a2 2 0 01-2.828 0l-7.425-7.425-7.778 7.778a2 2 0 11-2.828-2.828l7.778-7.778-7.425-7.425a2 2 0 112.828-2.828l7.425 7.425 7.071-7.071a2 2 0 112.828 2.828l-7.071 7.071 7.425 7.425a2 2 0 010 2.828z'/%3e %3c/svg%3e") no-repeat}.v-toast__item.v-toast__item--warning .v-toast__icon{background:url("data:image/svg+xml,%3csvg viewBox='0 0 52 52' xmlns='http://www.w3.org/2000/svg'%3e %3cpath fill='%23fff' d='M49.466 41.26L29.216 6.85c-.69-1.16-1.89-1.85-3.22-1.85-1.32 0-2.53.69-3.21 1.85L2.536 41.26c-.71 1.2-.72 2.64-.03 3.85.68 1.18 1.89 1.89 3.24 1.89h40.51c1.35 0 2.56-.71 3.23-1.89.7-1.21.69-2.65-.02-3.85zm-25.53-21.405h3.381v3.187l-.724 8.92H24.66l-.725-8.92v-3.187zm2.97 17.344a1.712 1.712 0 01-1.267.543c-.491 0-.914-.181-1.268-.543a1.788 1.788 0 01-.531-1.297c0-.502.176-.935.53-1.297a1.712 1.712 0 011.269-.544c.49 0 .914.181 1.268.544s.53.795.53 1.297c0 .503-.176.934-.53 1.297z'/%3e %3c/svg%3e") no-repeat}.placeholder\:text-gray-400::placeholder{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}@media (min-width: 640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}:root{--popper-theme-background-color: #fff;--popper-theme-background-color-hover: #fff;--popper-theme-text-color: #000;--popper-theme-border-color: #000;--popper-theme-border-width: 1px;--popper-theme-border-style: solid;--popper-theme-border-radius: 0px;--popper-theme-padding: 4px;--popper-theme-box-shadow: 0 6px 30px -6px rgba(0, 0, 0, .25)}.subtitle[data-v-177d004e],.subtitle[data-v-dbe09de2]{color:var(--black-70)}#source{position:relative;width:50%;height:50%}h3{margin:.1em;margin-block-end:.1em} From c6b6204e7bf1eca5c38b4455bfb742e26e596c1b Mon Sep 17 00:00:00 2001 From: Kschappacher <56745262+Kschappacher@users.noreply.github.com> Date: Tue, 29 Nov 2022 12:53:19 -0500 Subject: [PATCH 04/11] fix error function, linting and tests --- config/proto_conversions.go | 2 +- config/reader_ext_test.go | 2 +- robot/impl/local_robot_test.go | 2 +- utils/errors.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/proto_conversions.go b/config/proto_conversions.go index a7e9693f33c..25db41c1a4d 100644 --- a/config/proto_conversions.go +++ b/config/proto_conversions.go @@ -499,7 +499,7 @@ func CloudConfigFromProto(proto *pb.CloudConfig) (*Cloud, error) { return &Cloud{ ID: proto.GetId(), Secret: proto.GetSecret(), - + //nolint:staticcheck LocationSecret: proto.GetLocationSecret(), LocationSecrets: locationSecrets, ManagedBy: proto.GetManagedBy(), diff --git a/config/reader_ext_test.go b/config/reader_ext_test.go index 0352e29c4af..a121faa8f47 100644 --- a/config/reader_ext_test.go +++ b/config/reader_ext_test.go @@ -36,7 +36,7 @@ func TestFromReaderValidate(t *testing.T) { test.That(t, err, test.ShouldNotBeNil) test.That(t, err.Error(), test.ShouldContainSubstring, `"id" is required`) - _, err = config.FromReader(context.Background(), "somepath", strings.NewReader(`{"disablepartialstart":true,"components": [{}]}`), logger) + _, err = config.FromReader(context.Background(), "somepath", strings.NewReader(`{"disable_partial_start":true,"components": [{}]}`), logger) test.That(t, err, test.ShouldNotBeNil) test.That(t, err.Error(), test.ShouldContainSubstring, `components.0`) test.That(t, err.Error(), test.ShouldContainSubstring, `"name" is required`) diff --git a/robot/impl/local_robot_test.go b/robot/impl/local_robot_test.go index 92d3e97cb3d..9263dc37f32 100644 --- a/robot/impl/local_robot_test.go +++ b/robot/impl/local_robot_test.go @@ -1453,7 +1453,7 @@ func TestValidationErrorOnReconfigure(t *testing.T) { // Test Service Error s, err := r.ResourceByName(navigation.Named("fake1")) test.That(t, s, test.ShouldBeNil) - errTmp := errors.New("resource \"fake1\" not available: Config validation error found in service: fake1: fail") + errTmp := errors.New("resource \"rdk:service:navigation/fake1\" not available: Config validation error found in service: fake1: fail") test.That(t, err, test.ShouldBeError, errTmp) // Test Remote Error rem, ok := r.RemoteByName("remote") diff --git a/utils/errors.go b/utils/errors.go index e9cc7b5a40e..430b4b0a4f4 100644 --- a/utils/errors.go +++ b/utils/errors.go @@ -13,7 +13,7 @@ func NewResourceNotFoundError(name resource.Name) error { // NewResourceNotAvailableError is used when a resource is not available because of some error. func NewResourceNotAvailableError(name resource.Name, err error) error { - return errors.Wrapf(err, "resource %q not available", name.Name) + return errors.Wrapf(err, "resource %q not available", name) } // NewRemoteResourceClashError is used when you are more than one resource with the same name exist. From 533850b265db10443294f4410732296bde9bab8e Mon Sep 17 00:00:00 2001 From: Kschappacher <56745262+Kschappacher@users.noreply.github.com> Date: Tue, 29 Nov 2022 13:58:17 -0500 Subject: [PATCH 05/11] lint --- config/reader_ext_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/reader_ext_test.go b/config/reader_ext_test.go index a121faa8f47..e0138e84f92 100644 --- a/config/reader_ext_test.go +++ b/config/reader_ext_test.go @@ -36,7 +36,8 @@ func TestFromReaderValidate(t *testing.T) { test.That(t, err, test.ShouldNotBeNil) test.That(t, err.Error(), test.ShouldContainSubstring, `"id" is required`) - _, err = config.FromReader(context.Background(), "somepath", strings.NewReader(`{"disable_partial_start":true,"components": [{}]}`), logger) + _, err = config.FromReader(context.Background(), + "somepath", strings.NewReader(`{"disable_partial_start":true,"components": [{}]}`), logger) test.That(t, err, test.ShouldNotBeNil) test.That(t, err.Error(), test.ShouldContainSubstring, `components.0`) test.That(t, err.Error(), test.ShouldContainSubstring, `"name" is required`) From 5f726115acd2cd65989a190f152ebabca626c59e Mon Sep 17 00:00:00 2001 From: Kschappacher <56745262+Kschappacher@users.noreply.github.com> Date: Wed, 30 Nov 2022 08:07:21 -0500 Subject: [PATCH 06/11] fix lint --- web/runtime-shared/static/main.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/runtime-shared/static/main.css b/web/runtime-shared/static/main.css index 6fdf1078771..2d50c1df213 100644 --- a/web/runtime-shared/static/main.css +++ b/web/runtime-shared/static/main.css @@ -1 +1 @@ -@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/space-mono-vietnamese-400-normal.woff2) format("woff2"),url(/space-mono-all-400-normal.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/space-mono-latin-ext-400-normal.woff2) format("woff2"),url(/space-mono-all-400-normal.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/space-mono-latin-400-normal.woff2) format("woff2"),url(/space-mono-all-400-normal.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:400;src:url(/space-mono-vietnamese-400-italic.woff2) format("woff2"),url(/space-mono-all-400-italic.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:400;src:url(/space-mono-latin-ext-400-italic.woff2) format("woff2"),url(/space-mono-all-400-italic.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:400;src:url(/space-mono-latin-400-italic.woff2) format("woff2"),url(/space-mono-all-400-italic.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:700;src:url(/space-mono-vietnamese-700-normal.woff2) format("woff2"),url(/space-mono-all-700-normal.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:700;src:url(/space-mono-latin-ext-700-normal.woff2) format("woff2"),url(/space-mono-all-700-normal.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:700;src:url(/space-mono-latin-700-normal.woff2) format("woff2"),url(/space-mono-all-700-normal.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:700;src:url(/space-mono-vietnamese-700-italic.woff2) format("woff2"),url(/space-mono-all-700-italic.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:700;src:url(/space-mono-latin-ext-700-italic.woff2) format("woff2"),url(/space-mono-all-700-italic.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:700;src:url(/space-mono-latin-700-italic.woff2) format("woff2"),url(/space-mono-all-700-italic.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0px;bottom:0px}.right-0{right:0px}.float-right{float:right}.clear-both{clear:both}.m-0{margin:0}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mt-2{margin-top:.5rem}.mr-4{margin-right:1rem}.mr-2{margin-right:.5rem}.mb-1{margin-bottom:.25rem}.ml-0{margin-left:0}.ml-2{margin-left:.5rem}.block{display:block}.flex{display:flex}.table{display:table}.grid{display:grid}.inline-grid{display:inline-grid}.hidden{display:none}.h-auto{height:auto}.h-fit{height:fit-content}.h-\[30px\]{height:30px}.h-4{height:1rem}.h-full{height:100%}.h-\[250px\]{height:250px}.h-\[400px\]{height:400px}.w-96{width:24rem}.w-full{width:100%}.w-72{width:18rem}.w-64{width:16rem}.w-4{width:1rem}.w-\[8ex\]{width:8ex}.w-48{width:12rem}.w-32{width:8rem}.w-80{width:20rem}.min-w-\[90px\]{min-width:90px}.max-w-full{max-width:100%}.max-w-xs{max-width:20rem}.flex-auto{flex:1 1 auto}.grow{flex-grow:1}.table-auto{table-layout:auto}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.list-disc{list-style-type:disc}.appearance-none{appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-2{gap:.5rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-12{gap:3rem}.gap-3{gap:.75rem}.place-self-end{place-self:end}.self-end{align-self:flex-end}.overflow-auto{overflow:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.rounded-full{border-radius:9999px}.border{border-width:1px}.border-x{border-left-width:1px;border-right-width:1px}.border-l-4{border-left-width:4px}.border-t-0{border-top-width:0px}.border-b{border-bottom-width:1px}.border-solid{border-style:solid}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity))}.border-black{--tw-border-opacity: 1;border-color:rgb(0 0 0 / var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity: 1;border-color:rgb(107 114 128 / var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.bg-clip-padding{background-clip:padding-box}.stroke-2{stroke-width:2}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-0{padding-top:0;padding-bottom:0}.pl-4{padding-left:1rem}.pb-1{padding-bottom:.25rem}.pr-2{padding-right:.5rem}.pl-2{padding-left:.5rem}.pt-4{padding-top:1rem}.pt-2{padding-top:.5rem}.pl-6{padding-left:1.5rem}.pb-4{padding-bottom:1rem}.pt-7{padding-top:1.75rem}.pr-4{padding-right:1rem}.pb-8{padding-bottom:2rem}.pl-8{padding-left:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.font-normal{font-weight:400}.font-bold{font-weight:700}.capitalize{text-transform:capitalize}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-\[\#045681\]{--tw-text-opacity: 1;color:rgb(4 86 129 / var(--tw-text-opacity))}.underline{text-decoration-line:underline}.opacity-50{opacity:.5}.outline-none{outline:2px solid transparent;outline-offset:2px}.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)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-150{transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}*{font-family:Space Mono,monospace}body,table{font-size:14px}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.v-toast--fade-out{animation-name:fadeOut}@keyframes fadeInDown{0%{opacity:0;transform:translate3d(0,-100%,0)}to{opacity:1;transform:none}}.v-toast--fade-in-down{animation-name:fadeInDown}@keyframes fadeInUp{0%{opacity:0;transform:translate3d(0,100%,0)}to{opacity:1;transform:none}}.v-toast--fade-in-up{animation-name:fadeInUp}.fade-enter-active,.fade-leave-active{transition:opacity .15s ease-out}.fade-enter,.fade-leave-to{opacity:0}.v-toast{position:fixed;display:flex;top:0;bottom:0;left:0;right:0;padding:2em;overflow:hidden;z-index:1052;pointer-events:none}.v-toast__item{display:inline-flex;align-items:center;animation-duration:.15s;margin:.5em 0;box-shadow:0 1px 4px #0000001f,0 0 6px #0000000a;pointer-events:auto;opacity:.92;color:#fff;min-height:3em;cursor:pointer;text-transform:uppercase}.v-toast__item--success{background-color:#25933c}.v-toast__item--info,.v-toast__item--warning{background-color:#000}.v-toast__item--error{background-color:#d82323}.v-toast__item--default{background-color:#000}.v-toast__item.v-toast__item--top,.v-toast__item.v-toast__item--bottom{align-self:center}.v-toast__item.v-toast__item--top-right,.v-toast__item.v-toast__item--bottom-right{align-self:flex-end}.v-toast__item.v-toast__item--top-left,.v-toast__item.v-toast__item--bottom-left{align-self:flex-start}.v-toast__text{margin:0;padding:.5em 1em;word-break:break-word}.v-toast.v-toast--top{flex-direction:column}.v-toast.v-toast--bottom{flex-direction:column-reverse}.v-toast.v-toast--custom-parent{position:absolute}@media screen and (max-width: 768px){.v-toast{padding:0;position:fixed!important}}.v-toast__item{opacity:1;min-height:4em}.v-toast__item .v-toast__text{padding:1.5em 1em}.v-toast__item .v-toast__icon{display:block;width:27px;min-width:27px;height:27px;margin-left:1em;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 45.999 45.999'%3e %3cpath fill='%23fff' d='M39.264 6.736c-8.982-8.981-23.545-8.982-32.528 0-8.982 8.982-8.981 23.545 0 32.528 8.982 8.98 23.545 8.981 32.528 0 8.981-8.983 8.98-23.545 0-32.528zM25.999 33a3 3 0 11-6 0V21a3 3 0 116 0v12zm-3.053-17.128c-1.728 0-2.88-1.224-2.844-2.735-.036-1.584 1.116-2.771 2.879-2.771 1.764 0 2.88 1.188 2.917 2.771-.001 1.511-1.152 2.735-2.952 2.735z'/%3e %3c/svg%3e") no-repeat}.v-toast__item.v-toast__item--success .v-toast__icon{background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 52 52'%3e %3cpath fill='%23fff' d='M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329l-16 18a1.997 1.997 0 01-2.745.233l-10-8a2 2 0 012.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 012.99 2.659z'/%3e %3c/svg%3e") no-repeat}.v-toast__item.v-toast__item--error .v-toast__icon{background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 51.976 51.976'%3e %3cpath fill='%23fff' d='M44.373 7.603c-10.137-10.137-26.632-10.138-36.77 0-10.138 10.138-10.137 26.632 0 36.77s26.632 10.138 36.77 0c10.137-10.138 10.137-26.633 0-36.77zm-8.132 28.638a2 2 0 01-2.828 0l-7.425-7.425-7.778 7.778a2 2 0 11-2.828-2.828l7.778-7.778-7.425-7.425a2 2 0 112.828-2.828l7.425 7.425 7.071-7.071a2 2 0 112.828 2.828l-7.071 7.071 7.425 7.425a2 2 0 010 2.828z'/%3e %3c/svg%3e") no-repeat}.v-toast__item.v-toast__item--warning .v-toast__icon{background:url("data:image/svg+xml,%3csvg viewBox='0 0 52 52' xmlns='http://www.w3.org/2000/svg'%3e %3cpath fill='%23fff' d='M49.466 41.26L29.216 6.85c-.69-1.16-1.89-1.85-3.22-1.85-1.32 0-2.53.69-3.21 1.85L2.536 41.26c-.71 1.2-.72 2.64-.03 3.85.68 1.18 1.89 1.89 3.24 1.89h40.51c1.35 0 2.56-.71 3.23-1.89.7-1.21.69-2.65-.02-3.85zm-25.53-21.405h3.381v3.187l-.724 8.92H24.66l-.725-8.92v-3.187zm2.97 17.344a1.712 1.712 0 01-1.267.543c-.491 0-.914-.181-1.268-.543a1.788 1.788 0 01-.531-1.297c0-.502.176-.935.53-1.297a1.712 1.712 0 011.269-.544c.49 0 .914.181 1.268.544s.53.795.53 1.297c0 .503-.176.934-.53 1.297z'/%3e %3c/svg%3e") no-repeat}.placeholder\:text-gray-400::placeholder{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}@media (min-width: 640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}:root{--popper-theme-background-color: #fff;--popper-theme-background-color-hover: #fff;--popper-theme-text-color: #000;--popper-theme-border-color: #000;--popper-theme-border-width: 1px;--popper-theme-border-style: solid;--popper-theme-border-radius: 0px;--popper-theme-padding: 4px;--popper-theme-box-shadow: 0 6px 30px -6px rgba(0, 0, 0, .25)}.subtitle[data-v-177d004e],.subtitle[data-v-dbe09de2]{color:var(--black-70)}#source{position:relative;width:50%;height:50%}h3{margin:.1em;margin-block-end:.1em} +@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/space-mono-vietnamese-400-normal.woff2) format("woff2"),url(/space-mono-all-400-normal.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/space-mono-latin-ext-400-normal.woff2) format("woff2"),url(/space-mono-all-400-normal.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/space-mono-latin-400-normal.woff2) format("woff2"),url(/space-mono-all-400-normal.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:400;src:url(/space-mono-vietnamese-400-italic.woff2) format("woff2"),url(/space-mono-all-400-italic.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:400;src:url(/space-mono-latin-ext-400-italic.woff2) format("woff2"),url(/space-mono-all-400-italic.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:400;src:url(/space-mono-latin-400-italic.woff2) format("woff2"),url(/space-mono-all-400-italic.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:700;src:url(/space-mono-vietnamese-700-normal.woff2) format("woff2"),url(/space-mono-all-700-normal.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:700;src:url(/space-mono-latin-ext-700-normal.woff2) format("woff2"),url(/space-mono-all-700-normal.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:normal;font-display:swap;font-weight:700;src:url(/space-mono-latin-700-normal.woff2) format("woff2"),url(/space-mono-all-700-normal.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:700;src:url(/space-mono-vietnamese-700-italic.woff2) format("woff2"),url(/space-mono-all-700-italic.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:700;src:url(/space-mono-latin-ext-700-italic.woff2) format("woff2"),url(/space-mono-all-700-italic.woff) format("woff");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Space Mono;font-style:italic;font-display:swap;font-weight:700;src:url(/space-mono-latin-700-italic.woff2) format("woff2"),url(/space-mono-all-700-italic.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0px;bottom:0px}.right-0{right:0px}.float-right{float:right}.clear-both{clear:both}.m-0{margin:0}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mt-2{margin-top:.5rem}.mr-4{margin-right:1rem}.mr-2{margin-right:.5rem}.mb-1{margin-bottom:.25rem}.ml-0{margin-left:0}.ml-2{margin-left:.5rem}.block{display:block}.flex{display:flex}.table{display:table}.grid{display:grid}.inline-grid{display:inline-grid}.hidden{display:none}.h-auto{height:auto}.h-fit{height:fit-content}.h-\[30px\]{height:30px}.h-4{height:1rem}.h-full{height:100%}.h-\[250px\]{height:250px}.h-\[400px\]{height:400px}.w-96{width:24rem}.w-full{width:100%}.w-72{width:18rem}.w-64{width:16rem}.w-4{width:1rem}.w-\[8ex\]{width:8ex}.w-48{width:12rem}.w-32{width:8rem}.w-80{width:20rem}.min-w-\[90px\]{min-width:90px}.max-w-full{max-width:100%}.max-w-xs{max-width:20rem}.flex-auto{flex:1 1 auto}.grow{flex-grow:1}.table-auto{table-layout:auto}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.list-disc{list-style-type:disc}.appearance-none{appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-2{gap:.5rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-12{gap:3rem}.gap-3{gap:.75rem}.place-self-end{place-self:end}.self-end{align-self:flex-end}.overflow-auto{overflow:auto}.whitespace-nowrap{white-space:nowrap}.rounded-full{border-radius:9999px}.border{border-width:1px}.border-x{border-left-width:1px;border-right-width:1px}.border-l-4{border-left-width:4px}.border-t-0{border-top-width:0px}.border-b{border-bottom-width:1px}.border-solid{border-style:solid}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity))}.border-black{--tw-border-opacity: 1;border-color:rgb(0 0 0 / var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity: 1;border-color:rgb(107 114 128 / var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.bg-clip-padding{background-clip:padding-box}.stroke-2{stroke-width:2}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-0{padding-top:0;padding-bottom:0}.pl-4{padding-left:1rem}.pb-1{padding-bottom:.25rem}.pr-2{padding-right:.5rem}.pl-2{padding-left:.5rem}.pt-4{padding-top:1rem}.pt-2{padding-top:.5rem}.pl-6{padding-left:1.5rem}.pb-4{padding-bottom:1rem}.pt-7{padding-top:1.75rem}.pr-4{padding-right:1rem}.pb-8{padding-bottom:2rem}.pl-8{padding-left:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.font-normal{font-weight:400}.font-bold{font-weight:700}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-\[\#045681\]{--tw-text-opacity: 1;color:rgb(4 86 129 / var(--tw-text-opacity))}.underline{text-decoration-line:underline}.opacity-50{opacity:.5}.outline-none{outline:2px solid transparent;outline-offset:2px}.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)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-150{transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}*{font-family:Space Mono,monospace}body,table{font-size:14px}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.v-toast--fade-out{animation-name:fadeOut}@keyframes fadeInDown{0%{opacity:0;transform:translate3d(0,-100%,0)}to{opacity:1;transform:none}}.v-toast--fade-in-down{animation-name:fadeInDown}@keyframes fadeInUp{0%{opacity:0;transform:translate3d(0,100%,0)}to{opacity:1;transform:none}}.v-toast--fade-in-up{animation-name:fadeInUp}.fade-enter-active,.fade-leave-active{transition:opacity .15s ease-out}.fade-enter,.fade-leave-to{opacity:0}.v-toast{position:fixed;display:flex;top:0;bottom:0;left:0;right:0;padding:2em;overflow:hidden;z-index:1052;pointer-events:none}.v-toast__item{display:inline-flex;align-items:center;animation-duration:.15s;margin:.5em 0;box-shadow:0 1px 4px #0000001f,0 0 6px #0000000a;pointer-events:auto;opacity:.92;color:#fff;min-height:3em;cursor:pointer;text-transform:uppercase}.v-toast__item--success{background-color:#25933c}.v-toast__item--info,.v-toast__item--warning{background-color:#000}.v-toast__item--error{background-color:#d82323}.v-toast__item--default{background-color:#000}.v-toast__item.v-toast__item--top,.v-toast__item.v-toast__item--bottom{align-self:center}.v-toast__item.v-toast__item--top-right,.v-toast__item.v-toast__item--bottom-right{align-self:flex-end}.v-toast__item.v-toast__item--top-left,.v-toast__item.v-toast__item--bottom-left{align-self:flex-start}.v-toast__text{margin:0;padding:.5em 1em;word-break:break-word}.v-toast.v-toast--top{flex-direction:column}.v-toast.v-toast--bottom{flex-direction:column-reverse}.v-toast.v-toast--custom-parent{position:absolute}@media screen and (max-width: 768px){.v-toast{padding:0;position:fixed!important}}.v-toast__item{opacity:1;min-height:4em}.v-toast__item .v-toast__text{padding:1.5em 1em}.v-toast__item .v-toast__icon{display:block;width:27px;min-width:27px;height:27px;margin-left:1em;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 45.999 45.999'%3e %3cpath fill='%23fff' d='M39.264 6.736c-8.982-8.981-23.545-8.982-32.528 0-8.982 8.982-8.981 23.545 0 32.528 8.982 8.98 23.545 8.981 32.528 0 8.981-8.983 8.98-23.545 0-32.528zM25.999 33a3 3 0 11-6 0V21a3 3 0 116 0v12zm-3.053-17.128c-1.728 0-2.88-1.224-2.844-2.735-.036-1.584 1.116-2.771 2.879-2.771 1.764 0 2.88 1.188 2.917 2.771-.001 1.511-1.152 2.735-2.952 2.735z'/%3e %3c/svg%3e") no-repeat}.v-toast__item.v-toast__item--success .v-toast__icon{background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 52 52'%3e %3cpath fill='%23fff' d='M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329l-16 18a1.997 1.997 0 01-2.745.233l-10-8a2 2 0 012.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 012.99 2.659z'/%3e %3c/svg%3e") no-repeat}.v-toast__item.v-toast__item--error .v-toast__icon{background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 51.976 51.976'%3e %3cpath fill='%23fff' d='M44.373 7.603c-10.137-10.137-26.632-10.138-36.77 0-10.138 10.138-10.137 26.632 0 36.77s26.632 10.138 36.77 0c10.137-10.138 10.137-26.633 0-36.77zm-8.132 28.638a2 2 0 01-2.828 0l-7.425-7.425-7.778 7.778a2 2 0 11-2.828-2.828l7.778-7.778-7.425-7.425a2 2 0 112.828-2.828l7.425 7.425 7.071-7.071a2 2 0 112.828 2.828l-7.071 7.071 7.425 7.425a2 2 0 010 2.828z'/%3e %3c/svg%3e") no-repeat}.v-toast__item.v-toast__item--warning .v-toast__icon{background:url("data:image/svg+xml,%3csvg viewBox='0 0 52 52' xmlns='http://www.w3.org/2000/svg'%3e %3cpath fill='%23fff' d='M49.466 41.26L29.216 6.85c-.69-1.16-1.89-1.85-3.22-1.85-1.32 0-2.53.69-3.21 1.85L2.536 41.26c-.71 1.2-.72 2.64-.03 3.85.68 1.18 1.89 1.89 3.24 1.89h40.51c1.35 0 2.56-.71 3.23-1.89.7-1.21.69-2.65-.02-3.85zm-25.53-21.405h3.381v3.187l-.724 8.92H24.66l-.725-8.92v-3.187zm2.97 17.344a1.712 1.712 0 01-1.267.543c-.491 0-.914-.181-1.268-.543a1.788 1.788 0 01-.531-1.297c0-.502.176-.935.53-1.297a1.712 1.712 0 011.269-.544c.49 0 .914.181 1.268.544s.53.795.53 1.297c0 .503-.176.934-.53 1.297z'/%3e %3c/svg%3e") no-repeat}.placeholder\:text-gray-400::placeholder{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}@media (min-width: 640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}:root{--popper-theme-background-color: #fff;--popper-theme-background-color-hover: #fff;--popper-theme-text-color: #000;--popper-theme-border-color: #000;--popper-theme-border-width: 1px;--popper-theme-border-style: solid;--popper-theme-border-radius: 0px;--popper-theme-padding: 4px;--popper-theme-box-shadow: 0 6px 30px -6px rgba(0, 0, 0, .25)}.subtitle[data-v-177d004e],.subtitle[data-v-dbe09de2]{color:var(--black-70)}#source{position:relative;width:50%;height:50%}h3{margin:.1em;margin-block-end:.1em} From 7577e47f9b485c05232ee05b110e66ef4806f77d Mon Sep 17 00:00:00 2001 From: Kschappacher <56745262+Kschappacher@users.noreply.github.com> Date: Thu, 1 Dec 2022 09:31:47 -0500 Subject: [PATCH 07/11] updated tests to handle two more cases --- robot/impl/local_robot_test.go | 210 +++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/robot/impl/local_robot_test.go b/robot/impl/local_robot_test.go index 9263dc37f32..6562b6d09f0 100644 --- a/robot/impl/local_robot_test.go +++ b/robot/impl/local_robot_test.go @@ -1461,6 +1461,216 @@ func TestValidationErrorOnReconfigure(t *testing.T) { test.That(t, ok, test.ShouldBeFalse) } +func TestConfigStartsInvalidReconfiguresValid(t *testing.T) { + logger := golog.NewTestLogger(t) + ctx := context.Background() + + badConfig := &config.Config{ + Components: []config.Component{ + { + Namespace: resource.ResourceNamespaceRDK, + Name: "test", + Type: base.SubtypeName, + Model: "fake", + ConvertedAttributes: attrs{}, + }, + }, + Services: []config.Service{ + { + Namespace: resource.ResourceNamespaceRDK, + Name: "fake1", + Type: config.ServiceType(datamanager.SubtypeName), + ConvertedAttributes: attrs{}, + }, + }, + Remotes: []config.Remote{{ + Name: "remote", + Insecure: true, + Address: "", + }}, + Cloud: &config.Cloud{}, + } + r, err := robotimpl.New(ctx, badConfig, logger) + defer func() { + test.That(t, r.Close(context.Background()), test.ShouldBeNil) + }() + test.That(t, err, test.ShouldBeNil) + test.That(t, r, test.ShouldNotBeNil) + options1, _, addr1 := robottestutils.CreateBaseOptionsAndListener(t) + err = r.StartWeb(context.Background(), options1) + test.That(t, err, test.ShouldBeNil) + + goodConfig := &config.Config{ + Components: []config.Component{ + { + Namespace: resource.ResourceNamespaceRDK, + Name: "test", + Type: base.SubtypeName, + Model: "fake", + }, + }, + Services: []config.Service{ + { + Namespace: resource.ResourceNamespaceRDK, + Name: "fake1", + Type: config.ServiceType(datamanager.SubtypeName), + Model: resource.DefaultModelName, + ConvertedAttributes: &builtin.Config{}, + }, + }, + Remotes: []config.Remote{{ + Name: "remote", + Insecure: true, + Address: addr1, + }}, + Cloud: &config.Cloud{}, + } + + // Test Component Error + name := base.Named("test") + noBase, err := r.ResourceByName(name) + test.That( + t, + err, + test.ShouldBeError, + rutils.NewResourceNotAvailableError(name, errors.New("Config validation error found in component: test: fail")), + ) + test.That(t, noBase, test.ShouldBeNil) + // Test Service Error + s, err := r.ResourceByName(datamanager.Named("fake1")) + test.That(t, s, test.ShouldBeNil) + errTmp := errors.New("resource \"rdk:service:data_manager/fake1\" not available: Config validation error found in service: fake1: fail") + test.That(t, err, test.ShouldBeError, errTmp) + // Test Remote Error + rem, ok := r.RemoteByName("remote") + test.That(t, rem, test.ShouldBeNil) + test.That(t, ok, test.ShouldBeFalse) + + r.Reconfigure(ctx, goodConfig) + // Test Component Error + noBase, err = r.ResourceByName(base.Named("test")) + test.That(t, err, test.ShouldBeNil) + test.That(t, noBase, test.ShouldNotBeNil) + // Test Service Error + s, err = r.ResourceByName(datamanager.Named("fake1")) + test.That(t, err, test.ShouldBeNil) + test.That(t, s, test.ShouldNotBeNil) + // Test Remote Error + rem, ok = r.RemoteByName("remote") + test.That(t, ok, test.ShouldBeTrue) + test.That(t, rem, test.ShouldNotBeNil) +} + +func TestConfigStartsValidReconfiguresInvalid(t *testing.T) { + logger := golog.NewTestLogger(t) + ctx := context.Background() + armConfig := config.Component{ + Namespace: resource.ResourceNamespaceRDK, + Name: "arm1", + Type: arm.SubtypeName, + Model: "fake", + } + cfg := config.Config{ + Components: []config.Component{armConfig}, + } + + robotRemote, _ := robotimpl.New(ctx, &cfg, logger) + options1, _, addr1 := robottestutils.CreateBaseOptionsAndListener(t) + err := robotRemote.StartWeb(context.Background(), options1) + test.That(t, err, test.ShouldBeNil) + + goodConfig := &config.Config{ + Components: []config.Component{ + { + Namespace: resource.ResourceNamespaceRDK, + Name: "test", + Type: base.SubtypeName, + Model: "fake", + }, + }, + Services: []config.Service{ + { + Namespace: resource.ResourceNamespaceRDK, + Name: "fake1", + Type: config.ServiceType(datamanager.SubtypeName), + Model: resource.DefaultModelName, + ConvertedAttributes: &builtin.Config{}, + }, + }, + Remotes: []config.Remote{{ + Name: "remote", + Insecure: true, + Address: addr1, + }}, + Cloud: &config.Cloud{}, + } + r, err := robotimpl.New(ctx, goodConfig, logger) + defer func() { + test.That(t, r.Close(context.Background()), test.ShouldBeNil) + }() + test.That(t, err, test.ShouldBeNil) + test.That(t, r, test.ShouldNotBeNil) + + badConfig := &config.Config{ + Components: []config.Component{ + { + Namespace: resource.ResourceNamespaceRDK, + Name: "test", + Type: base.SubtypeName, + Model: "fake", + ConvertedAttributes: attrs{}, + }, + }, + Services: []config.Service{ + { + Namespace: resource.ResourceNamespaceRDK, + Name: "fake1", + Type: config.ServiceType(datamanager.SubtypeName), + ConvertedAttributes: attrs{}, + }, + }, + Remotes: []config.Remote{{ + Name: "remote", + Insecure: true, + Address: "", + }}, + Cloud: &config.Cloud{}, + } + // Test Component Error + noBase, err := r.ResourceByName(base.Named("test")) + test.That(t, err, test.ShouldBeNil) + test.That(t, noBase, test.ShouldNotBeNil) + // Test Service Error + s, err := r.ResourceByName(datamanager.Named("fake1")) + test.That(t, err, test.ShouldBeNil) + test.That(t, s, test.ShouldNotBeNil) + // Test Remote Error + rem, ok := r.RemoteByName("remote") + test.That(t, ok, test.ShouldBeTrue) + test.That(t, rem, test.ShouldNotBeNil) + + r.Reconfigure(ctx, badConfig) + // Test Component Error + name := base.Named("test") + noBase, err = r.ResourceByName(name) + test.That( + t, + err, + test.ShouldBeError, + rutils.NewResourceNotAvailableError(name, errors.New("Config validation error found in component: test: fail")), + ) + test.That(t, noBase, test.ShouldBeNil) + // Test Service Error + s, err = r.ResourceByName(datamanager.Named("fake1")) + test.That(t, s, test.ShouldBeNil) + errTmp := errors.New("resource \"rdk:service:data_manager/fake1\" not available: Config validation error found in service: fake1: fail") + test.That(t, err, test.ShouldBeError, errTmp) + // Test Remote Error + rem, ok = r.RemoteByName("remote") + test.That(t, rem, test.ShouldBeNil) + test.That(t, ok, test.ShouldBeFalse) +} + func TestResourceStartsOnReconfigure(t *testing.T) { logger := golog.NewTestLogger(t) ctx := context.Background() From 3a9dc5961efe961ee3c58ba14d9e4ff974bc0a2c Mon Sep 17 00:00:00 2001 From: Kschappacher <56745262+Kschappacher@users.noreply.github.com> Date: Thu, 1 Dec 2022 09:51:14 -0500 Subject: [PATCH 08/11] clean up robot in test --- robot/impl/local_robot_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/robot/impl/local_robot_test.go b/robot/impl/local_robot_test.go index 2249778514f..9ec335b9f38 100644 --- a/robot/impl/local_robot_test.go +++ b/robot/impl/local_robot_test.go @@ -1574,9 +1574,14 @@ func TestConfigStartsValidReconfiguresInvalid(t *testing.T) { Components: []config.Component{armConfig}, } - robotRemote, _ := robotimpl.New(ctx, &cfg, logger) + robotRemote, err := robotimpl.New(ctx, &cfg, logger) + test.That(t, err, test.ShouldBeNil) + test.That(t, robotRemote, test.ShouldNotBeNil) + defer func() { + test.That(t, utils.TryClose(context.Background(), robotRemote), test.ShouldBeNil) + }() options1, _, addr1 := robottestutils.CreateBaseOptionsAndListener(t) - err := robotRemote.StartWeb(context.Background(), options1) + err = robotRemote.StartWeb(context.Background(), options1) test.That(t, err, test.ShouldBeNil) goodConfig := &config.Config{ From e327c012b16fc10593cf50918d054551f84ceeb9 Mon Sep 17 00:00:00 2001 From: Kschappacher <56745262+Kschappacher@users.noreply.github.com> Date: Thu, 1 Dec 2022 09:52:59 -0500 Subject: [PATCH 09/11] fix nit --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index e8fdbcfad3f..639e1223b4b 100644 --- a/config/config.go +++ b/config/config.go @@ -47,7 +47,7 @@ type Config struct { // error messages that can indicate flags/config fields to use. FromCommand bool `json:"-"` - // DisablepartialStart ensures that a robot will only start when all the components, + // DisablePartialStart ensures that a robot will only start when all the components, // services, and remotes pass config validation. This value is false by default DisablePartialStart bool `json:"disable_partial_start"` } From 103cbf3de24bed9d746d4091f75eb91486f1be83 Mon Sep 17 00:00:00 2001 From: Kschappacher <56745262+Kschappacher@users.noreply.github.com> Date: Thu, 1 Dec 2022 13:07:09 -0500 Subject: [PATCH 10/11] use base from robot and correct comments about what we are testing --- robot/impl/local_robot_test.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/robot/impl/local_robot_test.go b/robot/impl/local_robot_test.go index 9ec335b9f38..c4f97cc57b3 100644 --- a/robot/impl/local_robot_test.go +++ b/robot/impl/local_robot_test.go @@ -18,6 +18,7 @@ import ( "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" + // registers all components. commonpb "go.viam.com/api/common/v1" armpb "go.viam.com/api/component/arm/v1" @@ -1528,7 +1529,7 @@ func TestConfigStartsInvalidReconfiguresValid(t *testing.T) { // Test Component Error name := base.Named("test") - noBase, err := r.ResourceByName(name) + noBase, err := base.FromRobot(r, "test") test.That( t, err, @@ -1547,15 +1548,15 @@ func TestConfigStartsInvalidReconfiguresValid(t *testing.T) { test.That(t, ok, test.ShouldBeFalse) r.Reconfigure(ctx, goodConfig) - // Test Component Error - noBase, err = r.ResourceByName(base.Named("test")) + // Test Component Valid + noBase, err = base.FromRobot(r, "test") test.That(t, err, test.ShouldBeNil) test.That(t, noBase, test.ShouldNotBeNil) - // Test Service Error + // Test Service Valid s, err = r.ResourceByName(datamanager.Named("fake1")) test.That(t, err, test.ShouldBeNil) test.That(t, s, test.ShouldNotBeNil) - // Test Remote Error + // Test Remote Valid rem, ok = r.RemoteByName("remote") test.That(t, ok, test.ShouldBeTrue) test.That(t, rem, test.ShouldNotBeNil) @@ -1641,15 +1642,15 @@ func TestConfigStartsValidReconfiguresInvalid(t *testing.T) { }}, Cloud: &config.Cloud{}, } - // Test Component Error - noBase, err := r.ResourceByName(base.Named("test")) + // Test Component Valid + noBase, err := base.FromRobot(r, "test") test.That(t, err, test.ShouldBeNil) test.That(t, noBase, test.ShouldNotBeNil) - // Test Service Error + // Test Service Valid s, err := r.ResourceByName(datamanager.Named("fake1")) test.That(t, err, test.ShouldBeNil) test.That(t, s, test.ShouldNotBeNil) - // Test Remote Error + // Test Remote Valid rem, ok := r.RemoteByName("remote") test.That(t, ok, test.ShouldBeTrue) test.That(t, rem, test.ShouldNotBeNil) @@ -1657,7 +1658,7 @@ func TestConfigStartsValidReconfiguresInvalid(t *testing.T) { r.Reconfigure(ctx, badConfig) // Test Component Error name := base.Named("test") - noBase, err = r.ResourceByName(name) + noBase, err = base.FromRobot(r, "test") test.That( t, err, From 9a956687000456362e950e2f57078eba469946a2 Mon Sep 17 00:00:00 2001 From: Kschappacher <56745262+Kschappacher@users.noreply.github.com> Date: Thu, 1 Dec 2022 13:08:16 -0500 Subject: [PATCH 11/11] lint --- robot/impl/local_robot_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/robot/impl/local_robot_test.go b/robot/impl/local_robot_test.go index c4f97cc57b3..c949d9a48f9 100644 --- a/robot/impl/local_robot_test.go +++ b/robot/impl/local_robot_test.go @@ -18,7 +18,6 @@ import ( "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" - // registers all components. commonpb "go.viam.com/api/common/v1" armpb "go.viam.com/api/component/arm/v1"