[#1223] lens/tui: Add search by attribute key or key and value

Signed-off-by: Aleksey Savchuk <a.savchuk@yadro.com>
This commit is contained in:
Aleksey Savchuk 2024-08-12 01:10:42 +03:00
parent c54ae73841
commit 1504a1ae19
No known key found for this signature in database
8 changed files with 173 additions and 132 deletions

View file

@ -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)

View file

@ -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 <cid>/<oid>")
}
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 <key> or <key>/<value>")
}
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
}

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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{

View file

@ -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
}

View file

@ -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) {