From 717e859a6f19300712280ed5d6b092849d97ce89 Mon Sep 17 00:00:00 2001 From: Navid Yaghoobi Date: Tue, 28 Nov 2023 20:08:46 +1100 Subject: [PATCH] running golangci-lint on ui/images package Signed-off-by: Navid Yaghoobi --- .golangci.yml | 1 - ui/images/commands.go | 107 +++++++-- ui/images/data.go | 11 +- ui/images/draw.go | 29 ++- ui/images/images.go | 105 +++++++-- ui/images/imgdialogs/build.go | 311 +++++++++++++++++++------ ui/images/imgdialogs/build_progress.go | 45 ++-- ui/images/imgdialogs/history.go | 109 +++++---- ui/images/imgdialogs/import.go | 67 ++++-- ui/images/imgdialogs/push.go | 86 ++++--- ui/images/imgdialogs/save.go | 81 +++++-- ui/images/imgdialogs/search.go | 160 +++++++++---- ui/images/key.go | 26 ++- ui/images/refresh.go | 16 +- 14 files changed, 853 insertions(+), 301 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 00036a9dc..6da59f19b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,7 +4,6 @@ run: - ".*_test.go" skip-dirs: - ui/containers - - ui/images linters: enable-all: true disable: diff --git a/ui/images/commands.go b/ui/images/commands.go index cee8c9164..60cae97eb 100644 --- a/ui/images/commands.go +++ b/ui/images/commands.go @@ -7,12 +7,10 @@ import ( "github.com/containers/podman-tui/pdcs/images" "github.com/containers/podman-tui/ui/dialogs" "github.com/containers/podman-tui/ui/style" - "github.com/rs/zerolog/log" ) -func (img *Images) runCommand(cmd string) { - +func (img *Images) runCommand(cmd string) { //nolint:cyclop switch cmd { case "build": img.buildDialog.Display() @@ -24,7 +22,7 @@ func (img *Images) runCommand(cmd string) { img.importDialog.Display() case "inspect": img.inspect() - case "prune": + case "prune": //nolint:goconst img.cprune() case "push": img.cpush() @@ -52,16 +50,21 @@ func (img *Images) displayError(title string, err error) { func (img *Images) build() { img.buildDialog.Hide() + opts, err := img.buildDialog.ImageBuildOptions() if err != nil { img.buildPrgDialog.Hide() img.displayError("IMAGE BUILD ERROR", err) + return } + if opts.BuildOptions.ContextDirectory == "" && len(opts.ContainerFiles) == 0 { - img.displayError("IMAGE BUILD ERROR", fmt.Errorf("both context directory path and container files fields are empty")) + img.displayError("IMAGE BUILD ERROR", errNoBuildDirOrCntFile) + return } + img.buildPrgDialog.Display() writer := img.buildPrgDialog.LogWriter() opts.BuildOptions.Out = writer @@ -69,15 +72,20 @@ func (img *Images) build() { buildFunc := func() { report, err := images.Build(opts) + img.buildPrgDialog.Hide() + if err != nil { img.displayError("IMAGE BUILD ERROR", err) + return } + img.messageDialog.SetTitle("podman image build") img.messageDialog.SetText(dialogs.MessageImageInfo, report, "") img.messageDialog.Display() } + go buildFunc() } @@ -85,17 +93,23 @@ func (img *Images) diff() { imageID, imageName := img.getSelectedItem() if imageID == "" { - img.displayError("", fmt.Errorf("there is no image to display diff")) + img.displayError("", errNoImageToDiff) + return } + img.progressDialog.SetTitle("image diff in progress") img.progressDialog.Display() + diff := func() { data, err := images.Diff(imageID) + img.progressDialog.Hide() + if err != nil { title := fmt.Sprintf("IMAGE (%s) DIFF ERROR", imageID) img.displayError(title, err) + return } @@ -105,14 +119,17 @@ func (img *Images) diff() { img.messageDialog.SetText(dialogs.MessageImageInfo, headerLabel, strings.Join(data, "\n")) img.messageDialog.Display() } + go diff() } func (img *Images) history() { if img.selectedID == "" { - img.displayError("", fmt.Errorf("there is no image to display history")) + img.displayError("", errNoImageToHistory) + return } + result, err := images.History(img.selectedID) if err != nil { title := fmt.Sprintf("IMAGE (%s) HISTORY ERROR", img.selectedID) @@ -122,45 +139,52 @@ func (img *Images) history() { img.historyDialog.SetImageInfo(img.selectedID, img.selectedName) img.historyDialog.UpdateResults(result) img.historyDialog.Display() - } func (img *Images) imageImport() { importOpts, err := img.importDialog.ImageImportOptions() if err != nil { img.displayError("IMAGE IMPORT ERROR", err) + return } + img.importDialog.Hide() img.progressDialog.SetTitle("image import in progress") img.progressDialog.Display() + importFunc := func() { newImageID, err := images.Import(importOpts) + img.progressDialog.Hide() + if err != nil { img.displayError("IMAGE IMPORT ERROR", err) + return } - headerLabel := fmt.Sprintf("%s", newImageID) - img.messageDialog.SetTitle("podman image import") - img.messageDialog.SetText(dialogs.MessageImageInfo, headerLabel, "") + img.messageDialog.SetText(dialogs.MessageImageInfo, newImageID, "") img.messageDialog.Display() } + go importFunc() } func (img *Images) inspect() { imageID, imageName := img.getSelectedItem() if imageID == "" { - img.displayError("", fmt.Errorf("there is no image to display inspect")) + img.displayError("", errNoImageToInspect) + return } + data, err := images.Inspect(imageID) if err != nil { title := fmt.Sprintf("IMAGE (%s) INSPECT ERROR", imageID) img.displayError(title, err) + return } @@ -181,23 +205,30 @@ func (img *Images) cprune() { func (img *Images) prune() { img.progressDialog.SetTitle("image purne in progress") img.progressDialog.Display() + prune := func() { err := images.Prune() + img.progressDialog.Hide() + if err != nil { img.displayError("IMAGE PRUNE ERROR", err) + return } } + go prune() } func (img *Images) cpush() { id, name := img.getSelectedItem() if id == "" { - img.displayError("", fmt.Errorf("there is no image to push")) + img.displayError("", errNoImageToPush) + return } + img.pushDialog.SetImageInfo(id, name) img.pushDialog.Display() } @@ -213,20 +244,24 @@ func (img *Images) push() { img.progressDialog.Hide() title := fmt.Sprintf("IMAGE (%s) PUSH ERROR", img.selectedID) img.displayError(title, err) + return } + img.progressDialog.Hide() } - go push() + go push() } func (img *Images) rm() { imageID, imageName := img.getSelectedItem() if imageID == "" { - img.displayError("", fmt.Errorf("there is no image to remove")) + img.displayError("", errNoImageToRemove) + return } + img.confirmDialog.SetTitle("podman image remove") img.confirmData = "rm" bgColor := style.GetColorHex(style.DialogBorderColor) @@ -242,9 +277,12 @@ func (img *Images) remove() { imageID, imageName := img.getSelectedItem() img.progressDialog.SetTitle("image remove in progress") img.progressDialog.Display() + remove := func() { data, err := images.Remove(imageID) + img.progressDialog.Hide() + if err != nil { title := fmt.Sprintf("IMAGE (%s) REMOVE ERROR", imageID) img.displayError(title, err) @@ -255,17 +293,19 @@ func (img *Images) remove() { img.messageDialog.SetText(dialogs.MessageImageInfo, headerLabel, strings.Join(data, "\n")) img.messageDialog.Display() } - } + go remove() } func (img *Images) csave() { id, name := img.getSelectedItem() if id == "" { - img.displayError("", fmt.Errorf("there is no image to save")) + img.displayError("", errNoImageToSave) + return } + img.saveDialog.SetImageInfo(id, name) img.saveDialog.Display() } @@ -275,22 +315,28 @@ func (img *Images) save() { if err != nil { title := fmt.Sprintf("IMAGE (%s) SAVE ERROR", img.selectedID) img.displayError(title, err) + return } + img.saveDialog.Hide() img.progressDialog.SetTitle("image save in progress") img.progressDialog.Display() + saveFunc := func() { err := images.Save(img.selectedID, saveOpts) + img.progressDialog.Hide() + if err != nil { title := fmt.Sprintf("IMAGE (%s) SAVE ERROR", img.selectedID) img.displayError(title, err) + return } } - go saveFunc() + go saveFunc() } func (img *Images) search(term string) { @@ -304,23 +350,29 @@ func (img *Images) search(term string) { title := fmt.Sprintf("IMAGE (%s) SEARCH ERROR", img.selectedID) img.displayError(title, err) } + img.searchDialog.UpdateResults(result) img.progressDialog.Hide() } + go search(term) } func (img *Images) ctag() { if img.selectedID == "" { - img.displayError("", fmt.Errorf("there is no image to tag")) + img.displayError("", errNoImageToTag) + return } + img.cmdInputDialog.SetTitle("podman image tag") + fgColor := style.GetColorHex(style.DialogFgColor) bgColor := style.GetColorHex(style.DialogBorderColor) description := fmt.Sprintf("[%s:%s:b]IMAGE ID:[:-:-] %s (%s)", fgColor, bgColor, img.selectedID, img.selectedName) + img.cmdInputDialog.SetDescription(description) img.cmdInputDialog.SetSelectButtonLabel("tag") img.cmdInputDialog.SetLabel("target name") @@ -328,6 +380,7 @@ func (img *Images) ctag() { img.tag(img.cmdInputDialog.GetInputText()) img.cmdInputDialog.Hide() }) + img.cmdInputDialog.Display() } @@ -340,9 +393,11 @@ func (img *Images) tag(tag string) { func (img *Images) cuntag() { if img.selectedID == "" { - img.displayError("", fmt.Errorf("there is no image to untag")) + img.displayError("", errNoImageToUntag) + return } + img.cmdInputDialog.SetTitle("podman image untag") img.cmdInputDialog.SetDescription("") img.cmdInputDialog.SetSelectButtonLabel("untag") @@ -352,13 +407,15 @@ func (img *Images) cuntag() { img.untag(img.cmdInputDialog.GetInputText()) img.cmdInputDialog.Hide() }) + img.cmdInputDialog.Display() } func (img *Images) tree() { imageID, imageName := img.getSelectedItem() if imageID == "" { - img.displayError("", fmt.Errorf("there is no image to display tree")) + img.displayError("", errNoImageToTree) + return } @@ -367,23 +424,28 @@ func (img *Images) tree() { if err != nil { title := fmt.Sprintf("IMAGE (%s) TREE ERROR", imageID) img.displayError(title, err) + return } headerLabel := fmt.Sprintf("%12s (%s)", imageID, imageName) + img.progressDialog.Hide() img.messageDialog.SetTitle("podman image tree") img.messageDialog.SetText(dialogs.MessageImageInfo, headerLabel, tree) img.messageDialog.Display() } + img.progressDialog.SetTitle("image tree in progress") img.progressDialog.Display() + go retTree() } func (img *Images) untag(id string) { if err := images.Untag(id); err != nil { title := fmt.Sprintf("IMAGE (%s) UNTAG ERROR", img.selectedID) + img.displayError(title, err) } } @@ -392,13 +454,16 @@ func (img *Images) pull(image string) { img.progressDialog.SetTitle("image pull in progress") img.progressDialog.Display() + pull := func(name string) { err := images.Pull(name) if err != nil { title := fmt.Sprintf("IMAGE (%s) PULL ERROR", img.selectedID) img.displayError(title, err) } + img.progressDialog.Hide() } + go pull(image) } diff --git a/ui/images/data.go b/ui/images/data.go index 8dc903c94..7a489dcc7 100644 --- a/ui/images/data.go +++ b/ui/images/data.go @@ -10,37 +10,41 @@ import ( "github.com/rs/zerolog/log" ) -// UpdateData retrieves images list data +// UpdateData retrieves images list data. func (img *Images) UpdateData() { images, err := images.List() if err != nil { log.Error().Msgf("view: images update %v", err) img.errorDialog.SetText(fmt.Sprintf("%v", err)) img.errorDialog.Display() + return } + img.imagesList.mu.Lock() img.imagesList.report = images img.imagesList.mu.Unlock() - } func (img *Images) getData() []images.ImageListReporter { img.imagesList.mu.Lock() data := img.imagesList.report img.imagesList.mu.Unlock() + return data } -// ClearData clears table data +// ClearData clears table data. func (img *Images) ClearData() { img.imagesList.mu.Lock() img.imagesList.report = nil img.imagesList.mu.Unlock() img.table.Clear() + expand := 1 fgColor := style.PageHeaderFgColor bgColor := style.PageHeaderBgColor + for i := 0; i < len(img.headers); i++ { img.table.SetCell(0, i, tview.NewTableCell(fmt.Sprintf("[::b]%s", strings.ToUpper(img.headers[i]))). @@ -50,5 +54,6 @@ func (img *Images) ClearData() { SetAlign(tview.AlignLeft). SetSelectable(false)) } + img.table.SetTitle(fmt.Sprintf("[::b]%s[0]", strings.ToUpper(img.title))) } diff --git a/ui/images/draw.go b/ui/images/draw.go index a38837ed6..8dac08bad 100644 --- a/ui/images/draw.go +++ b/ui/images/draw.go @@ -5,44 +5,56 @@ import ( ) // Draw draws this primitive onto the screen. -func (img *Images) Draw(screen tcell.Screen) { +func (img *Images) Draw(screen tcell.Screen) { //nolint:cyclop img.refresh() img.Box.DrawForSubclass(screen, img) img.Box.SetBorder(false) + x, y, width, height := img.GetInnerRect() + img.table.SetRect(x, y, width, height) img.table.SetBorder(true) img.table.Draw(screen) x, y, width, height = img.table.GetInnerRect() + // error dialog if img.errorDialog.IsDisplay() { img.errorDialog.SetRect(x, y, width, height) img.errorDialog.Draw(screen) + return } - // command dialog dialog + + // command dialog if img.cmdDialog.IsDisplay() { img.cmdDialog.SetRect(x, y, width, height) img.cmdDialog.Draw(screen) + return } + // command input dialog if img.cmdInputDialog.IsDisplay() { img.cmdInputDialog.SetRect(x, y, width, height) img.cmdInputDialog.Draw(screen) + return } + // message dialog if img.messageDialog.IsDisplay() { img.messageDialog.SetRect(x, y, width, height+1) img.messageDialog.Draw(screen) + return } + // confirm dialog if img.confirmDialog.IsDisplay() { img.confirmDialog.SetRect(x, y, width, height) img.confirmDialog.Draw(screen) + return } @@ -51,45 +63,58 @@ func (img *Images) Draw(screen tcell.Screen) { img.searchDialog.SetRect(x, y, width, height) img.searchDialog.Draw(screen) } + // progress dialog if img.progressDialog.IsDisplay() { img.progressDialog.SetRect(x, y, width, height) img.progressDialog.Draw(screen) } + // history dialog if img.historyDialog.IsDisplay() { img.historyDialog.SetRect(x, y, width, height) img.historyDialog.Draw(screen) + return } + // build dialog if img.buildDialog.IsDisplay() { img.buildDialog.SetRect(x, y, width, height) img.buildDialog.Draw(screen) + return } + // build progress dialog if img.buildPrgDialog.IsDisplay() { img.buildPrgDialog.SetRect(x, y, width, height) img.buildPrgDialog.Draw(screen) + return } + // save dialog if img.saveDialog.IsDisplay() { img.saveDialog.SetRect(x, y, width, height) img.saveDialog.Draw(screen) + return } + // import dialog if img.importDialog.IsDisplay() { img.importDialog.SetRect(x, y, width, height) img.importDialog.Draw(screen) + return } + // push dialog if img.pushDialog.IsDisplay() { img.pushDialog.SetRect(x, y, width, height) img.pushDialog.Draw(screen) + return } } diff --git a/ui/images/images.go b/ui/images/images.go index 5d43cbcbb..04a1fa2ce 100644 --- a/ui/images/images.go +++ b/ui/images/images.go @@ -1,6 +1,7 @@ package images import ( + "errors" "fmt" "strings" "sync" @@ -9,11 +10,31 @@ import ( "github.com/containers/podman-tui/ui/dialogs" "github.com/containers/podman-tui/ui/images/imgdialogs" "github.com/containers/podman-tui/ui/style" - "github.com/rivo/tview" ) -// Images implements the images primitive +const ( + viewImageRepoNameColIndex = 0 + iota + viewImageTagColIndex + viewImageIDColIndex + viewImageCreatedAtColIndex + viewImageSizeColIndex +) + +var ( + errNoImageToTree = errors.New("here is no image to display tree") + errNoImageToUntag = errors.New("here is no image to untag") + errNoImageToTag = errors.New("here is no image to tag") + errNoImageToSave = errors.New("here is no image to save") + errNoImageToPush = errors.New("here is no image to push") + errNoImageToHistory = errors.New("here is no image to display history") + errNoImageToDiff = errors.New("here is no image to display diff") + errNoImageToRemove = errors.New("there is no image to remove") + errNoImageToInspect = errors.New("there is no image to display inspect") + errNoBuildDirOrCntFile = errors.New("both context directory path and container files fields are empty") +) + +// Images implements the images primitive. type Images struct { *tview.Box title string @@ -44,7 +65,7 @@ type imageListReport struct { report []images.ImageListReporter } -// NewImages returns images page view +// NewImages returns images page view. func NewImages() *Images { images := &Images{ Box: tview.NewBox(), @@ -134,9 +155,11 @@ func NewImages() *Images { images.remove() } }) + images.confirmDialog.SetCancelFunc(func() { images.confirmDialog.Hide() }) + // set history dialogs functions images.historyDialog.SetCancelFunc(func() { images.historyDialog.Hide() @@ -146,13 +169,16 @@ func NewImages() *Images { images.searchDialog.SetCancelFunc(func() { images.searchDialog.Hide() }) + images.searchDialog.SetSearchFunc(func() { term := images.searchDialog.GetSearchText() if term == "" { return } + images.search(term) }) + images.searchDialog.SetPullFunc(func() { name := images.searchDialog.GetSelectedItem() images.pull(name) @@ -180,123 +206,158 @@ func NewImages() *Images { return images } -// GetTitle returns primitive title +// GetTitle returns primitive title. func (img *Images) GetTitle() string { return img.title } -// HasFocus returns whether or not this primitive has focus -func (img *Images) HasFocus() bool { +// HasFocus returns whether or not this primitive has focus. +func (img *Images) HasFocus() bool { //nolint:cyclop if img.table.HasFocus() || img.messageDialog.HasFocus() { return true } + if img.cmdDialog.HasFocus() || img.cmdInputDialog.HasFocus() { return true } + if img.confirmDialog.HasFocus() || img.errorDialog.HasFocus() { return true } + if img.searchDialog.HasFocus() || img.progressDialog.HasFocus() { return true } + if img.historyDialog.HasFocus() || img.buildDialog.HasFocus() { return true } + if img.buildPrgDialog.HasFocus() || img.saveDialog.HasFocus() { return true } + if img.importDialog.HasFocus() || img.pushDialog.HasFocus() { return true } + return img.Box.HasFocus() } -// SubDialogHasFocus returns whether or not sub dialog primitive has focus -func (img *Images) SubDialogHasFocus() bool { +// SubDialogHasFocus returns whether or not sub dialog primitive has focus. +func (img *Images) SubDialogHasFocus() bool { //nolint:cyclop if img.historyDialog.HasFocus() || img.messageDialog.HasFocus() { return true } + if img.cmdDialog.HasFocus() || img.cmdInputDialog.HasFocus() { return true } + if img.confirmDialog.HasFocus() || img.errorDialog.HasFocus() { return true } + if img.searchDialog.HasFocus() || img.progressDialog.HasFocus() { return true } + if img.buildDialog.HasFocus() || img.buildPrgDialog.HasFocus() { return true } + if img.saveDialog.HasFocus() || img.importDialog.HasFocus() { return true } + return img.pushDialog.HasFocus() } -// Focus is called when this primitive receives focus -func (img *Images) Focus(delegate func(p tview.Primitive)) { - +// Focus is called when this primitive receives focus. +func (img *Images) Focus(delegate func(p tview.Primitive)) { //nolint:cyclop // error dialog if img.errorDialog.IsDisplay() { delegate(img.errorDialog) + return } + // command dialog if img.cmdDialog.IsDisplay() { delegate(img.cmdDialog) + return } + // command input dialog if img.cmdInputDialog.IsDisplay() { delegate(img.cmdInputDialog) + return } + // message dialog if img.messageDialog.IsDisplay() { delegate(img.messageDialog) + return } + // confirm dialog if img.confirmDialog.IsDisplay() { delegate(img.confirmDialog) + return } + // search dialog if img.searchDialog.IsDisplay() { delegate(img.searchDialog) + return } + // history dialog if img.historyDialog.IsDisplay() { delegate(img.historyDialog) + return } + // build dialog if img.buildDialog.IsDisplay() { delegate(img.buildDialog) + return } + // build progress dialog if img.buildPrgDialog.IsDisplay() { delegate(img.buildPrgDialog) + return } + // save dialog if img.saveDialog.IsDisplay() { delegate(img.saveDialog) + return } // import dialog if img.importDialog.IsDisplay() { delegate(img.importDialog) + return } + // push dialog if img.pushDialog.IsDisplay() { delegate(img.pushDialog) + return } + delegate(img.table) } @@ -304,58 +365,72 @@ func (img *Images) getSelectedItem() (string, string) { if img.table.GetRowCount() <= 1 { return "", "" } + row, _ := img.table.GetSelection() imageRepo := img.table.GetCell(row, 0).Text imageTag := img.table.GetCell(row, 1).Text imageName := imageRepo + ":" + imageTag - imageID := img.table.GetCell(row, 2).Text + imageID := img.table.GetCell(row, 2).Text //nolint:gomnd + return imageID, imageName } -// HideAllDialogs hides all sub dialogs -func (img *Images) HideAllDialogs() { +// HideAllDialogs hides all sub dialogs. +func (img *Images) HideAllDialogs() { //nolint:cyclop if img.errorDialog.IsDisplay() { img.errorDialog.Hide() } + if img.progressDialog.IsDisplay() { img.progressDialog.Hide() } + if img.cmdDialog.IsDisplay() { img.cmdDialog.Hide() } + if img.cmdInputDialog.IsDisplay() { img.cmdInputDialog.Hide() } + if img.messageDialog.IsDisplay() { img.messageDialog.Hide() } + if img.searchDialog.IsDisplay() { img.searchDialog.Hide() } + if img.confirmDialog.IsDisplay() { img.confirmDialog.Hide() } + if img.historyDialog.IsDisplay() { img.historyDialog.Hide() } + if img.buildDialog.IsDisplay() { img.buildDialog.Hide() } + if img.buildPrgDialog.IsDisplay() { img.buildPrgDialog.Hide() } + if img.saveDialog.IsDisplay() { img.saveDialog.Hide() } + if img.importDialog.IsDisplay() { img.importDialog.Hide() } + if img.pushDialog.IsDisplay() { img.pushDialog.Hide() } } -// SetFastRefreshChannel sets channel for fastRefresh func +// SetFastRefreshChannel sets channel for fastRefresh func. func (img *Images) SetFastRefreshChannel(refresh chan bool) { img.fastRefreshChan = refresh } diff --git a/ui/images/imgdialogs/build.go b/ui/images/imgdialogs/build.go index 366a9dbe9..ec05f8f10 100644 --- a/ui/images/imgdialogs/build.go +++ b/ui/images/imgdialogs/build.go @@ -68,7 +68,7 @@ const ( buildDialogSecurityOptsPageIndex ) -// ImageBuildDialog represents image build dialog primitive +// ImageBuildDialog represents image build dialog primitive. type ImageBuildDialog struct { *tview.Box layout *tview.Flex @@ -121,18 +121,20 @@ type ImageBuildDialog struct { buildHandler func() } -// NewImageBuildDialog returns new image build dialog primitive -func NewImageBuildDialog() *ImageBuildDialog { +// NewImageBuildDialog returns new image build dialog primitive. +func NewImageBuildDialog() *ImageBuildDialog { //nolint:maintidx buildDialog := &ImageBuildDialog{ Box: tview.NewBox(), layout: tview.NewFlex().SetDirection(tview.FlexRow), form: tview.NewForm(), - categoryLabels: []string{"Basic Information", + categoryLabels: []string{ + "Basic Information", "Build Settings", "Capability", "CPU and Memory", "Networking", - "Security Options"}, + "Security Options", + }, categories: tview.NewTextView(), categoryPages: tview.NewPages(), basicInfoPage: tview.NewFlex(), @@ -174,6 +176,7 @@ func NewImageBuildDialog() *ImageBuildDialog { memoryField: tview.NewInputField(), memorySwapField: tview.NewInputField(), } + bgColor := style.DialogBgColor fgColor := style.DialogFgColor inputFieldBgColor := style.InputFieldBgColor @@ -213,7 +216,8 @@ func NewImageBuildDialog() *ImageBuildDialog { define.PullIfMissing.String(), define.PullAlways.String(), define.PullIfNewer.String(), - define.PullNever.String()}, + define.PullNever.String(), + }, nil) buildDialog.pullPolicyField.SetListStyles(ddUnselectedStyle, ddselectedStyle) buildDialog.pullPolicyField.SetFieldBackgroundColor(inputFieldBgColor) @@ -234,6 +238,7 @@ func NewImageBuildDialog() *ImageBuildDialog { // build settings page buildSettingFirstColWidth := 15 + buildDialog.buildArgsField.SetLabel("runtime args:") buildDialog.buildArgsField.SetLabelWidth(buildSettingFirstColWidth) buildDialog.buildArgsField.SetBackgroundColor(bgColor) @@ -242,6 +247,7 @@ func NewImageBuildDialog() *ImageBuildDialog { // format dropdown formatLabel := "output format:" + buildDialog.formatField.SetLabel(formatLabel) buildDialog.formatField.SetTitleAlign(tview.AlignRight) buildDialog.formatField.SetLabelWidth(buildSettingFirstColWidth) @@ -249,13 +255,15 @@ func NewImageBuildDialog() *ImageBuildDialog { buildDialog.formatField.SetLabelColor(fgColor) buildDialog.formatField.SetOptions([]string{ define.OCI, - define.DOCKER}, + define.DOCKER, + }, nil) buildDialog.formatField.SetListStyles(ddUnselectedStyle, ddselectedStyle) buildDialog.formatField.SetFieldBackgroundColor(inputFieldBgColor) // squash squashLabel := "squash:" + buildDialog.SquashField.SetBackgroundColor(bgColor) buildDialog.SquashField.SetBorder(false) buildDialog.SquashField.SetLabel(squashLabel) @@ -265,6 +273,7 @@ func NewImageBuildDialog() *ImageBuildDialog { // layers layersLabel := "layers:" + buildDialog.layersField.SetBackgroundColor(bgColor) buildDialog.layersField.SetBorder(false) buildDialog.layersField.SetLabel(layersLabel) @@ -274,6 +283,7 @@ func NewImageBuildDialog() *ImageBuildDialog { // no-cache noCacheLabel := "no cache:" + buildDialog.noCacheField.SetBackgroundColor(bgColor) buildDialog.noCacheField.SetBorder(false) buildDialog.noCacheField.SetLabel(noCacheLabel) @@ -309,6 +319,7 @@ func NewImageBuildDialog() *ImageBuildDialog { // security options page securityOptionsPAgeLabelWidth := 10 + // selinux Label buildDialog.selinuxLabelField.SetLabel("label:") buildDialog.selinuxLabelField.SetLabelWidth(securityOptionsPAgeLabelWidth) @@ -330,6 +341,7 @@ func NewImageBuildDialog() *ImageBuildDialog { // networking setup page networkingPageLabelWidth := 13 + // network dropdown buildDialog.networkField.SetLabel("network:") buildDialog.networkField.SetLabelWidth(networkingPageLabelWidth) @@ -338,7 +350,8 @@ func NewImageBuildDialog() *ImageBuildDialog { buildDialog.networkField.SetOptions([]string{ define.NetworkDefault.String(), define.NetworkDisabled.String(), - define.NetworkEnabled.String()}, + define.NetworkEnabled.String(), + }, nil) buildDialog.networkField.SetListStyles(ddUnselectedStyle, ddselectedStyle) buildDialog.networkField.SetFieldBackgroundColor(inputFieldBgColor) @@ -381,6 +394,7 @@ func NewImageBuildDialog() *ImageBuildDialog { // capability page capabilityPageLabelWidth := 12 + // add capability field buildDialog.addCapabilityField.SetLabel("add cap:") buildDialog.addCapabilityField.SetLabelWidth(capabilityPageLabelWidth) @@ -398,6 +412,7 @@ func NewImageBuildDialog() *ImageBuildDialog { // cpu and memory page cpuMemoryLabelWidth := 14 cpuMemoryFieldWidth := 17 + // cpu period field buildDialog.cpuPeriodField.SetLabel("cpu period:") buildDialog.cpuPeriodField.SetLabelWidth(cpuMemoryLabelWidth) @@ -493,7 +508,7 @@ func (d *ImageBuildDialog) setupLayout() { // layers setup page secondRowLayout := tview.NewFlex().SetDirection(tview.FlexColumn) secondRowLayout.SetBackgroundColor(bgColor) - secondRowLayout.AddItem(d.formatField, 0, 2, true) + secondRowLayout.AddItem(d.formatField, 0, 2, true) //nolint:gomnd secondRowLayout.AddItem(d.SquashField, 0, 1, true) secondRowLayout.AddItem(d.layersField, 0, 1, true) secondRowLayout.AddItem(d.noCacheField, 0, 1, true) @@ -501,7 +516,7 @@ func (d *ImageBuildDialog) setupLayout() { cntRmRowLayout := tview.NewFlex().SetDirection(tview.FlexColumn) cntRmRowLayout.SetBackgroundColor(bgColor) cntRmRowLayout.AddItem(d.forceRemoveCntField, 0, 1, true) - cntRmRowLayout.AddItem(d.removeCntField, 0, 2, true) + cntRmRowLayout.AddItem(d.removeCntField, 0, 2, true) //nolint:gomnd // build setup page d.buildInfoPage.SetDirection(tview.FlexRow) @@ -579,43 +594,45 @@ func (d *ImageBuildDialog) setupLayout() { // add it to layout. _, layoutWidth := utils.AlignStringListWidth(d.categoryLabels) layout := tview.NewFlex().SetDirection(tview.FlexColumn) - layout.AddItem(d.categories, layoutWidth+6, 0, true) + layout.AddItem(d.categories, layoutWidth+6, 0, true) //nolint:gomnd layout.AddItem(d.categoryPages, 0, 1, true) layout.SetBackgroundColor(bgColor) d.layout.AddItem(layout, 0, 1, true) } -// Display displays this primitive +// Display displays this primitive. func (d *ImageBuildDialog) Display() { d.focusElement = buildDialogContextDirectoryPathFieldFocus d.initData() d.display = true } -// IsDisplay returns true if primitive is shown +// IsDisplay returns true if primitive is shown. func (d *ImageBuildDialog) IsDisplay() bool { return d.display } -// Hide stops displaying this primitive +// Hide stops displaying this primitive. func (d *ImageBuildDialog) Hide() { d.display = false } -// HasFocus returns whether or not this primitive has focus +// HasFocus returns whether or not this primitive has focus. func (d *ImageBuildDialog) HasFocus() bool { if d.categories.HasFocus() || d.categoryPages.HasFocus() { return true } + if d.form.HasFocus() || d.layout.HasFocus() { return true } + return d.Box.HasFocus() } -// Focus is called when this primitive receives focus -func (d *ImageBuildDialog) Focus(delegate func(p tview.Primitive)) { +// Focus is called when this primitive receives focus. +func (d *ImageBuildDialog) Focus(delegate func(p tview.Primitive)) { //nolint:gocyclo,cyclop switch d.focusElement { // form focus case buildDialogFormFocus: @@ -625,10 +642,13 @@ func (d *ImageBuildDialog) Focus(delegate func(p tview.Primitive)) { d.focusElement = buildDialogCategoriesFocus // category text view d.Focus(delegate) d.form.SetFocus(0) + return nil } + return event }) + delegate(d.form) // category text view case buildDialogCategoriesFocus: @@ -636,16 +656,20 @@ func (d *ImageBuildDialog) Focus(delegate func(p tview.Primitive)) { if event.Key() == utils.SwitchFocusKey.Key { d.focusElement = buildDialogCategoryPagesFocus // category page view d.Focus(delegate) + return nil } + // scroll between categories event = utils.ParseKeyEventKey(event) if event.Key() == tcell.KeyDown { d.nextCategory() } + if event.Key() == tcell.KeyUp { d.previousCategory() } + return event }) delegate(d.categories) @@ -722,17 +746,17 @@ func (d *ImageBuildDialog) Focus(delegate func(p tview.Primitive)) { // category page case buildDialogCategoryPagesFocus: delegate(d.categoryPages) - } } -// InputHandler returns input handler function for this primitive -func (d *ImageBuildDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { +// InputHandler returns input handler function for this primitive. +func (d *ImageBuildDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { //nolint:gocognit,gocyclo,lll,cyclop return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { log.Debug().Msgf("image build dialog: event %v received", event) if event.Key() == utils.CloseDialogKey.Key { if !(d.pullPolicyField.HasFocus() || d.networkField.HasFocus() || d.formatField.HasFocus()) { d.cancelHandler() + return } } @@ -743,20 +767,26 @@ func (d *ImageBuildDialog) InputHandler() func(event *tcell.EventKey, setFocus f if event.Key() == utils.SwitchFocusKey.Key { d.setBasicInfoPageNextFocus() } + handler(event, setFocus) + return } } + if d.formatField.HasFocus() { event = utils.ParseKeyEventKey(event) if handler := d.formatField.InputHandler(); handler != nil { if event.Key() == utils.SwitchFocusKey.Key { d.setBuildSettingsPageNextFocus() } + handler(event, setFocus) + return } } + if d.networkField.HasFocus() { event = utils.ParseKeyEventKey(event) if handler := d.networkField.InputHandler(); handler != nil { @@ -764,79 +794,102 @@ func (d *ImageBuildDialog) InputHandler() func(event *tcell.EventKey, setFocus f d.setNetworkingPageNextFocus() } handler(event, setFocus) + return } } + // basic info page if d.basicInfoPage.HasFocus() { if handler := d.basicInfoPage.InputHandler(); handler != nil { if event.Key() == utils.SwitchFocusKey.Key { d.setBasicInfoPageNextFocus() } + handler(event, setFocus) + return } } + // build settings page if d.buildInfoPage.HasFocus() { if handler := d.buildInfoPage.InputHandler(); handler != nil { if event.Key() == tcell.KeyTab { d.setBuildSettingsPageNextFocus() } + handler(event, setFocus) + return } } + // security options page if d.securityOptsPage.HasFocus() { if handler := d.securityOptsPage.InputHandler(); handler != nil { if event.Key() == tcell.KeyTab { d.setSecurityOptionsPageNextFocus() } + handler(event, setFocus) + return } } + // networking page if d.networkingPage.HasFocus() { if handler := d.networkingPage.InputHandler(); handler != nil { if event.Key() == tcell.KeyTab { d.setNetworkingPageNextFocus() } + handler(event, setFocus) + return } } + // capability page if d.capabilityPage.HasFocus() { if handler := d.capabilityPage.InputHandler(); handler != nil { if event.Key() == tcell.KeyTab { d.setCapabilityPageNextFocus() } + handler(event, setFocus) + return } } + // cpu and memory page if d.cpuMemoryPage.HasFocus() { if handler := d.cpuMemoryPage.InputHandler(); handler != nil { if event.Key() == tcell.KeyTab { d.setCPUMemoryPageNextFocus() } + handler(event, setFocus) + return } } + if d.categories.HasFocus() { if categroryHandler := d.categories.InputHandler(); categroryHandler != nil { categroryHandler(event, setFocus) + return } } + // form if d.form.HasFocus() { if formHandler := d.form.InputHandler(); formHandler != nil { formHandler(event, setFocus) + return } } @@ -845,16 +898,15 @@ func (d *ImageBuildDialog) InputHandler() func(event *tcell.EventKey, setFocus f // SetRect set rects for this primitive. func (d *ImageBuildDialog) SetRect(x, y, width, height int) { - if width > buildDialogMaxWidth { - emptySpace := (width - buildDialogMaxWidth) / 2 - x = x + emptySpace + emptySpace := (width - buildDialogMaxWidth) / 2 //nolint:gomnd + x += emptySpace width = buildDialogMaxWidth } if height > buildDialogHeight { - emptySpace := (height - buildDialogHeight) / 2 - y = y + emptySpace + emptySpace := (height - buildDialogHeight) / 2 //nolint:gomnd + y += emptySpace height = buildDialogHeight } @@ -866,25 +918,28 @@ func (d *ImageBuildDialog) Draw(screen tcell.Screen) { if !d.display { return } + d.Box.DrawForSubclass(screen, d) x, y, width, height := d.Box.GetInnerRect() d.layout.SetRect(x, y, width, height) d.layout.Draw(screen) } -// SetCancelFunc sets form cancel button selected function +// SetCancelFunc sets form cancel button selected function. func (d *ImageBuildDialog) SetCancelFunc(handler func()) *ImageBuildDialog { d.cancelHandler = handler - cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) + cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) //nolint:gomnd cancelButton.SetSelectedFunc(handler) + return d } -// SetBuildFunc sets form build button selected function +// SetBuildFunc sets form build button selected function. func (d *ImageBuildDialog) SetBuildFunc(handler func()) *ImageBuildDialog { d.buildHandler = handler buildButton := d.form.GetButton(d.form.GetButtonCount() - 1) buildButton.SetSelectedFunc(handler) + return d } @@ -944,15 +999,21 @@ func (d *ImageBuildDialog) setActiveCategory(index int) { ctgBgColor := style.GetColorHex(bgColor) d.activePageIndex = index d.categories.Clear() + var ctgList []string + alignedList, _ := utils.AlignStringListWidth(d.categoryLabels) + for i := 0; i < len(alignedList); i++ { if i == index { ctgList = append(ctgList, fmt.Sprintf("[%s:%s:b]-> %s ", ctgFgColor, ctgBgColor, alignedList[i])) + continue } + ctgList = append(ctgList, fmt.Sprintf("[-:-:-] %s ", alignedList[i])) } + d.categories.SetText(strings.Join(ctgList, "\n")) // switch the page @@ -962,73 +1023,139 @@ func (d *ImageBuildDialog) setActiveCategory(index int) { func (d *ImageBuildDialog) nextCategory() { activePage := d.activePageIndex if d.activePageIndex < len(d.categoryLabels)-1 { - activePage = activePage + 1 + activePage++ d.setActiveCategory(activePage) + return } + d.setActiveCategory(0) } func (d *ImageBuildDialog) previousCategory() { activePage := d.activePageIndex if d.activePageIndex > 0 { - activePage = activePage - 1 + activePage-- d.setActiveCategory(activePage) + return } + d.setActiveCategory(len(d.categoryLabels) - 1) } func (d *ImageBuildDialog) setBasicInfoPageNextFocus() { if d.contextDirectoryPath.HasFocus() { d.focusElement = buildDialogContainerfilePathFocus - } else if d.containerFilePath.HasFocus() { + + return + } + + if d.containerFilePath.HasFocus() { d.focusElement = buildDialogPullPolicyFieldFocus - } else if d.pullPolicyField.HasFocus() { + + return + } + + if d.pullPolicyField.HasFocus() { d.focusElement = buildDialogTagFieldFocus - } else if d.tagField.HasFocus() { + + return + } + + if d.tagField.HasFocus() { d.focusElement = buildDialogRegistryFieldFocus - } else { - d.focusElement = buildDialogFormFocus + + return } + + d.focusElement = buildDialogFormFocus } func (d *ImageBuildDialog) setNetworkingPageNextFocus() { if d.networkField.HasFocus() { d.focusElement = buildDialogHTTPProxyFieldFocus - } else if d.httpProxyField.HasFocus() { + + return + } + + if d.httpProxyField.HasFocus() { d.focusElement = buildDialogAddHostFieldFocus - } else if d.addHostField.HasFocus() { + + return + } + + if d.addHostField.HasFocus() { d.focusElement = buildDialogDNSServersFieldFocus - } else if d.dnsServersField.HasFocus() { + + return + } + + if d.dnsServersField.HasFocus() { d.focusElement = buildDialogDNSOptionsFieldFocus - } else if d.dnsOptionsField.HasFocus() { + + return + } + + if d.dnsOptionsField.HasFocus() { d.focusElement = buildDialogDNSSearchFieldFocus - } else { - d.focusElement = buildDialogFormFocus + + return } + + d.focusElement = buildDialogFormFocus } func (d *ImageBuildDialog) setBuildSettingsPageNextFocus() { if d.buildArgsField.HasFocus() { d.focusElement = buildDialogFormatFieldFocus - } else if d.formatField.HasFocus() { + + return + } + + if d.formatField.HasFocus() { d.focusElement = buildDialogSquashFieldFocus - } else if d.SquashField.HasFocus() { + + return + } + + if d.SquashField.HasFocus() { d.focusElement = buildDialogLayersFieldFocus - } else if d.layersField.HasFocus() { + + return + } + + if d.layersField.HasFocus() { d.focusElement = buildDialogNoCacheFieldfocus - } else if d.noCacheField.HasFocus() { + + return + } + + if d.noCacheField.HasFocus() { d.focusElement = buildDialogLabelsFieldFocus - } else if d.labelsField.HasFocus() { + + return + } + + if d.labelsField.HasFocus() { d.focusElement = buildDialogAnnotationFieldFocus - } else if d.annotationsField.HasFocus() { + + return + } + + if d.annotationsField.HasFocus() { d.focusElement = buildDialogForceRemoveCntFieldFocus - } else if d.forceRemoveCntField.HasFocus() { + + return + } + + if d.forceRemoveCntField.HasFocus() { d.focusElement = buildDialogRemoveCntFieldFocus - } else { - d.focusElement = buildDialogFormFocus + + return } + + d.focusElement = buildDialogFormFocus } func (d *ImageBuildDialog) setCapabilityPageNextFocus() { @@ -1042,34 +1169,61 @@ func (d *ImageBuildDialog) setCapabilityPageNextFocus() { func (d *ImageBuildDialog) setCPUMemoryPageNextFocus() { if d.cpuPeriodField.HasFocus() { d.focusElement = buildDialogCPUQuataFieldFocus - } else if d.cpuQuataField.HasFocus() { + + return + } + + if d.cpuQuataField.HasFocus() { d.focusElement = buildDialogCPUSharesFieldFocus - } else if d.cpuSharesField.HasFocus() { + + return + } + + if d.cpuSharesField.HasFocus() { d.focusElement = buildDialogCPUSetCpusFieldFocus - } else if d.cpuSetCpusField.HasFocus() { + + return + } + + if d.cpuSetCpusField.HasFocus() { d.focusElement = buildDialogCPUSetMemsFieldFocus - } else if d.cpuSetMemsField.HasFocus() { + + return + } + + if d.cpuSetMemsField.HasFocus() { d.focusElement = buildDialogMemoryFieldFocus - } else if d.memoryField.HasFocus() { + + return + } + + if d.memoryField.HasFocus() { d.focusElement = buildDialogMemorySwapFieldFocus - } else { - d.focusElement = buildDialogFormFocus + + return } + + d.focusElement = buildDialogFormFocus } func (d *ImageBuildDialog) setSecurityOptionsPageNextFocus() { if d.selinuxLabelField.HasFocus() { d.focusElement = buildDialogApparmorProfileFieldFocus - } else if d.apparmorProfileField.HasFocus() { + + return + } + + if d.apparmorProfileField.HasFocus() { d.focusElement = buildDialogSeccompProfilePathFieldFocus - } else { - d.focusElement = buildDialogFormFocus + + return } -} -// ImageBuildOptions returns image build options -func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) { + d.focusElement = buildDialogFormFocus +} +// ImageBuildOptions returns image build options. +func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) { //nolint:gocognit,gocyclo,cyclop,maintidx,lll var ( memoryLimit int64 memorySwap int64 @@ -1087,6 +1241,7 @@ func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) apparmorProfile string seccompProfilePath string ) + // basic info page // Containerfiles for _, cntFile := range strings.Split(d.containerFilePath.GetText(), " ") { @@ -1095,6 +1250,7 @@ func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) if err != nil { return images.ImageBuildOptions{}, err } + containerFiles = append(containerFiles, cFile) } } @@ -1106,10 +1262,10 @@ func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) dir, err := utils.ResolveHomeDir(d.contextDirectoryPath.GetText()) if err != nil { - return images.ImageBuildOptions{}, fmt.Errorf("cannot resolve home directory %v", err) + return images.ImageBuildOptions{}, fmt.Errorf("cannot resolve home directory %w", err) } - opts.BuildOptions.ContextDirectory = dir + opts.BuildOptions.ContextDirectory = dir opts.BuildOptions.AdditionalTags = append(opts.BuildOptions.AdditionalTags, d.tagField.GetText()) opts.BuildOptions.Registry = d.registryField.GetText() @@ -1139,10 +1295,12 @@ func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) opts.BuildOptions.NoCache = d.noCacheField.IsChecked() opts.BuildOptions.RemoveIntermediateCtrs = d.removeCntField.IsChecked() opts.BuildOptions.ForceRmIntermediateCtrs = d.forceRemoveCntField.IsChecked() + labels := strings.TrimSpace(d.labelsField.GetText()) if labels != "" { opts.BuildOptions.Labels = strings.Split(labels, " ") } + annotations := strings.TrimSpace(d.annotationsField.GetText()) if annotations != "" { opts.BuildOptions.Annotations = strings.Split(annotations, " ") @@ -1161,12 +1319,14 @@ func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) // cpu and memory page opts.BuildOptions.CommonBuildOpts = &define.CommonBuildOptions{} + cpuPeriodVal := d.cpuPeriodField.GetText() if cpuPeriodVal != "" { period, err := strconv.Atoi(cpuPeriodVal) if err != nil { - return images.ImageBuildOptions{}, fmt.Errorf("invalid CPU period value %q %v", cpuPeriodVal, err) + return images.ImageBuildOptions{}, fmt.Errorf("invalid CPU period value %q %w", cpuPeriodVal, err) } + cpuPeriod = uint64(period) } @@ -1174,8 +1334,9 @@ func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) if cpuQuotaVal != "" { quota, err := strconv.Atoi(cpuQuotaVal) if err != nil { - return images.ImageBuildOptions{}, fmt.Errorf("invalid CPU quota value %q %v", cpuQuotaVal, err) + return images.ImageBuildOptions{}, fmt.Errorf("invalid CPU quota value %q %w", cpuQuotaVal, err) } + cpuQuota = int64(quota) } @@ -1183,8 +1344,9 @@ func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) if cpuSharesVal != "" { shares, err := strconv.Atoi(cpuSharesVal) if err != nil { - return images.ImageBuildOptions{}, fmt.Errorf("invalid CPU quota value %q %v", cpuSharesVal, err) + return images.ImageBuildOptions{}, fmt.Errorf("invalid CPU quota value %q %w", cpuSharesVal, err) } + cpuShares = uint64(shares) } @@ -1192,6 +1354,7 @@ func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) if cpuSetCpusVal != "" { cpuSetCpus = cpuSetCpusVal } + cpuSetMemsVal := d.cpuSetMemsField.GetText() if cpuSetMemsVal != "" { cpuSetMems = cpuSetMemsVal @@ -1201,16 +1364,19 @@ func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) if memoryVal != "" { memory, err := strconv.Atoi(memoryVal) if err != nil { - return images.ImageBuildOptions{}, fmt.Errorf("invalid memory value %q %v", memoryVal, err) + return images.ImageBuildOptions{}, fmt.Errorf("invalid memory value %q %w", memoryVal, err) } + memoryLimit = int64(memory) } + memorySwapVal := d.memorySwapField.GetText() if memorySwapVal != "" { swap, err := strconv.Atoi(memorySwapVal) if err != nil { - return images.ImageBuildOptions{}, fmt.Errorf("invalid memory swap value %q %v", memorySwapVal, err) + return images.ImageBuildOptions{}, fmt.Errorf("invalid memory swap value %q %w", memorySwapVal, err) } + memorySwap = int64(swap) } @@ -1238,11 +1404,13 @@ func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) dnsServers = append(dnsServers, dns) } } + for _, do := range strings.Split(d.dnsOptionsField.GetText(), " ") { if do != "" { dnsOptions = append(dnsOptions, do) } } + for _, ds := range strings.Split(d.dnsSearchField.GetText(), " ") { if ds != "" { dnsSearchDomains = append(dnsSearchDomains, ds) @@ -1255,10 +1423,12 @@ func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) labelOpts = append(labelOpts, selinuxLabel) } } + apparmor := strings.TrimSpace(d.apparmorProfileField.GetText()) if apparmor != "" { apparmorProfile = apparmor } + seccomp := strings.TrimSpace(d.seccompProfilePathField.GetText()) if seccomp != "" { seccompProfilePath = seccomp @@ -1283,5 +1453,6 @@ func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) } opts.BuildOptions.CommonBuildOpts = commonOpts + return opts, nil } diff --git a/ui/images/imgdialogs/build_progress.go b/ui/images/imgdialogs/build_progress.go index a6632e67b..e0af4690f 100644 --- a/ui/images/imgdialogs/build_progress.go +++ b/ui/images/imgdialogs/build_progress.go @@ -18,7 +18,7 @@ const ( buildPrgDialogHeight = 20 ) -// ImageBuildProgressDialog implements build progress dialog primitive +// ImageBuildProgressDialog implements build progress dialog primitive. type ImageBuildProgressDialog struct { *tview.Box layout *tview.Flex @@ -31,7 +31,7 @@ type ImageBuildProgressDialog struct { fastRefreshHandler func() } -// NewImageBuildProgressDialog returns new build progress dialog +// NewImageBuildProgressDialog returns new build progress dialog. func NewImageBuildProgressDialog() *ImageBuildProgressDialog { buildPrgDialog := &ImageBuildProgressDialog{ Box: tview.NewBox(), @@ -39,6 +39,7 @@ func NewImageBuildProgressDialog() *ImageBuildProgressDialog { output: tview.NewTextView(), progressBar: tvxwidgets.NewActivityModeGauge(), } + bgColor := style.DialogBgColor outputBgColor := style.TerminalBgColor outputFgColor := style.TerminalFgColor @@ -62,7 +63,7 @@ func NewImageBuildProgressDialog() *ImageBuildProgressDialog { buildPrgDialog.layout.SetBorder(true) buildPrgDialog.layout.SetBorderColor(style.DialogBorderColor) buildPrgDialog.layout.SetTitle("PODMAN IMAGE BUILD") - buildPrgDialog.layout.AddItem(buildPrgDialog.progressBar, 3, 0, false) + buildPrgDialog.layout.AddItem(buildPrgDialog.progressBar, 3, 0, false) //nolint:gomnd outputLayout := tview.NewFlex().SetDirection(tview.FlexColumn) outputLayout.AddItem(utils.EmptyBoxSpace(outputBgColor), 1, 0, false) @@ -73,20 +74,21 @@ func NewImageBuildProgressDialog() *ImageBuildProgressDialog { return buildPrgDialog } -// Display displays this primitive +// Display displays this primitive. func (d *ImageBuildProgressDialog) Display() { d.display = true d.cancelChan = make(chan bool) - d.writerChan = make(chan []byte, 100) + d.writerChan = make(chan []byte, 100) //nolint:gomnd + go d.outputReaderLoop() } -// IsDisplay returns true if primitive is shown +// IsDisplay returns true if primitive is shown. func (d *ImageBuildProgressDialog) IsDisplay() bool { return d.display } -// Hide stops displaying this primitive +// Hide stops displaying this primitive. func (d *ImageBuildProgressDialog) Hide() { d.display = false d.cancelChan <- true @@ -95,39 +97,38 @@ func (d *ImageBuildProgressDialog) Hide() { d.progressBar.Reset() } -// HasFocus returns whether or not this primitive has focus +// HasFocus returns whether or not this primitive has focus. func (d *ImageBuildProgressDialog) HasFocus() bool { if d.layout.HasFocus() || d.output.HasFocus() { return true } + return d.Box.HasFocus() } -// Focus is called when this primitive receives focus +// Focus is called when this primitive receives focus. func (d *ImageBuildProgressDialog) Focus(delegate func(p tview.Primitive)) { delegate(d.layout) } -// InputHandler returns input handler function for this primitive +// InputHandler returns input handler function for this primitive. func (d *ImageBuildProgressDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { log.Debug().Msgf("image build progress dialog: event %v received", event) - }) } // SetRect set rects for this primitive. func (d *ImageBuildProgressDialog) SetRect(x, y, width, height int) { - if width > buildPrgDialogMaxWidth { - emptySpace := (width - buildPrgDialogMaxWidth) / 2 - x = x + emptySpace + emptySpace := (width - buildPrgDialogMaxWidth) / 2 //nolint:gomnd + x += emptySpace width = buildPrgDialogMaxWidth } if height > buildPrgDialogHeight { - emptySpace := (height - buildPrgDialogHeight) / 2 - y = y + emptySpace + emptySpace := (height - buildPrgDialogHeight) / 2 //nolint:gomnd + y += emptySpace height = buildPrgDialogHeight } @@ -139,6 +140,7 @@ func (d *ImageBuildProgressDialog) Draw(screen tcell.Screen) { if !d.display { return } + d.Box.DrawForSubclass(screen, d) x, y, width, height := d.Box.GetInnerRect() d.layout.SetRect(x, y, width, height) @@ -147,7 +149,9 @@ func (d *ImageBuildProgressDialog) Draw(screen tcell.Screen) { func (d *ImageBuildProgressDialog) outputReaderLoop() { tick := time.NewTicker(utils.RefreshInterval) + log.Debug().Msg("image build progress dialog: output reader started") + for { select { case <-tick.C: @@ -156,23 +160,24 @@ func (d *ImageBuildProgressDialog) outputReaderLoop() { log.Debug().Msg("image build progress dialog: output reader stopped") close(d.cancelChan) tick.Stop() + return case data := <-d.writerChan: d.mu.Lock() - d.output.Write(data) + d.output.Write(data) //nolint:errcheck d.mu.Unlock() d.fastRefreshHandler() } } } -// LogWriter returns output log writer -func (d *ImageBuildProgressDialog) LogWriter() channel.WriteCloser { +// LogWriter returns output log writer. +func (d *ImageBuildProgressDialog) LogWriter() channel.WriteCloser { //nolint:ireturn return channel.NewWriter(d.writerChan) } // SetFastRefreshHandler sets fast refresh handler -// fast refresh is used to print image build output as fast as possible +// fast refresh is used to print image build output as fast as possible. func (d *ImageBuildProgressDialog) SetFastRefreshHandler(handler func()) { d.fastRefreshHandler = handler } diff --git a/ui/images/imgdialogs/history.go b/ui/images/imgdialogs/history.go index 0d89dbcac..d7381c366 100644 --- a/ui/images/imgdialogs/history.go +++ b/ui/images/imgdialogs/history.go @@ -16,7 +16,15 @@ const ( commentCellMaxWidth = 20 ) -// ImageHistoryDialog represents image history dialog primitive +const ( + viewHistoryIDColIndex = 0 + iota + viewHistoryCreatedColIndex + viewHistoryCreatedByColIndex + viewHistorySizeColIndex + viewHistoryCommentColIndex +) + +// ImageHistoryDialog represents image history dialog primitive. type ImageHistoryDialog struct { *tview.Box layout *tview.Flex @@ -29,7 +37,7 @@ type ImageHistoryDialog struct { cancelHandler func() } -// NewImageHistoryDialog returns new image history dialog +// NewImageHistoryDialog returns new image history dialog. func NewImageHistoryDialog() *ImageHistoryDialog { dialog := &ImageHistoryDialog{ Box: tview.NewBox(), @@ -45,6 +53,7 @@ func NewImageHistoryDialog() *ImageHistoryDialog { // image info field. imageInfoLabel := "IMAGE ID:" + dialog.imageInfo.SetBackgroundColor(style.DialogBgColor) dialog.imageInfo.SetLabel("[::b]" + imageInfoLabel) dialog.imageInfo.SetLabelWidth(len(imageInfoLabel) + 1) @@ -88,43 +97,46 @@ func NewImageHistoryDialog() *ImageHistoryDialog { return dialog } -// Display displays this primitive +// Display displays this primitive. func (d *ImageHistoryDialog) Display() { d.display = true } -// IsDisplay returns true if primitive is shown +// IsDisplay returns true if primitive is shown. func (d *ImageHistoryDialog) IsDisplay() bool { return d.display } -// Hide stops displaying this primitive +// Hide stops displaying this primitive. func (d *ImageHistoryDialog) Hide() { d.display = false } -// HasFocus returns whether or not this primitive has focus +// HasFocus returns whether or not this primitive has focus. func (d *ImageHistoryDialog) HasFocus() bool { return d.Box.HasFocus() || d.form.HasFocus() } -// Focus is called when this primitive receives focus +// Focus is called when this primitive receives focus. func (d *ImageHistoryDialog) Focus(delegate func(p tview.Primitive)) { delegate(d.form) } -// InputHandler returns input handler function for this primitive +// InputHandler returns input handler function for this primitive. func (d *ImageHistoryDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { log.Debug().Msgf("image history dialog: event %v received", event) if event.Key() == tcell.KeyEsc || event.Key() == tcell.KeyEnter { d.cancelHandler() + return } + event = utils.ParseKeyEventKey(event) - if event.Key() == tcell.KeyDown || event.Key() == tcell.KeyUp || event.Key() == tcell.KeyPgDn || event.Key() == tcell.KeyPgUp { + if event.Key() == tcell.KeyDown || event.Key() == tcell.KeyUp || event.Key() == tcell.KeyPgDn || event.Key() == tcell.KeyPgUp { //nolint:lll if tableHandler := d.table.InputHandler(); tableHandler != nil { tableHandler(event, setFocus) + return } } @@ -134,27 +146,29 @@ func (d *ImageHistoryDialog) InputHandler() func(event *tcell.EventKey, setFocus // SetRect set rects for this primitive. func (d *ImageHistoryDialog) SetRect(x, y, width, height int) { dX := x + dialogs.DialogPadding - dWidth := width - (2 * dialogs.DialogPadding) - dHeight := len(d.results) + dialogs.DialogFormHeight + 5 + dWidth := width - (2 * dialogs.DialogPadding) //nolint:gomnd + dHeight := len(d.results) + dialogs.DialogFormHeight + 5 //nolint:gomnd if dHeight > height { dHeight = height } - tableHeight := dHeight - dialogs.DialogFormHeight - 2 - hs := ((height - dHeight) / 2) + tableHeight := dHeight - dialogs.DialogFormHeight - 2 //nolint:gomnd + + hs := ((height - dHeight) / 2) //nolint:gomnd dY := y + hs d.Box.SetRect(dX, dY, dWidth, dHeight) - //set table height size + + // set table height size d.layout.ResizeItem(d.table, tableHeight, 0) cWidth := d.getCreatedByWidth() + for i := 0; i < d.table.GetRowCount(); i++ { - cell := d.table.GetCell(i, 2) - cell.SetMaxWidth(cWidth / 2) - d.table.SetCell(i, 2, cell) + cell := d.table.GetCell(i, 2) //nolint:gomnd + cell.SetMaxWidth(cWidth / 2) //nolint:gomnd + d.table.SetCell(i, 2, cell) //nolint:gomnd } - } // Draw draws this primitive onto the screen. @@ -162,17 +176,19 @@ func (d *ImageHistoryDialog) Draw(screen tcell.Screen) { if !d.display { return } + d.Box.DrawForSubclass(screen, d) x, y, width, height := d.Box.GetInnerRect() d.layout.SetRect(x, y, width, height) d.layout.Draw(screen) } -// SetCancelFunc sets form cancel button selected function +// SetCancelFunc sets form cancel button selected function. func (d *ImageHistoryDialog) SetCancelFunc(handler func()) *ImageHistoryDialog { d.cancelHandler = handler cancelButton := d.form.GetButton(d.form.GetButtonCount() - 1) cancelButton.SetSelectedFunc(handler) + return d } @@ -183,6 +199,7 @@ func (d *ImageHistoryDialog) initTable() { d.table.Clear() d.table.SetFixed(1, 1) d.table.SetSelectable(true, false) + for i := 0; i < len(d.tableHeaders); i++ { d.table.SetCell(0, i, tview.NewTableCell(fmt.Sprintf("[%s::b]%s", style.GetColorHex(fgColor), strings.ToUpper(d.tableHeaders[i]))). @@ -194,58 +211,63 @@ func (d *ImageHistoryDialog) initTable() { } } -// UpdateResults updates result table +// UpdateResults updates result table. func (d *ImageHistoryDialog) UpdateResults(data [][]string) { d.results = data d.initTable() + alignment := tview.AlignLeft rowIndex := 1 expand := 0 + for i := 0; i < len(data); i++ { id := data[i][0] if len(id) > utils.IDLength { id = id[0:utils.IDLength] } + created := data[i][1] createdBy := data[i][2] size := data[i][3] comment := data[i][4] + if len(comment) > commentCellMaxWidth { comment = comment[0:commentCellMaxWidth] } // id column - d.table.SetCell(rowIndex, 0, + d.table.SetCell(rowIndex, viewHistoryIDColIndex, tview.NewTableCell(id). SetExpansion(expand). SetAlign(alignment)) // created column - d.table.SetCell(rowIndex, 1, + d.table.SetCell(rowIndex, viewHistoryCreatedColIndex, tview.NewTableCell(created). SetExpansion(expand). SetAlign(alignment)) // createdBy column - d.table.SetCell(rowIndex, 2, + d.table.SetCell(rowIndex, viewHistoryCreatedByColIndex, tview.NewTableCell(createdBy). SetExpansion(1). SetAlign(alignment)) // size column - d.table.SetCell(rowIndex, 3, + d.table.SetCell(rowIndex, viewHistorySizeColIndex, tview.NewTableCell(size). SetExpansion(expand). SetAlign(alignment)) // comment column - d.table.SetCell(rowIndex, 4, + d.table.SetCell(rowIndex, viewHistoryCommentColIndex, tview.NewTableCell(comment). SetExpansion(expand). SetAlign(alignment)) rowIndex++ } + if len(data) > 0 { d.table.Select(1, 1) d.table.ScrollToBeginning() @@ -258,34 +280,41 @@ func (d *ImageHistoryDialog) SetImageInfo(id string, name string) { } func (d *ImageHistoryDialog) getCreatedByWidth() int { - var idWidth int - var createdWidth int - var createdByWidth int - var sizeWidth int - var commentWidth int + var ( + idWidth int + createdWidth int + createdByWidth int + sizeWidth int + commentWidth int + ) // get table inner rect - _, _, width, _ := d.table.GetInnerRect() + _, _, width, _ := d.table.GetInnerRect() //nolint:dogsled // get width used by other columns for _, row := range d.results { - if len(row[0]) > idWidth && len(row[0]) <= utils.IDLength { - idWidth = len(row[0]) + if len(row[viewHistoryIDColIndex]) > idWidth && len(row[viewHistoryIDColIndex]) <= utils.IDLength { + idWidth = len(row[viewHistoryIDColIndex]) } - if len(row[1]) > createdWidth { - createdWidth = len(row[1]) + + if len(row[viewHistoryCreatedColIndex]) > createdWidth { + createdWidth = len(row[viewHistoryCreatedColIndex]) } - if len(row[3]) > sizeWidth { - sizeWidth = len(row[3]) + + if len(row[viewHistorySizeColIndex]) > sizeWidth { + sizeWidth = len(row[viewHistorySizeColIndex]) } - if len(row[4]) > commentWidth && len(row[4]) < 40 { - commentWidth = len(row[4]) + + if len(row[viewHistoryCommentColIndex]) > commentWidth && len(row[viewHistoryCommentColIndex]) < 40 { + commentWidth = len(row[viewHistoryCommentColIndex]) } } usedWidth := idWidth + createdWidth + sizeWidth + commentWidth - createdByWidth = width - usedWidth*2 + 8 + createdByWidth = width - usedWidth*2 + 8 //nolint:gomnd + if createdByWidth <= 0 { createdByWidth = 0 } + return createdByWidth } diff --git a/ui/images/imgdialogs/import.go b/ui/images/imgdialogs/import.go index 62b1e7ea3..165b87b03 100644 --- a/ui/images/imgdialogs/import.go +++ b/ui/images/imgdialogs/import.go @@ -1,7 +1,7 @@ package imgdialogs import ( - "fmt" + "errors" "strings" "github.com/containers/podman-tui/pdcs/images" @@ -19,6 +19,8 @@ const ( imageImportDialogMaxHeight = 13 ) +var errImportEmptySource = errors.New("empty source value for the image tarball") + const ( imageImportPathFocus = 0 + iota imageImportCommitMessageFocus @@ -27,7 +29,7 @@ const ( imageImportFormFocus ) -// ImageImportDialog represents image import dialog primitive +// ImageImportDialog represents image import dialog primitive. type ImageImportDialog struct { *tview.Box layout *tview.Flex @@ -42,7 +44,7 @@ type ImageImportDialog struct { focusElement int } -// NewImageImportDialog returns new image import dialog +// NewImageImportDialog returns new image import dialog. func NewImageImportDialog() *ImageImportDialog { dialog := &ImageImportDialog{ Box: tview.NewBox(), @@ -122,41 +124,45 @@ func NewImageImportDialog() *ImageImportDialog { return dialog } -// Display displays this primitive +// Display displays this primitive. func (d *ImageImportDialog) Display() { d.display = true } -// IsDisplay returns true if primitive is shown +// IsDisplay returns true if primitive is shown. func (d *ImageImportDialog) IsDisplay() bool { return d.display } -// Hide stops displaying this primitive +// Hide stops displaying this primitive. func (d *ImageImportDialog) Hide() { d.display = false d.focusElement = imageImportPathFocus + d.path.SetText("") d.change.SetText("") d.commitMessage.SetText("") d.reference.SetText("") } -// HasFocus returns whether or not this primitive has focus +// HasFocus returns whether or not this primitive has focus. func (d *ImageImportDialog) HasFocus() bool { if d.path.HasFocus() || d.commitMessage.HasFocus() { return true } + if d.form.HasFocus() || d.reference.HasFocus() { return true } + if d.change.HasFocus() || d.layout.HasFocus() { return true } + return d.Box.HasFocus() } -// Focus is called when this primitive receives focus +// Focus is called when this primitive receives focus. func (d *ImageImportDialog) Focus(delegate func(p tview.Primitive)) { switch d.focusElement { case imageImportPathFocus: @@ -174,46 +180,60 @@ func (d *ImageImportDialog) Focus(delegate func(p tview.Primitive)) { d.focusElement = imageImportPathFocus d.Focus(delegate) d.form.SetFocus(0) + return nil } + return event }) + delegate(d.form) } } -// InputHandler returns input handler function for this primitive -func (d *ImageImportDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { +// InputHandler returns input handler function for this primitive. +func (d *ImageImportDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { //nolint:cyclop,lll return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { log.Debug().Msgf("image import dialog: event %v received", event) + if event.Key() == tcell.KeyEsc { d.cancelHandler() + return } + if event.Key() == utils.SwitchFocusKey.Key { d.setFocusElement() } + if d.path.HasFocus() { if pathHandler := d.path.InputHandler(); pathHandler != nil { pathHandler(event, setFocus) + return } } + if d.change.HasFocus() { if changeHandler := d.change.InputHandler(); changeHandler != nil { changeHandler(event, setFocus) + return } } + if d.commitMessage.HasFocus() { if commitHandler := d.commitMessage.InputHandler(); commitHandler != nil { commitHandler(event, setFocus) + return } } + if d.reference.HasFocus() { if referenceHandler := d.reference.InputHandler(); referenceHandler != nil { referenceHandler(event, setFocus) + return } } @@ -221,6 +241,7 @@ func (d *ImageImportDialog) InputHandler() func(event *tcell.EventKey, setFocus if d.form.HasFocus() { if formHandler := d.form.InputHandler(); formHandler != nil { formHandler(event, setFocus) + return } } @@ -229,16 +250,15 @@ func (d *ImageImportDialog) InputHandler() func(event *tcell.EventKey, setFocus // SetRect set rects for this primitive. func (d *ImageImportDialog) SetRect(x, y, width, height int) { - if width > imageImportDialogMaxWidth { - emptySpace := (width - imageImportDialogMaxWidth) / 2 - x = x + emptySpace + emptySpace := (width - imageImportDialogMaxWidth) / 2 //nolint:gomnd + x += emptySpace width = imageImportDialogMaxWidth } if height > imageImportDialogMaxHeight { - emptySpace := (height - imageImportDialogMaxHeight) / 2 - y = y + emptySpace + emptySpace := (height - imageImportDialogMaxHeight) / 2 //nolint:gomnd + y += emptySpace height = imageImportDialogMaxHeight } @@ -250,6 +270,7 @@ func (d *ImageImportDialog) Draw(screen tcell.Screen) { if !d.display { return } + d.Box.DrawForSubclass(screen, d) x, y, width, height := d.Box.GetInnerRect() d.layout.SetRect(x, y, width, height) @@ -269,23 +290,25 @@ func (d *ImageImportDialog) setFocusElement() { } } -// SetImportFunc sets form import button selected function +// SetImportFunc sets form import button selected function. func (d *ImageImportDialog) SetImportFunc(handler func()) *ImageImportDialog { d.importHandler = handler importButton := d.form.GetButton(d.form.GetButtonCount() - 1) importButton.SetSelectedFunc(handler) + return d } -// SetCancelFunc sets form cancel button selected function +// SetCancelFunc sets form cancel button selected function. func (d *ImageImportDialog) SetCancelFunc(handler func()) *ImageImportDialog { d.cancelHandler = handler - cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) + cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) //nolint:gomnd cancelButton.SetSelectedFunc(handler) + return d } -// ImageImportOptions return image import options +// ImageImportOptions return image import options. func (d *ImageImportDialog) ImageImportOptions() (images.ImageImportOptions, error) { var ( path string @@ -306,8 +329,9 @@ func (d *ImageImportDialog) ImageImportOptions() (images.ImageImportOptions, err path = strings.TrimSpace(d.path.GetText()) if path == "" { - return opts, fmt.Errorf("empty source value for the image tarball") + return opts, errImportEmptySource } + path, err := utils.ResolveHomeDir(path) if err != nil { return opts, err @@ -315,12 +339,15 @@ func (d *ImageImportDialog) ImageImportOptions() (images.ImageImportOptions, err errFileName := utils.ValidateFileName(path) errURL := utils.ValidURL(path) + if errURL == nil { opts.URL = true } + if errFileName != nil && errURL != nil { return opts, multierror.Append(errFileName, errURL) } + opts.Source = path return opts, nil diff --git a/ui/images/imgdialogs/push.go b/ui/images/imgdialogs/push.go index bcce6b8a4..a9de6cf70 100644 --- a/ui/images/imgdialogs/push.go +++ b/ui/images/imgdialogs/push.go @@ -29,7 +29,7 @@ const ( imagePushFormFocus ) -// ImagePushDialog represents image push dialog primitive +// ImagePushDialog represents image push dialog primitive. type ImagePushDialog struct { *tview.Box layout *tview.Flex @@ -48,7 +48,7 @@ type ImagePushDialog struct { focusElement int } -// NewImagePushDialog returns a new image push dialog primitive +// NewImagePushDialog returns a new image push dialog primitive. func NewImagePushDialog() *ImagePushDialog { dialog := &ImagePushDialog{ Box: tview.NewBox(), @@ -104,13 +104,15 @@ func NewImagePushDialog() *ImagePushDialog { dialog.format.SetOptions([]string{ "oci", "v2v2", - "v2v1"}, + "v2v1", + }, nil) dialog.format.SetListStyles(ddUnselectedStyle, ddselectedStyle) dialog.format.SetFieldBackgroundColor(inputFieldBgColor) // skipTLSVerify checkbox skipTLSVerifyLabel := "skip tls verify:" + dialog.skipTLSVerify.SetBackgroundColor(bgColor) dialog.skipTLSVerify.SetLabelColor(fgColor) dialog.skipTLSVerify.SetLabel(skipTLSVerifyLabel) @@ -133,6 +135,7 @@ func NewImagePushDialog() *ImagePushDialog { // password input field passwordLabel := "password:" + dialog.password.SetBackgroundColor(bgColor) dialog.password.SetLabelColor(fgColor) dialog.password.SetLabel(passwordLabel) @@ -151,15 +154,15 @@ func NewImagePushDialog() *ImagePushDialog { // dropdowns and checkbox row layour dcLayout := tview.NewFlex().SetDirection(tview.FlexColumn) dcLayout.AddItem(dialog.compress, labelWidth+1, 1, true) - dcLayout.AddItem(utils.EmptyBoxSpace(bgColor), 2, 0, false) - dcLayout.AddItem(dialog.format, len(formatLabel)+5, 0, true) - dcLayout.AddItem(utils.EmptyBoxSpace(bgColor), 2, 0, false) + dcLayout.AddItem(utils.EmptyBoxSpace(bgColor), 2, 0, false) //nolint:gomnd + dcLayout.AddItem(dialog.format, len(formatLabel)+5, 0, true) //nolint:gomnd + dcLayout.AddItem(utils.EmptyBoxSpace(bgColor), 2, 0, false) //nolint:gomnd dcLayout.AddItem(dialog.skipTLSVerify, 0, 1, true) // username and password row layout userPassLayout := tview.NewFlex().SetDirection(tview.FlexColumn) userPassLayout.AddItem(dialog.username, 0, 1, true) - userPassLayout.AddItem(utils.EmptyBoxSpace(bgColor), 3, 0, false) + userPassLayout.AddItem(utils.EmptyBoxSpace(bgColor), 3, 0, false) //nolint:gomnd userPassLayout.AddItem(dialog.password, 0, 1, true) layout := tview.NewFlex().SetDirection(tview.FlexRow) @@ -189,23 +192,25 @@ func NewImagePushDialog() *ImagePushDialog { dialog.layout.AddItem(dialog.form, dialogs.DialogFormHeight, 0, true) dialog.Hide() + return dialog } -// Display displays this primitive +// Display displays this primitive. func (d *ImagePushDialog) Display() { d.display = true } -// IsDisplay returns true if primitive is shown +// IsDisplay returns true if primitive is shown. func (d *ImagePushDialog) IsDisplay() bool { return d.display } -// Hide stops displaying this primitive +// Hide stops displaying this primitive. func (d *ImagePushDialog) Hide() { d.display = false d.focusElement = imagePushDesitnationFocus + d.destination.SetText("") d.compress.SetChecked(false) d.format.SetCurrentOption(0) @@ -215,33 +220,38 @@ func (d *ImagePushDialog) Hide() { d.password.SetText("") } -// HasFocus returns whether or not this primitive has focus -func (d *ImagePushDialog) HasFocus() bool { +// HasFocus returns whether or not this primitive has focus. +func (d *ImagePushDialog) HasFocus() bool { //nolint:cyclop if d.destination.HasFocus() || d.compress.HasFocus() { return true } + if d.format.HasFocus() || d.skipTLSVerify.HasFocus() { return true } + if d.username.HasFocus() || d.password.HasFocus() { return true } + if d.authFile.HasFocus() || d.form.HasFocus() { return true } + if d.layout.HasFocus() || d.Box.HasFocus() { return true } + return d.Box.HasFocus() } -// dropdownHasFocus returns true if image push dialog dropdown primitives -// has focus +// dropdownHasFocus returns true if image push dialog dropdown primitives. +// has focus. func (d *ImagePushDialog) dropdownHasFocus() bool { return d.format.HasFocus() } -// Focus is called when this primitive receives focus +// Focus is called when this primitive receives focus. func (d *ImagePushDialog) Focus(delegate func(p tview.Primitive)) { switch d.focusElement { case imagePushDesitnationFocus: @@ -265,71 +275,90 @@ func (d *ImagePushDialog) Focus(delegate func(p tview.Primitive)) { d.focusElement = imagePushDesitnationFocus d.Focus(delegate) d.form.SetFocus(0) + return nil } + return event }) + delegate(d.form) } } -// InputHandler returns input handler function for this primitive -func (d *ImagePushDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { +// InputHandler returns input handler function for this primitive. +func (d *ImagePushDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { //nolint:gocognit,lll,cyclop return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { log.Debug().Msgf("image push dialog: event %v received", event) + if event.Key() == utils.SwitchFocusKey.Key { d.setFocusElement() } + if event.Key() == tcell.KeyEsc && !d.dropdownHasFocus() { d.cancelHandler() + return } + if d.destination.HasFocus() { if destinationHandler := d.destination.InputHandler(); destinationHandler != nil { destinationHandler(event, setFocus) + return } } if d.compress.HasFocus() { if compressHandler := d.compress.InputHandler(); compressHandler != nil { compressHandler(event, setFocus) + return } } + if d.format.HasFocus() { if formatHandler := d.format.InputHandler(); formatHandler != nil { event = utils.ParseKeyEventKey(event) formatHandler(event, setFocus) + return } } + if d.skipTLSVerify.HasFocus() { if skipTLSVerifyHandler := d.skipTLSVerify.InputHandler(); skipTLSVerifyHandler != nil { skipTLSVerifyHandler(event, setFocus) + return } } if d.authFile.HasFocus() { if authFileHandler := d.authFile.InputHandler(); authFileHandler != nil { authFileHandler(event, setFocus) + return } } + if d.username.HasFocus() { if usernameHandler := d.username.InputHandler(); usernameHandler != nil { usernameHandler(event, setFocus) + return } } if d.password.HasFocus() { if passwordHandler := d.password.InputHandler(); passwordHandler != nil { passwordHandler(event, setFocus) + return } } + if d.form.HasFocus() { if formHandler := d.form.InputHandler(); formHandler != nil { formHandler(event, setFocus) + return } } @@ -357,16 +386,15 @@ func (d *ImagePushDialog) setFocusElement() { // SetRect set rects for this primitive. func (d *ImagePushDialog) SetRect(x, y, width, height int) { - if width > imagePushDialogMaxWidth { - emptySpace := (width - imagePushDialogMaxWidth) / 2 - x = x + emptySpace + emptySpace := (width - imagePushDialogMaxWidth) / 2 //nolint:gomnd + x += emptySpace width = imagePushDialogMaxWidth } if height > imagePushDialogMaxHeight { - emptySpace := (height - imagePushDialogMaxHeight) / 2 - y = y + emptySpace + emptySpace := (height - imagePushDialogMaxHeight) / 2 //nolint:gomnd + y += emptySpace height = imagePushDialogMaxHeight } @@ -378,35 +406,39 @@ func (d *ImagePushDialog) Draw(screen tcell.Screen) { if !d.display { return } + d.Box.DrawForSubclass(screen, d) x, y, width, height := d.Box.GetInnerRect() + d.layout.SetRect(x, y, width, height) d.layout.Draw(screen) } -// SetPushFunc sets form push button selected function +// SetPushFunc sets form push button selected function. func (d *ImagePushDialog) SetPushFunc(handler func()) *ImagePushDialog { d.pushHandler = handler pushButton := d.form.GetButton(d.form.GetButtonCount() - 1) pushButton.SetSelectedFunc(handler) + return d } -// SetCancelFunc sets form cancel button selected function +// SetCancelFunc sets form cancel button selected function. func (d *ImagePushDialog) SetCancelFunc(handler func()) *ImagePushDialog { d.cancelHandler = handler - cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) + cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) //nolint:gomnd cancelButton.SetSelectedFunc(handler) + return d } -// SetImageInfo sets selected image ID and name in push dialog +// SetImageInfo sets selected image ID and name in push dialog. func (d *ImagePushDialog) SetImageInfo(id string, name string) { containerInfo := fmt.Sprintf("%12s (%s)", id, name) d.imageInfo.SetText(containerInfo) } -// GetImagePushOptions returns image push options based on user inputs +// GetImagePushOptions returns image push options based on user inputs. func (d *ImagePushDialog) GetImagePushOptions() images.ImagePushOptions { var opts images.ImagePushOptions diff --git a/ui/images/imgdialogs/save.go b/ui/images/imgdialogs/save.go index 004fcae5f..e64464f0b 100644 --- a/ui/images/imgdialogs/save.go +++ b/ui/images/imgdialogs/save.go @@ -1,6 +1,7 @@ package imgdialogs import ( + "errors" "fmt" "strings" @@ -19,6 +20,8 @@ const ( imageSaveDialogMaxHeight = 13 ) +var errSaveEmptyOuputName = errors.New("empty output name") + const ( imageSaveOutputFocus = 0 + iota imageSaveCompressFocus @@ -27,7 +30,7 @@ const ( imageSaveFormFocus ) -// ImageSaveDialog represents image save dialog primitive +// ImageSaveDialog represents image save dialog primitive. type ImageSaveDialog struct { *tview.Box layout *tview.Flex @@ -43,7 +46,7 @@ type ImageSaveDialog struct { focusElement int } -// NewImageSaveDialog returns new image save dialog +// NewImageSaveDialog returns new image save dialog. func NewImageSaveDialog() *ImageSaveDialog { dialog := &ImageSaveDialog{ Box: tview.NewBox(), @@ -65,6 +68,7 @@ func NewImageSaveDialog() *ImageSaveDialog { // image info imageInfoLabel := "IMAGE ID:" + dialog.imageInfo.SetBackgroundColor(style.DialogBgColor) dialog.imageInfo.SetLabel("[::b]" + imageInfoLabel) dialog.imageInfo.SetLabelWidth(len(imageInfoLabel) + 1) @@ -96,7 +100,8 @@ func NewImageSaveDialog() *ImageSaveDialog { define.V2s2Archive, define.V2s2ManifestDir, define.OCIArchive, - define.OCIManifestDir}, + define.OCIManifestDir, + }, nil) dialog.format.SetListStyles(ddUnselectedStyle, ddselectedStyle) dialog.format.SetCurrentOption(0) @@ -119,7 +124,7 @@ func NewImageSaveDialog() *ImageSaveDialog { compressRow := tview.NewFlex().SetDirection(tview.FlexColumn) compressRow.SetBackgroundColor(bgColor) compressRow.AddItem(dialog.compress, 0, 1, true) - compressRow.AddItem(dialog.ociAcceptUncompressed, 0, 3, true) + compressRow.AddItem(dialog.ociAcceptUncompressed, 0, 3, true) //nolint:gomnd optionsLayout := tview.NewFlex().SetDirection(tview.FlexRow) optionsLayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, false) @@ -148,42 +153,48 @@ func NewImageSaveDialog() *ImageSaveDialog { return dialog } -// Display displays this primitive +// Display displays this primitive. func (d *ImageSaveDialog) Display() { d.display = true } -// IsDisplay returns true if primitive is shown +// IsDisplay returns true if primitive is shown. func (d *ImageSaveDialog) IsDisplay() bool { return d.display } -// Hide stops displaying this primitive +// Hide stops displaying this primitive. func (d *ImageSaveDialog) Hide() { d.display = false + d.SetImageInfo("", "") + d.focusElement = imageSaveOutputFocus + d.output.SetText("") d.compress.SetChecked(false) d.ociAcceptUncompressed.SetChecked(false) d.format.SetCurrentOption(0) } -// HasFocus returns whether or not this primitive has focus +// HasFocus returns whether or not this primitive has focus. func (d *ImageSaveDialog) HasFocus() bool { if d.output.HasFocus() || d.compress.HasFocus() { return true } + if d.format.HasFocus() || d.ociAcceptUncompressed.HasFocus() { return true } + if d.form.HasFocus() || d.layout.HasFocus() { return true } + return d.Box.HasFocus() } -// Focus is called when this primitive receives focus +// Focus is called when this primitive receives focus. func (d *ImageSaveDialog) Focus(delegate func(p tview.Primitive)) { switch d.focusElement { case imageSaveOutputFocus: @@ -201,56 +212,71 @@ func (d *ImageSaveDialog) Focus(delegate func(p tview.Primitive)) { d.focusElement = imageSaveOutputFocus d.Focus(delegate) d.form.SetFocus(0) + return nil } + return event }) + delegate(d.form) } } -// InputHandler returns input handler function for this primitive -func (d *ImageSaveDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { +// InputHandler returns input handler function for this primitive. +func (d *ImageSaveDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { //nolint:gocognit,lll,cyclop return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { log.Debug().Msgf("image save dialog: event %v received", event) if event.Key() == tcell.KeyEsc { if !d.format.HasFocus() { d.cancelHandler() + return } } + if event.Key() == utils.SwitchFocusKey.Key { d.setFocusElement() } + // drop down event if d.format.HasFocus() { event = utils.ParseKeyEventKey(event) if formatHandler := d.format.InputHandler(); formatHandler != nil { formatHandler(event, setFocus) + return } } + if d.output.HasFocus() { if outputHandler := d.output.InputHandler(); outputHandler != nil { outputHandler(event, setFocus) + return } } + if d.compress.HasFocus() { if compressHandler := d.compress.InputHandler(); compressHandler != nil { compressHandler(event, setFocus) + return } } + if d.ociAcceptUncompressed.HasFocus() { if acceptUncompressedhandler := d.ociAcceptUncompressed.InputHandler(); acceptUncompressedhandler != nil { acceptUncompressedhandler(event, setFocus) + return } } + if d.form.HasFocus() { if formHandler := d.form.InputHandler(); formHandler != nil { formHandler(event, setFocus) + return } } @@ -259,16 +285,15 @@ func (d *ImageSaveDialog) InputHandler() func(event *tcell.EventKey, setFocus fu // SetRect set rects for this primitive. func (d *ImageSaveDialog) SetRect(x, y, width, height int) { - if width > imageSaveDialogMaxWidth { - emptySpace := (width - imageSaveDialogMaxWidth) / 2 - x = x + emptySpace + emptySpace := (width - imageSaveDialogMaxWidth) / 2 //nolint:gomnd + x += emptySpace width = imageSaveDialogMaxWidth } if height > imageSaveDialogMaxHeight { - emptySpace := (height - imageSaveDialogMaxHeight) / 2 - y = y + emptySpace + emptySpace := (height - imageSaveDialogMaxHeight) / 2 //nolint:gomnd + y += emptySpace height = imageSaveDialogMaxHeight } @@ -280,6 +305,7 @@ func (d *ImageSaveDialog) Draw(screen tcell.Screen) { if !d.display { return } + d.Box.DrawForSubclass(screen, d) x, y, width, height := d.Box.GetInnerRect() d.layout.SetRect(x, y, width, height) @@ -299,51 +325,60 @@ func (d *ImageSaveDialog) setFocusElement() { } } -// SetSaveFunc sets form save button selected function +// SetSaveFunc sets form save button selected function. func (d *ImageSaveDialog) SetSaveFunc(handler func()) *ImageSaveDialog { d.saveHandler = handler saveButton := d.form.GetButton(d.form.GetButtonCount() - 1) + saveButton.SetSelectedFunc(handler) + return d } -// SetCancelFunc sets form cancel button selected function +// SetCancelFunc sets form cancel button selected function. func (d *ImageSaveDialog) SetCancelFunc(handler func()) *ImageSaveDialog { d.cancelHandler = handler - cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) + cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) //nolint:gomnd + cancelButton.SetSelectedFunc(handler) + return d } -// SetImageInfo sets selected image ID and name in save dialog +// SetImageInfo sets selected image ID and name in save dialog. func (d *ImageSaveDialog) SetImageInfo(id string, name string) { nameSplited := strings.Split(name, "/") + l := len(nameSplited) if l > 1 { name = nameSplited[l-1] } + imageInfo := fmt.Sprintf("Image ID: %s (%s)", id, name) + d.imageInfo.SetText(imageInfo) } -// ImageSaveOptions prepare and returns image save options +// ImageSaveOptions prepare and returns image save options. func (d *ImageSaveDialog) ImageSaveOptions() (images.ImageSaveOptions, error) { - opts := images.ImageSaveOptions{ Compressed: d.compress.IsChecked(), OciAcceptUncompressedLayers: d.ociAcceptUncompressed.IsChecked(), } + _, format := d.format.GetCurrentOption() opts.Format = format output := strings.TrimSpace(d.output.GetText()) if output == "" { - return opts, fmt.Errorf("empty output name") + return opts, errSaveEmptyOuputName } + outputPath, err := utils.ResolveHomeDir(output) if err != nil { return opts, err } + opts.Output = outputPath return opts, nil diff --git a/ui/images/imgdialogs/search.go b/ui/images/imgdialogs/search.go index 145996df3..dad7666e1 100644 --- a/ui/images/imgdialogs/search.go +++ b/ui/images/imgdialogs/search.go @@ -17,14 +17,32 @@ const ( searchButtonWidth = 10 searchInpuLabelWidth = 13 - // focus elements + // focus elements. sInputElement = 1 sSearchButtonElement = 2 sSearchResultElement = 3 sFormElement = 4 ) -// ImageSearchDialog represents image search dialogs +const ( + searchResultIndexIndex = 0 + iota + searchResultNameIndex + searchResultDescIndex + searchResultStarsIndex + searchResultOfficialIndex + searchResultAutomatedIndex +) + +const ( + searchResultIndexColIndex = 0 + iota + searchResultNameColIndex + searchResultStarsColIndex + searchResultOfficialColIndex + searchResultAutomatedColIndex + searchResultDescColIndex +) + +// ImageSearchDialog represents image search dialogs. type ImageSearchDialog struct { *tview.Box layout *tview.Flex @@ -41,7 +59,7 @@ type ImageSearchDialog struct { pullSelectHandler func() } -// NewImageSearchDialog returns new image search dialog primitive +// NewImageSearchDialog returns new image search dialog primitive. func NewImageSearchDialog() *ImageSearchDialog { dialog := &ImageSearchDialog{ Box: tview.NewBox(), @@ -51,6 +69,7 @@ func NewImageSearchDialog() *ImageSearchDialog { display: false, focusElement: sInputElement, } + bgColor := style.DialogBgColor fgColor := style.DialogFgColor inputFieldBgColor := style.InputFieldBgColor @@ -66,8 +85,9 @@ func NewImageSearchDialog() *ImageSearchDialog { dialog.input.SetFieldBackgroundColor(inputFieldBgColor) dialog.searchLayout = tview.NewFlex().SetDirection(tview.FlexColumn) + dialog.searchLayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) - dialog.searchLayout.AddItem(dialog.input, searchFieldMaxSize+searchInpuLabelWidth, 10, true) + dialog.searchLayout.AddItem(dialog.input, searchFieldMaxSize+searchInpuLabelWidth, 10, true) //nolint:gomnd dialog.searchLayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) dialog.searchLayout.AddItem(dialog.searchButton, searchButtonWidth, 0, true) dialog.searchLayout.SetBackgroundColor(bgColor) @@ -76,7 +96,9 @@ func NewImageSearchDialog() *ImageSearchDialog { dialog.searchResult.SetTitleColor(style.TableHeaderFgColor) dialog.searchResult.SetBorder(true) dialog.searchResult.SetBorderColor(style.DialogSubBoxBorderColor) + searchResultLayout := tview.NewFlex().SetDirection(tview.FlexColumn) + searchResultLayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, false) searchResultLayout.AddItem(dialog.searchResult, 0, 1, true) searchResultLayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, false) @@ -100,21 +122,24 @@ func NewImageSearchDialog() *ImageSearchDialog { dialog.layout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) dialog.layout.AddItem(searchResultLayout, 0, 1, true) dialog.layout.AddItem(dialog.form, dialogs.DialogFormHeight, 0, true) + return dialog } func (d *ImageSearchDialog) initTable() { bgColor := style.TableHeaderBgColor fgColor := style.TableHeaderFgColor + d.searchResult.Clear() - d.searchResult.SetCell(0, 0, + d.searchResult.SetCell(0, searchResultIndexColIndex, tview.NewTableCell(fmt.Sprintf("[%s::b]INDEX", style.GetColorHex(fgColor))). SetExpansion(1). SetBackgroundColor(bgColor). SetTextColor(fgColor). SetAlign(tview.AlignLeft). SetSelectable(false)) - d.searchResult.SetCell(0, 1, + + d.searchResult.SetCell(0, searchResultNameColIndex, tview.NewTableCell(fmt.Sprintf("[%s::b]NAME", style.GetColorHex(fgColor))). SetExpansion(1). SetBackgroundColor(bgColor). @@ -122,7 +147,7 @@ func (d *ImageSearchDialog) initTable() { SetAlign(tview.AlignLeft). SetSelectable(false)) - d.searchResult.SetCell(0, 2, + d.searchResult.SetCell(0, searchResultStarsColIndex, tview.NewTableCell(fmt.Sprintf("[%s::b]STARS", style.GetColorHex(fgColor))). SetExpansion(1). SetBackgroundColor(bgColor). @@ -130,14 +155,14 @@ func (d *ImageSearchDialog) initTable() { SetAlign(tview.AlignCenter). SetSelectable(false)) - d.searchResult.SetCell(0, 3, + d.searchResult.SetCell(0, searchResultOfficialColIndex, tview.NewTableCell(fmt.Sprintf("[%s::b]OFFICIAL", style.GetColorHex(fgColor))). SetExpansion(1). SetBackgroundColor(bgColor). SetTextColor(fgColor). SetAlign(tview.AlignCenter). SetSelectable(false)) - d.searchResult.SetCell(0, 4, + d.searchResult.SetCell(0, searchResultAutomatedColIndex, tview.NewTableCell(fmt.Sprintf("[%s::b]AUTOMATED", style.GetColorHex(fgColor))). SetExpansion(1). SetBackgroundColor(bgColor). @@ -145,7 +170,7 @@ func (d *ImageSearchDialog) initTable() { SetAlign(tview.AlignCenter). SetSelectable(false)) - d.searchResult.SetCell(0, 5, + d.searchResult.SetCell(0, searchResultDescColIndex, tview.NewTableCell(fmt.Sprintf("[%s::b]DESCRIPTION", style.GetColorHex(fgColor))). SetExpansion(1). SetBackgroundColor(bgColor). @@ -157,76 +182,97 @@ func (d *ImageSearchDialog) initTable() { d.searchResult.SetSelectable(true, false) } -// Display displays this primitive +// Display displays this primitive. func (d *ImageSearchDialog) Display() { d.display = true } -// IsDisplay returns true if primitive is shown +// IsDisplay returns true if primitive is shown. func (d *ImageSearchDialog) IsDisplay() bool { return d.display } -// Hide stops displaying this primitive +// Hide stops displaying this primitive. func (d *ImageSearchDialog) Hide() { d.focusElement = sInputElement d.display = false + d.input.SetText("") d.ClearResults() } -// Focus is called when this primitive receives focus -func (d *ImageSearchDialog) Focus(delegate func(p tview.Primitive)) { +// Focus is called when this primitive receives focus. +func (d *ImageSearchDialog) Focus(delegate func(p tview.Primitive)) { //nolint:cyclop switch d.focusElement { case sInputElement: d.input.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyTab { d.focusElement = sSearchButtonElement d.Focus(delegate) + return nil } + if event.Key() == tcell.KeyDown { d.focusElement = sSearchResultElement d.Focus(delegate) + return nil } + if event.Key() == tcell.KeyEnter { d.searchSelectHandler() + return nil } + return event }) + delegate(d.input) + return case sSearchButtonElement: d.searchButton.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyTab { d.focusElement = sSearchResultElement d.Focus(delegate) + return nil } + if event.Key() == tcell.KeyEnter { d.searchSelectHandler() + return nil } + return event }) + delegate(d.searchButton) + return case sSearchResultElement: d.searchResult.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyTab { d.focusElement = sFormElement d.Focus(delegate) + return nil } + if event.Key() == tcell.KeyEnter { d.pullSelectHandler() + return nil } + return event }) + delegate(d.searchResult) + return case sFormElement: button := d.form.GetButton(d.form.GetButtonCount() - 1) @@ -235,20 +281,23 @@ func (d *ImageSearchDialog) Focus(delegate func(p tview.Primitive)) { d.focusElement = sInputElement d.Focus(delegate) d.form.SetFocus(0) + return nil } if event.Key() == tcell.KeyEnter { d.pullSelectHandler() + return nil } + return event }) + delegate(d.form) } - } -// HasFocus returns whether or not this primitive has focus +// HasFocus returns whether or not this primitive has focus. func (d *ImageSearchDialog) HasFocus() bool { return d.form.HasFocus() || d.input.HasFocus() || d.searchResult.HasFocus() || d.searchButton.HasFocus() } @@ -259,25 +308,25 @@ func (d *ImageSearchDialog) SetRect(x, y, width, height int) { paddingY := 1 dX := x + paddingX dY := y + paddingY - dWidth := width - (2 * paddingX) - dHeight := height - (2 * paddingY) + dWidth := width - (2 * paddingX) //nolint:gomnd + dHeight := height - (2 * paddingY) //nolint:gomnd - //set search input field size - iwidth := dWidth - searchInpuLabelWidth - searchButtonWidth - 2 - 2 - 1 + // set search input field size + iwidth := dWidth - searchInpuLabelWidth - searchButtonWidth - 5 //nolint:gomnd if iwidth > searchFieldMaxSize { iwidth = searchFieldMaxSize } + d.input.SetFieldWidth(iwidth) d.searchLayout.ResizeItem(d.input, iwidth+searchInpuLabelWidth, 0) - //set table height size - d.layout.ResizeItem(d.searchResult, dHeight-dialogs.DialogFormHeight-5, 0) + // set table height size + d.layout.ResizeItem(d.searchResult, dHeight-dialogs.DialogFormHeight-5, 0) //nolint:gomnd d.Box.SetRect(dX, dY, dWidth, dHeight) } // Draw draws this primitive onto the screen. func (d *ImageSearchDialog) Draw(screen tcell.Screen) { - if !d.display { return } @@ -293,98 +342,116 @@ func (d *ImageSearchDialog) Draw(screen tcell.Screen) { d.layout.Draw(screen) } -// InputHandler returns input handler function for this primitive +// InputHandler returns input handler function for this primitive. func (d *ImageSearchDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { log.Debug().Msgf("confirm dialog: event %v received", event) + if event.Key() == tcell.KeyEsc { d.cancelHandler() + return } + if d.searchResult.HasFocus() { if searchResultHandler := d.searchResult.InputHandler(); searchResultHandler != nil { searchResultHandler(event, setFocus) + return } - } + if d.input.HasFocus() { if inputFieldHandler := d.input.InputHandler(); inputFieldHandler != nil { inputFieldHandler(event, setFocus) + return } } + if d.searchButton.HasFocus() { if searchButtonHandler := d.searchButton.InputHandler(); searchButtonHandler != nil { searchButtonHandler(event, setFocus) + return } } + if d.form.HasFocus() { if formHandler := d.form.InputHandler(); formHandler != nil { formHandler(event, setFocus) + return } } }) } -// SetCancelFunc sets form cancel button selected function +// SetCancelFunc sets form cancel button selected function. func (d *ImageSearchDialog) SetCancelFunc(handler func()) *ImageSearchDialog { d.cancelHandler = handler - cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) + cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) //nolint:gomnd cancelButton.SetSelectedFunc(handler) + return d } -// SetSearchFunc sets form cancel button selected function +// SetSearchFunc sets form cancel button selected function. func (d *ImageSearchDialog) SetSearchFunc(handler func()) *ImageSearchDialog { d.searchSelectHandler = handler + return d } -// SetPullFunc sets form pull button selected function +// SetPullFunc sets form pull button selected function. func (d *ImageSearchDialog) SetPullFunc(handler func()) *ImageSearchDialog { d.pullSelectHandler = handler + return d } -// GetSearchText returns search input field text +// GetSearchText returns search input field text. func (d *ImageSearchDialog) GetSearchText() string { return d.input.GetText() } -// GetSelectedItem returns selected image name from search result table +// GetSelectedItem returns selected image name from search result table. func (d *ImageSearchDialog) GetSelectedItem() string { row, _ := d.searchResult.GetSelection() if row >= 0 { return d.result[row-1][1] } + return "" } -// ClearResults clear image search result table +// ClearResults clear image search result table. func (d *ImageSearchDialog) ClearResults() { d.UpdateResults([][]string{}) } -// UpdateResults updates result table +// UpdateResults updates result table. func (d *ImageSearchDialog) UpdateResults(data [][]string) { d.result = data + d.initTable() + alignment := tview.AlignLeft rowIndex := 1 expand := 1 + for i := 0; i < len(data); i++ { - index := data[i][0] - name := data[i][1] - desc := data[i][2] - stars := data[i][3] - official := data[i][4] + index := data[i][searchResultIndexIndex] + name := data[i][searchResultNameIndex] + desc := data[i][searchResultDescIndex] + stars := data[i][searchResultStarsIndex] + official := data[i][searchResultOfficialIndex] + automated := data[i][searchResultAutomatedIndex] + if official == "[OK]" { official = style.HeavyGreenCheckMark } - automated := data[i][5] + if automated == "[OK]" { automated = style.HeavyGreenCheckMark } @@ -394,43 +461,44 @@ func (d *ImageSearchDialog) UpdateResults(data [][]string) { } // index column - d.searchResult.SetCell(rowIndex, 0, + d.searchResult.SetCell(rowIndex, searchResultIndexColIndex, tview.NewTableCell(index). SetExpansion(expand). SetAlign(alignment)) // name column - d.searchResult.SetCell(rowIndex, 1, + d.searchResult.SetCell(rowIndex, searchResultNameColIndex, tview.NewTableCell(name). SetExpansion(expand). SetAlign(alignment)) // stars column - d.searchResult.SetCell(rowIndex, 2, + d.searchResult.SetCell(rowIndex, searchResultStarsColIndex, tview.NewTableCell(stars). SetExpansion(expand). SetAlign(tview.AlignCenter)) // official column - d.searchResult.SetCell(rowIndex, 3, + d.searchResult.SetCell(rowIndex, searchResultOfficialColIndex, tview.NewTableCell(official). SetExpansion(expand). SetAlign(tview.AlignCenter)) // autoamted column - d.searchResult.SetCell(rowIndex, 4, + d.searchResult.SetCell(rowIndex, searchResultAutomatedColIndex, tview.NewTableCell(automated). SetExpansion(expand). SetAlign(tview.AlignCenter)) // description column - d.searchResult.SetCell(rowIndex, 5, + d.searchResult.SetCell(rowIndex, searchResultDescColIndex, tview.NewTableCell(desc). SetExpansion(expand). SetAlign(alignment)) rowIndex++ } + if len(data) > 0 { d.searchResult.Select(1, 1) d.searchResult.ScrollToBeginning() diff --git a/ui/images/key.go b/ui/images/key.go index 5c5bd8430..c5652b7b7 100644 --- a/ui/images/key.go +++ b/ui/images/key.go @@ -8,12 +8,14 @@ import ( ) // InputHandler returns the handler for this primitive. -func (img *Images) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { +func (img *Images) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { //nolint:gocognit,gocyclo,lll,cyclop return img.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { log.Debug().Msgf("view: images event %v received", event) + if img.progressDialog.IsDisplay() { return } + // error dialog handler if img.errorDialog.HasFocus() || img.errorDialog.IsDisplay() { if errorDialogHandler := img.errorDialog.InputHandler(); errorDialogHandler != nil { @@ -21,6 +23,7 @@ func (img *Images) InputHandler() func(event *tcell.EventKey, setFocus func(p tv setFocus(img.errorDialog) } } + // message dialog handler if img.messageDialog.HasFocus() || img.messageDialog.IsDisplay() { if messageDialogHandler := img.messageDialog.InputHandler(); messageDialogHandler != nil { @@ -99,19 +102,28 @@ func (img *Images) InputHandler() func(event *tcell.EventKey, setFocus func(p tv } // table handlers - if img.table.HasFocus() { + if img.table.HasFocus() { //nolint:nestif img.selectedID, img.selectedName = img.getSelectedItem() if event.Rune() == utils.CommandMenuKey.Rune() { if img.cmdDialog.GetCommandCount() <= 1 { return } + img.cmdDialog.Display() - } else if event.Key() == utils.DeleteKey.EventKey() { + setFocus(img) + + return + } + + if event.Key() == utils.DeleteKey.EventKey() { img.rm() - } else { - if tableHandler := img.table.InputHandler(); tableHandler != nil { - tableHandler(event, setFocus) - } + setFocus(img) + + return + } + + if tableHandler := img.table.InputHandler(); tableHandler != nil { + tableHandler(event, setFocus) } } diff --git a/ui/images/refresh.go b/ui/images/refresh.go index 35c15c229..a734acb63 100644 --- a/ui/images/refresh.go +++ b/ui/images/refresh.go @@ -11,8 +11,8 @@ import ( ) func (img *Images) refresh() { - img.table.Clear() + expand := 1 alignment := tview.AlignLeft @@ -25,47 +25,51 @@ func (img *Images) refresh() { SetAlign(tview.AlignLeft). SetSelectable(false)) } + rowIndex := 1 images := img.getData() img.table.SetTitle(fmt.Sprintf("[::b]%s[%d]", strings.ToUpper(img.title), len(images))) + for i := 0; i < len(images); i++ { repo := images[i].Repository tag := images[i].Tag imgID := images[i].ID imgIDString := imgID + if len(imgID) > utils.IDLength { imgIDString = imgIDString[:utils.IDLength] } + size := putils.SizeToStr(images[i].Size) created := putils.CreatedToStr(images[i].Created) // repository name column - img.table.SetCell(rowIndex, 0, + img.table.SetCell(rowIndex, viewImageRepoNameColIndex, tview.NewTableCell(repo). SetExpansion(expand). SetAlign(alignment)) // tag column - img.table.SetCell(rowIndex, 1, + img.table.SetCell(rowIndex, viewImageTagColIndex, tview.NewTableCell(tag). SetExpansion(expand). SetAlign(alignment)) // id column - img.table.SetCell(rowIndex, 2, + img.table.SetCell(rowIndex, viewImageIDColIndex, tview.NewTableCell(imgIDString). SetExpansion(expand). SetAlign(alignment)) // created at column - img.table.SetCell(rowIndex, 3, + img.table.SetCell(rowIndex, viewImageCreatedAtColIndex, tview.NewTableCell(created). SetExpansion(expand). SetAlign(alignment)) // size column - img.table.SetCell(rowIndex, 4, + img.table.SetCell(rowIndex, viewImageSizeColIndex, tview.NewTableCell(size). SetExpansion(expand). SetAlign(alignment))