diff --git a/cmd/frostfs-lens/internal/meta/tui.go b/cmd/frostfs-lens/internal/meta/tui.go index 1dd2a8ade..900402d81 100644 --- a/cmd/frostfs-lens/internal/meta/tui.go +++ b/cmd/frostfs-lens/internal/meta/tui.go @@ -2,15 +2,10 @@ package meta import ( "context" - "errors" "fmt" - "strings" common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/tuiutil" - cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" - "github.com/mr-tron/base58" "github.com/rivo/tview" "github.com/spf13/cobra" "go.etcd.io/bbolt" @@ -32,6 +27,10 @@ func tuiFunc(cmd *cobra.Command, _ []string) { } func runTUI(cmd *cobra.Command) error { + // tview.Styles.PrimaryTextColor = tcell.ColorBlack + // tview.Styles.PrimitiveBackgroundColor = tcell.ColorWhite + // tview.Styles.GraphicsColor = tcell.ColorBlack + db, err := openDB(false) common.ExitOnErr(cmd, err) defer db.Close() @@ -42,58 +41,10 @@ func runTUI(cmd *cobra.Command) error { app := tview.NewApplication() ui := tuiutil.NewUI(ctx, app, db) - _ = ui.AddFilter("cid", func(s string) (any, error) { - data, err := base58.Decode(s) - if err != nil { - return nil, err - } - var id cid.ID - if err = id.Decode(data); err != nil { - return nil, err - } - return id, nil - }) - _ = ui.AddFilter("oid", func(s string) (any, error) { - data, err := base58.Decode(s) - if err != nil { - return nil, err - } - var id oid.ID - if err = id.Decode(data); err != nil { - return nil, err - } - return id, nil - }) - _ = ui.AddCompositeFilter("addr", func(s string) (map[string]any, error) { - m := make(map[string]any) - - parts := strings.Split(s, "/") - if len(parts) != 2 { - return nil, errors.New("invalid syntax") - } - data, err := base58.Decode(parts[0]) - if err != nil { - return nil, err - } - cnr := cid.ID{} - if err = cnr.Decode(data); err != nil { - return nil, err - } - - data, err = base58.Decode(parts[1]) - if err != nil { - return nil, err - } - obj := oid.ID{} - if err = obj.Decode(data); err != nil { - return nil, err - } - - m["cid"] = cnr - m["oid"] = obj - - return m, nil - }) + _ = ui.AddFilter("cid", cidParser) + _ = ui.AddFilter("oid", oidParser) + _ = ui.AddCompositeFilter("addr", addrParser) + _ = ui.AddCompositeFilter("attr", attributeParser) app.SetRoot(ui, true).SetFocus(ui) diff --git a/cmd/frostfs-lens/internal/meta/util.go b/cmd/frostfs-lens/internal/meta/util.go new file mode 100644 index 000000000..a6e8adf13 --- /dev/null +++ b/cmd/frostfs-lens/internal/meta/util.go @@ -0,0 +1,97 @@ +package meta + +import ( + "errors" + "strings" + + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" + "github.com/mr-tron/base58" +) + +func cidParser(s string) (any, error) { + data, err := base58.Decode(s) + if err != nil { + return nil, err + } + var id cid.ID + if err = id.Decode(data); err != nil { + return nil, err + } + return id, nil +} + +func oidParser(s string) (any, error) { + data, err := base58.Decode(s) + if err != nil { + return nil, err + } + var id oid.ID + if err = id.Decode(data); err != nil { + return nil, err + } + return id, nil +} + +func addrParser(s string) (map[string]any, error) { + m := make(map[string]any) + + parts := strings.Split(s, "/") + if len(parts) != 2 { + return nil, errors.New("expected /") + } + cnr, err := cidParser(parts[0]) + if err != nil { + return nil, err + } + obj, err := oidParser(parts[1]) + if err != nil { + return nil, err + } + + m["cid"] = cnr + m["oid"] = obj + + return m, nil +} + +func keyParser(s string) (any, error) { + if s == "" { + return nil, errors.New("empty attribute key") + } + return s, nil +} + +func valueParser(s string) (any, error) { + if s == "" { + return nil, errors.New("empty attribute value") + } + return s, nil +} + +func attributeParser(s string) (map[string]any, error) { + m := make(map[string]any) + + parts := strings.Split(s, "/") + if len(parts) != 1 && len(parts) != 2 { + return nil, errors.New("expected or /") + } + + key, err := keyParser(parts[0]) + if err != nil { + return nil, err + } + m["key"] = key + + if len(parts) == 1 { + return m, nil + } + + value, err := valueParser(parts[1]) + if err != nil { + return nil, err + } + m["value"] = value + + return m, nil +} diff --git a/cmd/frostfs-lens/internal/schema/metabase/buckets.go b/cmd/frostfs-lens/internal/schema/metabase/buckets.go index 321bf1372..d16063800 100644 --- a/cmd/frostfs-lens/internal/schema/metabase/buckets.go +++ b/cmd/frostfs-lens/internal/schema/metabase/buckets.go @@ -21,13 +21,6 @@ type ( resolvers Resolvers } - PrefixContainerKeyBucket struct { - prefix Prefix - id cid.ID - key string - resolvers Resolvers - } - ContainerBucket struct { id cid.ID resolvers Resolvers @@ -38,9 +31,14 @@ type ( resolvers Resolvers } - ValueBucket struct { - value string - resolvers Resolvers + UserAttributeKeyBucket struct { + prefix Prefix + id cid.ID + key string + } + + UserAttributeValueBucket struct { + value string } ) @@ -102,27 +100,6 @@ func NewPrefixContainerBucketParser(prefix Prefix, next common.Parser, resolvers } } -func NewPrefixContainerKeyBucketParser(prefix Prefix, next common.Parser, resolvers Resolvers) common.Parser { - return func(key, value []byte) (common.SchemaEntry, common.Parser, error) { - if value != nil { - return nil, nil, ErrNotBucket - } - if len(key) < 34 { - return nil, nil, ErrInvalidKeyLength - } - var b PrefixContainerKeyBucket - if b.prefix = Prefix(key[0]); b.prefix != prefix { - return nil, nil, ErrInvalidPrefix - } - if err := b.id.Decode(key[1:33]); err != nil { - return nil, nil, err - } - b.key = string(key[33:]) - b.resolvers = resolvers - return &b, next, nil - } -} - func NewUserBucketParser(next common.Parser, resolvers Resolvers) common.Parser { return func(key, value []byte) (common.SchemaEntry, common.Parser, error) { if value != nil { @@ -154,7 +131,27 @@ func NewContainerBucketParser(next common.Parser, resolvers Resolvers) common.Pa } } -func NewValueBucketParser(next common.Parser, resolvers Resolvers) common.Parser { +func NewUserAttributeKeyBucketParser(next common.Parser) common.Parser { + return func(key, value []byte) (common.SchemaEntry, common.Parser, error) { + if value != nil { + return nil, nil, ErrNotBucket + } + if len(key) < 34 { + return nil, nil, ErrInvalidKeyLength + } + var b UserAttributeKeyBucket + if b.prefix = Prefix(key[0]); b.prefix != UserAttribute { + return nil, nil, ErrInvalidPrefix + } + if err := b.id.Decode(key[1:33]); err != nil { + return nil, nil, err + } + b.key = string(key[33:]) + return &b, next, nil + } +} + +func NewUserAttributeValueBucketParser(next common.Parser) common.Parser { return func(key, value []byte) (common.SchemaEntry, common.Parser, error) { if value != nil { return nil, nil, ErrNotBucket @@ -162,9 +159,8 @@ func NewValueBucketParser(next common.Parser, resolvers Resolvers) common.Parser if len(key) == 0 { return nil, nil, ErrInvalidKeyLength } - var b ValueBucket + var b UserAttributeValueBucket b.value = string(key) - b.resolvers = resolvers return &b, next, nil } } diff --git a/cmd/frostfs-lens/internal/schema/metabase/detailed.go b/cmd/frostfs-lens/internal/schema/metabase/detailed.go index 2a6bd0e30..0e9d091b5 100644 --- a/cmd/frostfs-lens/internal/schema/metabase/detailed.go +++ b/cmd/frostfs-lens/internal/schema/metabase/detailed.go @@ -12,10 +12,6 @@ func (b *PrefixContainerBucket) DetailedString() string { return spew.Sdump(*b) } -func (b *PrefixContainerKeyBucket) DetailedString() string { - return spew.Sdump(*b) -} - func (b *UserBucket) DetailedString() string { return spew.Sdump(*b) } @@ -24,6 +20,10 @@ func (b *ContainerBucket) DetailedString() string { return spew.Sdump(*b) } -func (b *ValueBucket) DetailedString() string { +func (b *UserAttributeKeyBucket) DetailedString() string { + return spew.Sdump(*b) +} + +func (b *UserAttributeValueBucket) DetailedString() string { return spew.Sdump(*b) } diff --git a/cmd/frostfs-lens/internal/schema/metabase/filter.go b/cmd/frostfs-lens/internal/schema/metabase/filter.go index 052007fc3..c17ea4611 100644 --- a/cmd/frostfs-lens/internal/schema/metabase/filter.go +++ b/cmd/frostfs-lens/internal/schema/metabase/filter.go @@ -28,18 +28,6 @@ func (b *PrefixContainerBucket) Filter(typ string, val any) common.FilterResult } } -func (b *PrefixContainerKeyBucket) Filter(typ string, val any) common.FilterResult { - switch typ { - case "cid": - id := val.(cid.ID) - return b.resolvers.cidResolver(b.id.Equals(id)) - case "oid": - return b.resolvers.oidResolver(false) - default: - return common.No - } -} - func (b *UserBucket) Filter(typ string, _ any) common.FilterResult { switch typ { case "cid": @@ -63,12 +51,30 @@ func (b *ContainerBucket) Filter(typ string, val any) common.FilterResult { } } -func (b *ValueBucket) Filter(typ string, _ any) common.FilterResult { +func (b *UserAttributeKeyBucket) Filter(typ string, val any) common.FilterResult { switch typ { case "cid": - return b.resolvers.cidResolver(false) + id := val.(cid.ID) + return common.IfThenElse(b.id.Equals(id), common.Yes, common.No) case "oid": - return b.resolvers.oidResolver(false) + return common.Maybe + case "key": + key := val.(string) + return common.IfThenElse(b.key == key, common.Yes, common.No) + case "value": + return common.Maybe + default: + return common.No + } +} + +func (b *UserAttributeValueBucket) Filter(typ string, val any) common.FilterResult { + switch typ { + case "oid": + return common.Maybe + case "value": + value := val.(string) + return common.IfThenElse(b.value == value, common.Yes, common.No) default: return common.No } diff --git a/cmd/frostfs-lens/internal/schema/metabase/parsers.go b/cmd/frostfs-lens/internal/schema/metabase/parsers.go index afec2e7d3..f162a09fb 100644 --- a/cmd/frostfs-lens/internal/schema/metabase/parsers.go +++ b/cmd/frostfs-lens/internal/schema/metabase/parsers.go @@ -104,19 +104,8 @@ var ( }, ) - UserAttributeParser = NewPrefixContainerKeyBucketParser( - UserAttribute, - NewValueBucketParser( - records.UserAttributeRecordParser, - Resolvers{ - cidResolver: StrictResolver, - oidResolver: LenientResolver, - }, - ), - Resolvers{ - cidResolver: StrictResolver, - oidResolver: LenientResolver, - }, + UserAttributeParser = NewUserAttributeKeyBucketParser( + NewUserAttributeValueBucketParser(records.UserAttributeRecordParser), ) PayloadHashParser = NewPrefixContainerBucketParser(PayloadHash, records.PayloadHashRecordParser, Resolvers{ diff --git a/cmd/frostfs-lens/internal/schema/metabase/string.go b/cmd/frostfs-lens/internal/schema/metabase/string.go index 950231c8e..b1ff498e1 100644 --- a/cmd/frostfs-lens/internal/schema/metabase/string.go +++ b/cmd/frostfs-lens/internal/schema/metabase/string.go @@ -10,10 +10,6 @@ func (b *PrefixContainerBucket) String() string { return fmt.Sprintf("%s CID [blue]%s[white]", b.prefix, b.id) } -func (b *PrefixContainerKeyBucket) String() string { - return fmt.Sprintf("%s CID [blue]%-44s[white] %s", b.prefix, b.id, b.key) -} - func (b *UserBucket) String() string { return fmt.Sprintf("UID [blue]%s[white]", b.id) } @@ -22,6 +18,10 @@ func (b *ContainerBucket) String() string { return fmt.Sprintf("CID [blue]%s[white]", b.id) } -func (b *ValueBucket) String() string { +func (b *UserAttributeKeyBucket) String() string { + return fmt.Sprintf("%s CID [blue]%-44s[white] %s", b.prefix, b.id, b.key) +} + +func (b *UserAttributeValueBucket) String() string { return b.value } diff --git a/cmd/frostfs-lens/internal/tuiutil/ui.go b/cmd/frostfs-lens/internal/tuiutil/ui.go index 6d9bb22cb..cbd418bae 100644 --- a/cmd/frostfs-lens/internal/tuiutil/ui.go +++ b/cmd/frostfs-lens/internal/tuiutil/ui.go @@ -269,7 +269,7 @@ func (ui *UI) mountAndUpdate(ctx context.Context) { // exceptional, we always want to have previously mounted page saved. ui.saveMounted = true - // After application started the first mount either fails or succeeds. + // If the first mount is canceled, quit the application. ui.isFirstMount = false }() @@ -427,15 +427,17 @@ func (ui *UI) handleInputOnSearching(event *tcell.EventKey) { } ui.isSearching = false - ui.searchBar.SetText("") + // ui.searchBar.SetText("") case k == tcell.KeyEsc: ui.isSearching = false - ui.searchBar.SetText("") + // ui.searchBar.SetText("") case (k == tcell.KeyBackspace2 || m&tcell.ModCtrl != 0 && k == tcell.KeyETB) && len(ui.searchBar.GetText()) == 0: ui.isSearching = false default: ui.searchBar.InputHandler()(event, func(tview.Primitive) {}) } + + ui.Box.MouseHandler() } func (ui *UI) processPrompt(prompt string) (filter *Filter, err error) {