Compare commits

..

34 Commits
v1.7.0 ... main

Author SHA1 Message Date
Navid Yaghoobi d6987b37b5
Merge pull request #654 from containers/dependabot/go_modules/github.com/rivo/tview-0.42.0
Bump github.com/rivo/tview from 0.0.0-20220307222120-9994674d60a8 to 0.42.0
2025-08-28 11:26:07 +10:00
dependabot[bot] 80c42437e2
Bump github.com/rivo/tview
Bumps [github.com/rivo/tview](https://github.com/rivo/tview) from 0.0.0-20220307222120-9994674d60a8 to 0.42.0.
- [Release notes](https://github.com/rivo/tview/releases)
- [Commits](https://github.com/rivo/tview/commits/v0.42.0)

---
updated-dependencies:
- dependency-name: github.com/rivo/tview
  dependency-version: 0.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-28 00:53:01 +00:00
Navid Yaghoobi 6120b52a9c
Merge pull request #653 from navidys/bugfix_views_last_row_removal
Bugfix ui views - update selected row after last row removal
2025-08-27 19:19:13 +10:00
Navid Yaghoobi ac941ce976 Bugfix ui views - update selected row after last row removal
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
2025-08-27 19:12:37 +10:00
Navid Yaghoobi 414e841aa7
Merge pull request #652 from containers/dependabot/go_modules/github.com/gdamore/tcell/v2-2.9.0
Bump github.com/gdamore/tcell/v2 from 2.8.1 to 2.9.0
2025-08-27 18:10:00 +10:00
dependabot[bot] 97377e5788
Bump github.com/gdamore/tcell/v2 from 2.8.1 to 2.9.0
Bumps [github.com/gdamore/tcell/v2](https://github.com/gdamore/tcell) from 2.8.1 to 2.9.0.
- [Release notes](https://github.com/gdamore/tcell/releases)
- [Changelog](https://github.com/gdamore/tcell/blob/main/CHANGESv2.md)
- [Commits](https://github.com/gdamore/tcell/compare/v2.8.1...v2.9.0)

---
updated-dependencies:
- dependency-name: github.com/gdamore/tcell/v2
  dependency-version: 2.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 12:47:49 +00:00
Navid Yaghoobi a1dc528dca
Merge pull request #650 from navidys/bats_functionality_tests
bats functionality tests update
2025-08-24 19:04:25 +10:00
Navid Yaghoobi e2f0ce0aea bats functionality tests update
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
2025-08-24 18:59:33 +10:00
Navid Yaghoobi 229d59e9b4
Merge pull request #649 from navidys/views_sort_dialog
System and secret view sort dialogs + code enhancement
2025-08-23 20:24:39 +10:00
Navid Yaghoobi f378359d11 System and secret view sort dialogs + code enhancement
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
2025-08-23 20:06:05 +10:00
Navid Yaghoobi 3b86104326
Merge pull request #648 from navidys/network_view_sort_dialog
Network view sort dialog
2025-08-23 11:11:55 +10:00
Navid Yaghoobi 8185525e3a Network view sort dialog
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
2025-08-23 11:09:03 +10:00
Navid Yaghoobi 839c196d2f
Merge pull request #647 from navidys/image_view_sort_dialog
Image view sort dialog
2025-08-23 10:07:13 +10:00
Navid Yaghoobi d13317c9df Image view sort dialog
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
2025-08-23 10:00:10 +10:00
Navid Yaghoobi b5dc83c06d
Merge pull request #646 from navidys/volumes_view_sort_dialog
Volumes view sort dialog
2025-08-22 11:34:27 +10:00
Navid Yaghoobi 57a2e0f7b1 Volumes view sort dialog
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
2025-08-21 18:50:37 +10:00
Navid Yaghoobi 81b2a03857
Merge pull request #645 from navidys/pod_view_sort_dialog
Pods view sort dialog
2025-08-20 20:59:00 +10:00
Navid Yaghoobi 7bfde770e7 Pods view sort dialog
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
2025-08-20 20:37:15 +10:00
Navid Yaghoobi 90c671d14f
Merge pull request #643 from navidys/sort_dialog
Containers view sort dialog
2025-08-20 05:28:53 +10:00
Navid Yaghoobi 7aeb812eaa Containers view sort dialog
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
2025-08-19 21:04:06 +10:00
Navid Yaghoobi e229db1dad
Merge pull request #640 from containers/dependabot/go_modules/github.com/containers/podman/v5-5.6.0
Bump github.com/containers/podman/v5 from 5.5.2 to 5.6.0
2025-08-16 21:00:16 +10:00
dependabot[bot] 4f9d64293f
Bump github.com/containers/podman/v5 from 5.5.2 to 5.6.0
Bumps [github.com/containers/podman/v5](https://github.com/containers/podman) from 5.5.2 to 5.6.0.
- [Release notes](https://github.com/containers/podman/releases)
- [Changelog](https://github.com/containers/podman/blob/main/RELEASE_NOTES.md)
- [Commits](https://github.com/containers/podman/compare/v5.5.2...v5.6.0)

---
updated-dependencies:
- dependency-name: github.com/containers/podman/v5
  dependency-version: 5.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-16 10:55:40 +00:00
Navid Yaghoobi a49fb6fbf3
Merge pull request #642 from navidys/podman_v5.6.0_lint_deprecated
golangci-lint v2.3.1 update for podman v5.6.0
2025-08-16 20:54:05 +10:00
Navid Yaghoobi ced7369298 golangci-lint v2.3.1 update for podman v5.6.0
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
2025-08-16 20:53:32 +10:00
Navid Yaghoobi d37c7751bb
Merge pull request #641 from navidys/golangci_update_2.3.1
golangci-lint update to v2.3.1
2025-08-16 20:33:00 +10:00
Navid Yaghoobi 2d5c903edc golangci-lint update to v2.3.1
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
2025-08-16 20:32:34 +10:00
Navid Yaghoobi cd462ee930
Merge pull request #637 from containers/dependabot/github_actions/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-08-15 08:38:19 +10:00
Navid Yaghoobi 82f26d2dcd
Merge pull request #635 from containers/dependabot/go_modules/golang.org/x/crypto-0.41.0
Bump golang.org/x/crypto from 0.40.0 to 0.41.0
2025-08-13 11:34:13 +10:00
dependabot[bot] 5e752b1321
Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 02:45:29 +00:00
dependabot[bot] 9320b4b5ba
Bump golang.org/x/crypto from 0.40.0 to 0.41.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.40.0 to 0.41.0.
- [Commits](https://github.com/golang/crypto/compare/v0.40.0...v0.41.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-07 21:04:24 +00:00
Navid Yaghoobi 415487648b
Merge pull request #629 from navidys/packit_epel10
Enable packit EPEL10 build
2025-07-12 11:24:26 +10:00
Navid Yaghoobi 7e7e0198b6 Enable packit EPEL10 build
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
2025-07-12 11:14:46 +10:00
Navid Yaghoobi 063c9c7d21
Merge pull request #628 from navidys/bump-1.7.0
Bump 1.7.0
2025-07-11 18:20:50 +10:00
Navid Yaghoobi e120c2a1f7 Bump to v1.8.0-dev
Signed-off-by: Navid Yaghoobi <navidys@fedoraproject.org>
2025-07-11 18:08:44 +10:00
1055 changed files with 19634 additions and 89293 deletions

View File

@ -18,7 +18,7 @@ jobs:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5

View File

@ -10,7 +10,7 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '>=1.23' go-version: '>=1.23'
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- run: | - run: |
go mod tidy go mod tidy
go mod vendor go mod vendor
@ -25,7 +25,7 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '1.23' go-version: '1.23'
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- run: | - run: |
bash hack/install_dep.sh bash hack/install_dep.sh
make .install.golangci-lint make .install.golangci-lint
@ -36,7 +36,7 @@ jobs:
container: container:
image: fedora:latest image: fedora:latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- run: | - run: |
# Preparing and running unit tests # # Preparing and running unit tests #
dnf -y install git-core golang glibc-static git-core wget gcc make dnf -y install git-core golang glibc-static git-core wget gcc make
@ -60,6 +60,6 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '>=1.23' go-version: '>=1.23'
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- run: | - run: |
make all make all

View File

@ -6,7 +6,7 @@ jobs:
pre-commit: pre-commit:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
- uses: pre-commit/action@v3.0.1 - uses: pre-commit/action@v3.0.1
@ -34,7 +34,7 @@ jobs:
codespell: codespell:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: codespell-project/actions-codespell@master - uses: codespell-project/actions-codespell@master
with: with:
check_filenames: true check_filenames: true
@ -46,7 +46,7 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '>=1.23' go-version: '>=1.23'
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- run: | - run: |
go mod tidy go mod tidy
go mod vendor go mod vendor
@ -61,7 +61,7 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '1.23' go-version: '1.23'
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- run: | - run: |
bash hack/install_dep.sh bash hack/install_dep.sh
make .install.golangci-lint make .install.golangci-lint
@ -72,7 +72,7 @@ jobs:
container: container:
image: fedora:latest image: fedora:latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- run: | - run: |
# Preparing and running unit tests # # Preparing and running unit tests #
dnf -y install git-core golang glibc-static git-core wget gcc make dnf -y install git-core golang glibc-static git-core wget gcc make
@ -87,7 +87,7 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '>=1.23' go-version: '>=1.23'
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- run: | - run: |
make all make all

View File

@ -1,33 +1,40 @@
version: "2"
run: run:
timeout: 10m modules-download-mode: readonly
deadline: 5m
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: disable
linters: linters:
enable-all: true default: all
disable: disable:
- gochecknoinits
- gochecknoglobals - gochecknoglobals
- exhaustruct - exhaustruct
- varnamelen - varnamelen
- gochecknoinits
- wrapcheck
- funlen - funlen
- depguard - depguard
- nolintlint - wrapcheck
# generics disabled - wsl
- wastedassign settings:
- rowserrcheck
# deprecated
- gomoddirectives
- tenv
linters-settings:
# typecheck:
# enabled: false
errcheck: errcheck:
check-blank: false check-blank: false
exclude-functions: exclude-functions:
- fmt:.* - fmt:.*
nolintlint: nolintlint:
require-specific: true require-specific: true
revive:
issues: rules:
exclude-files: - name: package-comments
disabled: true
exclusions:
paths:
- test/
- ".*_test.go" - ".*_test.go"
issues:
max-issues-per-linter: 0
max-same-issues: 0

View File

@ -16,13 +16,12 @@ jobs:
- fedora-all-aarch64 - fedora-all-aarch64
- epel-9-x86_64 - epel-9-x86_64
- epel-9-aarch64 - epel-9-aarch64
# temporary disabled since epel10 running go v1.23.1 and we need 1.23.3 - epel-10-x86_64
# - epel-10-x86_64 - epel-10-aarch64
# - epel-10-aarch64
- centos-stream-9-x86_64 - centos-stream-9-x86_64
- centos-stream-9-aarch64 - centos-stream-9-aarch64
# - centos-stream-10-x86_64 - centos-stream-10-x86_64
# - centos-stream-10-aarch64 - centos-stream-10-aarch64
actions: actions:
post-upstream-clone: post-upstream-clone:

View File

@ -88,7 +88,7 @@ install.tools: .install.ginkgo .install.bats .install.pre-commit .install.codesp
.PHONY: .install.golangci-lint .PHONY: .install.golangci-lint
.install.golangci-lint: .install.golangci-lint:
VERSION=1.64.4 ./hack/install_golangci.sh VERSION=2.3.1 ./hack/install_golangci.sh
.PHONY: .install.codespell .PHONY: .install.codespell
.install.codespell: .install.codespell:
@ -112,6 +112,7 @@ test-unit: ## Run unit tests
--coverprofile coverprofile \ --coverprofile coverprofile \
--output-dir ${COVERAGE_PATH} \ --output-dir ${COVERAGE_PATH} \
--succinct --succinct
$(GO) tool cover -html=${COVERAGE_PATH}/coverprofile -o ${COVERAGE_PATH}/coverage.html $(GO) tool cover -html=${COVERAGE_PATH}/coverprofile -o ${COVERAGE_PATH}/coverage.html
$(GO) tool cover -func=${COVERAGE_PATH}/coverprofile > ${COVERAGE_PATH}/functions $(GO) tool cover -func=${COVERAGE_PATH}/coverprofile > ${COVERAGE_PATH}/functions
cat ${COVERAGE_PATH}/functions | sed -n 's/\(total:\).*\([0-9][0-9].[0-9]\)/\1 \2/p' cat ${COVERAGE_PATH}/functions | sed -n 's/\(total:\).*\([0-9][0-9].[0-9]\)/\1 \2/p'

View File

@ -24,6 +24,7 @@ import (
// App represents main application struct. // App represents main application struct.
type App struct { type App struct {
*tview.Application *tview.Application
infoBar *infobar.InfoBar infoBar *infobar.InfoBar
pages *tview.Pages pages *tview.Pages
pods *pods.Pods pods *pods.Pods
@ -86,7 +87,8 @@ func NewApp(name string, version string) *App {
app.system.SetConnectionAddFunc(app.config.Add) app.system.SetConnectionAddFunc(app.config.Add)
app.system.SetConnectionRemoveFunc(app.config.Remove) app.system.SetConnectionRemoveFunc(app.config.Remove)
app.system.SetAppFocusHandler(func() { app.system.SetAppFocusHandler(func() {
app.Application.SetFocus(app.system) app.SetFocus(app.system)
app.fastRefreshChan <- true app.fastRefreshChan <- true
}) })
@ -102,32 +104,38 @@ func NewApp(name string, version string) *App {
// set app set focus // set app set focus
app.containers.SetAppFocusHandler(func() { app.containers.SetAppFocusHandler(func() {
app.Application.SetFocus(app.containers) app.SetFocus(app.containers)
app.fastRefreshChan <- true app.fastRefreshChan <- true
}) })
app.pods.SetAppFocusHandler(func() { app.pods.SetAppFocusHandler(func() {
app.Application.SetFocus(app.pods) app.SetFocus(app.pods)
app.fastRefreshChan <- true app.fastRefreshChan <- true
}) })
app.images.SetAppFocusHandler(func() { app.images.SetAppFocusHandler(func() {
app.Application.SetFocus(app.images) app.SetFocus(app.images)
app.fastRefreshChan <- true app.fastRefreshChan <- true
}) })
app.volumes.SetAppFocusHandler(func() { app.volumes.SetAppFocusHandler(func() {
app.Application.SetFocus(app.volumes) app.SetFocus(app.volumes)
app.fastRefreshChan <- true app.fastRefreshChan <- true
}) })
app.networks.SetAppFocusHandler(func() { app.networks.SetAppFocusHandler(func() {
app.Application.SetFocus(app.networks) app.SetFocus(app.networks)
app.fastRefreshChan <- true app.fastRefreshChan <- true
}) })
app.secrets.SetAppFocusHandler(func() { app.secrets.SetAppFocusHandler(func() {
app.Application.SetFocus(app.secrets) app.SetFocus(app.secrets)
app.fastRefreshChan <- true app.fastRefreshChan <- true
}) })
@ -272,7 +280,8 @@ func (app *App) Run() error { //nolint:cyclop
// start fast refresh loop // start fast refresh loop
go app.fastRefresh() go app.fastRefresh()
if err := app.SetRoot(flex, true).SetFocus(app.system).EnableMouse(false).Run(); err != nil { err := app.SetRoot(flex, true).SetFocus(app.system).EnableMouse(false).Run()
if err != nil {
return err return err
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/containers/podman-tui/ui/style" "github.com/containers/podman-tui/ui/style"
"github.com/rivo/tview" "github.com/rivo/tview"
"github.com/rs/zerolog/log"
) )
func newMenu(menuItems [][]string) *tview.TextView { func newMenu(menuItems [][]string) *tview.TextView {
@ -27,7 +28,10 @@ func newMenu(menuItems [][]string) *tview.TextView {
menuList = append(menuList, key+item) menuList = append(menuList, key+item)
} }
fmt.Fprintf(menu, "%s", strings.Join(menuList, " ")) _, err := fmt.Fprintf(menu, "%s", strings.Join(menuList, " "))
if err != nil {
log.Warn().Msgf("failed to create new menu: %s", err.Error())
}
return menu return menu
} }

View File

@ -45,7 +45,7 @@ func (app *App) refresh() {
app.initInfoBar() app.initInfoBar()
app.infoBar.UpdateConnStatus(connStatus) app.infoBar.UpdateConnStatus(connStatus)
app.Application.Draw() app.Draw()
} }
} }
@ -97,7 +97,7 @@ func (app *App) fastRefresh() {
for { for {
refresh := <-app.fastRefreshChan refresh := <-app.fastRefreshChan
if refresh { if refresh {
app.Application.Draw() app.Draw()
} }
} }
} }

View File

@ -88,21 +88,21 @@ func (app *App) switchToNextScreen() {
func (app *App) setPageFocus(page string) { func (app *App) setPageFocus(page string) {
switch page { switch page {
case app.help.GetTitle(): case app.help.GetTitle():
app.Application.SetFocus(app.help) app.SetFocus(app.help)
case app.system.GetTitle(): case app.system.GetTitle():
app.Application.SetFocus(app.system) app.SetFocus(app.system)
case app.pods.GetTitle(): case app.pods.GetTitle():
app.Application.SetFocus(app.pods) app.SetFocus(app.pods)
case app.containers.GetTitle(): case app.containers.GetTitle():
app.Application.SetFocus(app.containers) app.SetFocus(app.containers)
case app.networks.GetTitle(): case app.networks.GetTitle():
app.Application.SetFocus(app.networks) app.SetFocus(app.networks)
case app.images.GetTitle(): case app.images.GetTitle():
app.Application.SetFocus(app.images) app.SetFocus(app.images)
case app.volumes.GetTitle(): case app.volumes.GetTitle():
app.Application.SetFocus(app.volumes) app.SetFocus(app.volumes)
case app.secrets.GetTitle(): case app.secrets.GetTitle():
app.Application.SetFocus(app.secrets) app.SetFocus(app.secrets)
} }
} }

View File

@ -29,7 +29,7 @@ func Execute() {
cobra.CheckErr(rootCmd.Execute()) cobra.CheckErr(rootCmd.Execute())
} }
func run(cmd *cobra.Command, args []string) error { //nolint:cyclop,revive func run(cmd *cobra.Command, args []string) error { //nolint:cyclop
var ( var (
logOutput = io.Discard logOutput = io.Discard
runLog = fmt.Sprintf("starting %s version %s", appName, appVersion) runLog = fmt.Sprintf("starting %s version %s", appName, appVersion)
@ -54,12 +54,12 @@ func run(cmd *cobra.Command, args []string) error { //nolint:cyclop,revive
return err return err
} }
logFD, err := os.OpenFile(logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm) logFD, err := os.OpenFile(logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm) //nolint:gosec
if err != nil { if err != nil {
return err return err
} }
defer logFD.Close() defer logFD.Close() //nolint:errcheck
logOutput = logFD logOutput = logFD
} }
@ -95,9 +95,11 @@ func run(cmd *cobra.Command, args []string) error { //nolint:cyclop,revive
} }
app := app.NewApp(appName, appVersion) app := app.NewApp(appName, appVersion)
if err := app.Run(); err != nil {
err = app.Run()
if err != nil {
if setSSHIdentityPassphrase { if setSSHIdentityPassphrase {
os.Unsetenv("CONTAINER_PASSPHRASE") os.Unsetenv("CONTAINER_PASSPHRASE") //nolint:errcheck
} }
return err return err
@ -106,7 +108,10 @@ func run(cmd *cobra.Command, args []string) error { //nolint:cyclop,revive
// unset CONTAINER_PASSPHRASE environment variable if we have set it // unset CONTAINER_PASSPHRASE environment variable if we have set it
// after application exits // after application exits
if setSSHIdentityPassphrase { if setSSHIdentityPassphrase {
os.Unsetenv("CONTAINER_PASSPHRASE") err := os.Unsetenv("CONTAINER_PASSPHRASE")
if err != nil {
log.Error().Msgf("failed to unset env var CONTAINER_PASSPHRASE: %s", err.Error())
}
} }
return nil return nil

View File

@ -7,7 +7,7 @@ import (
) )
const ( const (
appVersion = "1.7.0" appVersion = "1.8.0-dev"
) )
// versionCmd represents the version command. // versionCmd represents the version command.
@ -15,7 +15,7 @@ var versionCmd = &cobra.Command{
Use: "version", Use: "version",
Short: fmt.Sprintf("Display %s version and exit.\n", appName), Short: fmt.Sprintf("Display %s version and exit.\n", appName),
Long: fmt.Sprintf("Display %s version and exit.\n", appName), Long: fmt.Sprintf("Display %s version and exit.\n", appName),
Run: func(cmd *cobra.Command, args []string) { //nolint:revive Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("%s v%s\n", appName, appVersion) //nolint:forbidigo fmt.Printf("%s v%s\n", appName, appVersion) //nolint:forbidigo
}, },
} }

View File

@ -2,7 +2,6 @@ package pconfig
import ( import (
"slices" "slices"
"sort"
cconfig "github.com/containers/common/pkg/config" cconfig "github.com/containers/common/pkg/config"
"github.com/containers/podman-tui/config/utils" "github.com/containers/podman-tui/config/utils"
@ -55,8 +54,6 @@ func (c *Config) RemoteConnections() []registry.Connection {
}) })
} }
sort.Sort(utils.ConnectionListSortedName{rconn}) //nolint:govet
return rconn return rconn
} }

View File

@ -20,11 +20,13 @@ func (c *Config) Add(name string, uri string, identity string) error {
Default: false, Default: false,
} }
if err := c.add(name, conn); err != nil { err = c.add(name, conn)
if err != nil {
return err return err
} }
if err := c.write(); err != nil { err = c.write()
if err != nil {
return err return err
} }

View File

@ -3,7 +3,6 @@ package tconfig
import ( import (
"errors" "errors"
"os" "os"
"sort"
"sync" "sync"
"github.com/containers/podman-tui/config/utils" "github.com/containers/podman-tui/config/utils"
@ -54,33 +53,21 @@ func NewConfig() (*Config, error) {
newConfig := &Config{} newConfig := &Config{}
newConfig.Connection.Connections = make(map[string]RemoteConnection) newConfig.Connection.Connections = make(map[string]RemoteConnection)
if _, err := os.Stat(path); err == nil { _, err = os.Stat(path)
if err := newConfig.readConfigFromFile(path); err != nil { if err == nil {
err := newConfig.readConfigFromFile(path)
if err != nil {
return nil, err return nil, err
} }
} else { } else if !os.IsNotExist(err) {
if !os.IsNotExist(err) {
return nil, err return nil, err
} }
}
newConfig.addLocalHostIfEmptyConfig() newConfig.addLocalHostIfEmptyConfig()
return newConfig, nil return newConfig, nil
} }
func (c *Config) addLocalHostIfEmptyConfig() {
if len(c.Connection.Connections) > 0 {
return
}
c.Connection.Connections = make(map[string]RemoteConnection)
c.Connection.Connections["localhost"] = RemoteConnection{
URI: utils.LocalNodeUnixSocket(),
Default: true,
}
}
// RemoteConnections returns list of available connections. // RemoteConnections returns list of available connections.
func (c *Config) RemoteConnections() []registry.Connection { func (c *Config) RemoteConnections() []registry.Connection {
rconn := make([]registry.Connection, 0) rconn := make([]registry.Connection, 0)
@ -97,7 +84,17 @@ func (c *Config) RemoteConnections() []registry.Connection {
}) })
} }
sort.Sort(utils.ConnectionListSortedName{rconn}) //nolint:govet
return rconn return rconn
} }
func (c *Config) addLocalHostIfEmptyConfig() {
if len(c.Connection.Connections) > 0 {
return
}
c.Connection.Connections = make(map[string]RemoteConnection)
c.Connection.Connections["localhost"] = RemoteConnection{
URI: utils.LocalNodeUnixSocket(),
Default: true,
}
}

View File

@ -10,34 +10,19 @@ import (
func (c *Config) SetDefaultConnection(name string) error { func (c *Config) SetDefaultConnection(name string) error {
log.Debug().Msgf("config: set %s as default connection", name) log.Debug().Msgf("config: set %s as default connection", name)
if err := c.setDef(name); err != nil { err := c.setDef(name)
if err != nil {
return err return err
} }
if err := c.write(); err != nil { err = c.write()
if err != nil {
return err return err
} }
return c.reload() return c.reload()
} }
func (c *Config) setDef(name string) error {
c.mu.Lock()
defer c.mu.Unlock()
for connName := range c.Connection.Connections {
if connName == name {
dest := c.Connection.Connections[connName]
dest.Default = true
c.Connection.Connections[connName] = dest
return nil
}
}
return utils.ErrConnectionNotFound
}
func (c *Config) GetDefaultConnection() registry.Connection { func (c *Config) GetDefaultConnection() registry.Connection {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -54,3 +39,20 @@ func (c *Config) GetDefaultConnection() registry.Connection {
return registry.Connection{} return registry.Connection{}
} }
func (c *Config) setDef(name string) error {
c.mu.Lock()
defer c.mu.Unlock()
for connName := range c.Connection.Connections {
if connName == name {
dest := c.Connection.Connections[connName]
dest.Default = true
c.Connection.Connections[connName] = dest
return nil
}
}
return utils.ErrConnectionNotFound
}

View File

@ -16,7 +16,7 @@ func (c *Config) readConfigFromFile(path string) error {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
cfgFile, err := os.Open(path) cfgFile, err := os.Open(path) //nolint:gosec
if err != nil { if err != nil {
return fmt.Errorf("config: %w open configuration %q", err, path) return fmt.Errorf("config: %w open configuration %q", err, path)
} }
@ -37,7 +37,8 @@ func (c *Config) reload() error {
return err return err
} }
if err := c.readConfigFromFile(path); err != nil { err = c.readConfigFromFile(path)
if err != nil {
return err return err
} }

View File

@ -8,7 +8,8 @@ func (c *Config) Remove(name string) error {
c.remove(name) c.remove(name)
if err := c.write(); err != nil { err := c.write()
if err != nil {
return err return err
} }

View File

@ -24,23 +24,30 @@ func (c *Config) write() error {
log.Debug().Msgf("config: write configuration file %q", path) log.Debug().Msgf("config: write configuration file %q", path)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { //nolint:mnd err = os.MkdirAll(filepath.Dir(path), 0o750) //nolint:mnd
return err
}
configFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o640) //nolint:mnd
if err != nil { if err != nil {
return err return err
} }
defer configFile.Close() configFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o600) //nolint:mnd,gosec
if err != nil {
return err
}
defer func() {
err := configFile.Close()
if err != nil {
log.Error().Msgf("failed to close config file after write: %s", err.Error())
}
}()
jsonData, err := json.Marshal(c.Connection) jsonData, err := json.Marshal(c.Connection)
if err != nil { if err != nil {
return fmt.Errorf("config: configuration json marshal %w", err) return fmt.Errorf("config: configuration json marshal %w", err)
} }
if _, err := configFile.Write(jsonData); err != nil { _, err = configFile.Write(jsonData)
if err != nil {
return fmt.Errorf("config: %w write configuration %q", err, path) return fmt.Errorf("config: %w write configuration %q", err, path)
} }

View File

@ -34,7 +34,8 @@ func ValidateNewConnection(name string, dest string, identity string) (string, e
return "", ErrEmptyURIDestination return "", ErrEmptyURIDestination
} }
if match, err := regexp.Match("^[A-Za-z][A-Za-z0-9+.-]*://", []byte(dest)); err != nil { //nolint:mirror match, err := regexp.Match("^[A-Za-z][A-Za-z0-9+.-]*://", []byte(dest)) //nolint:mirror
if err != nil {
return "", fmt.Errorf("%w invalid destition", err) return "", fmt.Errorf("%w invalid destition", err)
} else if !match { } else if !match {
dest = "ssh://" + dest dest = "ssh://" + dest
@ -48,7 +49,8 @@ func ValidateNewConnection(name string, dest string, identity string) (string, e
switch uri.Scheme { switch uri.Scheme {
case "ssh": case "ssh":
if uri.User.Username() == "" { if uri.User.Username() == "" {
if uri.User, err = getUserInfo(uri); err != nil { uri.User, err = getUserInfo(uri)
if err != nil {
return "", err return "", err
} }
} }
@ -67,7 +69,8 @@ func ValidateNewConnection(name string, dest string, identity string) (string, e
} }
if uri.Path == "" || uri.Path == "/" { if uri.Path == "" || uri.Path == "/" {
if uri.Path, err = getUDS(uri, connIdentity); err != nil { uri.Path, err = getUDS(uri, connIdentity)
if err != nil {
return "", err return "", err
} }
} }

View File

@ -1,14 +0,0 @@
package utils
import "github.com/containers/podman-tui/pdcs/registry"
type ConnSort []registry.Connection
func (a ConnSort) Len() int { return len(a) }
func (a ConnSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type ConnectionListSortedName struct{ ConnSort }
func (a ConnectionListSortedName) Less(i, j int) bool {
return a.ConnSort[i].Name < a.ConnSort[j].Name
}

View File

@ -91,7 +91,7 @@ func getUserInfo(uri *url.URL) (*url.Userinfo, error) {
} }
// most of the codes are from https://github.com/containers/podman/blob/main/cmd/podman/system/connection/add.go. // most of the codes are from https://github.com/containers/podman/blob/main/cmd/podman/system/connection/add.go.
func getUDS(uri *url.URL, iden string) (string, error) { func getUDS(uri *url.URL, iden string) (string, error) { //nolint:cyclop
cfg, err := validateAndConfigure(uri, iden) cfg, err := validateAndConfigure(uri, iden)
if err != nil { if err != nil {
return "", fmt.Errorf("%w failed to validate", err) return "", fmt.Errorf("%w failed to validate", err)
@ -102,14 +102,24 @@ func getUDS(uri *url.URL, iden string) (string, error) {
return "", fmt.Errorf("%w failed to connect", err) return "", fmt.Errorf("%w failed to connect", err)
} }
defer dial.Close() defer func() {
err := dial.Close()
if err != nil {
log.Error().Msgf("failed to close SSH tcp connection: %s", err.Error())
}
}()
session, err := dial.NewSession() session, err := dial.NewSession()
if err != nil { if err != nil {
return "", fmt.Errorf("%w failed to create new ssh session on %q", err, uri.Host) return "", fmt.Errorf("%w failed to create new ssh session on %q", err, uri.Host)
} }
defer session.Close() defer func() {
err := session.Close()
if err != nil {
log.Error().Msgf("failed to close SSH session: %s", err.Error())
}
}()
// Override podman binary for testing etc // Override podman binary for testing etc
podman := "podman" podman := "podman"
@ -123,7 +133,9 @@ func getUDS(uri *url.URL, iden string) (string, error) {
} }
var info define.Info var info define.Info
if err := json.Unmarshal(infoJSON, &info); err != nil {
err = json.Unmarshal(infoJSON, &info)
if err != nil {
return "", fmt.Errorf("%w failed to parse 'podman info' results", err) return "", fmt.Errorf("%w failed to parse 'podman info' results", err)
} }
@ -167,7 +179,7 @@ func validateAndConfigure(uri *url.URL, iden string) (*ssh.ClientConfig, error)
if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found {
log.Debug().Msgf("config: Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock) log.Debug().Msgf("config: Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock)
c, err := net.Dial("unix", sock) c, err := net.Dial("unix", sock) //nolint:noctx
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -233,7 +245,12 @@ func execRemoteCommand(dial *ssh.Client, run string) ([]byte, error) {
return nil, err return nil, err
} }
defer sess.Close() defer func() {
err := sess.Close()
if err != nil {
log.Error().Msgf("failed to close exec session: %s", err.Error())
}
}()
var ( var (
buffer bytes.Buffer buffer bytes.Buffer
@ -243,7 +260,8 @@ func execRemoteCommand(dial *ssh.Client, run string) ([]byte, error) {
sess.Stdout = &buffer // output from client funneled into buffer sess.Stdout = &buffer // output from client funneled into buffer
sess.Stderr = &bufferErr // err form client funneled into buffer sess.Stderr = &bufferErr // err form client funneled into buffer
if err := sess.Run(run); err != nil { // run the command on the ssh client err = sess.Run(run)
if err != nil { // run the command on the ssh client
return nil, fmt.Errorf("%w %s", err, bufferErr.String()) return nil, fmt.Errorf("%w %s", err, bufferErr.String())
} }

80
go.mod
View File

@ -3,35 +3,34 @@ module github.com/containers/podman-tui
go 1.23.3 go 1.23.3
require ( require (
github.com/containers/buildah v1.40.1 github.com/containers/buildah v1.41.3
github.com/containers/common v0.63.1 github.com/containers/common v0.64.1
github.com/containers/podman/v5 v5.5.2 github.com/containers/podman/v5 v5.6.0
github.com/containers/storage v1.58.0 github.com/containers/storage v1.59.1
github.com/distribution/reference v0.6.0 github.com/distribution/reference v0.6.0
github.com/docker/go-units v0.5.0 github.com/docker/go-units v0.5.0
github.com/gdamore/tcell/v2 v2.8.1 github.com/gdamore/tcell/v2 v2.9.0
github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-multierror v1.1.1
github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02
github.com/navidys/tvxwidgets v0.4.1 github.com/navidys/tvxwidgets v0.4.1
github.com/onsi/ginkgo/v2 v2.23.4 github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.37.0 github.com/onsi/gomega v1.38.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8 github.com/rivo/tview v0.42.0
github.com/rs/zerolog v1.34.0 github.com/rs/zerolog v1.34.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
golang.org/x/crypto v0.40.0 golang.org/x/crypto v0.41.0
) )
require ( require (
dario.cat/mergo v1.0.1 // indirect dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/BurntSushi/toml v1.5.0 // indirect github.com/BurntSushi/toml v1.5.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.12.9 // indirect github.com/Microsoft/hcsshim v0.13.0 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect github.com/VividCortex/ewma v1.2.0 // indirect
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect github.com/chzyer/readline v1.5.1 // indirect
github.com/containerd/cgroups/v3 v3.0.5 // indirect github.com/containerd/cgroups/v3 v3.0.5 // indirect
@ -41,7 +40,7 @@ require (
github.com/containerd/platforms v1.0.0-rc.1 // indirect github.com/containerd/platforms v1.0.0-rc.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/containers/image/v5 v5.35.0 // indirect github.com/containers/image/v5 v5.36.1 // indirect
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
github.com/containers/ocicrypt v1.2.1 // indirect github.com/containers/ocicrypt v1.2.1 // indirect
github.com/containers/psgo v1.9.0 // indirect github.com/containers/psgo v1.9.0 // indirect
@ -50,25 +49,15 @@ require (
github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/disiqueira/gotree/v3 v3.0.2 // indirect github.com/disiqueira/gotree/v3 v3.0.2 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v28.1.1+incompatible // indirect github.com/docker/docker v28.3.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-connections v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gdamore/encoding v1.0.1 // indirect github.com/gdamore/encoding v1.0.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/errors v0.22.1 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/loads v0.22.0 // indirect
github.com/go-openapi/runtime v0.28.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/strfmt v0.23.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-openapi/validate v0.24.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/godbus/dbus/v5 v5.1.1-0.20241109141217-c266b19b28e9 // indirect github.com/godbus/dbus/v5 v5.1.1-0.20241109141217-c266b19b28e9 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
@ -84,7 +73,6 @@ require (
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/copier v0.4.0 // indirect github.com/jinzhu/copier v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
@ -92,7 +80,6 @@ require (
github.com/kr/fs v0.1.0 // indirect github.com/kr/fs v0.1.0 // indirect
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
@ -100,7 +87,6 @@ require (
github.com/mattn/go-sqlite3 v1.14.28 // indirect github.com/mattn/go-sqlite3 v1.14.28 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mistifyio/go-zfs/v3 v3.0.1 // indirect github.com/mistifyio/go-zfs/v3 v3.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/capability v0.4.0 // indirect github.com/moby/sys/capability v0.4.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect
@ -111,55 +97,51 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/nxadm/tail v1.4.11 // indirect github.com/nxadm/tail v1.4.11 // indirect
github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/cgroups v0.0.4 // indirect
github.com/opencontainers/cgroups v0.0.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.2.6 // indirect github.com/opencontainers/runc v1.3.0 // indirect
github.com/opencontainers/runtime-spec v1.2.1 // indirect github.com/opencontainers/runtime-spec v1.2.1 // indirect
github.com/opencontainers/runtime-tools v0.9.1-0.20250303011046-260e151b8552 // indirect github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2 // indirect
github.com/opencontainers/selinux v1.12.0 // indirect github.com/opencontainers/selinux v1.12.0 // indirect
github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f // indirect
github.com/pkg/sftp v1.13.9 // indirect github.com/pkg/sftp v1.13.9 // indirect
github.com/proglottis/gpgme v0.1.4 // indirect github.com/proglottis/gpgme v0.1.4 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect
github.com/sigstore/fulcio v1.6.6 // indirect github.com/sigstore/fulcio v1.6.6 // indirect
github.com/sigstore/protobuf-specs v0.4.1 // indirect github.com/sigstore/protobuf-specs v0.4.1 // indirect
github.com/sigstore/rekor v1.3.10 // indirect github.com/sigstore/sigstore v1.9.5 // indirect
github.com/sigstore/sigstore v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect github.com/skeema/knownhosts v1.3.1 // indirect
github.com/smallstep/pkcs7 v0.1.1 // indirect github.com/smallstep/pkcs7 v0.1.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.6 // indirect
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect
github.com/sylabs/sif/v2 v2.21.1 // indirect github.com/sylabs/sif/v2 v2.21.1 // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/tchap/go-patricia/v2 v2.3.3 // indirect
github.com/tchap/go-patricia/v2 v2.3.2 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect github.com/ulikunitz/xz v0.5.12 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect github.com/vbatts/tar-split v0.12.1 // indirect
github.com/vbauerster/mpb/v8 v8.9.3 // indirect github.com/vbauerster/mpb/v8 v8.10.2 // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect
golang.org/x/net v0.41.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sync v0.16.0 // indirect golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect golang.org/x/sys v0.35.0 // indirect
golang.org/x/term v0.33.0 // indirect golang.org/x/term v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.11.0 // indirect golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.34.0 // indirect golang.org/x/tools v0.35.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
google.golang.org/grpc v1.71.0 // indirect google.golang.org/grpc v1.72.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect sigs.k8s.io/yaml v1.5.0 // indirect
tags.cncf.io/container-device-interface v1.0.1 // indirect tags.cncf.io/container-device-interface v1.0.1 // indirect
) )

211
go.sum
View File

@ -1,8 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 h1:SCbEWT58NSt7d2mcFdvxC9uyrdcTfvBbPLThhkDmXzg=
github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@ -10,14 +8,12 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=
github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y= github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
@ -52,22 +48,22 @@ github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRcc
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
github.com/containers/buildah v1.40.1 h1:RW+Fbelwblzg1mJfKfyGZPS4Nbc5QtT866fJ9pYFtYo= github.com/containers/buildah v1.41.3 h1:L6wyRXGR+Cejx+/LMsEryi1JmuwbAICZEje+tQ7ihMs=
github.com/containers/buildah v1.40.1/go.mod h1:1UCQBc3LZrT4u5R/u7igGgUQxeDlJmn/OyYDQ9mumFk= github.com/containers/buildah v1.41.3/go.mod h1:vVIYC6f5gbPNfhprdMZh9lkOJzzM7lta0romUtBFSw0=
github.com/containers/common v0.63.1 h1:6g02gbW34PaRVH4Heb2Pk11x0SdbQ+8AfeKKeQGqYBE= github.com/containers/common v0.64.1 h1:E8vSiL+B84/UCsyVSb70GoxY9cu+0bseLujm4EKF6GE=
github.com/containers/common v0.63.1/go.mod h1:+3GCotSqNdIqM3sPs152VvW7m5+Mg8Kk+PExT3G9hZw= github.com/containers/common v0.64.1/go.mod h1:CtfQNHoCAZqWeXMwdShcsxmMJSeGRgKKMqAwRKmWrHE=
github.com/containers/image/v5 v5.35.0 h1:T1OeyWp3GjObt47bchwD9cqiaAm/u4O4R9hIWdrdrP8= github.com/containers/image/v5 v5.36.1 h1:6zpXBqR59UcAzoKpa/By5XekeqFV+htWYfr65+Cgjqo=
github.com/containers/image/v5 v5.35.0/go.mod h1:8vTsgb+1gKcBL7cnjyNOInhJQfTUQjJoO2WWkKDoebM= github.com/containers/image/v5 v5.36.1/go.mod h1:b4GMKH2z/5t6/09utbse2ZiLK/c72GuGLFdp7K69eA4=
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA=
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM= github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM=
github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ= github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ=
github.com/containers/podman/v5 v5.5.2 h1:H9C6hRs+Aa9g5x/wApHoVqT0fxFr0DH0tgs1h1/ktHc= github.com/containers/podman/v5 v5.6.0 h1:tBs3B4RJHeCJsYPK90DkQYSCupa0vau/8JmBNH/Kz9k=
github.com/containers/podman/v5 v5.5.2/go.mod h1:mxncFv2XqD6ZlXZnBk4JpGmQfkx1+k7KyuBTy1d4Xfc= github.com/containers/podman/v5 v5.6.0/go.mod h1:Qxcq3VrAbVqdw7C5MPtGoGxTNpfnJFACL5+flxNgf/Y=
github.com/containers/psgo v1.9.0 h1:eJ74jzSaCHnWt26OlKZROSyUyRcGDf+gYBdXnxrMW4g= github.com/containers/psgo v1.9.0 h1:eJ74jzSaCHnWt26OlKZROSyUyRcGDf+gYBdXnxrMW4g=
github.com/containers/psgo v1.9.0/go.mod h1:0YoluUm43Mz2UnBIh1P+6V6NWcbpTL5uRtXyOcH0B5A= github.com/containers/psgo v1.9.0/go.mod h1:0YoluUm43Mz2UnBIh1P+6V6NWcbpTL5uRtXyOcH0B5A=
github.com/containers/storage v1.58.0 h1:Q7SyyCCjqgT3wYNgRNIL8o/wUS92heIj2/cc8Sewvcc= github.com/containers/storage v1.59.1 h1:11Zu68MXsEQGBBd+GadPrHPpWeqjKS8hJDGiAHgIqDs=
github.com/containers/storage v1.58.0/go.mod h1:w7Jl6oG+OpeLGLzlLyOZPkmUso40kjpzgrHUk5tyBlo= github.com/containers/storage v1.59.1/go.mod h1:KoAYHnAjP3/cTsRS+mmWZGkufSY2GACiKQ4V3ZLQnR0=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09 h1:OoRAFlvDGCUqDLampLQjk0yeeSGdF9zzst/3G9IkBbc= github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09 h1:OoRAFlvDGCUqDLampLQjk0yeeSGdF9zzst/3G9IkBbc=
github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09/go.mod h1:m2r/smMKsKwgMSAoFKHaa68ImdCSNuKE1MxvQ64xuCQ= github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09/go.mod h1:m2r/smMKsKwgMSAoFKHaa68ImdCSNuKE1MxvQ64xuCQ=
@ -86,12 +82,12 @@ github.com/disiqueira/gotree/v3 v3.0.2 h1:ik5iuLQQoufZBNPY518dXhiO5056hyNBIK9lWh
github.com/disiqueira/gotree/v3 v3.0.2/go.mod h1:ZuyjE4+mUQZlbpkI24AmruZKhg3VHEgPLDY8Qk+uUu8= github.com/disiqueira/gotree/v3 v3.0.2/go.mod h1:ZuyjE4+mUQZlbpkI24AmruZKhg3VHEgPLDY8Qk+uUu8=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v28.0.4+incompatible h1:pBJSJeNd9QeIWPjRcV91RVJihd/TXB77q1ef64XEu4A= github.com/docker/cli v28.3.2+incompatible h1:mOt9fcLE7zaACbxW1GeS65RI67wIJrTnqS3hP2huFsY=
github.com/docker/cli v28.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v28.3.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= github.com/docker/docker v28.3.2+incompatible h1:wn66NJ6pWB1vBZIilP8G3qQPqHy5XymfYn5vsqeA5oA=
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
@ -109,39 +105,17 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04= github.com/gdamore/tcell/v2 v2.9.0 h1:N6t+eqK7/xwtRPwxzs1PXeRWnm0H9l02CrgJ7DLn1ys=
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU= github.com/gdamore/tcell/v2 v2.9.0/go.mod h1:8/ZoqM9rxzYphT9tH/9LnunhV9oPBqwS8WHGYm5nrmo=
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU=
github.com/go-openapi/errors v0.22.1/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
@ -174,7 +148,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
@ -208,8 +181,6 @@ github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs=
github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
@ -230,8 +201,6 @@ github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2T
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk= github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@ -240,7 +209,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
@ -249,8 +217,6 @@ github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mistifyio/go-zfs/v3 v3.0.1 h1:YaoXgBePoMA12+S1u/ddkv+QqxcfiZK4prI6HPnkFiU= github.com/mistifyio/go-zfs/v3 v3.0.1 h1:YaoXgBePoMA12+S1u/ddkv+QqxcfiZK4prI6HPnkFiU=
github.com/mistifyio/go-zfs/v3 v3.0.1/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k= github.com/mistifyio/go-zfs/v3 v3.0.1/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
@ -280,28 +246,24 @@ github.com/navidys/tvxwidgets v0.4.1 h1:abITHN2R0AN1G5XYBDCeTBfR+E1FRsDKN5j1FKI8
github.com/navidys/tvxwidgets v0.4.1/go.mod h1:VJRhOCt9q4cuqN1UebaWRAc0MG4pmpHMBWL3tRSoqdI= github.com/navidys/tvxwidgets v0.4.1/go.mod h1:VJRhOCt9q4cuqN1UebaWRAc0MG4pmpHMBWL3tRSoqdI=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o=
github.com/opencontainers/cgroups v0.0.1 h1:MXjMkkFpKv6kpuirUa4USFBas573sSAY082B4CiHEVA= github.com/opencontainers/cgroups v0.0.4 h1:XVj8P/IHVms/j+7eh8ggdkTLAxjz84ZzuFyGoE28DR4=
github.com/opencontainers/cgroups v0.0.1/go.mod h1:s8lktyhlGUqM7OSRL5P7eAW6Wb+kWPNvt4qvVfzA5vs= github.com/opencontainers/cgroups v0.0.4/go.mod h1:s8lktyhlGUqM7OSRL5P7eAW6Wb+kWPNvt4qvVfzA5vs=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runc v1.2.6 h1:P7Hqg40bsMvQGCS4S7DJYhUZOISMLJOB2iGX5COWiPk= github.com/opencontainers/runc v1.3.0 h1:cvP7xbEvD0QQAs0nZKLzkVog2OPZhI/V2w3WmTmUSXI=
github.com/opencontainers/runc v1.2.6/go.mod h1:dOQeFo29xZKBNeRBI0B19mJtfHv68YgCTh1X+YphA+4= github.com/opencontainers/runc v1.3.0/go.mod h1:9wbWt42gV+KRxKRVVugNP6D5+PQciRbenB4fLVsqGPs=
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-tools v0.9.1-0.20250303011046-260e151b8552 h1:CkXngT0nixZqQUPDVfwVs3GiuhfTqCMk0V+OoHpxIvA= github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2 h1:2xZEHOdeQBV6PW8ZtimN863bIOl7OCW/X10K0cnxKeA=
github.com/opencontainers/runtime-tools v0.9.1-0.20250303011046-260e151b8552/go.mod h1:T487Kf80NeF2i0OyVXHiylg217e0buz8pQsa0T791RA= github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2/go.mod h1:MXdPzqAA8pHC58USHqNCSjyLnRQ6D+NjbpP+02Z1U/0=
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f h1:/UDgs8FGMqwnHagNDPGOlts35QkhAZ8by3DR7nMih7M=
github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw= github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
@ -313,19 +275,18 @@ github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/proglottis/gpgme v0.1.4 h1:3nE7YNA70o2aLjcg63tXMOhPD7bplfE5CBdV+hLAm2M= github.com/proglottis/gpgme v0.1.4 h1:3nE7YNA70o2aLjcg63tXMOhPD7bplfE5CBdV+hLAm2M=
github.com/proglottis/gpgme v0.1.4/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM= github.com/proglottis/gpgme v0.1.4/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM=
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8 h1:xe+mmCnDN82KhC010l3NfYlA8ZbOuzbXAzSYBa6wbMc= github.com/rivo/tview v0.42.0 h1:b/ftp+RxtDsHSaynXTbJb+/n/BxDEi+W3UfF5jILK6c=
github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8/go.mod h1:WIfMkQNY+oq/mWwtsjOYHIZBuwthioY2srOmljJkTnk= github.com/rivo/tview v0.42.0/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
@ -334,8 +295,8 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY= github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY=
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc=
@ -346,10 +307,8 @@ github.com/sigstore/fulcio v1.6.6 h1:XaMYX6TNT+8n7Npe8D94nyZ7/ERjEsNGFC+REdi/wzw
github.com/sigstore/fulcio v1.6.6/go.mod h1:BhQ22lwaebDgIxVBEYOOqLRcN5+xOV+C9bh/GUXRhOk= github.com/sigstore/fulcio v1.6.6/go.mod h1:BhQ22lwaebDgIxVBEYOOqLRcN5+xOV+C9bh/GUXRhOk=
github.com/sigstore/protobuf-specs v0.4.1 h1:5SsMqZbdkcO/DNHudaxuCUEjj6x29tS2Xby1BxGU7Zc= github.com/sigstore/protobuf-specs v0.4.1 h1:5SsMqZbdkcO/DNHudaxuCUEjj6x29tS2Xby1BxGU7Zc=
github.com/sigstore/protobuf-specs v0.4.1/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= github.com/sigstore/protobuf-specs v0.4.1/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc=
github.com/sigstore/rekor v1.3.10 h1:/mSvRo4MZ/59ECIlARhyykAlQlkmeAQpvBPlmJtZOCU= github.com/sigstore/sigstore v1.9.5 h1:Wm1LT9yF4LhQdEMy5A2JeGRHTrAWGjT3ubE5JUSrGVU=
github.com/sigstore/rekor v1.3.10/go.mod h1:JvryKJ40O0XA48MdzYUPu0y4fyvqt0C4iSY7ri9iu3A= github.com/sigstore/sigstore v1.9.5/go.mod h1:VtxgvGqCmEZN9X2zhFSOkfXxvKUjpy8RpUW39oCtoII=
github.com/sigstore/sigstore v1.9.3 h1:y2qlTj+vh+Or3ictKuR3JUFawZPdDxAjrWkeFhon0OQ=
github.com/sigstore/sigstore v1.9.3/go.mod h1:VwYkiw0G0dRtwL25KSs04hCyVFF6CYMd/qvNeYrl7EQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
@ -374,18 +333,16 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/sylabs/sif/v2 v2.21.1 h1:GZ0b5//AFAqJEChd8wHV/uSKx/l1iuGYwjR8nx+4wPI= github.com/sylabs/sif/v2 v2.21.1 h1:GZ0b5//AFAqJEChd8wHV/uSKx/l1iuGYwjR8nx+4wPI=
github.com/sylabs/sif/v2 v2.21.1/go.mod h1:YoqEGQnb5x/ItV653bawXHZJOXQaEWpGwHsSD3YePJI= github.com/sylabs/sif/v2 v2.21.1/go.mod h1:YoqEGQnb5x/ItV653bawXHZJOXQaEWpGwHsSD3YePJI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM=
github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/vbauerster/mpb/v8 v8.9.3 h1:PnMeF+sMvYv9u23l6DO6Q3+Mdj408mjLRXIzmUmU2Z8= github.com/vbauerster/mpb/v8 v8.10.2 h1:2uBykSHAYHekE11YvJhKxYmLATKHAGorZwFlyNw4hHM=
github.com/vbauerster/mpb/v8 v8.9.3/go.mod h1:hxS8Hz4C6ijnppDSIX6LjG8FYJSoPo9iIOcE53Zik0c= github.com/vbauerster/mpb/v8 v8.10.2/go.mod h1:+Ja4P92E3/CorSZgfDtK46D7AVbDqmBQRTmyTqPElo0=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
@ -395,32 +352,34 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -430,8 +389,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -459,8 +418,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -481,7 +440,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -497,13 +455,10 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
@ -511,12 +466,10 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
@ -524,8 +477,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -540,8 +493,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -560,8 +513,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -585,7 +538,7 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc= tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc=
tags.cncf.io/container-device-interface v1.0.1/go.mod h1:JojJIOeW3hNbcnOH2q0NrWNha/JuHoDZcmYxAZwb2i0= tags.cncf.io/container-device-interface v1.0.1/go.mod h1:JojJIOeW3hNbcnOH2q0NrWNha/JuHoDZcmYxAZwb2i0=

View File

@ -254,7 +254,8 @@ func Create(opts CreateOptions, run bool) ([]string, string, error) { //nolint:c
} }
// add healthcheck options // add healthcheck options
if err := containerHealthOptions(&createOptions, opts); err != nil { err = containerHealthOptions(&createOptions, opts)
if err != nil {
return warningResponse, containerID, err return warningResponse, containerID, err
} }
@ -389,7 +390,9 @@ func Create(opts CreateOptions, run bool) ([]string, string, error) { //nolint:c
// generate spec // generate spec
s := specgen.NewSpecGenerator(opts.Name, false) s := specgen.NewSpecGenerator(opts.Name, false)
if err := specgenutil.FillOutSpecGen(s, &createOptions, nil); err != nil {
err = specgenutil.FillOutSpecGen(s, &createOptions, nil)
if err != nil {
return warningResponse, containerID, err return warningResponse, containerID, err
} }
@ -403,7 +406,8 @@ func Create(opts CreateOptions, run bool) ([]string, string, error) { //nolint:c
} }
// validate spec // validate spec
if err := s.Validate(); err != nil { err = s.Validate()
if err != nil {
return warningResponse, containerID, err return warningResponse, containerID, err
} }

View File

@ -95,7 +95,8 @@ func Exec(sessionID string, opts ExecOption) { //nolint:cyclop
conn, err := registry.GetConnection() conn, err := registry.GetConnection()
if err != nil { if err != nil {
if _, err := opts.OutputStream.Write([]byte(fmt.Sprintf("%v", err))); err != nil { _, err := opts.OutputStream.Write([]byte(fmt.Sprintf("%v", err))) //nolint:staticcheck
if err != nil {
log.Error().Msgf("%v", err) log.Error().Msgf("%v", err)
} }
@ -116,10 +117,12 @@ func Exec(sessionID string, opts ExecOption) { //nolint:cyclop
execStartAttachOpts.InputStream = opts.InputStream execStartAttachOpts.InputStream = opts.InputStream
} }
if err := containers.ExecStartAndAttach(conn, sessionID, execStartAttachOpts); err != nil { err := containers.ExecStartAndAttach(conn, sessionID, execStartAttachOpts)
if err != nil {
log.Error().Msgf("pdcs: podman session (%s) exec error %v", sessionID, err) log.Error().Msgf("pdcs: podman session (%s) exec error %v", sessionID, err)
if _, err := opts.OutputStream.Write([]byte(fmt.Sprintf("%v", err))); err != nil { _, err := opts.OutputStream.Write([]byte(fmt.Sprintf("%v", err))) //nolint:staticcheck
if err != nil {
log.Error().Msgf("%v", err) log.Error().Msgf("%v", err)
} }
} }
@ -129,10 +132,12 @@ func Exec(sessionID string, opts ExecOption) { //nolint:cyclop
return return
} }
if err := containers.ExecStart(conn, sessionID, &containers.ExecStartOptions{}); err != nil { err = containers.ExecStart(conn, sessionID, &containers.ExecStartOptions{})
if err != nil {
log.Error().Msgf("pdcs: podman session (%s) exec error %v", sessionID, err) log.Error().Msgf("pdcs: podman session (%s) exec error %v", sessionID, err)
if _, err := opts.OutputStream.Write([]byte(fmt.Sprintf("%v", err))); err != nil { _, err := opts.OutputStream.Write([]byte(fmt.Sprintf("%v", err))) //nolint:staticcheck
if err != nil {
log.Error().Msgf("%v", err) log.Error().Msgf("%v", err)
} }
@ -141,19 +146,23 @@ func Exec(sessionID string, opts ExecOption) { //nolint:cyclop
log.Debug().Msgf("pdcs: podman session (%s) exec finished successfully", sessionID) log.Debug().Msgf("pdcs: podman session (%s) exec finished successfully", sessionID)
if _, err := opts.OutputStream.Write([]byte(fmt.Sprintf("session_id ...... : %s\r\n", sessionID))); err != nil { _, err = opts.OutputStream.Write([]byte(fmt.Sprintf("session_id ...... : %s\r\n", sessionID))) //nolint:staticcheck
if err != nil {
log.Error().Msgf("%v", err) log.Error().Msgf("%v", err)
} }
if _, err := opts.OutputStream.Write([]byte(fmt.Sprintf("exec_mode ...... : %s\r\n", "detached"))); err != nil { _, err = opts.OutputStream.Write([]byte(fmt.Sprintf("exec_mode ...... : %s\r\n", "detached"))) //nolint:staticcheck
if err != nil {
log.Error().Msgf("%v", err) log.Error().Msgf("%v", err)
} }
if _, err := opts.OutputStream.Write([]byte(fmt.Sprintf("exec_command .... : %s\r\n", strings.Join(opts.Cmd, " ")))); err != nil { //nolint:lll _, err = opts.OutputStream.Write([]byte(fmt.Sprintf("exec_command .... : %s\r\n", strings.Join(opts.Cmd, " ")))) //nolint:staticcheck,lll
if err != nil {
log.Error().Msgf("%v", err) log.Error().Msgf("%v", err)
} }
if _, err := opts.OutputStream.Write([]byte(fmt.Sprintf("exec_status ..... : %s\r\n", "OK"))); err != nil { _, err = opts.OutputStream.Write([]byte(fmt.Sprintf("exec_status ..... : %s\r\n", "OK"))) //nolint:staticcheck
if err != nil {
log.Error().Msgf("%v", err) log.Error().Msgf("%v", err)
} }
} }
@ -164,7 +173,7 @@ func genExecCreateConfig(opts ExecOption) (*handlers.ExecCreateConfig, error) {
createCfg := &handlers.ExecCreateConfig{} createCfg := &handlers.ExecCreateConfig{}
createCfg.Cmd = opts.Cmd createCfg.Cmd = opts.Cmd
createCfg.Tty = opts.Tty createCfg.Tty = opts.Tty
createCfg.Detach = opts.Detach createCfg.Detach = opts.Detach //nolint:staticcheck
createCfg.WorkingDir = opts.WorkDir createCfg.WorkingDir = opts.WorkDir
createCfg.User = opts.User createCfg.User = opts.User

View File

@ -1,8 +1,6 @@
package containers package containers
import ( import (
"sort"
"github.com/containers/podman-tui/pdcs/registry" "github.com/containers/podman-tui/pdcs/registry"
"github.com/containers/podman/v5/pkg/bindings/containers" "github.com/containers/podman/v5/pkg/bindings/containers"
"github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/domain/entities"
@ -23,20 +21,7 @@ func List() ([]entities.ListContainer, error) {
return nil, err return nil, err
} }
sort.Sort(containerListSortedName{response})
log.Debug().Msgf("pdcs: %v", response) log.Debug().Msgf("pdcs: %v", response)
return response, nil return response, nil
} }
type lprSort []entities.ListContainer
func (a lprSort) Len() int { return len(a) }
func (a lprSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type containerListSortedName struct{ lprSort }
func (a containerListSortedName) Less(i, j int) bool {
return a.lprSort[i].Names[0] < a.lprSort[j].Names[0]
}

View File

@ -44,6 +44,7 @@ func Logs(id string) ([]string, error) {
if err != nil { if err != nil {
return logs, err return logs, err
} }
done <- true done <- true
// logs = append(logs, <-logout) // logs = append(logs, <-logout)
// logs = append(logs, <-logerr) // logs = append(logs, <-logerr)

View File

@ -43,9 +43,9 @@ type conReporter struct {
} }
func (con conReporter) ports() string { func (con conReporter) ports() string {
if len(con.ListContainer.Ports) < 1 { if len(con.Ports) < 1 {
return "" return ""
} }
return utils.PortsToString(con.ListContainer.Ports) return utils.PortsToString(con.Ports)
} }

View File

@ -20,11 +20,13 @@ func RunInitAttach(cntID string, stdin io.Reader, stdout io.Writer, attachReady
attachOptions := new(containers.AttachOptions) attachOptions := new(containers.AttachOptions)
attachOptions.WithDetachKeys(detachKey) attachOptions.WithDetachKeys(detachKey)
if err := containers.ContainerInit(conn, cntID, new(containers.InitOptions)); err != nil { err = containers.ContainerInit(conn, cntID, new(containers.InitOptions))
if err != nil {
return err return err
} }
if err := containers.Attach(conn, cntID, stdin, stdout, stdout, attachReady, attachOptions); err != nil { err = containers.Attach(conn, cntID, stdin, stdout, stdout, attachReady, attachOptions)
if err != nil {
return err return err
} }

View File

@ -40,7 +40,12 @@ func Import(opts ImageImportOptions) (string, error) {
return "", err return "", err
} }
defer tarFile.Close() defer func() {
err := tarFile.Close()
if err != nil {
log.Error().Msgf("failed to close tar file: %s", err.Error())
}
}()
reader = bufio.NewReader(tarFile) reader = bufio.NewReader(tarFile)
} }

View File

@ -2,11 +2,10 @@ package images
import ( import (
"fmt" "fmt"
"sort"
"github.com/containers/podman-tui/pdcs/registry" "github.com/containers/podman-tui/pdcs/registry"
"github.com/containers/podman/v5/pkg/bindings/images" "github.com/containers/podman/v5/pkg/bindings/images"
"github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/domain/entities/types"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -30,7 +29,7 @@ func List() ([]ImageListReporter, error) {
return nil, err return nil, err
} }
imgs, err := sortImages(response) imgs, err := imageListReporter(response)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -42,12 +41,13 @@ func List() ([]ImageListReporter, error) {
// ImageListReporter image list report. // ImageListReporter image list report.
type ImageListReporter struct { type ImageListReporter struct {
types.ImageSummary
Repository string `json:"repository,omitempty"` Repository string `json:"repository,omitempty"`
Tag string `json:"tag,omitempty"` Tag string `json:"tag,omitempty"`
entities.ImageSummary
} }
func sortImages(imageS []*entities.ImageSummary) ([]ImageListReporter, error) { func imageListReporter(imageS []*types.ImageSummary) ([]ImageListReporter, error) {
imgs := make([]ImageListReporter, 0, len(imageS)) imgs := make([]ImageListReporter, 0, len(imageS))
var err error var err error
@ -88,17 +88,9 @@ func sortImages(imageS []*entities.ImageSummary) ([]ImageListReporter, error) {
} }
} }
sort.Slice(imgs, sortFunc(imgs))
return imgs, err return imgs, err
} }
func sortFunc(data []ImageListReporter) func(i, j int) bool {
return func(i, j int) bool {
return data[i].Repository < data[j].Repository
}
}
func tokenRepoTag(ref string) (string, string, error) { func tokenRepoTag(ref string) (string, string, error) {
tagRef := fmt.Sprintf("%s:%s", noneTag, noneTag) tagRef := fmt.Sprintf("%s:%s", noneTag, noneTag)
if ref == tagRef { if ref == tagRef {

View File

@ -44,7 +44,13 @@ func Save(imageID string, opts ImageSaveOptions) error { //nolint:cyclop
return err return err
} }
defer outputFile.Close()
defer func() {
err := outputFile.Close()
if err != nil {
log.Error().Msgf("failed to close image save output file: %s", err.Error())
}
}()
cancelChan := make(chan bool, 1) cancelChan := make(chan bool, 1)
writerChan := make(chan []byte, 1024) //nolint:mnd writerChan := make(chan []byte, 1024) //nolint:mnd
@ -62,7 +68,8 @@ func Save(imageID string, opts ImageSaveOptions) error { //nolint:cyclop
return return
case data := <-writerChan: case data := <-writerChan:
if _, err := outputFile.Write(data); err != nil { _, err := outputFile.Write(data)
if err != nil {
outputErrors = append(outputErrors, err) outputErrors = append(outputErrors, err)
} }
} }
@ -77,6 +84,7 @@ func Save(imageID string, opts ImageSaveOptions) error { //nolint:cyclop
saveOpts.WithOciAcceptUncompressedLayers(opts.OciAcceptUncompressedLayers) saveOpts.WithOciAcceptUncompressedLayers(opts.OciAcceptUncompressedLayers)
err = images.Export(conn, []string{imageID}, outputWriter, &saveOpts) err = images.Export(conn, []string{imageID}, outputWriter, &saveOpts)
cancelChan <- true cancelChan <- true
if err != nil { if err != nil {

View File

@ -1,8 +1,6 @@
package networks package networks
import ( import (
"sort"
"github.com/containers/common/libnetwork/types" "github.com/containers/common/libnetwork/types"
"github.com/containers/podman-tui/pdcs/registry" "github.com/containers/podman-tui/pdcs/registry"
"github.com/containers/podman/v5/pkg/bindings/network" "github.com/containers/podman/v5/pkg/bindings/network"
@ -10,10 +8,10 @@ import (
) )
// List returns list of podman networks. // List returns list of podman networks.
func List() ([][]string, error) { func List() ([]types.Network, error) {
log.Debug().Msg("pdcs: podman network ls") log.Debug().Msg("pdcs: podman network ls")
report := make([][]string, 0) report := make([]types.Network, 0)
conn, err := registry.GetConnection() conn, err := registry.GetConnection()
if err != nil { if err != nil {
@ -25,26 +23,7 @@ func List() ([][]string, error) {
return report, err return report, err
} }
sort.Sort(netListSortedName{response}) log.Debug().Msgf("pdcs: %v", response)
for _, item := range response { return response, nil
report = append(report, []string{
item.ID,
item.Name,
item.Driver,
})
} }
log.Debug().Msgf("pdcs: %v", report)
return report, nil
}
type lprSort []types.Network
func (a lprSort) Len() int { return len(a) }
func (a lprSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type netListSortedName struct{ lprSort }
func (a netListSortedName) Less(i, j int) bool { return a.lprSort[i].Name < a.lprSort[j].Name }

View File

@ -224,7 +224,8 @@ func Create(opts CreateOptions) error { //nolint:cyclop,gocognit,gocyclo
} }
// validate spec // validate spec
if err := podSpec.Validate(); err != nil { err = podSpec.Validate()
if err != nil {
errList = append(errList, err) errList = append(errList, err)
} }

View File

@ -1,8 +1,6 @@
package pods package pods
import ( import (
"sort"
"github.com/containers/podman-tui/pdcs/registry" "github.com/containers/podman-tui/pdcs/registry"
"github.com/containers/podman/v5/pkg/bindings/pods" "github.com/containers/podman/v5/pkg/bindings/pods"
"github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/domain/entities"
@ -23,18 +21,7 @@ func List() ([]*entities.ListPodsReport, error) {
return nil, err return nil, err
} }
sort.Sort(podPsSortedName{response})
log.Debug().Msgf("pdcs: %v", response) log.Debug().Msgf("pdcs: %v", response)
return response, nil return response, nil
} }
type lprSort []*entities.ListPodsReport
func (a lprSort) Len() int { return len(a) }
func (a lprSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type podPsSortedName struct{ lprSort }
func (a podPsSortedName) Less(i, j int) bool { return a.lprSort[i].Name < a.lprSort[j].Name }

View File

@ -54,6 +54,7 @@ func sortStats(podSReport []*entities.PodStatsReport, sortBy int) []StatReporter
for _, item := range podSReport { for _, item := range podSReport {
var reporterItem StatReporter var reporterItem StatReporter
reporterItem.Pod = item.Pod reporterItem.Pod = item.Pod
reporterItem.CID = item.CID reporterItem.CID = item.CID
reporterItem.Name = item.Name reporterItem.Name = item.Name

View File

@ -49,14 +49,17 @@ func SetConnectionStatus(status ConnStatus) {
pdcsRegistry.mu.Lock() pdcsRegistry.mu.Lock()
defer pdcsRegistry.mu.Unlock() defer pdcsRegistry.mu.Unlock()
pdcsRegistry.connection.Status = status pdcsRegistry.connection.Status = status
} }
// SetConnection sets registry connection. // SetConnection sets registry connection.
func SetConnection(connection Connection) { func SetConnection(connection Connection) {
log.Debug().Msgf("pdcs: registry set connection %v", connection) log.Debug().Msgf("pdcs: registry set connection %v", connection)
pdcsRegistry.mu.Lock() pdcsRegistry.mu.Lock()
defer pdcsRegistry.mu.Unlock() defer pdcsRegistry.mu.Unlock()
pdcsRegistry.connection = connection pdcsRegistry.connection = connection
pdcsRegistry.connectionIsSet = true pdcsRegistry.connectionIsSet = true
} }

View File

@ -77,7 +77,13 @@ func Create(opts *SecretCreateOptions) error { //nolint:cyclop
return err return err
} }
defer file.Close() defer func() {
err := file.Close()
if err != nil {
log.Error().Msgf("failed to close secret file input: %s", err.Error())
}
}()
reader = file reader = file
} }
@ -90,7 +96,8 @@ func Create(opts *SecretCreateOptions) error { //nolint:cyclop
return err return err
} }
if _, err := secrets.Create(conn, reader, createOpts); err != nil { _, err = secrets.Create(conn, reader, createOpts)
if err != nil {
return err return err
} }

View File

@ -1,8 +1,6 @@
package volumes package volumes
import ( import (
"sort"
"github.com/containers/podman-tui/pdcs/registry" "github.com/containers/podman-tui/pdcs/registry"
"github.com/containers/podman/v5/pkg/bindings/volumes" "github.com/containers/podman/v5/pkg/bindings/volumes"
"github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/domain/entities"
@ -23,17 +21,7 @@ func List() ([]*entities.VolumeListReport, error) {
return nil, err return nil, err
} }
sort.Sort(volumeListSortedName{response})
log.Debug().Msgf("pdcs: %v", response) log.Debug().Msgf("pdcs: %v", response)
return response, nil return response, nil
} }
type lprSort []*entities.VolumeListReport
func (a lprSort) Len() int { return len(a) }
func (a lprSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type volumeListSortedName struct{ lprSort }
func (a volumeListSortedName) Less(i, j int) bool { return a.lprSort[i].Name < a.lprSort[j].Name }

View File

@ -15,8 +15,8 @@
%global git0 https://%{import_path} %global git0 https://%{import_path}
Name: podman-tui Name: podman-tui
Version: 1.7.0 Version: 1.8.0
Release: 1%{?dist} Release: dev.1%{?dist}
Summary: Podman Terminal User Interface Summary: Podman Terminal User Interface
License: ASL 2.0 License: ASL 2.0
URL: %{git0} URL: %{git0}
@ -60,6 +60,8 @@ install -p ./bin/%{name} %{buildroot}%{_bindir}
%{_bindir}/%{name} %{_bindir}/%{name}
%changelog %changelog
* Fri Jul 11 2025 Navid Yaghoobi <navidys@fedoraproject.org> 1.8.0-dev-1
* Fri Jul 11 2025 Navid Yaghoobi <navidys@fedoraproject.org> 1.7.0-1 * Fri Jul 11 2025 Navid Yaghoobi <navidys@fedoraproject.org> 1.7.0-1
- Bump golang.org/x/crypto from 0.39.0 to 0.40.0 - Bump golang.org/x/crypto from 0.39.0 to 0.40.0
- Remove unused BUILDTAGS - Remove unused BUILDTAGS

View File

@ -15,9 +15,10 @@ type apiConn struct {
// ConnStatus returns connection status. // ConnStatus returns connection status.
func (conn *apiConn) ConnStatus() (registry.ConnStatus, string) { func (conn *apiConn) ConnStatus() (registry.ConnStatus, string) {
var status registry.ConnStatus var (
status registry.ConnStatus
message := "" message string
)
conn.mu.Lock() conn.mu.Lock()
status = conn.connStaus status = conn.connStaus

View File

@ -40,7 +40,8 @@ func (engine *Engine) startEventStreamer() {
go engine.eventReader() go engine.eventReader()
go func() { go func() {
if err := sysinfo.Events(engine.sysEvents.eventChan, engine.sysEvents.eventCancelChan); err != nil { err := sysinfo.Events(engine.sysEvents.eventChan, engine.sysEvents.eventCancelChan)
if err != nil {
log.Error().Msgf("health check: event streamer %v", err) log.Error().Msgf("health check: event streamer %v", err)
engine.sysEvents.cancelChan <- true engine.sysEvents.cancelChan <- true
@ -55,6 +56,7 @@ func (engine *Engine) eventReader() {
select { select {
case <-engine.sysEvents.cancelChan: case <-engine.sysEvents.cancelChan:
log.Debug().Msg("health check: event reader stopped") log.Debug().Msg("health check: event reader stopped")
engine.sysEvents.eventCancelChan <- true engine.sysEvents.eventCancelChan <- true
engine.sysEvents.mu.Lock() engine.sysEvents.mu.Lock()
@ -94,10 +96,10 @@ func (engine *Engine) GetEventMessages() []string {
// HasNewEvent returns true if there is new event added to event buffer. // HasNewEvent returns true if there is new event added to event buffer.
func (engine *Engine) HasNewEvent() bool { func (engine *Engine) HasNewEvent() bool {
hasEvent := false
engine.sysEvents.mu.Lock() engine.sysEvents.mu.Lock()
hasEvent = engine.sysEvents.hasNewEvent
hasEvent := engine.sysEvents.hasNewEvent
engine.sysEvents.mu.Unlock() engine.sysEvents.mu.Unlock()
return hasEvent return hasEvent
@ -105,6 +107,7 @@ func (engine *Engine) HasNewEvent() bool {
func (engine *Engine) addEventMessage(msg string) { func (engine *Engine) addEventMessage(msg string) {
engine.sysEvents.mu.Lock() engine.sysEvents.mu.Lock()
if len(engine.sysEvents.messageBuffer) == engine.sysEvents.messageBufferSize { if len(engine.sysEvents.messageBuffer) == engine.sysEvents.messageBufferSize {
// empty first 10 entries // empty first 10 entries
engine.sysEvents.messageBuffer = engine.sysEvents.messageBuffer[20:] engine.sysEvents.messageBuffer = engine.sysEvents.messageBuffer[20:]

View File

@ -29,7 +29,6 @@ load helpers_tui
@test "image save" { @test "image save" {
podman image pull docker.io/library/busybox || echo done podman image pull docker.io/library/busybox || echo done
image_index=$(podman image ls --sort repository --noheading | nl -v 0 | grep 'busybox ' | awk '{print $1}')
[ -f "${TEST_IMAGE_SAVE_PATH}" ] && /bin/rm -rf $TEST_IMAGE_SAVE_PATH [ -f "${TEST_IMAGE_SAVE_PATH}" ] && /bin/rm -rf $TEST_IMAGE_SAVE_PATH
# switch to images view # switch to images view
@ -40,7 +39,7 @@ load helpers_tui
# go to save button and press enter # go to save button and press enter
podman_tui_set_view "images" podman_tui_set_view "images"
podman_tui_select_item $image_index podman_tui_select_item 0
podman_tui_select_image_cmd "save" podman_tui_select_image_cmd "save"
podman_tui_send_inputs $TEST_IMAGE_SAVE_PATH "Tab" podman_tui_send_inputs $TEST_IMAGE_SAVE_PATH "Tab"
@ -104,14 +103,12 @@ load helpers_tui
} }
@test "image diff" { @test "image diff" {
image_index=$(podman image ls --sort repository --noheading | nl -v 0 | grep 'busybox ' | awk '{print $1}')
# switch to images view # switch to images view
# select busybox image from list # select busybox image from list
# select diff command from image commands dialog # select diff command from image commands dialog
# close busybox image diff result message dialog # close busybox image diff result message dialog
podman_tui_set_view "images" podman_tui_set_view "images"
podman_tui_select_item $image_index podman_tui_select_item 2
podman_tui_select_image_cmd "diff" podman_tui_select_image_cmd "diff"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
podman_tui_send_inputs "Tab" "Enter" podman_tui_send_inputs "Tab" "Enter"
@ -121,14 +118,12 @@ load helpers_tui
} }
@test "image history" { @test "image history" {
image_index=$(podman image ls --sort repository --noheading | nl -v 0 | grep 'busybox ' | awk '{print $1}')
image_id=$(podman image ls --sort repository --filter "reference=docker.io/library/busybox" --format "{{ .ID }}")
# switch to images view # switch to images view
# select busybox image from list # select busybox image from list
# select history command from image commands dialog # select history command from image commands dialog
# close busybox image history result message dialog # close busybox image history result message dialog
podman_tui_set_view "images" podman_tui_set_view "images"
podman_tui_select_item $image_index podman_tui_select_item 2
podman_tui_select_image_cmd "history" podman_tui_select_image_cmd "history"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
podman_tui_send_inputs "Tab" "Enter" podman_tui_send_inputs "Tab" "Enter"
@ -138,7 +133,6 @@ load helpers_tui
} }
@test "image inspect" { @test "image inspect" {
image_index=$(podman image ls --sort repository --noheading | nl -v 0 | grep 'busybox ' | awk '{print $1}')
image_id=$(podman image ls --sort repository --noheading | nl -v 0 | grep 'busybox ' | awk '{print $4}') image_id=$(podman image ls --sort repository --noheading | nl -v 0 | grep 'busybox ' | awk '{print $4}')
# switch to images view # switch to images view
@ -146,7 +140,7 @@ load helpers_tui
# select inspect command from image commands dialog # select inspect command from image commands dialog
# close busybox image inspect result message dialog # close busybox image inspect result message dialog
podman_tui_set_view "images" podman_tui_set_view "images"
podman_tui_select_item $image_index podman_tui_select_item 2
podman_tui_select_image_cmd "inspect" podman_tui_select_image_cmd "inspect"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter"
@ -156,13 +150,11 @@ load helpers_tui
} }
@test "image tag" { @test "image tag" {
busybox_index=$(podman image ls --sort repository --noheading | nl -v 0 | grep 'busybox ' | awk '{print $1}')
# switch to images view # switch to images view
# select busybox image from list # select busybox image from list
# select tag command from image commands dialog # select tag command from image commands dialog
podman_tui_set_view "images" podman_tui_set_view "images"
podman_tui_select_item $busyboxIndex podman_tui_select_item 2
podman_tui_select_image_cmd "tag" podman_tui_select_image_cmd "tag"
podman_tui_send_inputs "$TEST_IMAGE_TAG_NAME" "Tab" "Tab" "Enter" podman_tui_send_inputs "$TEST_IMAGE_TAG_NAME" "Tab" "Tab" "Enter"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -172,14 +164,12 @@ load helpers_tui
} }
@test "image untag" { @test "image untag" {
busybox_tagindex=$(podman image ls --sort repository --noheading | nl -v 0 | grep "$TEST_IMAGE_TAG_NAME " | awk '{print $1}')
# switch to images view # switch to images view
# select busybox image from list # select busybox image from list
# select untag command from image commands dialog # select untag command from image commands dialog
# press "Tab" 2 times and "Enter" to untag busybox image # press "Tab" 2 times and "Enter" to untag busybox image
podman_tui_set_view "images" podman_tui_set_view "images"
podman_tui_select_item $busybox_tagindex podman_tui_select_item 2
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
podman_tui_select_image_cmd "untag" podman_tui_select_image_cmd "untag"
podman_tui_send_inputs "Tab" "Tab" "Enter" podman_tui_send_inputs "Tab" "Tab" "Enter"
@ -193,7 +183,6 @@ load helpers_tui
@test "image remove" { @test "image remove" {
run_helper podman image ls --format "'{{ .Repository }}'" run_helper podman image ls --format "'{{ .Repository }}'"
before="$output" before="$output"
untagged_image=$(podman image ls --sort repository --noheading | nl -v 0 | grep '<none> ' | awk '{print $1}')
# switch to images view # switch to images view
# select <none> image from list # select <none> image from list
@ -202,7 +191,7 @@ load helpers_tui
# wait for image removal operation # wait for image removal operation
# close image removal result message dialog # close image removal result message dialog
podman_tui_set_view "images" podman_tui_set_view "images"
podman_tui_select_item $untagged_image podman_tui_select_item 2
podman_tui_select_image_cmd "remove" podman_tui_select_image_cmd "remove"
podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW

View File

@ -25,14 +25,12 @@ load helpers_tui
} }
@test "volume inspect" { @test "volume inspect" {
vol_index=$(podman volume ls -q | nl -v 0 | grep "$TEST_VOLUME_NAME" | awk '{print $1}')
# switch to volumes view # switch to volumes view
# select test volume from list # select test volume from list
# select inspect command from volume commands dialog # select inspect command from volume commands dialog
# close volume inspect result message dialog # close volume inspect result message dialog
podman_tui_set_view "volumes" podman_tui_set_view "volumes"
podman_tui_select_item $vol_index podman_tui_select_item 0
podman_tui_select_volume_cmd "inspect" podman_tui_select_volume_cmd "inspect"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter"
@ -43,13 +41,11 @@ load helpers_tui
} }
@test "volume remove" { @test "volume remove" {
vol_index=$(podman volume ls -q | nl -v 0 | grep "$TEST_VOLUME_NAME" | awk '{print $1}')
# switch to volumes view # switch to volumes view
# select test volume from list # select test volume from list
# select remove command from volume commands dialog # select remove command from volume commands dialog
podman_tui_set_view "volumes" podman_tui_set_view "volumes"
podman_tui_select_item $vol_index podman_tui_select_item 0
podman_tui_select_volume_cmd "remove" podman_tui_select_volume_cmd "remove"
podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW

View File

@ -17,6 +17,7 @@ load helpers_tui
# select connect button # select connect button
podman_tui_set_view "networks" podman_tui_set_view "networks"
podman_tui_select_item 1
podman_tui_select_network_cmd "connect" podman_tui_select_network_cmd "connect"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
podman_tui_send_inputs "Tab" podman_tui_send_inputs "Tab"
@ -38,6 +39,7 @@ load helpers_tui
# select disconnect button # select disconnect button
podman_tui_set_view "networks" podman_tui_set_view "networks"
podman_tui_select_item 1
podman_tui_select_network_cmd "disconnect" podman_tui_select_network_cmd "disconnect"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
podman_tui_send_inputs "Tab" "Tab" "Enter" podman_tui_send_inputs "Tab" "Tab" "Enter"
@ -68,14 +70,12 @@ load helpers_tui
} }
@test "network inspect" { @test "network inspect" {
net_index=$(podman network ls -q | nl -v 0 | grep "$TEST_NETWORK_NAME" | awk '{print $1}')
# switch to networks view # switch to networks view
# select test network from list # select test network from list
# select inspect command from network commands dialog # select inspect command from network commands dialog
# close network inspect result message dialog # close network inspect result message dialog
podman_tui_set_view "networks" podman_tui_set_view "networks"
podman_tui_select_item $net_index podman_tui_select_item 1
podman_tui_select_network_cmd "inspect" podman_tui_select_network_cmd "inspect"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter"
@ -86,13 +86,11 @@ load helpers_tui
} }
@test "network remove" { @test "network remove" {
net_index=$(podman network ls -q | nl -v 0 | grep "$TEST_NETWORK_NAME" | awk '{print $1}')
# switch to networks view # switch to networks view
# select test network from list # select test network from list
# select remove command from network commands dialog # select remove command from network commands dialog
podman_tui_set_view "networks" podman_tui_set_view "networks"
podman_tui_select_item $net_index podman_tui_select_item 1
podman_tui_select_network_cmd "remove" podman_tui_select_network_cmd "remove"
podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW

View File

@ -43,7 +43,6 @@ load helpers_tui
podman image pull pause:3.5 || echo done podman image pull pause:3.5 || echo done
podman network create $TEST_POD_NETWORK_NAME || echo done podman network create $TEST_POD_NETWORK_NAME || echo done
net_index=$(podman network ls -q | nl -v 1 | grep "$TEST_POD_NETWORK_NAME" | awk '{print $1}')
# switch to pods view # switch to pods view
# select create command from pod commands dialog # select create command from pod commands dialog
# fillout name field # fillout name field
@ -59,7 +58,7 @@ load helpers_tui
podman_tui_send_inputs $TEST_POD_NAME "Tab" "Tab" $TEST_LABEL podman_tui_send_inputs $TEST_POD_NAME "Tab" "Tab" $TEST_LABEL
podman_tui_send_inputs "Tab" "Tab" "Tab" "Down" "Down" "Down" "Tab" podman_tui_send_inputs "Tab" "Tab" "Tab" "Down" "Down" "Down" "Tab"
podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" "Down" podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" "Down"
podman_tui_select_item $net_index podman_tui_select_item 1
podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter"
podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" "Down" "Tab" podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" "Down" "Tab"
podman_tui_send_inputs "disable" podman_tui_send_inputs "disable"
@ -77,13 +76,11 @@ load helpers_tui
} }
@test "pod start" { @test "pod start" {
pod_index=$(podman pod ls --sort name --format "{{ .Name }}" | nl -v 0 | grep "$TEST_POD_NAME" | awk '{print $1}')
# switch to pods view # switch to pods view
# select test pod from list # select test pod from list
# select start command from pod commands dialog # select start command from pod commands dialog
podman_tui_set_view "pods" podman_tui_set_view "pods"
podman_tui_select_item $pod_index podman_tui_select_item 0
podman_tui_select_pod_cmd "start" podman_tui_select_pod_cmd "start"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -92,13 +89,11 @@ load helpers_tui
} }
@test "pod pause" { @test "pod pause" {
pod_index=$(podman pod ls --sort name --format "{{ .Name }}" | nl -v 0 | grep "$TEST_POD_NAME" | awk '{print $1}')
# switch to pods view # switch to pods view
# select test pod from list # select test pod from list
# select pause command from pod commands dialog # select pause command from pod commands dialog
podman_tui_set_view "pods" podman_tui_set_view "pods"
podman_tui_select_item $pod_index podman_tui_select_item 0
podman_tui_select_pod_cmd "pause" podman_tui_select_pod_cmd "pause"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -107,13 +102,11 @@ load helpers_tui
} }
@test "pod unpause" { @test "pod unpause" {
pod_index=$(podman pod ls --sort name --format "{{ .Name }}" | nl -v 0 | grep "$TEST_POD_NAME" | awk '{print $1}')
# switch to pods view # switch to pods view
# select test pod from list # select test pod from list
# select unpause command from pod commands dialog # select unpause command from pod commands dialog
podman_tui_set_view "pods" podman_tui_set_view "pods"
podman_tui_select_item $pod_index podman_tui_select_item 0
podman_tui_select_pod_cmd "unpause" podman_tui_select_pod_cmd "unpause"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -122,13 +115,11 @@ load helpers_tui
} }
@test "pod stop" { @test "pod stop" {
pod_index=$(podman pod ls --sort name --format "{{ .Name }}" | nl -v 0 | grep "$TEST_POD_NAME" | awk '{print $1}')
# switch to pods view # switch to pods view
# select test pod from list # select test pod from list
# select stop command from pod commands dialog # select stop command from pod commands dialog
podman_tui_set_view "pods" podman_tui_set_view "pods"
podman_tui_select_item $pod_index podman_tui_select_item 0
podman_tui_select_pod_cmd "stop" podman_tui_select_pod_cmd "stop"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -137,13 +128,11 @@ load helpers_tui
} }
@test "pod restart" { @test "pod restart" {
pod_index=$(podman pod ls --sort name --format "{{ .Name }}" | nl -v 0 | grep "$TEST_POD_NAME" | awk '{print $1}')
# switch to pods view # switch to pods view
# select test pod from list # select test pod from list
# select restart command from pod commands dialog # select restart command from pod commands dialog
podman_tui_set_view "pods" podman_tui_set_view "pods"
podman_tui_select_item $pod_index podman_tui_select_item 0
podman_tui_select_pod_cmd "restart" podman_tui_select_pod_cmd "restart"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -152,13 +141,11 @@ load helpers_tui
} }
@test "pod kill" { @test "pod kill" {
pod_index=$(podman pod ls --sort name --format "{{ .Name }}" | nl -v 0 | grep "$TEST_POD_NAME" | awk '{print $1}')
# switch to pods view # switch to pods view
# select test pod from list # select test pod from list
# select kill command from pod commands dialog # select kill command from pod commands dialog
podman_tui_set_view "pods" podman_tui_set_view "pods"
podman_tui_select_item $pod_index podman_tui_select_item 0
podman_tui_select_pod_cmd "kill" podman_tui_select_pod_cmd "kill"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -167,14 +154,12 @@ load helpers_tui
} }
@test "pod inspect" { @test "pod inspect" {
pod_index=$(podman pod ls --sort name --format "{{ .Name }}" | nl -v 0 | grep "$TEST_POD_NAME" | awk '{print $1}')
# switch to pods view # switch to pods view
# select test pod from list # select test pod from list
# select inspect command from pod commands dialog # select inspect command from pod commands dialog
# close pod inspect result message dialog # close pod inspect result message dialog
podman_tui_set_view "pods" podman_tui_set_view "pods"
podman_tui_select_item $pod_index podman_tui_select_item 0
podman_tui_select_pod_cmd "inspect" podman_tui_select_pod_cmd "inspect"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter"
@ -184,14 +169,12 @@ load helpers_tui
} }
@test "pod remove" { @test "pod remove" {
pod_index=$(podman pod ls --sort name --format "{{ .Name }}" | nl -v 0 | grep "$TEST_POD_NAME" | awk '{print $1}')
# switch to pods view # switch to pods view
# select test pod from list # select test pod from list
# select remove command from pod commands dialog # select remove command from pod commands dialog
# confirm the operation on warnings dialog # confirm the operation on warnings dialog
podman_tui_set_view "pods" podman_tui_set_view "pods"
podman_tui_select_item $pod_index podman_tui_select_item 0
podman_tui_select_pod_cmd "remove" podman_tui_select_pod_cmd "remove"
podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter"
podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter"

View File

@ -14,13 +14,11 @@ load helpers_tui
podman image pull docker.io/library/busybox podman image pull docker.io/library/busybox
fi fi
image_index=$(podman image ls --sort repository --noheading | nl -v 1 | grep 'busybox ' | awk '{print $1}')
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_container_cmd "run" podman_tui_select_container_cmd "run"
podman_tui_send_inputs $TEST_CONTAINER_NAME "Tab" "$TEST_CONTAINER_RUN_CMD" "Tab" podman_tui_send_inputs $TEST_CONTAINER_NAME "Tab" "$TEST_CONTAINER_RUN_CMD" "Tab"
podman_tui_send_inputs "Down" podman_tui_send_inputs "Down"
podman_tui_select_item $image_index podman_tui_select_item 1
podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter"
podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" "Space" podman_tui_send_inputs "Tab" "Tab" "Tab" "Tab" "Space"
podman_tui_send_inputs "Tab" "Tab" "Space" podman_tui_send_inputs "Tab" "Tab" "Space"
@ -47,8 +45,6 @@ load helpers_tui
podman image pull docker.io/library/busybox podman image pull docker.io/library/busybox
fi fi
image_index=$(podman image ls --sort repository --noheading | nl -v 1 | grep 'busybox ' | awk '{print $1}')
# switch to containers view # switch to containers view
# select create command from container commands dialog # select create command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
@ -60,7 +56,7 @@ load helpers_tui
# set timeout to 10 # set timeout to 10
podman_tui_send_inputs $TEST_CONTAINER_NAME "Tab" "Tab" podman_tui_send_inputs $TEST_CONTAINER_NAME "Tab" "Tab"
podman_tui_send_inputs "Down" podman_tui_send_inputs "Down"
podman_tui_select_item $image_index podman_tui_select_item 1
podman_tui_send_inputs "Enter" "Tab" "Tab" "Tab" podman_tui_send_inputs "Enter" "Tab" "Tab" "Tab"
podman_tui_send_inputs "Space" "Tab" "Space" "Tab" "$TEST_CONTAINER_TIMEOUT" podman_tui_send_inputs "Space" "Tab" "Space" "Tab" "$TEST_CONTAINER_TIMEOUT"
podman_tui_send_inputs "Tab" "Tab" "Tab" podman_tui_send_inputs "Tab" "Tab" "Tab"
@ -78,7 +74,7 @@ load helpers_tui
assert "$cnt_timeout" =~ "$TEST_CONTAINER_TIMEOUT" "expected container config timeout to match: $TEST_CONTAINER_TIMEOUT" assert "$cnt_timeout" =~ "$TEST_CONTAINER_TIMEOUT" "expected container config timeout to match: $TEST_CONTAINER_TIMEOUT"
} }
@test "container create (environment page)" { @test "container create (environment)" {
podman container rm -f $TEST_CONTAINER_NAME || echo done podman container rm -f $TEST_CONTAINER_NAME || echo done
buysbox_image=$(podman image ls --sort repository --format "{{ .Repository }}" --filter "reference=docker.io/library/busybox") buysbox_image=$(podman image ls --sort repository --format "{{ .Repository }}" --filter "reference=docker.io/library/busybox")
@ -86,8 +82,6 @@ load helpers_tui
podman image pull docker.io/library/busybox podman image pull docker.io/library/busybox
fi fi
image_index=$(podman image ls --sort repository --noheading | nl -v 1 | grep 'busybox ' | awk '{print $1}')
# switch to containers view # switch to containers view
# select create command from container commands dialog # select create command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
@ -97,7 +91,7 @@ load helpers_tui
# select image from dropdown widget # select image from dropdown widget
podman_tui_send_inputs $TEST_CONTAINER_NAME "Tab" "Tab" podman_tui_send_inputs $TEST_CONTAINER_NAME "Tab" "Tab"
podman_tui_send_inputs "Down" podman_tui_send_inputs "Down"
podman_tui_select_item $image_index podman_tui_select_item 1
podman_tui_send_inputs "Enter" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" podman_tui_send_inputs "Enter" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -122,7 +116,7 @@ load helpers_tui
assert "$cnt_vars" =~ "$TEST_CONTAINER_ENV2" "expected container env to match: $TEST_CONTAINER_ENV2" assert "$cnt_vars" =~ "$TEST_CONTAINER_ENV2" "expected container env to match: $TEST_CONTAINER_ENV2"
} }
@test "container create (resource page)" { @test "container create (resource)" {
podman container rm -f $TEST_CONTAINER_NAME || echo done podman container rm -f $TEST_CONTAINER_NAME || echo done
buysbox_image=$(podman image ls --sort repository --format "{{ .Repository }}" --filter "reference=docker.io/library/busybox") buysbox_image=$(podman image ls --sort repository --format "{{ .Repository }}" --filter "reference=docker.io/library/busybox")
@ -130,8 +124,6 @@ load helpers_tui
podman image pull docker.io/library/busybox podman image pull docker.io/library/busybox
fi fi
image_index=$(podman image ls --sort repository --noheading | nl -v 1 | grep 'busybox ' | awk '{print $1}')
# switch to containers view # switch to containers view
# select create command from container commands dialog # select create command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
@ -141,7 +133,7 @@ load helpers_tui
# select image from dropdown widget # select image from dropdown widget
podman_tui_send_inputs $TEST_CONTAINER_NAME "Tab" "Tab" podman_tui_send_inputs $TEST_CONTAINER_NAME "Tab" "Tab"
podman_tui_send_inputs "Down" podman_tui_send_inputs "Down"
podman_tui_select_item $image_index podman_tui_select_item 1
podman_tui_send_inputs "Enter" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" podman_tui_send_inputs "Enter" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" "Tab"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -198,12 +190,6 @@ load helpers_tui
podman pod create --name $TEST_CONTAINER_POD_NAME --network $TEST_CONTAINER_NETWORK_NAME --publish $TEST_CONTAINER_PORT || echo done podman pod create --name $TEST_CONTAINER_POD_NAME --network $TEST_CONTAINER_NETWORK_NAME --publish $TEST_CONTAINER_PORT || echo done
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
# get required pod, image, network and volume index for number of KeyDown stroke
pod_index=$(podman pod ls --sort name --format "{{ .Name }}" | nl -v 1 | grep "$TEST_CONTAINER_POD_NAME" | awk '{print $1}')
image_index=$(podman image ls --sort repository --noheading | nl -v 1 | grep 'httpd ' | awk '{print $1}')
net_index=$(podman network ls -q | nl -v 1 | grep "$TEST_CONTAINER_NETWORK_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select create command from container commands dialog # select create command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
@ -215,10 +201,10 @@ load helpers_tui
# fillout label field # fillout label field
podman_tui_send_inputs $TEST_CONTAINER_NAME "Tab" "Tab" podman_tui_send_inputs $TEST_CONTAINER_NAME "Tab" "Tab"
podman_tui_send_inputs "Down" podman_tui_send_inputs "Down"
podman_tui_select_item $image_index podman_tui_select_item 2
podman_tui_send_inputs "Enter" "Tab" podman_tui_send_inputs "Enter" "Tab"
podman_tui_send_inputs "Down" podman_tui_send_inputs "Down"
podman_tui_select_item $pod_index podman_tui_select_item 1
podman_tui_send_inputs "Enter" "Tab" podman_tui_send_inputs "Enter" "Tab"
podman_tui_send_inputs $TEST_LABEL "Tab" "Tab" "Tab" "Tab" "Tab" "Tab" podman_tui_send_inputs $TEST_LABEL "Tab" "Tab" "Tab" "Tab" "Tab" "Tab"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -293,15 +279,13 @@ load helpers_tui
} }
@test "container commit" { @test "container commit" {
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select container from the list # select container from the list
# select commit command from container commands dialog # select commit command from container commands dialog
# fillout image input field # fillout image input field
# go to commit button and press enter # go to commit button and press enter
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 0
podman_tui_select_container_cmd "commit" podman_tui_select_container_cmd "commit"
podman_tui_send_inputs $TEST_CONTAINER_COMMIT_IMAGE_NAME podman_tui_send_inputs $TEST_CONTAINER_COMMIT_IMAGE_NAME
@ -314,13 +298,11 @@ load helpers_tui
} }
@test "container start" { @test "container start" {
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select start command from container commands dialog # select start command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 0
podman_tui_select_container_cmd "start" podman_tui_select_container_cmd "start"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -332,9 +314,6 @@ load helpers_tui
@test "container checkpoint" { @test "container checkpoint" {
podman container create --name ${TEST_CONTAINER_CHECKPOINT_NAME} docker.io/library/httpd podman container create --name ${TEST_CONTAINER_CHECKPOINT_NAME} docker.io/library/httpd
podman container start ${TEST_CONTAINER_CHECKPOINT_NAME} podman container start ${TEST_CONTAINER_CHECKPOINT_NAME}
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_CHECKPOINT_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select checkpoint command from container commands dialog # select checkpoint command from container commands dialog
@ -342,7 +321,7 @@ load helpers_tui
# go to checkpoint button and Enter # go to checkpoint button and Enter
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 0
podman_tui_select_container_cmd "checkpoint" podman_tui_select_container_cmd "checkpoint"
podman_tui_send_inputs "Tab" podman_tui_send_inputs "Tab"
@ -380,8 +359,6 @@ load helpers_tui
} }
@test "container exec" { @test "container exec" {
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select exec command from container commands dialog # select exec command from container commands dialog
@ -392,7 +369,7 @@ load helpers_tui
# type "echo test > a.txt" # type "echo test > a.txt"
# go to Close button and Enter # go to Close button and Enter
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 2
podman_tui_select_container_cmd "exec" podman_tui_select_container_cmd "exec"
podman_tui_send_inputs "/bin/bash" podman_tui_send_inputs "/bin/bash"
podman_tui_send_inputs "Tab" "Space" "Tab" podman_tui_send_inputs "Tab" "Space" "Tab"
@ -409,13 +386,11 @@ load helpers_tui
} }
@test "container inspect" { @test "container inspect" {
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select inspect command from container commands dialog # select inspect command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 2
podman_tui_select_container_cmd "inspect" podman_tui_select_container_cmd "inspect"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -425,13 +400,11 @@ load helpers_tui
} }
@test "container diff" { @test "container diff" {
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select diff command from container commands dialog # select diff command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 2
podman_tui_select_container_cmd "diff" podman_tui_select_container_cmd "diff"
sleep $TEST_TIMEOUT_MEDIUM sleep $TEST_TIMEOUT_MEDIUM
@ -440,13 +413,11 @@ load helpers_tui
} }
@test "container top" { @test "container top" {
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select top command from container commands dialog # select top command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 2
podman_tui_select_container_cmd "top" podman_tui_select_container_cmd "top"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -455,13 +426,11 @@ load helpers_tui
} }
@test "container port" { @test "container port" {
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select port command from container commands dialog # select port command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 2
podman_tui_select_container_cmd "port" podman_tui_select_container_cmd "port"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -471,13 +440,11 @@ load helpers_tui
} }
@test "container pause" { @test "container pause" {
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select pause command from container commands dialog # select pause command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 2
podman_tui_select_container_cmd "pause" podman_tui_select_container_cmd "pause"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -486,13 +453,11 @@ load helpers_tui
} }
@test "container unpause" { @test "container unpause" {
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select unpause command from container commands dialog # select unpause command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 2
podman_tui_select_container_cmd "unpause" podman_tui_select_container_cmd "unpause"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -501,13 +466,11 @@ load helpers_tui
} }
@test "container stop" { @test "container stop" {
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select stop command from container commands dialog # select stop command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 2
podman_tui_select_container_cmd "stop" podman_tui_select_container_cmd "stop"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -517,13 +480,11 @@ load helpers_tui
@test "container kill" { @test "container kill" {
podman container start $TEST_CONTAINER_NAME || echo done podman container start $TEST_CONTAINER_NAME || echo done
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select kill command from container commands dialog # select kill command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 2
podman_tui_select_container_cmd "kill" podman_tui_select_container_cmd "kill"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -532,13 +493,11 @@ load helpers_tui
} }
@test "container remove" { @test "container remove" {
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select remove command from container commands dialog # select remove command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 2
podman_tui_select_container_cmd "remove" podman_tui_select_container_cmd "remove"
podman_tui_send_inputs "Enter" podman_tui_send_inputs "Enter"
sleep $TEST_TIMEOUT_LOW sleep $TEST_TIMEOUT_LOW
@ -548,15 +507,14 @@ load helpers_tui
} }
@test "container rename" { @test "container rename" {
podman container rm $TEST_CONTAINER_NAME || echo done
podman container create --name $TEST_CONTAINER_NAME httpd podman container create --name $TEST_CONTAINER_NAME httpd
container_index=$(podman container ls --all --format "{{ .Names }}" | sort | nl -v 0 | grep "$TEST_CONTAINER_NAME" | awk '{print $1}')
# switch to containers view # switch to containers view
# select test container from list # select test container from list
# select rename command from container commands dialog # select rename command from container commands dialog
podman_tui_set_view "containers" podman_tui_set_view "containers"
podman_tui_select_item $container_index podman_tui_select_item 0
podman_tui_select_container_cmd "rename" podman_tui_select_container_cmd "rename"
podman_tui_send_inputs ${TEST_CONTAINER_NAME}_renamed podman_tui_send_inputs ${TEST_CONTAINER_NAME}_renamed
podman_tui_send_inputs "Tab" "Tab" "Enter" podman_tui_send_inputs "Tab" "Tab" "Enter"

View File

@ -3,9 +3,9 @@
TEST_IMAGE_TAG_NAME="podman_tui_busybox_tag01" TEST_IMAGE_TAG_NAME="podman_tui_busybox_tag01"
TEST_NAME="ptui_test" TEST_NAME="ptui_test"
TEST_VOLUME_NAME="${TEST_NAME}_vol01" TEST_VOLUME_NAME="${TEST_NAME}_vol01"
TEST_NETWORK_NAME="${TEST_NAME}_net01" TEST_NETWORK_NAME="zz_${TEST_NAME}_net01"
TEST_NETWORK_CONNECT="net_connect_${TEST_NAME}" TEST_NETWORK_CONNECT="zz_${TEST_NAME}_net0_connect"
TEST_NETWORK_CONNECT_ALIAS="net_connect_alias" TEST_NETWORK_CONNECT_ALIAS="zz_${TEST_NAME}_net0_connect_alias"
TEST_POD_NAME="${TEST_NAME}_pod01" TEST_POD_NAME="${TEST_NAME}_pod01"
TEST_POD_NETWORK_NAME="${TEST_NAME}_pod01_net" TEST_POD_NETWORK_NAME="${TEST_NAME}_pod01_net"
TEST_CONTAINER_NAME="${TEST_NAME}_cnt01" TEST_CONTAINER_NAME="${TEST_NAME}_cnt01"
@ -15,7 +15,7 @@ TEST_CONTAINER_HEALTH_INTERVAL="30s"
TEST_CONTAINER_HEALTH_TIMEOUT="30s" TEST_CONTAINER_HEALTH_TIMEOUT="30s"
TEST_CONTAINER_HEALTH_RETRIES="3" TEST_CONTAINER_HEALTH_RETRIES="3"
TEST_CONTAINER_HEALTH_ONFAILURE="restart" TEST_CONTAINER_HEALTH_ONFAILURE="restart"
TEST_CONTAINER_POD_NAME="${TEST_NAME}_cnt01_pod" TEST_CONTAINER_POD_NAME="${TEST_POD_NAME}"
TEST_CONTAINER_NETWORK_NAME="${TEST_NAME}_cnt01_net" TEST_CONTAINER_NETWORK_NAME="${TEST_NAME}_cnt01_net"
TEST_CONTAINER_VOLUME_NAME="${TEST_NAME}_cnt01_vol" TEST_CONTAINER_VOLUME_NAME="${TEST_NAME}_cnt01_vol"
TEST_CONTAINER_VOLUME_MOUNT_POINT="/data_mount01" TEST_CONTAINER_VOLUME_MOUNT_POINT="/data_mount01"

View File

@ -35,6 +35,7 @@ const (
// ContainerCheckpointDialog implements container checkpoint dialog primitive. // ContainerCheckpointDialog implements container checkpoint dialog primitive.
type ContainerCheckpointDialog struct { type ContainerCheckpointDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
containerInfo *tview.InputField containerInfo *tview.InputField
createImage *tview.InputField createImage *tview.InputField
@ -82,7 +83,7 @@ func NewContainerCheckpointDialog() *ContainerCheckpointDialog {
// containerInfo // containerInfo
dialog.containerInfo.SetBackgroundColor(style.DialogBgColor) dialog.containerInfo.SetBackgroundColor(style.DialogBgColor)
dialog.containerInfo.SetLabel("[::b]CONTAINER ID:") dialog.containerInfo.SetLabel(fmt.Sprintf("[::b]%s:", utils.ContainerIDLabel))
dialog.containerInfo.SetLabelWidth(labelWidth) dialog.containerInfo.SetLabelWidth(labelWidth)
dialog.containerInfo.SetFieldBackgroundColor(style.DialogBgColor) dialog.containerInfo.SetFieldBackgroundColor(style.DialogBgColor)
dialog.containerInfo.SetLabelStyle(tcell.StyleDefault. dialog.containerInfo.SetLabelStyle(tcell.StyleDefault.
@ -453,9 +454,9 @@ func (d *ContainerCheckpointDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
@ -481,31 +482,6 @@ func (d *ContainerCheckpointDialog) SetCancelFunc(handler func()) *ContainerChec
return d return d
} }
func (d *ContainerCheckpointDialog) setFocusElement() { //nolint:cyclop
switch d.focusElement {
case cntCheckpointImageFocus:
d.focusElement = cntCheckpointExportFocus
case cntCheckpointExportFocus:
d.focusElement = cntCheckpointFileLockFocus
case cntCheckpointFileLockFocus:
d.focusElement = cntCheckpointIgnoreRootFsFocus
case cntCheckpointIgnoreRootFsFocus:
d.focusElement = cntCheckpointTCPEstablishedFocus
case cntCheckpointTCPEstablishedFocus:
d.focusElement = cntCheckpointPreCheckpointFocus
case cntCheckpointPreCheckpointFocus:
d.focusElement = cntCheckpointPrintStatsFocus
case cntCheckpointPrintStatsFocus:
d.focusElement = cntCheckpointKeepFocus
case cntCheckpointKeepFocus:
d.focusElement = cntCheckpointLeaveRunningFocus
case cntCheckpointLeaveRunningFocus:
d.focusElement = cntCheckpointWithPreviousFocus
case cntCheckpointWithPreviousFocus:
d.focusElement = cntCheckpointFormFocus
}
}
// SetContainerInfo sets selected container ID and name information. // SetContainerInfo sets selected container ID and name information.
func (d *ContainerCheckpointDialog) SetContainerInfo(id string, name string) { func (d *ContainerCheckpointDialog) SetContainerInfo(id string, name string) {
d.containerID = id d.containerID = id
@ -538,3 +514,28 @@ func (d *ContainerCheckpointDialog) GetCheckpointOptions() containers.CntCheckPo
return opts return opts
} }
func (d *ContainerCheckpointDialog) setFocusElement() { //nolint:cyclop
switch d.focusElement {
case cntCheckpointImageFocus:
d.focusElement = cntCheckpointExportFocus
case cntCheckpointExportFocus:
d.focusElement = cntCheckpointFileLockFocus
case cntCheckpointFileLockFocus:
d.focusElement = cntCheckpointIgnoreRootFsFocus
case cntCheckpointIgnoreRootFsFocus:
d.focusElement = cntCheckpointTCPEstablishedFocus
case cntCheckpointTCPEstablishedFocus:
d.focusElement = cntCheckpointPreCheckpointFocus
case cntCheckpointPreCheckpointFocus:
d.focusElement = cntCheckpointPrintStatsFocus
case cntCheckpointPrintStatsFocus:
d.focusElement = cntCheckpointKeepFocus
case cntCheckpointKeepFocus:
d.focusElement = cntCheckpointLeaveRunningFocus
case cntCheckpointLeaveRunningFocus:
d.focusElement = cntCheckpointWithPreviousFocus
case cntCheckpointWithPreviousFocus:
d.focusElement = cntCheckpointFormFocus
}
}

View File

@ -33,6 +33,7 @@ const (
// ContainerCommitDialog represents container commit dialog primitive. // ContainerCommitDialog represents container commit dialog primitive.
type ContainerCommitDialog struct { type ContainerCommitDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
cntInfo *tview.InputField cntInfo *tview.InputField
image *tview.InputField image *tview.InputField
@ -71,11 +72,9 @@ func NewContainerCommitDialog() *ContainerCommitDialog {
labelWidth := 9 labelWidth := 9
// container info input field // container info input field
cntInfoLabel := "CONTAINER ID:" //nolint:goconst
dialog.cntInfo.SetBackgroundColor(style.DialogBgColor) dialog.cntInfo.SetBackgroundColor(style.DialogBgColor)
dialog.cntInfo.SetLabel("[::b]" + cntInfoLabel) dialog.cntInfo.SetLabel("[::b]" + utils.ContainerIDLabel)
dialog.cntInfo.SetLabelWidth(len(cntInfoLabel) + 1) dialog.cntInfo.SetLabelWidth(len(utils.ContainerIDLabel) + 1)
dialog.cntInfo.SetFieldBackgroundColor(style.DialogBgColor) dialog.cntInfo.SetFieldBackgroundColor(style.DialogBgColor)
dialog.cntInfo.SetLabelStyle(tcell.StyleDefault. dialog.cntInfo.SetLabelStyle(tcell.StyleDefault.
Background(style.DialogBorderColor). Background(style.DialogBorderColor).
@ -366,25 +365,6 @@ func (d *ContainerCommitDialog) InputHandler() func(event *tcell.EventKey, setFo
}) })
} }
func (d *ContainerCommitDialog) setFocusElement() {
switch d.focusElement {
case cntCommitImageFocus:
d.focusElement = cntCommitAuthorFocus
case cntCommitAuthorFocus:
d.focusElement = cntCommitChangeFocus
case cntCommitChangeFocus:
d.focusElement = cntCommitFormatFocus
case cntCommitFormatFocus:
d.focusElement = cntCommitSquashFocus
case cntCommitSquashFocus:
d.focusElement = cntCommitPauseFocus
case cntCommitPauseFocus:
d.focusElement = cntCommitMessageFocus
case cntCommitMessageFocus:
d.focusElement = cntCommitFormFocus
}
}
// SetRect set rects for this primitive. // SetRect set rects for this primitive.
func (d *ContainerCommitDialog) SetRect(x, y, width, height int) { func (d *ContainerCommitDialog) SetRect(x, y, width, height int) {
if width > cntCommitDialogMaxWidth { if width > cntCommitDialogMaxWidth {
@ -408,9 +388,9 @@ func (d *ContainerCommitDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
@ -465,3 +445,22 @@ func (d *ContainerCommitDialog) GetContainerCommitOptions() containers.CntCommit
return opts return opts
} }
func (d *ContainerCommitDialog) setFocusElement() {
switch d.focusElement {
case cntCommitImageFocus:
d.focusElement = cntCommitAuthorFocus
case cntCommitAuthorFocus:
d.focusElement = cntCommitChangeFocus
case cntCommitChangeFocus:
d.focusElement = cntCommitFormatFocus
case cntCommitFormatFocus:
d.focusElement = cntCommitSquashFocus
case cntCommitSquashFocus:
d.focusElement = cntCommitPauseFocus
case cntCommitPauseFocus:
d.focusElement = cntCommitMessageFocus
case cntCommitMessageFocus:
d.focusElement = cntCommitFormFocus
}
}

File diff suppressed because it is too large Load Diff

View File

@ -90,6 +90,8 @@ var _ = Describe("container create", Ordered, func() {
createAction = createWants createAction = createWants
} }
createDialog.SetHandlerFunc(createFunc) createDialog.SetHandlerFunc(createFunc)
createDialog.Hide()
createDialog.Display()
createDialog.focusElement = createContainerFormFocus createDialog.focusElement = createContainerFormFocus
createDialogApp.SetFocus(createDialog) createDialogApp.SetFocus(createDialog)
createDialogApp.Draw() createDialogApp.Draw()

View File

@ -36,6 +36,7 @@ const (
// ContainerExecDialog represents container exec dialog primitive. // ContainerExecDialog represents container exec dialog primitive.
type ContainerExecDialog struct { type ContainerExecDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
cntInfo *tview.InputField cntInfo *tview.InputField
command *tview.InputField command *tview.InputField
@ -77,11 +78,9 @@ func NewContainerExecDialog() *ContainerExecDialog {
inputFieldBgColor := style.InputFieldBgColor inputFieldBgColor := style.InputFieldBgColor
// label (container ID and Name) // label (container ID and Name)
cntInfoLabel := "CONTAINER ID:"
dialog.cntInfo.SetBackgroundColor(style.DialogBgColor) dialog.cntInfo.SetBackgroundColor(style.DialogBgColor)
dialog.cntInfo.SetLabel("[::b]" + cntInfoLabel) dialog.cntInfo.SetLabel("[::b]" + utils.ContainerIDLabel)
dialog.cntInfo.SetLabelWidth(len(cntInfoLabel) + 1) dialog.cntInfo.SetLabelWidth(len(utils.ContainerIDLabel) + 1)
dialog.cntInfo.SetFieldBackgroundColor(style.DialogBgColor) dialog.cntInfo.SetFieldBackgroundColor(style.DialogBgColor)
dialog.cntInfo.SetLabelStyle(tcell.StyleDefault. dialog.cntInfo.SetLabelStyle(tcell.StyleDefault.
Background(style.DialogBorderColor). Background(style.DialogBorderColor).
@ -606,9 +605,9 @@ func (d *ContainerExecDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)

View File

@ -48,6 +48,7 @@ const (
// ContainerRestoreDialog implements container restore dialog primitive. // ContainerRestoreDialog implements container restore dialog primitive.
type ContainerRestoreDialog struct { type ContainerRestoreDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
containers *tview.DropDown containers *tview.DropDown
pods *tview.DropDown pods *tview.DropDown
@ -434,9 +435,9 @@ func (d *ContainerRestoreDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
@ -462,36 +463,6 @@ func (d *ContainerRestoreDialog) SetCancelFunc(handler func()) *ContainerRestore
return d return d
} }
func (d *ContainerRestoreDialog) setFocusElement() {
if d.focusElement < cntRestoreFormFocus {
d.focusElement++
return
}
d.focusElement = cntRestoreContainersFocus
}
func (d *ContainerRestoreDialog) getInnerPrimitives() []tview.Primitive {
// the item sort is important to be same as focus element number
return []tview.Primitive{
d.containers,
d.pods,
d.name,
d.publishPorts,
d.importArchive,
d.keep,
d.ignoreStaticIP,
d.ignoreStaticMAC,
d.fileLocks,
d.printStats,
d.tcpEstablished,
d.ignoreVolumes,
d.ignoreRootFS,
d.form,
}
}
// SetContainers sets containers dropdown options. // SetContainers sets containers dropdown options.
func (d *ContainerRestoreDialog) SetContainers(cnts [][]string) { func (d *ContainerRestoreDialog) SetContainers(cnts [][]string) {
emptyOptions := fmt.Sprintf("%*s", cntRestoreDialogSingleFieldWidth, " ") emptyOptions := fmt.Sprintf("%*s", cntRestoreDialogSingleFieldWidth, " ")
@ -556,3 +527,33 @@ func (d *ContainerRestoreDialog) GetRestoreOptions() containers.CntRestoreOption
return opts return opts
} }
func (d *ContainerRestoreDialog) setFocusElement() {
if d.focusElement < cntRestoreFormFocus {
d.focusElement++
return
}
d.focusElement = cntRestoreContainersFocus
}
func (d *ContainerRestoreDialog) getInnerPrimitives() []tview.Primitive {
// the item sort is important to be same as focus element number
return []tview.Primitive{
d.containers,
d.pods,
d.name,
d.publishPorts,
d.importArchive,
d.keep,
d.ignoreStaticIP,
d.ignoreStaticMAC,
d.fileLocks,
d.printStats,
d.tcpEstablished,
d.ignoreVolumes,
d.ignoreRootFS,
d.form,
}
}

View File

@ -17,6 +17,7 @@ import (
// ContainerStatsDialog implements the containers stats dialog primitive. // ContainerStatsDialog implements the containers stats dialog primitive.
type ContainerStatsDialog struct { type ContainerStatsDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
form *tview.Form form *tview.Form
table *tview.Table table *tview.Table
@ -48,11 +49,9 @@ func NewContainerStatsDialog() *ContainerStatsDialog {
statsDialog.initTableUI() statsDialog.initTableUI()
// container info text view // container info text view
cntInfoLabel := "CONTAINER ID:"
statsDialog.containerInfo.SetBackgroundColor(style.DialogBgColor) statsDialog.containerInfo.SetBackgroundColor(style.DialogBgColor)
statsDialog.containerInfo.SetLabel("[::b]" + cntInfoLabel) statsDialog.containerInfo.SetLabel("[::b]" + utils.ContainerIDLabel)
statsDialog.containerInfo.SetLabelWidth(len(cntInfoLabel) + 1) statsDialog.containerInfo.SetLabelWidth(len(utils.ContainerIDLabel) + 1)
statsDialog.containerInfo.SetFieldBackgroundColor(style.DialogBgColor) statsDialog.containerInfo.SetFieldBackgroundColor(style.DialogBgColor)
statsDialog.containerInfo.SetLabelStyle(tcell.StyleDefault. statsDialog.containerInfo.SetLabelStyle(tcell.StyleDefault.
Background(style.DialogBorderColor). Background(style.DialogBorderColor).
@ -121,7 +120,9 @@ func (d *ContainerStatsDialog) Hide() {
d.setContainerBlockOutput(0) d.setContainerBlockOutput(0)
d.setContainerNetInput(0) d.setContainerNetInput(0)
d.setContainerNetOutput(0) d.setContainerNetOutput(0)
d.mu.Lock() d.mu.Lock()
defer d.mu.Unlock() defer d.mu.Unlock()
*d.statsStream = false *d.statsStream = false
@ -168,8 +169,8 @@ func (d *ContainerStatsDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
} }

View File

@ -49,7 +49,9 @@ func (w *writer) Write(b []byte) (int, error) {
} }
buf := make([]byte, len(b)) buf := make([]byte, len(b))
copy(buf, b) copy(buf, b)
w.ch <- buf w.ch <- buf
return len(b), nil return len(b), nil

View File

@ -32,8 +32,9 @@ const (
// VtermDialog implements virtual terminal that can be used during // VtermDialog implements virtual terminal that can be used during
// exec, attach, run activity. // exec, attach, run activity.
type VtermDialog struct { //nolint:revive type VtermDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
form *tview.Form form *tview.Form
containerInfo *tview.InputField containerInfo *tview.InputField
@ -86,73 +87,6 @@ func NewVtermDialog() *VtermDialog {
return dialog return dialog
} }
func (d *VtermDialog) initLayoutUI() {
bgColor := style.DialogBgColor
borderColor := style.DialogBorderColor
terminalBgColor := style.TerminalBgColor
terminalBorderColor := style.TerminalBorderColor
// container information field
// label
cntInfoLabel := "CONTAINER ID:"
d.containerInfo.SetBackgroundColor(bgColor)
d.containerInfo.SetLabel("[::b]" + cntInfoLabel)
d.containerInfo.SetLabelWidth(len(cntInfoLabel) + 1)
d.containerInfo.SetFieldBackgroundColor(bgColor)
d.containerInfo.SetLabelStyle(tcell.StyleDefault.
Background(borderColor).
Foreground(style.DialogFgColor))
// form fields
d.form.AddButton("Cancel", nil).
SetButtonsAlign(tview.AlignRight)
d.form.SetBackgroundColor(bgColor)
d.form.SetButtonBackgroundColor(style.ButtonBgColor)
// terminal screen
d.termScreen.SetBackgroundColor(terminalBgColor)
d.termScreen.SetBorder(true)
d.termScreen.SetBorderColor(terminalBorderColor)
// layout setup
layout := tview.NewFlex().SetDirection(tview.FlexRow)
termLayout := tview.NewFlex().SetDirection(tview.FlexColumn)
layout.SetBackgroundColor(bgColor)
layout.SetBorder(false)
layout.AddItem(d.containerInfo, 1, 0, true)
// layout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
layout.AddItem(d.termScreen, 0, 1, true)
termLayout.SetBackgroundColor(bgColor)
termLayout.SetBorder(false)
termLayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, false)
termLayout.AddItem(layout, 0, 1, false)
termLayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, false)
// main layout
d.layout.SetDirection(tview.FlexRow)
d.layout.SetBorder(true)
d.layout.SetBorderColor(borderColor)
d.layout.SetBackgroundColor(bgColor)
// d.layout.SetTitle("CONTAINER TERMINAL")
d.layout.AddItem(termLayout, 0, 1, true)
d.layout.AddItem(d.form, dialogs.DialogFormHeight, 0, true)
}
func (d *VtermDialog) initChannelsCommon() {
d.sessionOutputDoneChan = make(chan bool, 2) //nolint:mnd
d.vtTerminal = vt10x.New()
sessionStdinPipeIn, sessionStdinPipeOut := io.Pipe()
d.sessionStdin = bufio.NewReader(sessionStdinPipeIn)
d.sessionStdinWriter = bufio.NewWriter(sessionStdinPipeOut)
d.vtTermPipeReader, d.vtTermPipeWriter = io.Pipe()
d.vtTermBuffer = bufio.NewReader(d.vtTermPipeReader)
}
// InitChannels will init buffers and channels for attach. // InitChannels will init buffers and channels for attach.
func (d *VtermDialog) InitAttachChannels() (io.Reader, io.Writer) { func (d *VtermDialog) InitAttachChannels() (io.Reader, io.Writer) {
log.Debug().Msg("view: container terminal dialog init channels (attach)") log.Debug().Msg("view: container terminal dialog init channels (attach)")
@ -194,9 +128,10 @@ func (d *VtermDialog) Display() {
return return
} }
if d.sessionMode == sessionModeAttach { switch d.sessionMode {
case sessionModeAttach:
go d.sessionOutputStreamer() go d.sessionOutputStreamer()
} else if d.sessionMode == sessionModeExec { case sessionModeExec:
go d.execSessionOutputStreamer() go d.execSessionOutputStreamer()
} }
@ -249,11 +184,21 @@ func (d *VtermDialog) Hide() {
} }
if d.sessionMode == sessionModeExec { if d.sessionMode == sessionModeExec {
d.execSessionStdout.Close() err := d.execSessionStdout.Close()
if err != nil {
log.Error().Msgf("failed to close vterm exec stdout session: %s", err.Error())
}
} }
d.vtTermPipeReader.Close() err := d.vtTermPipeReader.Close()
d.vtTermPipeWriter.Close() if err != nil {
log.Error().Msgf("failed to close vterm pipe reader: %s", err.Error())
}
err = d.vtTermPipeWriter.Close()
if err != nil {
log.Error().Msgf("failed to close vterm pipe writer: %s", err.Error())
}
} }
// HasFocus returns true if terminal dialog has focus. // HasFocus returns true if terminal dialog has focus.
@ -354,8 +299,8 @@ func (d *VtermDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
@ -390,23 +335,6 @@ func (d *VtermDialog) Draw(screen tcell.Screen) {
} }
} }
func (d *VtermDialog) setTTYSize(width int, height int) {
if width < 0 || height < 0 {
return
}
d.ttyWidth = width
d.ttyHeight = height
d.vtTerminal.Resize(width, height)
if d.sessionMode == sessionModeAttach {
go containers.ResizeContainerTTY(d.containerID, width, height) //nolint:errcheck
} else if d.sessionMode == sessionModeExec {
go containers.ResizeExecTty(d.sessionID, d.ttyHeight, d.ttyWidth)
}
}
// SetCancelFunc sets form close button selected function. // SetCancelFunc sets form close button selected function.
func (d *VtermDialog) SetCancelFunc(handler func()) *VtermDialog { func (d *VtermDialog) SetCancelFunc(handler func()) *VtermDialog {
d.cancelHandler = handler d.cancelHandler = handler
@ -454,28 +382,31 @@ func (d *VtermDialog) SetFastRefreshHandler(handler func()) {
func (d *VtermDialog) writeToSession(event *tcell.EventKey) { func (d *VtermDialog) writeToSession(event *tcell.EventKey) {
switch event.Key() { //nolint:exhaustive switch event.Key() { //nolint:exhaustive
case tcell.KeyUp: case tcell.KeyUp:
d.sessionStdinWriter.WriteRune(rune(27)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(27)) //nolint:mnd
d.sessionStdinWriter.WriteRune(rune(91)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(91)) //nolint:mnd
d.sessionStdinWriter.WriteRune(rune(65)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(65)) //nolint:mnd
case tcell.KeyDown: case tcell.KeyDown:
d.sessionStdinWriter.WriteRune(rune(27)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(27)) //nolint:mnd
d.sessionStdinWriter.WriteRune(rune(91)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(91)) //nolint:mnd
d.sessionStdinWriter.WriteRune(rune(66)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(66)) //nolint:mnd
case tcell.KeyRight: case tcell.KeyRight:
d.sessionStdinWriter.WriteRune(rune(27)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(27)) //nolint:mnd
d.sessionStdinWriter.WriteRune(rune(91)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(91)) //nolint:mnd
d.sessionStdinWriter.WriteRune(rune(67)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(67)) //nolint:mnd
case tcell.KeyLeft: case tcell.KeyLeft:
d.sessionStdinWriter.WriteRune(rune(27)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(27)) //nolint:mnd
d.sessionStdinWriter.WriteRune(rune(91)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(91)) //nolint:mnd
d.sessionStdinWriter.WriteRune(rune(68)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(68)) //nolint:mnd
case tcell.KeyEsc: case tcell.KeyEsc:
d.sessionStdinWriter.WriteRune(rune(27)) //nolint:errcheck,mnd d.writeToStdinSessionWriter(rune(27)) //nolint:mnd
default: default:
d.sessionStdinWriter.WriteRune(event.Rune()) //nolint:errcheck d.writeToStdinSessionWriter(event.Rune())
} }
d.sessionStdinWriter.Flush() err := d.sessionStdinWriter.Flush()
if err != nil {
log.Error().Msgf("failed to flush vterm stdin writer session: %s", err.Error())
}
} }
func (d *VtermDialog) sendDetachToSession() { func (d *VtermDialog) sendDetachToSession() {
@ -484,10 +415,13 @@ func (d *VtermDialog) sendDetachToSession() {
keys := d.detachKeys.keys() keys := d.detachKeys.keys()
for i := range keys { for i := range keys {
d.sessionStdinWriter.WriteRune(rune(keys[i])) //nolint:errcheck d.writeToStdinSessionWriter(rune(keys[i]))
} }
d.sessionStdinWriter.Flush() err := d.sessionStdinWriter.Flush()
if err != nil {
log.Error().Msgf("failed to flush vterm stdin writer session: %s", err.Error())
}
} }
// vtContent returns current content and cursor location of vt10x terminal. // vtContent returns current content and cursor location of vt10x terminal.
@ -530,7 +464,11 @@ func (d *VtermDialog) execSessionOutputStreamer() {
return return
case data := <-d.execSessionStdout.Chan(): case data := <-d.execSessionStdout.Chan():
d.vtTermPipeWriter.Write(data) //nolint:errcheck _, err := d.vtTermPipeWriter.Write(data)
if err != nil {
log.Error().Msgf("failed to write %s to vterm pipe writer: %s", data, err.Error())
}
d.fastRefreshHandler() d.fastRefreshHandler()
} }
} }
@ -549,7 +487,11 @@ func (d *VtermDialog) sessionOutputStreamer() {
dataString := string(data) dataString := string(data)
dataString = strings.ReplaceAll(dataString, "\n", "\r\n") dataString = strings.ReplaceAll(dataString, "\n", "\r\n")
d.vtTermPipeWriter.Write([]byte(dataString)) //nolint:errcheck _, err := d.vtTermPipeWriter.Write([]byte(dataString))
if err != nil {
log.Error().Msgf("failed to write %s to vterm pipe writer: %s", []byte(dataString), err.Error())
}
d.fastRefreshHandler() d.fastRefreshHandler()
} }
} }
@ -567,3 +509,93 @@ func (dkey termDetachKeys) string() string {
func (dkey termDetachKeys) keys() []tcell.Key { func (dkey termDetachKeys) keys() []tcell.Key {
return dkey.tcellKeys return dkey.tcellKeys
} }
func (d *VtermDialog) initLayoutUI() {
bgColor := style.DialogBgColor
borderColor := style.DialogBorderColor
terminalBgColor := style.TerminalBgColor
terminalBorderColor := style.TerminalBorderColor
// container information field
// label
d.containerInfo.SetBackgroundColor(bgColor)
d.containerInfo.SetLabel("[::b]" + utils.ContainerIDLabel)
d.containerInfo.SetLabelWidth(len(utils.ContainerIDLabel) + 1)
d.containerInfo.SetFieldBackgroundColor(bgColor)
d.containerInfo.SetLabelStyle(tcell.StyleDefault.
Background(borderColor).
Foreground(style.DialogFgColor))
// form fields
d.form.AddButton("Cancel", nil).
SetButtonsAlign(tview.AlignRight)
d.form.SetBackgroundColor(bgColor)
d.form.SetButtonBackgroundColor(style.ButtonBgColor)
// terminal screen
d.termScreen.SetBackgroundColor(terminalBgColor)
d.termScreen.SetBorder(true)
d.termScreen.SetBorderColor(terminalBorderColor)
// layout setup
layout := tview.NewFlex().SetDirection(tview.FlexRow)
termLayout := tview.NewFlex().SetDirection(tview.FlexColumn)
layout.SetBackgroundColor(bgColor)
layout.SetBorder(false)
layout.AddItem(d.containerInfo, 1, 0, true)
// layout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
layout.AddItem(d.termScreen, 0, 1, true)
termLayout.SetBackgroundColor(bgColor)
termLayout.SetBorder(false)
termLayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, false)
termLayout.AddItem(layout, 0, 1, false)
termLayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, false)
// main layout
d.layout.SetDirection(tview.FlexRow)
d.layout.SetBorder(true)
d.layout.SetBorderColor(borderColor)
d.layout.SetBackgroundColor(bgColor)
// d.layout.SetTitle("CONTAINER TERMINAL")
d.layout.AddItem(termLayout, 0, 1, true)
d.layout.AddItem(d.form, dialogs.DialogFormHeight, 0, true)
}
func (d *VtermDialog) initChannelsCommon() {
d.sessionOutputDoneChan = make(chan bool, 2) //nolint:mnd
d.vtTerminal = vt10x.New()
sessionStdinPipeIn, sessionStdinPipeOut := io.Pipe()
d.sessionStdin = bufio.NewReader(sessionStdinPipeIn)
d.sessionStdinWriter = bufio.NewWriter(sessionStdinPipeOut)
d.vtTermPipeReader, d.vtTermPipeWriter = io.Pipe()
d.vtTermBuffer = bufio.NewReader(d.vtTermPipeReader)
}
func (d *VtermDialog) setTTYSize(width int, height int) {
if width < 0 || height < 0 {
return
}
d.ttyWidth = width
d.ttyHeight = height
d.vtTerminal.Resize(width, height)
switch d.sessionMode {
case sessionModeAttach:
go containers.ResizeContainerTTY(d.containerID, width, height) //nolint:errcheck
case sessionModeExec:
go containers.ResizeExecTty(d.sessionID, d.ttyHeight, d.ttyWidth)
}
}
func (d *VtermDialog) writeToStdinSessionWriter(val rune) {
_, err := d.sessionStdinWriter.WriteRune(val)
if err != nil {
log.Error().Msgf("failed to write value %d to vterm session writer rune: %s", val, err.Error())
}
}

View File

@ -8,6 +8,7 @@ import (
"github.com/containers/podman-tui/pdcs/pods" "github.com/containers/podman-tui/pdcs/pods"
"github.com/containers/podman-tui/ui/dialogs" "github.com/containers/podman-tui/ui/dialogs"
"github.com/containers/podman-tui/ui/style" "github.com/containers/podman-tui/ui/style"
"github.com/containers/podman-tui/ui/utils"
bcontainers "github.com/containers/podman/v5/pkg/bindings/containers" bcontainers "github.com/containers/podman/v5/pkg/bindings/containers"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -36,7 +37,7 @@ func (cnt *Containers) runCommand(cmd string) { //nolint:cyclop
cnt.logs() cnt.logs()
case "pause": case "pause":
cnt.pause() cnt.pause()
case "prune": //nolint:goconst case utils.PruneCommandLabel:
cnt.cprune() cnt.cprune()
case "rename": case "rename":
cnt.rename() cnt.rename()
@ -333,7 +334,7 @@ func (cnt *Containers) stats() {
} }
if cntStatus != "running" { if cntStatus != "running" {
cnt.displayError("", fmt.Errorf("container (%s) status improper", cntID)) //nolint:goerr113 cnt.displayError("", fmt.Errorf("container (%s) status improper", cntID)) //nolint:err113
return return
} }
@ -450,7 +451,8 @@ func (cnt *Containers) runDetach(runOpts containers.CreateOptions) {
return return
} }
if err := containers.Start(cntID); err != nil { err = containers.Start(cntID)
if err != nil {
cnt.progressDialog.Hide() cnt.progressDialog.Hide()
cnt.displayError("CONTAINER RUN ERROR", err) cnt.displayError("CONTAINER RUN ERROR", err)
cnt.appFocusHandler() cnt.appFocusHandler()
@ -525,7 +527,8 @@ func (cnt *Containers) runAttach(runOpts containers.CreateOptions) {
cnt.progressDialog.Hide() cnt.progressDialog.Hide()
if isReady { if isReady {
if err := containers.Start(cntID); err != nil { err := containers.Start(cntID)
if err != nil {
cnt.displayError("CONTAINER RUN ERROR", err) cnt.displayError("CONTAINER RUN ERROR", err)
cnt.appFocusHandler() cnt.appFocusHandler()
@ -768,7 +771,7 @@ func (cnt *Containers) prune() {
} }
if len(errData) > 0 { if len(errData) > 0 {
cnt.displayError("CONTAINER PRUNE ERROR", fmt.Errorf("%v", errData)) //nolint:goerr113 cnt.displayError("CONTAINER PRUNE ERROR", fmt.Errorf("%v", errData)) //nolint:err113
} }
} }
@ -787,8 +790,8 @@ func (cnt *Containers) rename() {
fgColor := style.GetColorHex(style.DialogFgColor) fgColor := style.GetColorHex(style.DialogFgColor)
bgColor := fmt.Sprintf("#%x", style.DialogBorderColor.Hex()) bgColor := fmt.Sprintf("#%x", style.DialogBorderColor.Hex())
containerInfo := fmt.Sprintf("%s (%s)", cnt.selectedID, cnt.selectedName) containerInfo := fmt.Sprintf("%s (%s)", cnt.selectedID, cnt.selectedName)
description := fmt.Sprintf("[%s:%s:b]CONTAINER ID:[:-:-] %s", description := fmt.Sprintf("[%s:%s:b]%s[:-:-] %s",
fgColor, bgColor, containerInfo) fgColor, bgColor, utils.ContainerIDLabel, containerInfo)
cnt.cmdInputDialog.SetDescription(description) cnt.cmdInputDialog.SetDescription(description)
cnt.cmdInputDialog.SetSelectButtonLabel("rename") cnt.cmdInputDialog.SetSelectButtonLabel("rename")
@ -838,7 +841,7 @@ func (cnt *Containers) rm() {
cnt.confirmData = "rm" cnt.confirmData = "rm"
bgColor := style.GetColorHex(style.DialogBorderColor) bgColor := style.GetColorHex(style.DialogBorderColor)
fgColor := style.GetColorHex(style.DialogFgColor) fgColor := style.GetColorHex(style.DialogFgColor)
containerItem := fmt.Sprintf("[%s:%s:b]CONTAINER ID:[:-:-] %s(%s)", fgColor, bgColor, cntID, cntName) containerItem := fmt.Sprintf("[%s:%s:b]%s[:-:-] %s(%s)", fgColor, bgColor, utils.ContainerIDLabel, cntID, cntName)
description := fmt.Sprintf("%s\n\nAre you sure you want to remove the selected container ?", //nolint:perfsprint description := fmt.Sprintf("%s\n\nAre you sure you want to remove the selected container ?", //nolint:perfsprint
containerItem) containerItem)
@ -867,7 +870,7 @@ func (cnt *Containers) remove() {
if len(errData) > 0 { if len(errData) > 0 {
title := fmt.Sprintf("CONTAINER (%s) REMOVE ERROR", cnt.selectedID) title := fmt.Sprintf("CONTAINER (%s) REMOVE ERROR", cnt.selectedID)
cnt.displayError(title, fmt.Errorf("%v", errData)) //nolint:goerr113 cnt.displayError(title, fmt.Errorf("%v", errData)) //nolint:err113
cnt.appFocusHandler() cnt.appFocusHandler()
} }
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/containers/podman-tui/ui/containers/cntdialogs/vterm" "github.com/containers/podman-tui/ui/containers/cntdialogs/vterm"
"github.com/containers/podman-tui/ui/dialogs" "github.com/containers/podman-tui/ui/dialogs"
"github.com/containers/podman-tui/ui/style" "github.com/containers/podman-tui/ui/style"
"github.com/containers/podman-tui/ui/utils"
"github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/domain/entities"
"github.com/rivo/tview" "github.com/rivo/tview"
) )
@ -49,6 +50,7 @@ var (
// Containers implements the containers page primitive. // Containers implements the containers page primitive.
type Containers struct { type Containers struct {
*tview.Box *tview.Box
title string title string
headers []string headers []string
table *tview.Table table *tview.Table
@ -58,15 +60,16 @@ type Containers struct {
confirmDialog *dialogs.ConfirmDialog confirmDialog *dialogs.ConfirmDialog
messageDialog *dialogs.MessageDialog messageDialog *dialogs.MessageDialog
progressDialog *dialogs.ProgressDialog progressDialog *dialogs.ProgressDialog
sortDialog *dialogs.SortDialog
topDialog *dialogs.TopDialog topDialog *dialogs.TopDialog
createDialog *cntdialogs.ContainerCreateDialog createDialog *cntdialogs.ContainerCreateDialog
runDialog *cntdialogs.ContainerCreateDialog runDialog *cntdialogs.ContainerCreateDialog
execDialog *cntdialogs.ContainerExecDialog execDialog *cntdialogs.ContainerExecDialog
terminalDialog *vterm.VtermDialog
statsDialog *cntdialogs.ContainerStatsDialog statsDialog *cntdialogs.ContainerStatsDialog
commitDialog *cntdialogs.ContainerCommitDialog commitDialog *cntdialogs.ContainerCommitDialog
checkpointDialog *cntdialogs.ContainerCheckpointDialog checkpointDialog *cntdialogs.ContainerCheckpointDialog
restoreDialog *cntdialogs.ContainerRestoreDialog restoreDialog *cntdialogs.ContainerRestoreDialog
terminalDialog *vterm.VtermDialog
containersList containerListReport containersList containerListReport
selectedID string selectedID string
selectedName string selectedName string
@ -78,6 +81,8 @@ type Containers struct {
type containerListReport struct { type containerListReport struct {
mu sync.Mutex mu sync.Mutex
report []entities.ListContainer report []entities.ListContainer
sortBy string
ascending bool
} }
// NewContainers returns containers page view. // NewContainers returns containers page view.
@ -92,15 +97,18 @@ func NewContainers() *Containers {
progressDialog: dialogs.NewProgressDialog(), progressDialog: dialogs.NewProgressDialog(),
confirmDialog: dialogs.NewConfirmDialog(), confirmDialog: dialogs.NewConfirmDialog(),
topDialog: dialogs.NewTopDialog(), topDialog: dialogs.NewTopDialog(),
sortDialog: dialogs.NewSortDialog([]string{"name", "pod", "image", "created", "status"}, 3), //nolint:mnd
createDialog: cntdialogs.NewContainerCreateDialog(cntdialogs.ContainerCreateOnlyDialogMode), createDialog: cntdialogs.NewContainerCreateDialog(cntdialogs.ContainerCreateOnlyDialogMode),
runDialog: cntdialogs.NewContainerCreateDialog(cntdialogs.ContainerCreateAndRunDialogMode), runDialog: cntdialogs.NewContainerCreateDialog(cntdialogs.ContainerCreateAndRunDialogMode),
execDialog: cntdialogs.NewContainerExecDialog(), execDialog: cntdialogs.NewContainerExecDialog(),
terminalDialog: vterm.NewVtermDialog(),
statsDialog: cntdialogs.NewContainerStatsDialog(), statsDialog: cntdialogs.NewContainerStatsDialog(),
commitDialog: cntdialogs.NewContainerCommitDialog(), commitDialog: cntdialogs.NewContainerCommitDialog(),
checkpointDialog: cntdialogs.NewContainerCheckpointDialog(), checkpointDialog: cntdialogs.NewContainerCheckpointDialog(),
restoreDialog: cntdialogs.NewContainerRestoreDialog(), restoreDialog: cntdialogs.NewContainerRestoreDialog(),
terminalDialog: vterm.NewVtermDialog(),
containersList: containerListReport{sortBy: "created", ascending: true},
} }
containers.topDialog.SetTitle("podman container top") containers.topDialog.SetTitle("podman container top")
containers.cmdDialog = dialogs.NewCommandDialog([][]string{ containers.cmdDialog = dialogs.NewCommandDialog([][]string{
@ -170,7 +178,7 @@ func NewContainers() *Containers {
containers.confirmDialog.Hide() containers.confirmDialog.Hide()
switch containers.confirmData { switch containers.confirmData {
case "prune": case utils.PruneCommandLabel:
containers.prune() containers.prune()
case "rm": case "rm":
containers.remove() containers.remove()
@ -224,6 +232,10 @@ func NewContainers() *Containers {
containers.restoreDialog.SetRestoreFunc(containers.restore) containers.restoreDialog.SetRestoreFunc(containers.restore)
containers.restoreDialog.SetCancelFunc(containers.restoreDialog.Hide) containers.restoreDialog.SetCancelFunc(containers.restoreDialog.Hide)
// set sort dialog functions
containers.sortDialog.SetSelectFunc(containers.SortView)
containers.sortDialog.SetCancelFunc(containers.sortDialog.Hide)
return containers return containers
} }
@ -271,7 +283,11 @@ func (cnt *Containers) HasFocus() bool { //nolint:cyclop
return true return true
} }
return cnt.Box.HasFocus() if cnt.sortDialog.HasFocus() || cnt.Box.HasFocus() {
return true
}
return false
} }
// SubDialogHasFocus returns whether or not sub dialog primitive has focus. // SubDialogHasFocus returns whether or not sub dialog primitive has focus.
@ -304,7 +320,11 @@ func (cnt *Containers) SubDialogHasFocus() bool { //nolint:cyclop
return true return true
} }
return cnt.runDialog.HasFocus() if cnt.sortDialog.HasFocus() || cnt.runDialog.HasFocus() {
return true
}
return false
} }
// Focus is called when this primitive receives focus. // Focus is called when this primitive receives focus.
@ -407,26 +427,16 @@ func (cnt *Containers) Focus(delegate func(p tview.Primitive)) { //nolint:cyclop
return return
} }
// sort dialog
if cnt.sortDialog.IsDisplay() {
delegate(cnt.sortDialog)
return
}
delegate(cnt.table) delegate(cnt.table)
} }
func (cnt *Containers) getSelectedItem() (string, string) {
var (
cntID string
cntName string
)
if cnt.table.GetRowCount() <= 1 {
return cntID, cntName
}
row, _ := cnt.table.GetSelection()
cntID = cnt.table.GetCell(row, viewContainersIDColIndex).Text
cntName = cnt.table.GetCell(row, viewContainersNamesColIndex).Text
return cntID, cntName
}
// SetFastRefreshChannel sets channel for fastRefresh func. // SetFastRefreshChannel sets channel for fastRefresh func.
func (cnt *Containers) SetFastRefreshChannel(refresh chan bool) { func (cnt *Containers) SetFastRefreshChannel(refresh chan bool) {
cnt.fastRefreshChan = refresh cnt.fastRefreshChan = refresh
@ -493,4 +503,25 @@ func (cnt *Containers) HideAllDialogs() { //nolint:cyclop
if cnt.terminalDialog.IsDisplay() { if cnt.terminalDialog.IsDisplay() {
cnt.terminalDialog.Hide() cnt.terminalDialog.Hide()
} }
if cnt.sortDialog.IsDisplay() {
cnt.sortDialog.Hide()
}
}
func (cnt *Containers) getSelectedItem() (string, string) {
var (
cntID string
cntName string
)
if cnt.table.GetRowCount() <= 1 {
return cntID, cntName
}
row, _ := cnt.table.GetSelection()
cntID = cnt.table.GetCell(row, viewContainersIDColIndex).Text
cntName = cnt.table.GetCell(row, viewContainersNamesColIndex).Text
return cntID, cntName
} }

View File

@ -2,6 +2,7 @@ package containers
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"time" "time"
@ -14,6 +15,18 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
// SortView sorts data view called from sort dialog.
func (cnt *Containers) SortView(option string, ascending bool) {
log.Debug().Msgf("view: containers sort by %s", option)
cnt.containersList.mu.Lock()
defer cnt.containersList.mu.Unlock()
cnt.containersList.sortBy = option
cnt.containersList.ascending = ascending
sort.Sort(containerListSorted{cnt.containersList.report, option, ascending})
}
// UpdateData retrieves containers list data. // UpdateData retrieves containers list data.
func (cnt *Containers) UpdateData() { func (cnt *Containers) UpdateData() {
cntList, err := containers.List() cntList, err := containers.List()
@ -28,6 +41,8 @@ func (cnt *Containers) UpdateData() {
cnt.containersList.mu.Lock() cnt.containersList.mu.Lock()
defer cnt.containersList.mu.Unlock() defer cnt.containersList.mu.Unlock()
sort.Sort(containerListSorted{cntList, cnt.containersList.sortBy, cnt.containersList.ascending})
cnt.containersList.report = cntList cnt.containersList.report = cntList
} }
@ -76,7 +91,7 @@ func (con conReporter) names() string {
func (con conReporter) state() string { func (con conReporter) state() string {
var state string var state string
switch con.ListContainer.State { switch con.State {
case "running": case "running":
t := units.HumanDuration(time.Since(time.Unix(con.StartedAt, 0))) t := units.HumanDuration(time.Since(time.Unix(con.StartedAt, 0)))
state = "Up " + t + " ago" state = "Up " + t + " ago"
@ -86,14 +101,14 @@ func (con conReporter) state() string {
t := units.HumanDuration(time.Since(time.Unix(con.ExitedAt, 0))) t := units.HumanDuration(time.Since(time.Unix(con.ExitedAt, 0)))
state = fmt.Sprintf("Exited (%d) %s ago", con.ExitCode, t) state = fmt.Sprintf("Exited (%d) %s ago", con.ExitCode, t)
default: default:
state = con.ListContainer.State state = con.State
} }
return state return state
} }
func (con conReporter) status() string { func (con conReporter) status() string {
hc := con.ListContainer.Status hc := con.Status
if hc != "" { if hc != "" {
return con.state() + " (" + hc + ")" return con.state() + " (" + hc + ")"
} }
@ -102,9 +117,56 @@ func (con conReporter) status() string {
} }
func (con conReporter) ports() string { func (con conReporter) ports() string {
if len(con.ListContainer.Ports) < 1 { if len(con.Ports) < 1 {
return "" return ""
} }
return putils.PortsToString(con.ListContainer.Ports) return putils.PortsToString(con.Ports)
}
type lprSort []entities.ListContainer
func (a lprSort) Len() int { return len(a) }
func (a lprSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type containerListSorted struct {
lprSort
option string
ascending bool
}
func (a containerListSorted) Less(i, j int) bool {
switch a.option {
case "pod":
if a.ascending {
return a.lprSort[i].Pod < a.lprSort[j].Pod
}
return a.lprSort[i].Pod > a.lprSort[j].Pod
case "image":
if a.ascending {
return a.lprSort[i].Image < a.lprSort[j].Image
}
return a.lprSort[i].Image > a.lprSort[j].Image
case "status":
if a.ascending {
return a.lprSort[i].State < a.lprSort[j].State
}
return a.lprSort[i].State > a.lprSort[j].State
case "created":
if a.ascending {
return a.lprSort[i].Created.After(a.lprSort[j].Created)
}
return a.lprSort[i].Created.Before(a.lprSort[j].Created)
}
if a.ascending {
return a.lprSort[i].Names[0] < a.lprSort[j].Names[0]
}
return a.lprSort[i].Names[0] > a.lprSort[j].Names[0]
} }

View File

@ -6,8 +6,8 @@ import (
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
func (cnt *Containers) Draw(screen tcell.Screen) { //nolint:cyclop func (cnt *Containers) Draw(screen tcell.Screen) { //nolint:cyclop
cnt.Box.DrawForSubclass(screen, cnt) cnt.DrawForSubclass(screen, cnt)
cnt.Box.SetBorder(false) cnt.SetBorder(false)
cntViewX, cntViewY, cntViewW, cntViewH := cnt.GetInnerRect() cntViewX, cntViewY, cntViewW, cntViewH := cnt.GetInnerRect()
@ -140,4 +140,12 @@ func (cnt *Containers) Draw(screen tcell.Screen) { //nolint:cyclop
return return
} }
// sort dialog
if cnt.sortDialog.IsDisplay() {
cnt.sortDialog.SetRect(cntViewX, cntViewY, cntViewW, cntViewH)
cnt.sortDialog.Draw(screen)
return
}
} }

View File

@ -107,6 +107,13 @@ func (cnt *Containers) InputHandler() func(event *tcell.EventKey, setFocus func(
} }
} }
// container sort dialog handler
if cnt.sortDialog.HasFocus() {
if cntSortDialogHandler := cnt.sortDialog.InputHandler(); cntSortDialogHandler != nil {
cntSortDialogHandler(event, setFocus)
}
}
// container terminal dialog handler // container terminal dialog handler
if cnt.terminalDialog.HasFocus() { if cnt.terminalDialog.HasFocus() {
if cntTerminalDialogHandler := cnt.terminalDialog.InputHandler(); cntTerminalDialogHandler != nil { if cntTerminalDialogHandler := cnt.terminalDialog.InputHandler(); cntTerminalDialogHandler != nil {
@ -117,6 +124,7 @@ func (cnt *Containers) InputHandler() func(event *tcell.EventKey, setFocus func(
// table handlers // table handlers
if cnt.table.HasFocus() { //nolint:nestif if cnt.table.HasFocus() { //nolint:nestif
cnt.selectedID, cnt.selectedName = cnt.getSelectedItem() cnt.selectedID, cnt.selectedName = cnt.getSelectedItem()
// display command menu
if event.Rune() == utils.CommandMenuKey.Rune() { if event.Rune() == utils.CommandMenuKey.Rune() {
if cnt.cmdDialog.GetCommandCount() <= 1 { if cnt.cmdDialog.GetCommandCount() <= 1 {
return return
@ -128,6 +136,14 @@ func (cnt *Containers) InputHandler() func(event *tcell.EventKey, setFocus func(
return return
} }
// display sort menu
if event.Rune() == utils.SortMenuKey.Rune() {
cnt.sortDialog.Display()
setFocus(cnt)
return
}
if event.Key() == utils.DeleteKey.EventKey() { if event.Key() == utils.DeleteKey.EventKey() {
cnt.rm() cnt.rm()
setFocus(cnt) setFocus(cnt)

View File

@ -30,6 +30,7 @@ func (cnt *Containers) refresh(maxWidth int) {
SetSelectable(false)) SetSelectable(false))
} }
currentSelectedRow, _ := cnt.table.GetSelection()
rowIndex := 1 rowIndex := 1
cntList := cnt.getData() cntList := cnt.getData()
@ -116,4 +117,11 @@ func (cnt *Containers) refresh(maxWidth int) {
rowIndex++ rowIndex++
} }
if currentSelectedRow > len(cntList) {
currentSelectedRow--
if currentSelectedRow >= 0 {
cnt.table.Select(currentSelectedRow, -1)
}
}
} }

View File

@ -22,6 +22,7 @@ const (
// CommandDialog is a commands list dialog. // CommandDialog is a commands list dialog.
type CommandDialog struct { type CommandDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
table *tview.Table table *tview.Table
form *tview.Form form *tview.Form
@ -37,6 +38,12 @@ type CommandDialog struct {
// NewCommandDialog returns a command list primitive. // NewCommandDialog returns a command list primitive.
func NewCommandDialog(options [][]string) *CommandDialog { func NewCommandDialog(options [][]string) *CommandDialog {
var cmdWidth int
// command table items
col1Width := 0
col2Width := 0
form := tview.NewForm(). form := tview.NewForm().
AddButton("Cancel", nil). AddButton("Cancel", nil).
SetButtonsAlign(tview.AlignRight) SetButtonsAlign(tview.AlignRight)
@ -47,7 +54,6 @@ func NewCommandDialog(options [][]string) *CommandDialog {
cmdsTable := tview.NewTable() cmdsTable := tview.NewTable()
cmdsTable.SetBackgroundColor(style.DialogBgColor) cmdsTable.SetBackgroundColor(style.DialogBgColor)
cmdWidth := 0
// command table header // command table header
cmdsTable.SetCell(0, 0, cmdsTable.SetCell(0, 0,
tview.NewTableCell(fmt.Sprintf("[%s::b]COMMAND", style.GetColorHex(style.TableHeaderFgColor))). tview.NewTableCell(fmt.Sprintf("[%s::b]COMMAND", style.GetColorHex(style.TableHeaderFgColor))).
@ -65,10 +71,6 @@ func NewCommandDialog(options [][]string) *CommandDialog {
SetAlign(tview.AlignCenter). SetAlign(tview.AlignCenter).
SetSelectable(false)) SetSelectable(false))
// command table items
col1Width := 0
col2Width := 0
for i := range options { for i := range options {
cmdsTable.SetCell(i+1, 0, cmdsTable.SetCell(i+1, 0,
tview.NewTableCell(options[i][0]). tview.NewTableCell(options[i][0]).
@ -273,7 +275,7 @@ func (cmd *CommandDialog) SetRect(x, y, width, height int) {
cmd.Box.SetRect(x+ws, dy, bWidth, bHeight) cmd.Box.SetRect(x+ws, dy, bWidth, bHeight)
x, y, width, height = cmd.Box.GetInnerRect() x, y, width, height = cmd.GetInnerRect()
cmd.layout.SetRect(x, y, width, height) cmd.layout.SetRect(x, y, width, height)
} }
@ -284,7 +286,7 @@ func (cmd *CommandDialog) Draw(screen tcell.Screen) {
return return
} }
cmd.Box.DrawForSubclass(screen, cmd) cmd.DrawForSubclass(screen, cmd)
cmd.layout.Draw(screen) cmd.layout.Draw(screen)
} }

View File

@ -13,6 +13,7 @@ import (
// ConfirmDialog is a simple confirmation dialog primitive. // ConfirmDialog is a simple confirmation dialog primitive.
type ConfirmDialog struct { type ConfirmDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
textview *tview.TextView textview *tview.TextView
form *tview.Form form *tview.Form
@ -112,6 +113,56 @@ func (d *ConfirmDialog) SetRect(x, y, width, height int) {
d.setRect() d.setRect()
} }
// Draw draws this primitive onto the screen.
func (d *ConfirmDialog) Draw(screen tcell.Screen) {
if !d.display {
return
}
d.DrawForSubclass(screen, d)
x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen)
}
// InputHandler returns input handler function for this primitive.
func (d *ConfirmDialog) 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 formHandler := d.form.InputHandler(); formHandler != nil {
formHandler(event, setFocus)
return
}
})
}
// SetCancelFunc sets form cancel button selected function.
func (d *ConfirmDialog) SetCancelFunc(handler func()) *ConfirmDialog {
d.cancelHandler = handler
cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) //nolint:mnd
cancelButton.SetSelectedFunc(handler)
return d
}
// SetSelectedFunc sets form select button selected function.
func (d *ConfirmDialog) SetSelectedFunc(handler func()) *ConfirmDialog {
d.selectHandler = handler
enterButton := d.form.GetButton(d.form.GetButtonCount() - 1)
enterButton.SetSelectedFunc(handler)
return d
}
func (d *ConfirmDialog) setRect() { func (d *ConfirmDialog) setRect() {
maxHeight := d.height maxHeight := d.height
maxWidth := d.width maxWidth := d.width
@ -156,53 +207,3 @@ func (d *ConfirmDialog) setRect() {
d.Box.SetRect(d.x, d.y, d.width, d.height) d.Box.SetRect(d.x, d.y, d.width, d.height)
} }
// Draw draws this primitive onto the screen.
func (d *ConfirmDialog) 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)
}
// InputHandler returns input handler function for this primitive.
func (d *ConfirmDialog) 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 formHandler := d.form.InputHandler(); formHandler != nil {
formHandler(event, setFocus)
return
}
})
}
// SetCancelFunc sets form cancel button selected function.
func (d *ConfirmDialog) SetCancelFunc(handler func()) *ConfirmDialog {
d.cancelHandler = handler
cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) //nolint:mnd
cancelButton.SetSelectedFunc(handler)
return d
}
// SetSelectedFunc sets form select button selected function.
func (d *ConfirmDialog) SetSelectedFunc(handler func()) *ConfirmDialog {
d.selectHandler = handler
enterButton := d.form.GetButton(d.form.GetButtonCount() - 1)
enterButton.SetSelectedFunc(handler)
return d
}

View File

@ -12,6 +12,7 @@ import (
// ErrorDialog is an error dialog primitive. // ErrorDialog is an error dialog primitive.
type ErrorDialog struct { type ErrorDialog struct {
*tview.Box *tview.Box
modal *tview.Modal modal *tview.Modal
title string title string
message string message string
@ -29,7 +30,7 @@ func NewErrorDialog() *ErrorDialog {
dialog.modal.SetButtonBackgroundColor(style.ErrorDialogButtonBgColor) dialog.modal.SetButtonBackgroundColor(style.ErrorDialogButtonBgColor)
dialog.modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) { //nolint:revive dialog.modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
dialog.Hide() dialog.Hide()
}) })
@ -116,7 +117,7 @@ func (d *ErrorDialog) Draw(screen tcell.Screen) {
// SetDoneFunc sets modal done function. // SetDoneFunc sets modal done function.
func (d *ErrorDialog) SetDoneFunc(handler func()) *ErrorDialog { func (d *ErrorDialog) SetDoneFunc(handler func()) *ErrorDialog {
d.modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) { //nolint:revive d.modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
handler() handler()
}) })

View File

@ -23,6 +23,7 @@ const (
// SimpleInputDialog is an input dialog primitive. // SimpleInputDialog is an input dialog primitive.
type SimpleInputDialog struct { type SimpleInputDialog struct {
*tview.Box *tview.Box
height int height int
layout *tview.Flex layout *tview.Flex
textview *tview.TextView textview *tview.TextView
@ -95,44 +96,16 @@ func (d *SimpleInputDialog) Hide() {
d.display = false d.display = false
} }
func (d *SimpleInputDialog) setLayout(haveDesc bool) {
d.layout.Clear()
descHeight := siDescHeight
if !haveDesc {
descHeight = 1
d.height = siDialogHeight - 3 //nolint:mnd
} else {
d.height = siDialogHeight
}
d.layout.AddItem(
tview.NewFlex().SetDirection(tview.FlexColumn).
AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false).
AddItem(d.textview, 0, 1, false).
AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false),
descHeight, 0, true)
d.layout.AddItem(tview.NewFlex().SetDirection(tview.FlexColumn).
AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false).
AddItem(d.input, 0, 1, false).
AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false),
1, 0, true)
d.layout.AddItem(d.form, DialogFormHeight, 0, true)
d.layout.SetBorder(true)
d.layout.SetBorderColor(style.DialogBorderColor)
d.layout.SetBackgroundColor(style.DialogBgColor)
}
// SetDescription sets dialogs description. // SetDescription sets dialogs description.
func (d *SimpleInputDialog) SetDescription(text string) { func (d *SimpleInputDialog) SetDescription(text string) {
d.textview.Clear() d.textview.Clear()
haveDesc := true haveDesc := true
fmt.Fprintf(d.textview, "\n%s", text) _, err := fmt.Fprintf(d.textview, "\n%s", text)
if err != nil {
log.Error().Msgf("failed to write to ui text view: %s", err.Error())
}
if len(text) == 0 { if len(text) == 0 {
haveDesc = false haveDesc = false
@ -293,8 +266,8 @@ func (d *SimpleInputDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
} }
@ -316,3 +289,34 @@ func (d *SimpleInputDialog) SetCancelFunc(handler func()) *SimpleInputDialog {
return d return d
} }
func (d *SimpleInputDialog) setLayout(haveDesc bool) {
d.layout.Clear()
descHeight := siDescHeight
if !haveDesc {
descHeight = 1
d.height = siDialogHeight - 3 //nolint:mnd
} else {
d.height = siDialogHeight
}
d.layout.AddItem(
tview.NewFlex().SetDirection(tview.FlexColumn).
AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false).
AddItem(d.textview, 0, 1, false).
AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false),
descHeight, 0, true)
d.layout.AddItem(tview.NewFlex().SetDirection(tview.FlexColumn).
AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false).
AddItem(d.input, 0, 1, false).
AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false),
1, 0, true)
d.layout.AddItem(d.form, DialogFormHeight, 0, true)
d.layout.SetBorder(true)
d.layout.SetBorderColor(style.DialogBorderColor)
d.layout.SetBackgroundColor(style.DialogBgColor)
}

View File

@ -13,6 +13,7 @@ import (
// MessageDialog is a simaple message dialog primitive. // MessageDialog is a simaple message dialog primitive.
type MessageDialog struct { type MessageDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
infoType *tview.InputField infoType *tview.InputField
textview *tview.TextView textview *tview.TextView
@ -132,7 +133,7 @@ func (d *MessageDialog) SetText(headerType messageInfo, headerMessage string, me
case MessagePodInfo: case MessagePodInfo:
msgTypeLabel = "POD ID:" msgTypeLabel = "POD ID:"
case MessageContainerInfo: case MessageContainerInfo:
msgTypeLabel = "CONTAINER ID:" msgTypeLabel = utils.ContainerIDLabel
case MessageVolumeInfo: case MessageVolumeInfo:
msgTypeLabel = "VOLUME NAME:" msgTypeLabel = "VOLUME NAME:"
case MessageImageInfo: case MessageImageInfo:
@ -236,8 +237,8 @@ func (d *MessageDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
} }

View File

@ -17,6 +17,7 @@ const (
// ProgressDialog represents progress bar permitive. // ProgressDialog represents progress bar permitive.
type ProgressDialog struct { type ProgressDialog struct {
*tview.Box *tview.Box
x int x int
y int y int
width int width int
@ -46,8 +47,8 @@ func (d *ProgressDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, _ := d.Box.GetInnerRect() x, y, width, _ := d.GetInnerRect()
tickStr := d.tickStr(width) tickStr := d.tickStr(width)
tview.Print(screen, tickStr, x, y, width, tview.AlignLeft, tcell.ColorYellow) tview.Print(screen, tickStr, x, y, width, tview.AlignLeft, tcell.ColorYellow)
} }
@ -90,7 +91,7 @@ func (d *ProgressDialog) IsDisplay() bool {
} }
// Focus is called when this primitive receives focus. // Focus is called when this primitive receives focus.
func (d *ProgressDialog) Focus(delegate func(p tview.Primitive)) {} //nolint:revive func (d *ProgressDialog) Focus(delegate func(p tview.Primitive)) {}
// HasFocus returns whether or not this primitive has focus. // HasFocus returns whether or not this primitive has focus.
func (d *ProgressDialog) HasFocus() bool { func (d *ProgressDialog) HasFocus() bool {
@ -99,12 +100,16 @@ func (d *ProgressDialog) HasFocus() bool {
// InputHandler returns input handler function for this primitive. // InputHandler returns input handler function for this primitive.
func (d *ProgressDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { func (d *ProgressDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { //nolint:revive return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
log.Debug().Msgf("progress dialog: event %v received", event) log.Debug().Msgf("progress dialog: event %v received", event)
}) })
} }
func (d *ProgressDialog) tickStr(maxCount int) string { func (d *ProgressDialog) tickStr(maxCount int) string {
prgStr := prgCell + prgCell + prgCell + prgCell
prgHeadStr := ""
hWidth := 0
prgEndStr := ""
barColor := style.GetColorHex(style.PrgBarColor) barColor := style.GetColorHex(style.PrgBarColor)
counter := d.counterValue counter := d.counterValue
@ -114,18 +119,11 @@ func (d *ProgressDialog) tickStr(maxCount int) string {
d.counterValue = 0 d.counterValue = 0
} }
prgHeadStr := ""
hWidth := 0
prgEndStr := ""
prgStr := ""
for range d.counterValue { for range d.counterValue {
prgHeadStr += fmt.Sprintf("[black::]%s", prgCell) //nolint:perfsprint prgHeadStr += fmt.Sprintf("[black::]%s", prgCell) //nolint:perfsprint
hWidth++ hWidth++
} }
prgStr = prgCell + prgCell + prgCell + prgCell
for range maxCount + hWidth + 4 { for range maxCount + hWidth + 4 {
prgEndStr += fmt.Sprintf("[black::]%s", prgCell) //nolint:perfsprint prgEndStr += fmt.Sprintf("[black::]%s", prgCell) //nolint:perfsprint
} }

262
ui/dialogs/sort.go Normal file
View File

@ -0,0 +1,262 @@
package dialogs
import (
"github.com/containers/podman-tui/ui/style"
"github.com/containers/podman-tui/ui/utils"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"github.com/rs/zerolog/log"
)
const (
sortDialogMaxWidth = 30
sortDialogMaxHeight = 8
)
const (
sortDialogOptionsFocus = 0 + iota
sortDialogOrderFocus
sortDialogFormFocus
)
type SortDialog struct {
*tview.Box
layout *tview.Flex
sortBy *tview.DropDown
sortOrder *tview.DropDown
form *tview.Form
display bool
selectHandler func()
cancelHandler func()
focusElement int
}
func NewSortDialog(options []string, defaultOption int) *SortDialog {
sd := SortDialog{
Box: tview.NewBox(),
form: tview.NewForm(),
layout: tview.NewFlex(),
focusElement: sortDialogOptionsFocus,
}
sortOrder := "Sort order:"
sd.sortOrder = tview.NewDropDown()
sd.sortOrder.SetLabel(sortOrder)
sd.sortOrder.SetLabelWidth(len(sortOrder) + 1)
sd.sortOrder.SetTitleAlign(tview.AlignRight)
sd.sortOrder.SetLabelColor(style.DialogFgColor)
sd.sortOrder.SetBackgroundColor(style.DialogBgColor)
sd.sortOrder.SetOptions([]string{"ascending", "descending"}, nil)
sd.sortOrder.SetListStyles(style.DropDownUnselected, style.DropDownSelected)
sd.sortOrder.SetFieldBackgroundColor(style.InputFieldBgColor)
sd.sortOrder.SetFieldWidth(0)
sd.sortOrder.SetCurrentOption(0)
sd.sortBy = tview.NewDropDown()
sd.sortBy.SetLabel("Sort by:")
sd.sortBy.SetLabelWidth(len(sortOrder) + 1)
sd.sortBy.SetTitleAlign(tview.AlignRight)
sd.sortBy.SetLabelColor(style.DialogFgColor)
sd.sortBy.SetBackgroundColor(style.DialogBgColor)
sd.sortBy.SetOptions(options, nil)
sd.sortBy.SetListStyles(style.DropDownUnselected, style.DropDownSelected)
sd.sortBy.SetFieldBackgroundColor(style.InputFieldBgColor)
sd.sortBy.SetFieldWidth(0)
if len(options) > 0 {
sd.sortBy.SetCurrentOption(defaultOption)
}
// form
sd.form.AddButton("Cancel", nil)
sd.form.AddButton(" Sort ", nil)
sd.form.SetButtonsAlign(tview.AlignRight)
sd.form.SetBackgroundColor(style.DialogBgColor)
sd.form.SetButtonBackgroundColor(style.ButtonBgColor)
// main layout
sd.layout.SetDirection(tview.FlexRow)
sd.layout.SetBackgroundColor(style.DialogBgColor)
sd.layout.SetBorder(true)
sd.layout.SetBorderColor(style.DialogBorderColor)
sd.layout.AddItem(sd.sortBy, 0, 1, true)
sd.layout.AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 0, 1, false)
sd.layout.AddItem(sd.sortOrder, 0, 1, true)
sd.layout.AddItem(sd.form, DialogFormHeight, 0, true)
return &sd
}
// Display displays this primitive.
func (d *SortDialog) Display() {
d.display = true
}
// IsDisplay returns true if primitive is shown.
func (d *SortDialog) IsDisplay() bool {
return d.display
}
// Hide stops displaying this primitive.
func (d *SortDialog) Hide() {
d.display = false
d.focusElement = sortDialogOptionsFocus
d.form.SetFocus(0)
}
// HasFocus returns whether or not this primitive has focus.
func (d *SortDialog) HasFocus() bool {
if d.sortBy.HasFocus() || d.sortOrder.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 *SortDialog) Focus(delegate func(p tview.Primitive)) {
switch d.focusElement {
case sortDialogOptionsFocus:
delegate(d.sortBy)
case sortDialogOrderFocus:
delegate(d.sortOrder)
case sortDialogFormFocus:
sortButton := d.form.GetButton(1)
sortButton.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyTab {
d.focusElement = sortDialogOptionsFocus
d.Focus(delegate)
d.form.SetFocus(0)
return nil
}
if event.Key() == tcell.KeyEnter {
d.selectHandler()
}
return event
})
delegate(d.form)
}
}
// InputHandler returns input handler function for this primitive.
func (d *SortDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { //nolint:cyclop
return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
log.Debug().Msgf("sort dialog dialog: event %v received", event)
if event.Key() == utils.SwitchFocusKey.Key {
switch d.focusElement {
case sortDialogOptionsFocus:
d.focusElement = sortDialogOrderFocus
case sortDialogOrderFocus:
d.focusElement = sortDialogFormFocus
}
}
// dropdown widgets shall handle events before "Esc" key handler
if d.sortBy.HasFocus() {
event = utils.ParseKeyEventKey(event)
if sortByHandler := d.sortBy.InputHandler(); sortByHandler != nil {
sortByHandler(event, setFocus)
return
}
}
if d.sortOrder.HasFocus() {
event = utils.ParseKeyEventKey(event)
if sortOrderHandler := d.sortOrder.InputHandler(); sortOrderHandler != nil {
sortOrderHandler(event, setFocus)
return
}
}
if d.form.HasFocus() {
if event.Key() == tcell.KeyEsc {
d.cancelHandler()
return
}
if formHandler := d.form.InputHandler(); formHandler != nil {
formHandler(event, setFocus)
return
}
}
})
}
// SetRect set rects for this primitive.
func (d *SortDialog) SetRect(x, y, width, height int) {
if width > sortDialogMaxWidth {
emptySpace := (width - sortDialogMaxWidth) / 2 //nolint:mnd
x += emptySpace
width = sortDialogMaxWidth
}
if height > sortDialogMaxHeight {
emptySpace := (height - sortDialogMaxHeight) / 2 //nolint:mnd
y += emptySpace
height = sortDialogMaxHeight
}
d.Box.SetRect(x, y, width, height)
}
// Draw draws this primitive onto the screen.
func (d *SortDialog) Draw(screen tcell.Screen) {
if !d.display {
return
}
d.DrawForSubclass(screen, d)
x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen)
}
// SetSelectFunc sets form sort button selected function.
func (d *SortDialog) SetSelectFunc(handler func(string, bool)) *SortDialog {
selectHandler := func() {
if d.sortBy.GetOptionCount() > 0 {
_, sortOpt := d.sortBy.GetCurrentOption()
_, order := d.sortOrder.GetCurrentOption()
ascending := true
if order == "descending" {
ascending = false
}
handler(sortOpt, ascending)
}
d.Hide()
}
d.selectHandler = selectHandler
return d
}
// SetCancelFunc sets form cancel button selected function.
func (d *SortDialog) SetCancelFunc(handler func()) *SortDialog {
d.cancelHandler = handler
cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) //nolint:mnd
cancelButton.SetSelectedFunc(handler)
return d
}

View File

@ -25,6 +25,7 @@ const (
// TopDialog is a simple dialog with pod/container top result table. // TopDialog is a simple dialog with pod/container top result table.
type TopDialog struct { type TopDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
table *tview.Table table *tview.Table
info *tview.InputField info *tview.InputField
@ -174,8 +175,8 @@ func (d *TopDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
} }
@ -187,28 +188,9 @@ func (d *TopDialog) SetCancelFunc(handler func()) *TopDialog {
return d return d
} }
func (d *TopDialog) initTable() {
bgColor := style.TableHeaderBgColor
fgColor := style.TableHeaderFgColor
d.table.Clear()
d.table.SetFixed(1, 1)
d.table.SetSelectable(true, false)
for i := range d.tableHeaders {
d.table.SetCell(0, i,
tview.NewTableCell(fmt.Sprintf("[%s::b]%s", style.GetColorHex(fgColor), strings.ToUpper(d.tableHeaders[i]))).
SetExpansion(0).
SetBackgroundColor(bgColor).
SetTextColor(fgColor).
SetAlign(tview.AlignLeft).
SetSelectable(false))
}
}
// UpdateResults updates result table. // UpdateResults updates result table.
func (d *TopDialog) UpdateResults(infoType topInfo, id string, name string, data [][]string) { func (d *TopDialog) UpdateResults(infoType topInfo, id string, name string, data [][]string) {
headerInfo := "CONTAINER ID:" headerInfo := utils.ContainerIDLabel
if infoType == TopPodInfo { if infoType == TopPodInfo {
headerInfo = "POD ID:" headerInfo = "POD ID:"
} }
@ -297,6 +279,25 @@ func (d *TopDialog) UpdateResults(infoType topInfo, id string, name string, data
} }
} }
func (d *TopDialog) initTable() {
bgColor := style.TableHeaderBgColor
fgColor := style.TableHeaderFgColor
d.table.Clear()
d.table.SetFixed(1, 1)
d.table.SetSelectable(true, false)
for i := range d.tableHeaders {
d.table.SetCell(0, i,
tview.NewTableCell(fmt.Sprintf("[%s::b]%s", style.GetColorHex(fgColor), strings.ToUpper(d.tableHeaders[i]))).
SetExpansion(0).
SetBackgroundColor(bgColor).
SetTextColor(fgColor).
SetAlign(tview.AlignLeft).
SetSelectable(false))
}
}
func (d *TopDialog) getCommandWidth() int { func (d *TopDialog) getCommandWidth() int {
var ( var (
commandWidth int commandWidth int

View File

@ -12,6 +12,7 @@ import (
// Help is a help primitive dialog. // Help is a help primitive dialog.
type Help struct { type Help struct {
*tview.Box *tview.Box
title string title string
layout *tview.Flex layout *tview.Flex
} }
@ -43,8 +44,8 @@ func NewHelp(appName string, appVersion string) *Help {
SetTextAlign(tview.AlignLeft) SetTextAlign(tview.AlignLeft)
appinfo.SetBackgroundColor(bgColor) appinfo.SetBackgroundColor(bgColor)
licenseInfo := "Released under the Apache License 2.0." licenseInfo := "released under the Apache License 2.0."
appInfoText := fmt.Sprintf("%s %s - (C) 2022 podman-tui dev team.\n%s", appName, appVersion, licenseInfo) appInfoText := fmt.Sprintf("%s %s - %s", appName, appVersion, licenseInfo)
appinfo.SetText(appInfoText) appinfo.SetText(appInfoText)
appinfo.SetTextColor(headerColor) appinfo.SetTextColor(headerColor)
@ -54,7 +55,7 @@ func NewHelp(appName string, appVersion string) *Help {
rowIndex := 0 rowIndex := 0
colIndex := 0 colIndex := 0
needInit := true needInit := true
maxRowIndex := len(utils.UIKeysBindings)/2 + 1 //nolint:mnd maxRowIndex := len(utils.UIKeysBindings) / 2 //nolint:mnd
for i := range utils.UIKeysBindings { for i := range utils.UIKeysBindings {
if i >= maxRowIndex { if i >= maxRowIndex {
@ -82,7 +83,7 @@ func NewHelp(appName string, appVersion string) *Help {
// appinfo and appkeys layout // appinfo and appkeys layout
mlayout := tview.NewFlex().SetDirection(tview.FlexRow) mlayout := tview.NewFlex().SetDirection(tview.FlexRow)
mlayout.AddItem(appinfo, 2, 0, false) //nolint:mnd mlayout.AddItem(appinfo, 1, 0, false)
mlayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, false) mlayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, false)
mlayout.AddItem(keyinfo, 0, 1, false) mlayout.AddItem(keyinfo, 0, 1, false)
mlayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, false) mlayout.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, false)
@ -116,12 +117,12 @@ func (help *Help) Focus(delegate func(p tview.Primitive)) {
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
func (help *Help) Draw(screen tcell.Screen) { func (help *Help) Draw(screen tcell.Screen) {
x, y, width, height := help.Box.GetInnerRect() x, y, width, height := help.GetInnerRect()
if height <= 3 { //nolint:mnd if height <= 3 { //nolint:mnd
return return
} }
help.Box.DrawForSubclass(screen, help) help.DrawForSubclass(screen, help)
help.layout.SetRect(x, y, width, height) help.layout.SetRect(x, y, width, height)
help.layout.Draw(screen) help.layout.Draw(screen)
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/containers/podman-tui/pdcs/images" "github.com/containers/podman-tui/pdcs/images"
"github.com/containers/podman-tui/ui/dialogs" "github.com/containers/podman-tui/ui/dialogs"
"github.com/containers/podman-tui/ui/style" "github.com/containers/podman-tui/ui/style"
"github.com/containers/podman-tui/ui/utils"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -22,7 +23,7 @@ func (img *Images) runCommand(cmd string) { //nolint:cyclop
img.importDialog.Display() img.importDialog.Display()
case "inspect": case "inspect":
img.inspect() img.inspect()
case "prune": //nolint:goconst case utils.PruneCommandLabel:
img.cprune() img.cprune()
case "push": case "push":
img.cpush() img.cpush()
@ -139,6 +140,8 @@ func (img *Images) history() {
if err != nil { if err != nil {
title := fmt.Sprintf("IMAGE (%s) HISTORY ERROR", img.selectedID) title := fmt.Sprintf("IMAGE (%s) HISTORY ERROR", img.selectedID)
img.displayError(title, err) img.displayError(title, err)
return
} }
img.historyDialog.SetImageInfo(img.selectedID, img.selectedName) img.historyDialog.SetImageInfo(img.selectedID, img.selectedName)
@ -220,10 +223,9 @@ func (img *Images) prune() {
if err != nil { if err != nil {
img.displayError("IMAGE PRUNE ERROR", err) img.displayError("IMAGE PRUNE ERROR", err)
img.appFocusHandler()
return
} }
img.appFocusHandler()
} }
go prune() go prune()
@ -248,17 +250,17 @@ func (img *Images) push() {
img.progressDialog.Display() img.progressDialog.Display()
push := func() { push := func() {
if err := images.Push(img.selectedID, pushOptions); err != nil { err := images.Push(img.selectedID, pushOptions)
img.progressDialog.Hide() img.progressDialog.Hide()
if err != nil {
title := fmt.Sprintf("IMAGE (%s) PUSH ERROR", img.selectedID) title := fmt.Sprintf("IMAGE (%s) PUSH ERROR", img.selectedID)
img.displayError(title, err) img.displayError(title, err)
img.appFocusHandler()
return
} }
img.progressDialog.Hide() img.appFocusHandler()
} }
go push() go push()
@ -345,10 +347,9 @@ func (img *Images) save() {
title := fmt.Sprintf("IMAGE (%s) SAVE ERROR", img.selectedID) title := fmt.Sprintf("IMAGE (%s) SAVE ERROR", img.selectedID)
img.displayError(title, err) img.displayError(title, err)
img.appFocusHandler()
return
} }
img.appFocusHandler()
} }
go saveFunc() go saveFunc()
@ -361,15 +362,17 @@ func (img *Images) search(term string) {
search := func(term string) { search := func(term string) {
result, err := images.Search(term) result, err := images.Search(term)
img.progressDialog.Hide()
if err != nil { if err != nil {
title := fmt.Sprintf("IMAGE (%s) SEARCH ERROR", img.selectedID) title := fmt.Sprintf("IMAGE (%s) SEARCH ERROR", img.selectedID)
img.displayError(title, err) img.displayError(title, err)
img.appFocusHandler()
} }
img.searchDialog.UpdateResults(result) img.searchDialog.UpdateResults(result)
img.progressDialog.Hide() img.appFocusHandler()
} }
go search(term) go search(term)
@ -402,7 +405,8 @@ func (img *Images) ctag() {
} }
func (img *Images) tag(tag string) { func (img *Images) tag(tag string) {
if err := images.Tag(img.selectedID, tag); err != nil { err := images.Tag(img.selectedID, tag)
if err != nil {
title := fmt.Sprintf("IMAGE (%s) TAG ERROR", img.selectedID) title := fmt.Sprintf("IMAGE (%s) TAG ERROR", img.selectedID)
img.displayError(title, err) img.displayError(title, err)
} }
@ -463,7 +467,8 @@ func (img *Images) tree() {
} }
func (img *Images) untag(id string) { func (img *Images) untag(id string) {
if err := images.Untag(id); err != nil { err := images.Untag(id)
if err != nil {
title := fmt.Sprintf("IMAGE (%s) UNTAG ERROR", img.selectedID) title := fmt.Sprintf("IMAGE (%s) UNTAG ERROR", img.selectedID)
img.displayError(title, err) img.displayError(title, err)
@ -477,14 +482,15 @@ func (img *Images) pull(image string) {
pull := func(name string) { pull := func(name string) {
err := images.Pull(name) err := images.Pull(name)
img.progressDialog.Hide()
if err != nil { if err != nil {
title := fmt.Sprintf("IMAGE (%s) PULL ERROR", img.selectedID) title := fmt.Sprintf("IMAGE (%s) PULL ERROR", img.selectedID)
img.displayError(title, err) img.displayError(title, err)
img.appFocusHandler() img.appFocusHandler()
} }
img.progressDialog.Hide()
} }
go pull(image) go pull(image)

View File

@ -2,7 +2,9 @@ package images
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"time"
"github.com/containers/podman-tui/pdcs/images" "github.com/containers/podman-tui/pdcs/images"
"github.com/containers/podman-tui/ui/style" "github.com/containers/podman-tui/ui/style"
@ -10,6 +12,19 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
// SortView sorts data view called from sort dialog.
func (img *Images) SortView(option string, ascending bool) {
log.Debug().Msgf("view: images sort by %s", option)
img.imagesList.mu.Lock()
defer img.imagesList.mu.Unlock()
img.imagesList.sortBy = option
img.imagesList.ascending = ascending
sort.Sort(imgListSorted{img.imagesList.report, option, ascending})
}
// UpdateData retrieves images list data. // UpdateData retrieves images list data.
func (img *Images) UpdateData() { func (img *Images) UpdateData() {
images, err := images.List() images, err := images.List()
@ -24,6 +39,8 @@ func (img *Images) UpdateData() {
img.imagesList.mu.Lock() img.imagesList.mu.Lock()
defer img.imagesList.mu.Unlock() defer img.imagesList.mu.Unlock()
sort.Sort(imgListSorted{images, img.imagesList.sortBy, img.imagesList.ascending})
img.imagesList.report = images img.imagesList.report = images
} }
@ -61,3 +78,41 @@ func (img *Images) ClearData() {
img.table.SetTitle(fmt.Sprintf("[::b]%s[0]", strings.ToUpper(img.title))) img.table.SetTitle(fmt.Sprintf("[::b]%s[0]", strings.ToUpper(img.title)))
} }
type lprSort []images.ImageListReporter
func (a lprSort) Len() int { return len(a) }
func (a lprSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type imgListSorted struct {
lprSort
option string
ascending bool
}
func (a imgListSorted) Less(i, j int) bool {
switch a.option {
case "size":
if a.ascending {
return a.lprSort[i].Size < a.lprSort[j].Size
}
return a.lprSort[i].Size > a.lprSort[j].Size
case "created":
icreated := time.Unix(a.lprSort[i].Created, 0).UTC()
jcreated := time.Unix(a.lprSort[j].Created, 0).UTC()
if a.ascending {
return icreated.After(jcreated)
}
return icreated.Before(jcreated)
}
if a.ascending {
return a.lprSort[i].Repository < a.lprSort[j].Repository
}
return a.lprSort[i].Repository > a.lprSort[j].Repository
}

View File

@ -5,9 +5,9 @@ import (
) )
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
func (img *Images) Draw(screen tcell.Screen) { //nolint:cyclop func (img *Images) Draw(screen tcell.Screen) {
img.Box.DrawForSubclass(screen, img) img.DrawForSubclass(screen, img)
img.Box.SetBorder(false) img.SetBorder(false)
imagewViewX, imagewViewY, imagewViewW, imagewViewH := img.GetInnerRect() imagewViewX, imagewViewY, imagewViewW, imagewViewH := img.GetInnerRect()
@ -18,108 +18,21 @@ func (img *Images) Draw(screen tcell.Screen) { //nolint:cyclop
img.table.Draw(screen) img.table.Draw(screen)
x, y, width, height := img.table.GetInnerRect() x, y, width, height := img.table.GetInnerRect()
// error dialog for _, dialog := range img.getInnerDialogs() {
if img.errorDialog.IsDisplay() { if dialog.IsDisplay() {
img.errorDialog.SetRect(x, y, width, height) dialog.SetRect(x, y, width, height)
img.errorDialog.Draw(screen) dialog.Draw(screen)
return break
} }
}
// command dialog
if img.cmdDialog.IsDisplay() { for _, dialog := range img.getInnerTopDialogs() {
img.cmdDialog.SetRect(x, y, width, height) if dialog.IsDisplay() {
img.cmdDialog.Draw(screen) dialog.SetRect(x, y, width, height)
dialog.Draw(screen)
return
} break
}
// 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() {
if img.messageDialog.IsDisplayFullSize() {
img.messageDialog.SetRect(imagewViewX, imagewViewY, imagewViewW, imagewViewH)
} else {
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
}
// search dialog
if img.searchDialog.IsDisplay() {
img.searchDialog.SetRect(imagewViewX, imagewViewY, imagewViewW, imagewViewH)
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(imagewViewX, imagewViewY, imagewViewW, imagewViewH)
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
} }
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/containers/podman-tui/ui/dialogs" "github.com/containers/podman-tui/ui/dialogs"
"github.com/containers/podman-tui/ui/images/imgdialogs" "github.com/containers/podman-tui/ui/images/imgdialogs"
"github.com/containers/podman-tui/ui/style" "github.com/containers/podman-tui/ui/style"
"github.com/containers/podman-tui/ui/utils"
"github.com/rivo/tview" "github.com/rivo/tview"
) )
@ -37,20 +38,22 @@ var (
// Images implements the images primitive. // Images implements the images primitive.
type Images struct { type Images struct {
*tview.Box *tview.Box
title string title string
headers []string headers []string
table *tview.Table table *tview.Table
errorDialog *dialogs.ErrorDialog errorDialog *dialogs.ErrorDialog
progressDialog *dialogs.ProgressDialog
cmdDialog *dialogs.CommandDialog cmdDialog *dialogs.CommandDialog
cmdInputDialog *dialogs.SimpleInputDialog cmdInputDialog *dialogs.SimpleInputDialog
messageDialog *dialogs.MessageDialog messageDialog *dialogs.MessageDialog
confirmDialog *dialogs.ConfirmDialog confirmDialog *dialogs.ConfirmDialog
sortDialog *dialogs.SortDialog
searchDialog *imgdialogs.ImageSearchDialog searchDialog *imgdialogs.ImageSearchDialog
historyDialog *imgdialogs.ImageHistoryDialog historyDialog *imgdialogs.ImageHistoryDialog
importDialog *imgdialogs.ImageImportDialog importDialog *imgdialogs.ImageImportDialog
buildDialog *imgdialogs.ImageBuildDialog buildDialog *imgdialogs.ImageBuildDialog
buildPrgDialog *imgdialogs.ImageBuildProgressDialog buildPrgDialog *imgdialogs.ImageBuildProgressDialog
progressDialog *dialogs.ProgressDialog
saveDialog *imgdialogs.ImageSaveDialog saveDialog *imgdialogs.ImageSaveDialog
pushDialog *imgdialogs.ImagePushDialog pushDialog *imgdialogs.ImagePushDialog
imagesList imageListReport imagesList imageListReport
@ -64,6 +67,8 @@ type Images struct {
type imageListReport struct { type imageListReport struct {
mu sync.Mutex mu sync.Mutex
report []images.ImageListReporter report []images.ImageListReporter
sortBy string
ascending bool
} }
// NewImages returns images page view. // NewImages returns images page view.
@ -73,9 +78,11 @@ func NewImages() *Images {
title: "images", title: "images",
headers: []string{"repository", "tag", "image id", "created at", "size"}, headers: []string{"repository", "tag", "image id", "created at", "size"},
errorDialog: dialogs.NewErrorDialog(), errorDialog: dialogs.NewErrorDialog(),
progressDialog: dialogs.NewProgressDialog(),
cmdInputDialog: dialogs.NewSimpleInputDialog(""), cmdInputDialog: dialogs.NewSimpleInputDialog(""),
messageDialog: dialogs.NewMessageDialog(""), messageDialog: dialogs.NewMessageDialog(""),
confirmDialog: dialogs.NewConfirmDialog(), confirmDialog: dialogs.NewConfirmDialog(),
sortDialog: dialogs.NewSortDialog([]string{"repository", "created", "size"}, 1),
searchDialog: imgdialogs.NewImageSearchDialog(), searchDialog: imgdialogs.NewImageSearchDialog(),
historyDialog: imgdialogs.NewImageHistoryDialog(), historyDialog: imgdialogs.NewImageHistoryDialog(),
importDialog: imgdialogs.NewImageImportDialog(), importDialog: imgdialogs.NewImageImportDialog(),
@ -83,7 +90,7 @@ func NewImages() *Images {
buildPrgDialog: imgdialogs.NewImageBuildProgressDialog(), buildPrgDialog: imgdialogs.NewImageBuildProgressDialog(),
saveDialog: imgdialogs.NewImageSaveDialog(), saveDialog: imgdialogs.NewImageSaveDialog(),
pushDialog: imgdialogs.NewImagePushDialog(), pushDialog: imgdialogs.NewImagePushDialog(),
progressDialog: dialogs.NewProgressDialog(), imagesList: imageListReport{sortBy: "created", ascending: true},
} }
images.cmdDialog = dialogs.NewCommandDialog([][]string{ images.cmdDialog = dialogs.NewCommandDialog([][]string{
@ -151,7 +158,7 @@ func NewImages() *Images {
images.confirmDialog.Hide() images.confirmDialog.Hide()
switch images.confirmData { switch images.confirmData {
case "prune": case utils.PruneCommandLabel:
images.prune() images.prune()
case "rm": case "rm":
images.remove() images.remove()
@ -205,6 +212,10 @@ func NewImages() *Images {
images.pushDialog.SetPushFunc(images.push) images.pushDialog.SetPushFunc(images.push)
images.pushDialog.SetCancelFunc(images.pushDialog.Hide) images.pushDialog.SetCancelFunc(images.pushDialog.Hide)
// set sort dialog functions
images.sortDialog.SetSelectFunc(images.SortView)
images.sortDialog.SetCancelFunc(images.sortDialog.Hide)
return images return images
} }
@ -219,155 +230,89 @@ func (img *Images) GetTitle() string {
} }
// HasFocus returns whether or not this primitive has focus. // HasFocus returns whether or not this primitive has focus.
func (img *Images) HasFocus() bool { //nolint:cyclop func (img *Images) HasFocus() bool {
if img.table.HasFocus() || img.messageDialog.HasFocus() { if img.SubDialogHasFocus() {
return true return true
} }
if img.cmdDialog.HasFocus() || img.cmdInputDialog.HasFocus() { if img.table.HasFocus() || img.Box.HasFocus() {
return true return true
} }
if img.confirmDialog.HasFocus() || img.errorDialog.HasFocus() { return false
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. // SubDialogHasFocus returns whether or not sub dialog primitive has focus.
func (img *Images) SubDialogHasFocus() bool { //nolint:cyclop func (img *Images) SubDialogHasFocus() bool {
if img.historyDialog.HasFocus() || img.messageDialog.HasFocus() { for _, dialog := range img.getInnerDialogs() {
if dialog.HasFocus() {
return true return true
} }
if img.cmdDialog.HasFocus() || img.cmdInputDialog.HasFocus() {
return true
} }
if img.confirmDialog.HasFocus() || img.errorDialog.HasFocus() { for _, dialog := range img.getInnerTopDialogs() {
if dialog.HasFocus() {
return true return true
} }
if img.searchDialog.HasFocus() || img.progressDialog.HasFocus() {
return true
} }
if img.buildDialog.HasFocus() || img.buildPrgDialog.HasFocus() { return false
return true
}
if img.saveDialog.HasFocus() || img.importDialog.HasFocus() {
return true
}
return img.pushDialog.HasFocus()
} }
// Focus is called when this primitive receives focus. // Focus is called when this primitive receives focus.
func (img *Images) Focus(delegate func(p tview.Primitive)) { //nolint:cyclop func (img *Images) Focus(delegate func(p tview.Primitive)) {
// error dialog // since error and confirm dialog can get focus on top of other dialogs
if img.errorDialog.IsDisplay() { if img.errorDialog.IsDisplay() {
delegate(img.errorDialog) delegate(img.errorDialog)
return 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() { if img.confirmDialog.IsDisplay() {
delegate(img.confirmDialog) delegate(img.confirmDialog)
return return
} }
// search dialog for _, dialog := range img.getInnerDialogs() {
if img.searchDialog.IsDisplay() { if dialog.IsDisplay() {
delegate(img.searchDialog) delegate(dialog)
return return
} }
}
// history dialog for _, dialog := range img.getInnerTopDialogs() {
if img.historyDialog.IsDisplay() { if dialog.IsDisplay() {
delegate(img.historyDialog) delegate(dialog)
return 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) delegate(img.table)
} }
// HideAllDialogs hides all sub dialogs.
func (img *Images) HideAllDialogs() {
for _, dialog := range img.getInnerDialogs() {
if dialog.IsDisplay() {
dialog.Hide()
}
}
for _, dialog := range img.getInnerTopDialogs() {
if dialog.IsDisplay() {
dialog.Hide()
}
}
}
// SetFastRefreshChannel sets channel for fastRefresh func.
func (img *Images) SetFastRefreshChannel(refresh chan bool) {
img.fastRefreshChan = refresh
}
func (img *Images) getSelectedItem() (string, string) { func (img *Images) getSelectedItem() (string, string) {
if img.table.GetRowCount() <= 1 { if img.table.GetRowCount() <= 1 {
return "", "" return "", ""
@ -382,62 +327,30 @@ func (img *Images) getSelectedItem() (string, string) {
return imageID, imageName return imageID, imageName
} }
// HideAllDialogs hides all sub dialogs. func (img *Images) getInnerDialogs() []utils.UIDialog {
func (img *Images) HideAllDialogs() { //nolint:cyclop dialogs := []utils.UIDialog{
if img.errorDialog.IsDisplay() { img.cmdDialog,
img.errorDialog.Hide() img.cmdInputDialog,
img.messageDialog,
img.searchDialog,
img.historyDialog,
img.importDialog,
img.buildDialog,
img.buildPrgDialog,
img.saveDialog,
img.pushDialog,
img.sortDialog,
} }
if img.progressDialog.IsDisplay() { return dialogs
img.progressDialog.Hide()
} }
if img.cmdDialog.IsDisplay() { func (img *Images) getInnerTopDialogs() []utils.UIDialog {
img.cmdDialog.Hide() dialogs := []utils.UIDialog{
img.errorDialog,
img.progressDialog,
img.confirmDialog,
} }
if img.cmdInputDialog.IsDisplay() { return dialogs
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.
func (img *Images) SetFastRefreshChannel(refresh chan bool) {
img.fastRefreshChan = refresh
} }

View File

@ -71,6 +71,7 @@ const (
// ImageBuildDialog represents image build dialog primitive. // ImageBuildDialog represents image build dialog primitive.
type ImageBuildDialog struct { type ImageBuildDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
form *tview.Form form *tview.Form
categoryLabels []string categoryLabels []string
@ -490,117 +491,6 @@ func NewImageBuildDialog() *ImageBuildDialog { //nolint:maintidx
return buildDialog return buildDialog
} }
func (d *ImageBuildDialog) setupLayout() {
bgColor := style.DialogBgColor
// basic info page
d.basicInfoPage.SetDirection(tview.FlexRow)
d.basicInfoPage.AddItem(d.contextDirectoryPath, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.containerFilePath, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.pullPolicyField, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.tagField, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.registryField, 1, 0, true)
d.basicInfoPage.SetBackgroundColor(bgColor)
// layers setup page
secondRowLayout := tview.NewFlex().SetDirection(tview.FlexColumn)
secondRowLayout.SetBackgroundColor(bgColor)
secondRowLayout.AddItem(d.formatField, 0, 2, true) //nolint:mnd
secondRowLayout.AddItem(d.SquashField, 0, 1, true)
secondRowLayout.AddItem(d.layersField, 0, 1, true)
secondRowLayout.AddItem(d.noCacheField, 0, 1, true)
cntRmRowLayout := tview.NewFlex().SetDirection(tview.FlexColumn)
cntRmRowLayout.SetBackgroundColor(bgColor)
cntRmRowLayout.AddItem(d.forceRemoveCntField, 0, 1, true)
cntRmRowLayout.AddItem(d.removeCntField, 0, 2, true) //nolint:mnd
// build setup page
d.buildInfoPage.SetDirection(tview.FlexRow)
d.buildInfoPage.AddItem(d.buildArgsField, 1, 0, true)
d.buildInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.buildInfoPage.AddItem(secondRowLayout, 1, 0, true)
d.buildInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.buildInfoPage.AddItem(d.labelsField, 1, 0, true)
d.buildInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.buildInfoPage.AddItem(d.annotationsField, 1, 0, true)
d.buildInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.buildInfoPage.AddItem(cntRmRowLayout, 1, 0, true)
d.buildInfoPage.SetBackgroundColor(bgColor)
// security options page
d.securityOptsPage.SetDirection(tview.FlexRow)
d.securityOptsPage.AddItem(d.selinuxLabelField, 1, 0, true)
d.securityOptsPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.securityOptsPage.AddItem(d.apparmorProfileField, 1, 0, true)
d.securityOptsPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.securityOptsPage.AddItem(d.seccompProfilePathField, 1, 0, true)
// networking setup page
netFirstRow := tview.NewFlex().SetDirection(tview.FlexColumn)
netFirstRow.SetBackgroundColor(bgColor)
netFirstRow.AddItem(d.networkField, 0, 1, true)
netFirstRow.AddItem(d.httpProxyField, 0, 1, true)
d.networkingPage.SetDirection(tview.FlexRow)
d.networkingPage.AddItem(netFirstRow, 1, 0, true)
d.networkingPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.networkingPage.AddItem(d.addHostField, 1, 0, true)
d.networkingPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.networkingPage.AddItem(d.dnsServersField, 1, 0, true)
d.networkingPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.networkingPage.AddItem(d.dnsOptionsField, 1, 0, true)
d.networkingPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.networkingPage.AddItem(d.dnsSearchField, 1, 0, true)
d.networkingPage.SetBackgroundColor(bgColor)
// capability page
d.capabilityPage.SetDirection(tview.FlexRow)
d.capabilityPage.AddItem(d.addCapabilityField, 1, 0, true)
d.capabilityPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.capabilityPage.AddItem(d.removeCapabilityField, 1, 0, true)
// cpu and memory page
cpuSetRow := tview.NewFlex().SetDirection(tview.FlexColumn)
cpuSetRow.AddItem(d.cpuSetCpusField, 0, 1, true)
cpuSetRow.AddItem(d.cpuSetMemsField, 0, 1, true)
// memory and swap
memSwapRow := tview.NewFlex().SetDirection(tview.FlexColumn)
memSwapRow.AddItem(d.memoryField, 0, 1, true)
memSwapRow.AddItem(d.memorySwapField, 0, 1, true)
d.cpuMemoryPage.SetDirection(tview.FlexRow)
d.cpuMemoryPage.AddItem(d.cpuPeriodField, 0, 1, true)
d.cpuMemoryPage.AddItem(utils.EmptyBoxSpace(bgColor), 0, 1, true)
d.cpuMemoryPage.AddItem(d.cpuQuataField, 0, 1, true)
d.cpuMemoryPage.AddItem(utils.EmptyBoxSpace(bgColor), 0, 1, true)
d.cpuMemoryPage.AddItem(d.cpuSharesField, 0, 1, true)
d.cpuMemoryPage.AddItem(utils.EmptyBoxSpace(bgColor), 0, 1, true)
d.cpuMemoryPage.AddItem(cpuSetRow, 0, 1, true)
d.cpuMemoryPage.AddItem(utils.EmptyBoxSpace(bgColor), 0, 1, true)
d.cpuMemoryPage.AddItem(memSwapRow, 0, 1, true)
// adding category pages
d.categoryPages.AddPage(d.categoryLabels[buildDialogBasicInfoPageIndex], d.basicInfoPage, true, true)
d.categoryPages.AddPage(d.categoryLabels[buildDialogBuildInfoPageIndex], d.buildInfoPage, true, true)
d.categoryPages.AddPage(d.categoryLabels[buildDialogCapabilityPageIndex], d.capabilityPage, true, true)
d.categoryPages.AddPage(d.categoryLabels[buildDialogCPUMemoryPageIndex], d.cpuMemoryPage, true, true)
d.categoryPages.AddPage(d.categoryLabels[buildDialogNetworkingPageIndex], d.networkingPage, true, true)
d.categoryPages.AddPage(d.categoryLabels[buildDialogSecurityOptsPageIndex], d.securityOptsPage, true, true)
// add it to layout.
_, layoutWidth := utils.AlignStringListWidth(d.categoryLabels)
layout := tview.NewFlex().SetDirection(tview.FlexColumn)
layout.AddItem(d.categories, layoutWidth+6, 0, true) //nolint:mnd
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() { func (d *ImageBuildDialog) Display() {
d.focusElement = buildDialogContextDirectoryPathFieldFocus d.focusElement = buildDialogContextDirectoryPathFieldFocus
@ -755,7 +645,7 @@ func (d *ImageBuildDialog) InputHandler() func(event *tcell.EventKey, setFocus f
log.Debug().Msgf("image build dialog: event %v received", event) log.Debug().Msgf("image build dialog: event %v received", event)
if event.Key() == utils.CloseDialogKey.Key { if event.Key() == utils.CloseDialogKey.Key {
if !(d.pullPolicyField.HasFocus() || d.networkField.HasFocus() || d.formatField.HasFocus()) { if !d.pullPolicyField.HasFocus() && !d.networkField.HasFocus() && !d.formatField.HasFocus() {
d.cancelHandler() d.cancelHandler()
return return
@ -921,8 +811,8 @@ func (d *ImageBuildDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
} }
@ -945,6 +835,352 @@ func (d *ImageBuildDialog) SetBuildFunc(handler func()) *ImageBuildDialog {
return d return d
} }
// ImageBuildOptions returns image build options.
func (d *ImageBuildDialog) ImageBuildOptions() (images.ImageBuildOptions, error) { //nolint:gocognit,gocyclo,cyclop,maintidx,lll
var (
memoryLimit int64
memorySwap int64
cpuPeriod uint64
cpuQuota int64
cpuShares uint64
cpuSetCpus string
cpuSetMems string
containerFiles []string
dnsServers []string
dnsOptions []string
dnsSearchDomains []string
addHost []string
labelOpts []string
apparmorProfile string
seccompProfilePath string
)
// basic info page
// Containerfiles
for _, cntFile := range strings.Split(strings.TrimSpace(d.containerFilePath.GetText()), " ") {
if cntFile != "" {
cFile, err := utils.ResolveHomeDir(cntFile)
if err != nil {
return images.ImageBuildOptions{}, err
}
containerFiles = append(containerFiles, cFile)
}
}
opts := images.ImageBuildOptions{
ContainerFiles: containerFiles,
BuildOptions: entities.BuildOptions{},
}
dir, err := utils.ResolveHomeDir(strings.TrimSpace(d.contextDirectoryPath.GetText()))
if err != nil {
return images.ImageBuildOptions{}, fmt.Errorf("cannot resolve home directory %w", err)
}
opts.BuildOptions.ContextDirectory = dir
opts.BuildOptions.AdditionalTags = append(opts.BuildOptions.AdditionalTags, strings.TrimSpace(d.tagField.GetText()))
opts.BuildOptions.Registry = strings.TrimSpace(d.registryField.GetText())
_, pullOption := d.pullPolicyField.GetCurrentOption()
switch pullOption {
case "missing":
opts.BuildOptions.PullPolicy = define.PullIfMissing
case "always":
opts.BuildOptions.PullPolicy = define.PullAlways
case "ifnewer":
opts.BuildOptions.PullPolicy = define.PullIfNewer
case "never":
opts.BuildOptions.PullPolicy = define.PullNever
}
_, format := d.formatField.GetCurrentOption()
switch format {
case "oci":
opts.BuildOptions.OutputFormat = define.OCIv1ImageManifest
case "docker":
opts.BuildOptions.OutputFormat = define.Dockerv2ImageManifest
}
// build settings
opts.BuildOptions.Squash = d.SquashField.IsChecked()
opts.BuildOptions.Layers = d.layersField.IsChecked()
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, " ")
}
// capability pages
addCap := strings.TrimSpace(d.addCapabilityField.GetText())
if addCap != "" {
opts.BuildOptions.AddCapabilities = strings.Split(addCap, " ")
}
removeCap := strings.TrimSpace(d.removeCapabilityField.GetText())
if removeCap != "" {
opts.BuildOptions.DropCapabilities = strings.Split(removeCap, " ")
}
// 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 %w", cpuPeriodVal, err)
}
cpuPeriod = uint64(period) //nolint:gosec
}
cpuQuotaVal := d.cpuQuataField.GetText()
if cpuQuotaVal != "" {
quota, err := strconv.Atoi(cpuQuotaVal)
if err != nil {
return images.ImageBuildOptions{}, fmt.Errorf("invalid CPU quota value %q %w", cpuQuotaVal, err)
}
cpuQuota = int64(quota)
}
cpuSharesVal := d.cpuSharesField.GetText()
if cpuSharesVal != "" {
shares, err := strconv.Atoi(cpuSharesVal)
if err != nil {
return images.ImageBuildOptions{}, fmt.Errorf("invalid CPU quota value %q %w", cpuSharesVal, err)
}
cpuShares = uint64(shares) //nolint:gosec
}
cpuSetCpusVal := d.cpuSetCpusField.GetText()
if cpuSetCpusVal != "" {
cpuSetCpus = cpuSetCpusVal
}
cpuSetMemsVal := d.cpuSetMemsField.GetText()
if cpuSetMemsVal != "" {
cpuSetMems = cpuSetMemsVal
}
memoryVal := d.memoryField.GetText()
if memoryVal != "" {
memory, err := strconv.Atoi(memoryVal)
if err != nil {
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 %w", memorySwapVal, err)
}
memorySwap = int64(swap)
}
// networking page
// network policy
_, configureNetwork := d.networkField.GetCurrentOption()
switch configureNetwork {
case "NetworkDefault":
opts.BuildOptions.ConfigureNetwork = define.NetworkDefault
case "NetworkDisabled":
opts.BuildOptions.ConfigureNetwork = define.NetworkDisabled
case "NetworkEnabled":
opts.BuildOptions.ConfigureNetwork = define.NetworkEnabled
}
// add hosts
hosts := strings.TrimSpace(d.addHostField.GetText())
if hosts != "" {
addHost = strings.Split(hosts, " ")
}
// dns page
dnsServersList := strings.TrimSpace(d.dnsServersField.GetText())
for _, dns := range strings.Split(dnsServersList, " ") {
if dns != "" {
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)
}
}
// security options page
for _, selinuxLabel := range strings.Split(d.selinuxLabelField.GetText(), " ") {
if selinuxLabel != "" {
labelOpts = append(labelOpts, selinuxLabel)
}
}
apparmor := strings.TrimSpace(d.apparmorProfileField.GetText())
if apparmor != "" {
apparmorProfile = apparmor
}
seccomp := strings.TrimSpace(d.seccompProfilePathField.GetText())
if seccomp != "" {
seccompProfilePath = seccomp
}
commonOpts := &define.CommonBuildOptions{
AddHost: addHost,
HTTPProxy: d.httpProxyField.IsChecked(),
CPUPeriod: cpuPeriod,
CPUQuota: cpuQuota,
CPUSetCPUs: cpuSetCpus,
CPUSetMems: cpuSetMems,
CPUShares: cpuShares,
DNSServers: dnsServers,
DNSOptions: dnsOptions,
DNSSearch: dnsSearchDomains,
Memory: memoryLimit,
MemorySwap: memorySwap,
LabelOpts: labelOpts,
ApparmorProfile: apparmorProfile,
SeccompProfilePath: seccompProfilePath,
}
opts.BuildOptions.CommonBuildOpts = commonOpts
return opts, nil
}
func (d *ImageBuildDialog) setupLayout() {
bgColor := style.DialogBgColor
// basic info page
d.basicInfoPage.SetDirection(tview.FlexRow)
d.basicInfoPage.AddItem(d.contextDirectoryPath, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.containerFilePath, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.pullPolicyField, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.tagField, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.registryField, 1, 0, true)
d.basicInfoPage.SetBackgroundColor(bgColor)
// layers setup page
secondRowLayout := tview.NewFlex().SetDirection(tview.FlexColumn)
secondRowLayout.SetBackgroundColor(bgColor)
secondRowLayout.AddItem(d.formatField, 0, 2, true) //nolint:mnd
secondRowLayout.AddItem(d.SquashField, 0, 1, true)
secondRowLayout.AddItem(d.layersField, 0, 1, true)
secondRowLayout.AddItem(d.noCacheField, 0, 1, true)
cntRmRowLayout := tview.NewFlex().SetDirection(tview.FlexColumn)
cntRmRowLayout.SetBackgroundColor(bgColor)
cntRmRowLayout.AddItem(d.forceRemoveCntField, 0, 1, true)
cntRmRowLayout.AddItem(d.removeCntField, 0, 2, true) //nolint:mnd
// build setup page
d.buildInfoPage.SetDirection(tview.FlexRow)
d.buildInfoPage.AddItem(d.buildArgsField, 1, 0, true)
d.buildInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.buildInfoPage.AddItem(secondRowLayout, 1, 0, true)
d.buildInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.buildInfoPage.AddItem(d.labelsField, 1, 0, true)
d.buildInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.buildInfoPage.AddItem(d.annotationsField, 1, 0, true)
d.buildInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.buildInfoPage.AddItem(cntRmRowLayout, 1, 0, true)
d.buildInfoPage.SetBackgroundColor(bgColor)
// security options page
d.securityOptsPage.SetDirection(tview.FlexRow)
d.securityOptsPage.AddItem(d.selinuxLabelField, 1, 0, true)
d.securityOptsPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.securityOptsPage.AddItem(d.apparmorProfileField, 1, 0, true)
d.securityOptsPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.securityOptsPage.AddItem(d.seccompProfilePathField, 1, 0, true)
// networking setup page
netFirstRow := tview.NewFlex().SetDirection(tview.FlexColumn)
netFirstRow.SetBackgroundColor(bgColor)
netFirstRow.AddItem(d.networkField, 0, 1, true)
netFirstRow.AddItem(d.httpProxyField, 0, 1, true)
d.networkingPage.SetDirection(tview.FlexRow)
d.networkingPage.AddItem(netFirstRow, 1, 0, true)
d.networkingPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.networkingPage.AddItem(d.addHostField, 1, 0, true)
d.networkingPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.networkingPage.AddItem(d.dnsServersField, 1, 0, true)
d.networkingPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.networkingPage.AddItem(d.dnsOptionsField, 1, 0, true)
d.networkingPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.networkingPage.AddItem(d.dnsSearchField, 1, 0, true)
d.networkingPage.SetBackgroundColor(bgColor)
// capability page
d.capabilityPage.SetDirection(tview.FlexRow)
d.capabilityPage.AddItem(d.addCapabilityField, 1, 0, true)
d.capabilityPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.capabilityPage.AddItem(d.removeCapabilityField, 1, 0, true)
// cpu and memory page
cpuSetRow := tview.NewFlex().SetDirection(tview.FlexColumn)
cpuSetRow.AddItem(d.cpuSetCpusField, 0, 1, true)
cpuSetRow.AddItem(d.cpuSetMemsField, 0, 1, true)
// memory and swap
memSwapRow := tview.NewFlex().SetDirection(tview.FlexColumn)
memSwapRow.AddItem(d.memoryField, 0, 1, true)
memSwapRow.AddItem(d.memorySwapField, 0, 1, true)
d.cpuMemoryPage.SetDirection(tview.FlexRow)
d.cpuMemoryPage.AddItem(d.cpuPeriodField, 0, 1, true)
d.cpuMemoryPage.AddItem(utils.EmptyBoxSpace(bgColor), 0, 1, true)
d.cpuMemoryPage.AddItem(d.cpuQuataField, 0, 1, true)
d.cpuMemoryPage.AddItem(utils.EmptyBoxSpace(bgColor), 0, 1, true)
d.cpuMemoryPage.AddItem(d.cpuSharesField, 0, 1, true)
d.cpuMemoryPage.AddItem(utils.EmptyBoxSpace(bgColor), 0, 1, true)
d.cpuMemoryPage.AddItem(cpuSetRow, 0, 1, true)
d.cpuMemoryPage.AddItem(utils.EmptyBoxSpace(bgColor), 0, 1, true)
d.cpuMemoryPage.AddItem(memSwapRow, 0, 1, true)
// adding category pages
d.categoryPages.AddPage(d.categoryLabels[buildDialogBasicInfoPageIndex], d.basicInfoPage, true, true)
d.categoryPages.AddPage(d.categoryLabels[buildDialogBuildInfoPageIndex], d.buildInfoPage, true, true)
d.categoryPages.AddPage(d.categoryLabels[buildDialogCapabilityPageIndex], d.capabilityPage, true, true)
d.categoryPages.AddPage(d.categoryLabels[buildDialogCPUMemoryPageIndex], d.cpuMemoryPage, true, true)
d.categoryPages.AddPage(d.categoryLabels[buildDialogNetworkingPageIndex], d.networkingPage, true, true)
d.categoryPages.AddPage(d.categoryLabels[buildDialogSecurityOptsPageIndex], d.securityOptsPage, true, true)
// add it to layout.
_, layoutWidth := utils.AlignStringListWidth(d.categoryLabels)
layout := tview.NewFlex().SetDirection(tview.FlexColumn)
layout.AddItem(d.categories, layoutWidth+6, 0, true) //nolint:mnd
layout.AddItem(d.categoryPages, 0, 1, true)
layout.SetBackgroundColor(bgColor)
d.layout.AddItem(layout, 0, 1, true)
}
func (d *ImageBuildDialog) initData() { func (d *ImageBuildDialog) initData() {
d.setActiveCategory(0) d.setActiveCategory(0)
@ -1223,238 +1459,3 @@ func (d *ImageBuildDialog) setSecurityOptionsPageNextFocus() {
d.focusElement = buildDialogFormFocus 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
cpuPeriod uint64
cpuQuota int64
cpuShares uint64
cpuSetCpus string
cpuSetMems string
containerFiles []string
dnsServers []string
dnsOptions []string
dnsSearchDomains []string
addHost []string
labelOpts []string
apparmorProfile string
seccompProfilePath string
)
// basic info page
// Containerfiles
for _, cntFile := range strings.Split(strings.TrimSpace(d.containerFilePath.GetText()), " ") {
if cntFile != "" {
cFile, err := utils.ResolveHomeDir(cntFile)
if err != nil {
return images.ImageBuildOptions{}, err
}
containerFiles = append(containerFiles, cFile)
}
}
opts := images.ImageBuildOptions{
ContainerFiles: containerFiles,
BuildOptions: entities.BuildOptions{},
}
dir, err := utils.ResolveHomeDir(strings.TrimSpace(d.contextDirectoryPath.GetText()))
if err != nil {
return images.ImageBuildOptions{}, fmt.Errorf("cannot resolve home directory %w", err)
}
opts.BuildOptions.ContextDirectory = dir
opts.BuildOptions.AdditionalTags = append(opts.BuildOptions.AdditionalTags, strings.TrimSpace(d.tagField.GetText()))
opts.BuildOptions.Registry = strings.TrimSpace(d.registryField.GetText())
_, pullOption := d.pullPolicyField.GetCurrentOption()
switch pullOption {
case "missing":
opts.BuildOptions.PullPolicy = define.PullIfMissing
case "always":
opts.BuildOptions.PullPolicy = define.PullAlways
case "ifnewer":
opts.BuildOptions.PullPolicy = define.PullIfNewer
case "never":
opts.BuildOptions.PullPolicy = define.PullNever
}
_, format := d.formatField.GetCurrentOption()
switch format {
case "oci":
opts.BuildOptions.OutputFormat = define.OCIv1ImageManifest
case "docker":
opts.BuildOptions.OutputFormat = define.Dockerv2ImageManifest
}
// build settings
opts.BuildOptions.Squash = d.SquashField.IsChecked()
opts.BuildOptions.Layers = d.layersField.IsChecked()
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, " ")
}
// capability pages
addCap := strings.TrimSpace(d.addCapabilityField.GetText())
if addCap != "" {
opts.BuildOptions.AddCapabilities = strings.Split(addCap, " ")
}
removeCap := strings.TrimSpace(d.removeCapabilityField.GetText())
if removeCap != "" {
opts.BuildOptions.DropCapabilities = strings.Split(removeCap, " ")
}
// 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 %w", cpuPeriodVal, err)
}
cpuPeriod = uint64(period) //nolint:gosec
}
cpuQuotaVal := d.cpuQuataField.GetText()
if cpuQuotaVal != "" {
quota, err := strconv.Atoi(cpuQuotaVal)
if err != nil {
return images.ImageBuildOptions{}, fmt.Errorf("invalid CPU quota value %q %w", cpuQuotaVal, err)
}
cpuQuota = int64(quota)
}
cpuSharesVal := d.cpuSharesField.GetText()
if cpuSharesVal != "" {
shares, err := strconv.Atoi(cpuSharesVal)
if err != nil {
return images.ImageBuildOptions{}, fmt.Errorf("invalid CPU quota value %q %w", cpuSharesVal, err)
}
cpuShares = uint64(shares) //nolint:gosec
}
cpuSetCpusVal := d.cpuSetCpusField.GetText()
if cpuSetCpusVal != "" {
cpuSetCpus = cpuSetCpusVal
}
cpuSetMemsVal := d.cpuSetMemsField.GetText()
if cpuSetMemsVal != "" {
cpuSetMems = cpuSetMemsVal
}
memoryVal := d.memoryField.GetText()
if memoryVal != "" {
memory, err := strconv.Atoi(memoryVal)
if err != nil {
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 %w", memorySwapVal, err)
}
memorySwap = int64(swap)
}
// networking page
// network policy
_, configureNetwork := d.networkField.GetCurrentOption()
switch configureNetwork {
case "NetworkDefault":
opts.BuildOptions.ConfigureNetwork = define.NetworkDefault
case "NetworkDisabled":
opts.BuildOptions.ConfigureNetwork = define.NetworkDisabled
case "NetworkEnabled":
opts.BuildOptions.ConfigureNetwork = define.NetworkEnabled
}
// add hosts
hosts := strings.TrimSpace(d.addHostField.GetText())
if hosts != "" {
addHost = strings.Split(hosts, " ")
}
// dns page
dnsServersList := strings.TrimSpace(d.dnsServersField.GetText())
for _, dns := range strings.Split(dnsServersList, " ") {
if dns != "" {
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)
}
}
// security options page
for _, selinuxLabel := range strings.Split(d.selinuxLabelField.GetText(), " ") {
if selinuxLabel != "" {
labelOpts = append(labelOpts, selinuxLabel)
}
}
apparmor := strings.TrimSpace(d.apparmorProfileField.GetText())
if apparmor != "" {
apparmorProfile = apparmor
}
seccomp := strings.TrimSpace(d.seccompProfilePathField.GetText())
if seccomp != "" {
seccompProfilePath = seccomp
}
commonOpts := &define.CommonBuildOptions{
AddHost: addHost,
HTTPProxy: d.httpProxyField.IsChecked(),
CPUPeriod: cpuPeriod,
CPUQuota: cpuQuota,
CPUSetCPUs: cpuSetCpus,
CPUSetMems: cpuSetMems,
CPUShares: cpuShares,
DNSServers: dnsServers,
DNSOptions: dnsOptions,
DNSSearch: dnsSearchDomains,
Memory: memoryLimit,
MemorySwap: memorySwap,
LabelOpts: labelOpts,
ApparmorProfile: apparmorProfile,
SeccompProfilePath: seccompProfilePath,
}
opts.BuildOptions.CommonBuildOpts = commonOpts
return opts, nil
}

View File

@ -21,6 +21,7 @@ const (
// ImageBuildProgressDialog implements build progress dialog primitive. // ImageBuildProgressDialog implements build progress dialog primitive.
type ImageBuildProgressDialog struct { type ImageBuildProgressDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
output *tview.TextView output *tview.TextView
progressBar *tvxwidgets.ActivityModeGauge progressBar *tvxwidgets.ActivityModeGauge
@ -92,7 +93,9 @@ func (d *ImageBuildProgressDialog) IsDisplay() bool {
func (d *ImageBuildProgressDialog) Hide() { func (d *ImageBuildProgressDialog) Hide() {
d.display = false d.display = false
d.cancelChan <- true d.cancelChan <- true
close(d.writerChan) close(d.writerChan)
d.output.SetText("") d.output.SetText("")
d.progressBar.Reset() d.progressBar.Reset()
} }
@ -113,7 +116,7 @@ func (d *ImageBuildProgressDialog) Focus(delegate func(p tview.Primitive)) {
// 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)) { 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)) { //nolint:revive return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
log.Debug().Msgf("image build progress dialog: event %v received", event) log.Debug().Msgf("image build progress dialog: event %v received", event)
}) })
} }
@ -141,12 +144,23 @@ func (d *ImageBuildProgressDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
} }
// 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.
func (d *ImageBuildProgressDialog) SetFastRefreshHandler(handler func()) {
d.fastRefreshHandler = handler
}
func (d *ImageBuildProgressDialog) outputReaderLoop() { func (d *ImageBuildProgressDialog) outputReaderLoop() {
tick := time.NewTicker(utils.RefreshInterval) tick := time.NewTicker(utils.RefreshInterval)
@ -164,20 +178,14 @@ func (d *ImageBuildProgressDialog) outputReaderLoop() {
return return
case data := <-d.writerChan: case data := <-d.writerChan:
d.mu.Lock() d.mu.Lock()
d.output.Write(data) //nolint:errcheck
_, err := d.output.Write(data)
if err != nil {
log.Error().Msgf("failed to write data to output: %s", err.Error())
}
d.mu.Unlock() d.mu.Unlock()
d.fastRefreshHandler() d.fastRefreshHandler()
} }
} }
} }
// 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.
func (d *ImageBuildProgressDialog) SetFastRefreshHandler(handler func()) {
d.fastRefreshHandler = handler
}

View File

@ -27,6 +27,7 @@ const (
// ImageHistoryDialog represents image history dialog primitive. // ImageHistoryDialog represents image history dialog primitive.
type ImageHistoryDialog struct { type ImageHistoryDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
imageInfo *tview.InputField imageInfo *tview.InputField
table *tview.Table table *tview.Table
@ -170,8 +171,8 @@ func (d *ImageHistoryDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
} }
@ -185,25 +186,6 @@ func (d *ImageHistoryDialog) SetCancelFunc(handler func()) *ImageHistoryDialog {
return d return d
} }
func (d *ImageHistoryDialog) initTable() {
bgColor := style.TableHeaderBgColor
fgColor := style.TableHeaderFgColor
d.table.Clear()
d.table.SetFixed(1, 1)
d.table.SetSelectable(true, false)
for i := range d.tableHeaders {
d.table.SetCell(0, i,
tview.NewTableCell(fmt.Sprintf("[%s::b]%s", style.GetColorHex(fgColor), strings.ToUpper(d.tableHeaders[i]))).
SetExpansion(1).
SetBackgroundColor(bgColor).
SetTextColor(fgColor).
SetAlign(tview.AlignLeft).
SetSelectable(false))
}
}
// UpdateResults updates result table. // UpdateResults updates result table.
func (d *ImageHistoryDialog) UpdateResults(data [][]string) { func (d *ImageHistoryDialog) UpdateResults(data [][]string) {
d.results = data d.results = data
@ -311,3 +293,22 @@ func (d *ImageHistoryDialog) getCreatedByWidth() int {
return createdByWidth return createdByWidth
} }
func (d *ImageHistoryDialog) initTable() {
bgColor := style.TableHeaderBgColor
fgColor := style.TableHeaderFgColor
d.table.Clear()
d.table.SetFixed(1, 1)
d.table.SetSelectable(true, false)
for i := range d.tableHeaders {
d.table.SetCell(0, i,
tview.NewTableCell(fmt.Sprintf("[%s::b]%s", style.GetColorHex(fgColor), strings.ToUpper(d.tableHeaders[i]))).
SetExpansion(1).
SetBackgroundColor(bgColor).
SetTextColor(fgColor).
SetAlign(tview.AlignLeft).
SetSelectable(false))
}
}

View File

@ -32,6 +32,7 @@ const (
// ImageImportDialog represents image import dialog primitive. // ImageImportDialog represents image import dialog primitive.
type ImageImportDialog struct { type ImageImportDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
path *tview.InputField path *tview.InputField
change *tview.InputField change *tview.InputField
@ -271,25 +272,12 @@ func (d *ImageImportDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
} }
func (d *ImageImportDialog) setFocusElement() {
switch d.focusElement {
case imageImportPathFocus:
d.focusElement = imageImportChangeFocus
case imageImportChangeFocus:
d.focusElement = imageImportCommitMessageFocus
case imageImportCommitMessageFocus:
d.focusElement = imageImportReferenceFocus
case imageImportReferenceFocus:
d.focusElement = imageImportFormFocus
}
}
// SetImportFunc sets form import button selected function. // SetImportFunc sets form import button selected function.
func (d *ImageImportDialog) SetImportFunc(handler func()) *ImageImportDialog { func (d *ImageImportDialog) SetImportFunc(handler func()) *ImageImportDialog {
d.importHandler = handler d.importHandler = handler
@ -338,8 +326,8 @@ func (d *ImageImportDialog) ImageImportOptions() (images.ImageImportOptions, err
} }
errFileName := utils.ValidateFileName(path) errFileName := utils.ValidateFileName(path)
errURL := utils.ValidURL(path)
errURL := utils.ValidURL(path)
if errURL == nil { if errURL == nil {
opts.URL = true opts.URL = true
} }
@ -352,3 +340,16 @@ func (d *ImageImportDialog) ImageImportOptions() (images.ImageImportOptions, err
return opts, nil return opts, nil
} }
func (d *ImageImportDialog) setFocusElement() {
switch d.focusElement {
case imageImportPathFocus:
d.focusElement = imageImportChangeFocus
case imageImportChangeFocus:
d.focusElement = imageImportCommitMessageFocus
case imageImportCommitMessageFocus:
d.focusElement = imageImportReferenceFocus
case imageImportReferenceFocus:
d.focusElement = imageImportFormFocus
}
}

View File

@ -32,6 +32,7 @@ const (
// ImagePushDialog represents image push dialog primitive. // ImagePushDialog represents image push dialog primitive.
type ImagePushDialog struct { type ImagePushDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
imageInfo *tview.InputField imageInfo *tview.InputField
destination *tview.InputField destination *tview.InputField
@ -245,12 +246,6 @@ func (d *ImagePushDialog) HasFocus() bool { //nolint:cyclop
return d.Box.HasFocus() return d.Box.HasFocus()
} }
// 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)) { func (d *ImagePushDialog) Focus(delegate func(p tview.Primitive)) {
switch d.focusElement { switch d.focusElement {
@ -368,25 +363,6 @@ func (d *ImagePushDialog) InputHandler() func(event *tcell.EventKey, setFocus fu
}) })
} }
func (d *ImagePushDialog) setFocusElement() {
switch d.focusElement {
case imagePushDesitnationFocus:
d.focusElement = imagePushCompressFocus
case imagePushCompressFocus:
d.focusElement = imagePushFormatFocus
case imagePushFormatFocus:
d.focusElement = imagePushSkipTLSVerifyFocus
case imagePushSkipTLSVerifyFocus:
d.focusElement = imagePushUsernameFocus
case imagePushUsernameFocus:
d.focusElement = imagePushPasswordFocus
case imagePushPasswordFocus:
d.focusElement = imagePushAuthFileFocus
case imagePushAuthFileFocus:
d.focusElement = imagePushFormFocus
}
}
// SetRect set rects for this primitive. // SetRect set rects for this primitive.
func (d *ImagePushDialog) SetRect(x, y, width, height int) { func (d *ImagePushDialog) SetRect(x, y, width, height int) {
if width > imagePushDialogMaxWidth { if width > imagePushDialogMaxWidth {
@ -410,8 +386,8 @@ func (d *ImagePushDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
@ -457,3 +433,28 @@ func (d *ImagePushDialog) GetImagePushOptions() images.ImagePushOptions {
return opts return opts
} }
// dropdownHasFocus returns true if image push dialog dropdown primitives.
// has focus.
func (d *ImagePushDialog) dropdownHasFocus() bool {
return d.format.HasFocus()
}
func (d *ImagePushDialog) setFocusElement() {
switch d.focusElement {
case imagePushDesitnationFocus:
d.focusElement = imagePushCompressFocus
case imagePushCompressFocus:
d.focusElement = imagePushFormatFocus
case imagePushFormatFocus:
d.focusElement = imagePushSkipTLSVerifyFocus
case imagePushSkipTLSVerifyFocus:
d.focusElement = imagePushUsernameFocus
case imagePushUsernameFocus:
d.focusElement = imagePushPasswordFocus
case imagePushPasswordFocus:
d.focusElement = imagePushAuthFileFocus
case imagePushAuthFileFocus:
d.focusElement = imagePushFormFocus
}
}

View File

@ -33,6 +33,7 @@ const (
// ImageSaveDialog represents image save dialog primitive. // ImageSaveDialog represents image save dialog primitive.
type ImageSaveDialog struct { type ImageSaveDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
imageInfo *tview.InputField imageInfo *tview.InputField
output *tview.InputField output *tview.InputField
@ -307,25 +308,12 @@ func (d *ImageSaveDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
} }
func (d *ImageSaveDialog) setFocusElement() {
switch d.focusElement {
case imageSaveOutputFocus:
d.focusElement = imageSaveCompressFocus
case imageSaveCompressFocus:
d.focusElement = imageSaveAcceptUncompressedFocus
case imageSaveAcceptUncompressedFocus:
d.focusElement = imageSaveFormatFocus
case imageSaveFormatFocus:
d.focusElement = imageSaveFormFocus
}
}
// SetSaveFunc sets form save button selected function. // SetSaveFunc sets form save button selected function.
func (d *ImageSaveDialog) SetSaveFunc(handler func()) *ImageSaveDialog { func (d *ImageSaveDialog) SetSaveFunc(handler func()) *ImageSaveDialog {
d.saveHandler = handler d.saveHandler = handler
@ -384,3 +372,16 @@ func (d *ImageSaveDialog) ImageSaveOptions() (images.ImageSaveOptions, error) {
return opts, nil return opts, nil
} }
func (d *ImageSaveDialog) setFocusElement() {
switch d.focusElement {
case imageSaveOutputFocus:
d.focusElement = imageSaveCompressFocus
case imageSaveCompressFocus:
d.focusElement = imageSaveAcceptUncompressedFocus
case imageSaveAcceptUncompressedFocus:
d.focusElement = imageSaveFormatFocus
case imageSaveFormatFocus:
d.focusElement = imageSaveFormFocus
}
}

View File

@ -45,6 +45,7 @@ const (
// ImageSearchDialog represents image search dialogs. // ImageSearchDialog represents image search dialogs.
type ImageSearchDialog struct { type ImageSearchDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
searchLayout *tview.Flex searchLayout *tview.Flex
input *tview.InputField input *tview.InputField
@ -126,62 +127,6 @@ func NewImageSearchDialog() *ImageSearchDialog {
return dialog return dialog
} }
func (d *ImageSearchDialog) initTable() {
bgColor := style.TableHeaderBgColor
fgColor := style.TableHeaderFgColor
d.searchResult.Clear()
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, searchResultNameColIndex,
tview.NewTableCell(fmt.Sprintf("[%s::b]NAME", style.GetColorHex(fgColor))).
SetExpansion(1).
SetBackgroundColor(bgColor).
SetTextColor(fgColor).
SetAlign(tview.AlignLeft).
SetSelectable(false))
d.searchResult.SetCell(0, searchResultStarsColIndex,
tview.NewTableCell(fmt.Sprintf("[%s::b]STARS", style.GetColorHex(fgColor))).
SetExpansion(1).
SetBackgroundColor(bgColor).
SetTextColor(fgColor).
SetAlign(tview.AlignCenter).
SetSelectable(false))
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, searchResultAutomatedColIndex,
tview.NewTableCell(fmt.Sprintf("[%s::b]AUTOMATED", style.GetColorHex(fgColor))).
SetExpansion(1).
SetBackgroundColor(bgColor).
SetTextColor(fgColor).
SetAlign(tview.AlignCenter).
SetSelectable(false))
d.searchResult.SetCell(0, searchResultDescColIndex,
tview.NewTableCell(fmt.Sprintf("[%s::b]DESCRIPTION", style.GetColorHex(fgColor))).
SetExpansion(1).
SetBackgroundColor(bgColor).
SetTextColor(fgColor).
SetAlign(tview.AlignCenter).
SetSelectable(false))
d.searchResult.SetFixed(1, 1)
d.searchResult.SetSelectable(true, false)
}
// Display displays this primitive. // Display displays this primitive.
func (d *ImageSearchDialog) Display() { func (d *ImageSearchDialog) Display() {
d.display = true d.display = true
@ -333,9 +278,9 @@ func (d *ImageSearchDialog) Draw(screen tcell.Screen) {
} }
bgColor := style.DialogBgColor bgColor := style.DialogBgColor
d.Box.SetBackgroundColor(bgColor) d.SetBackgroundColor(bgColor)
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.SetBorder(true) d.layout.SetBorder(true)
d.layout.SetBackgroundColor(bgColor) d.layout.SetBackgroundColor(bgColor)
@ -505,3 +450,59 @@ func (d *ImageSearchDialog) UpdateResults(data [][]string) {
d.searchResult.ScrollToBeginning() d.searchResult.ScrollToBeginning()
} }
} }
func (d *ImageSearchDialog) initTable() {
bgColor := style.TableHeaderBgColor
fgColor := style.TableHeaderFgColor
d.searchResult.Clear()
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, searchResultNameColIndex,
tview.NewTableCell(fmt.Sprintf("[%s::b]NAME", style.GetColorHex(fgColor))).
SetExpansion(1).
SetBackgroundColor(bgColor).
SetTextColor(fgColor).
SetAlign(tview.AlignLeft).
SetSelectable(false))
d.searchResult.SetCell(0, searchResultStarsColIndex,
tview.NewTableCell(fmt.Sprintf("[%s::b]STARS", style.GetColorHex(fgColor))).
SetExpansion(1).
SetBackgroundColor(bgColor).
SetTextColor(fgColor).
SetAlign(tview.AlignCenter).
SetSelectable(false))
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, searchResultAutomatedColIndex,
tview.NewTableCell(fmt.Sprintf("[%s::b]AUTOMATED", style.GetColorHex(fgColor))).
SetExpansion(1).
SetBackgroundColor(bgColor).
SetTextColor(fgColor).
SetAlign(tview.AlignCenter).
SetSelectable(false))
d.searchResult.SetCell(0, searchResultDescColIndex,
tview.NewTableCell(fmt.Sprintf("[%s::b]DESCRIPTION", style.GetColorHex(fgColor))).
SetExpansion(1).
SetBackgroundColor(bgColor).
SetTextColor(fgColor).
SetAlign(tview.AlignCenter).
SetSelectable(false))
d.searchResult.SetFixed(1, 1)
d.searchResult.SetSelectable(true, false)
}

View File

@ -8,7 +8,7 @@ import (
) )
// InputHandler returns the handler for this primitive. // InputHandler returns the handler for this primitive.
func (img *Images) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { //nolint:gocognit,gocyclo,lll,cyclop func (img *Images) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { //nolint:gocognit,cyclop,lll
return img.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { return img.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
log.Debug().Msgf("view: images event %v received", event) log.Debug().Msgf("view: images event %v received", event)
@ -16,89 +16,20 @@ func (img *Images) InputHandler() func(event *tcell.EventKey, setFocus func(p tv
return return
} }
// error dialog handler for _, dialog := range img.getInnerTopDialogs() {
if img.errorDialog.HasFocus() || img.errorDialog.IsDisplay() { if dialog.HasFocus() {
if errorDialogHandler := img.errorDialog.InputHandler(); errorDialogHandler != nil { if handler := dialog.InputHandler(); handler != nil {
errorDialogHandler(event, setFocus) handler(event, setFocus)
setFocus(img.errorDialog) }
} }
} }
// message dialog handler for _, dialog := range img.getInnerDialogs() {
if img.messageDialog.HasFocus() || img.messageDialog.IsDisplay() { if dialog.HasFocus() {
if messageDialogHandler := img.messageDialog.InputHandler(); messageDialogHandler != nil { if handler := dialog.InputHandler(); handler != nil {
messageDialogHandler(event, setFocus) handler(event, setFocus)
setFocus(img.messageDialog)
} }
} }
// command dialog handler
if img.cmdDialog.HasFocus() {
if cmdHandler := img.cmdDialog.InputHandler(); cmdHandler != nil {
cmdHandler(event, setFocus)
}
}
// input dialog handler
if img.cmdInputDialog.HasFocus() {
if cmdInputHandler := img.cmdInputDialog.InputHandler(); cmdInputHandler != nil {
cmdInputHandler(event, setFocus)
}
}
// confirm dialog handler
if img.confirmDialog.HasFocus() {
if confirmDialogHandler := img.confirmDialog.InputHandler(); confirmDialogHandler != nil {
confirmDialogHandler(event, setFocus)
}
}
// search dialog handler
if img.searchDialog.HasFocus() {
if searchDialogHandler := img.searchDialog.InputHandler(); searchDialogHandler != nil {
searchDialogHandler(event, setFocus)
}
}
// history dialog handler
if img.historyDialog.HasFocus() {
if historyDialogHandler := img.historyDialog.InputHandler(); historyDialogHandler != nil {
historyDialogHandler(event, setFocus)
}
}
// build dialog handler
if img.buildDialog.HasFocus() {
if buildDialogHandler := img.buildDialog.InputHandler(); buildDialogHandler != nil {
buildDialogHandler(event, setFocus)
}
}
// build progress dialog handler
if img.buildPrgDialog.HasFocus() {
if buildPrgDialogHandler := img.buildPrgDialog.InputHandler(); buildPrgDialogHandler != nil {
buildPrgDialogHandler(event, setFocus)
}
}
// save dialog handler
if img.saveDialog.HasFocus() {
if saveDialogHandler := img.saveDialog.InputHandler(); saveDialogHandler != nil {
saveDialogHandler(event, setFocus)
}
}
// import dialog handler
if img.importDialog.HasFocus() {
if importDialogHandler := img.importDialog.InputHandler(); importDialogHandler != nil {
importDialogHandler(event, setFocus)
}
}
// push dialog handler
if img.pushDialog.HasFocus() {
if pushDialogHandler := img.pushDialog.InputHandler(); pushDialogHandler != nil {
pushDialogHandler(event, setFocus)
}
} }
// table handlers // table handlers
@ -115,6 +46,14 @@ func (img *Images) InputHandler() func(event *tcell.EventKey, setFocus func(p tv
return return
} }
// display sort menu
if event.Rune() == utils.SortMenuKey.Rune() {
img.sortDialog.Display()
setFocus(img)
return
}
if event.Key() == utils.DeleteKey.EventKey() { if event.Key() == utils.DeleteKey.EventKey() {
img.rm() img.rm()
setFocus(img) setFocus(img)

View File

@ -26,6 +26,7 @@ func (img *Images) refresh(_ int) {
SetSelectable(false)) SetSelectable(false))
} }
currentSelectedRow, _ := img.table.GetSelection()
rowIndex := 1 rowIndex := 1
images := img.getData() images := img.getData()
@ -76,4 +77,11 @@ func (img *Images) refresh(_ int) {
rowIndex++ rowIndex++
} }
if currentSelectedRow > len(images) {
currentSelectedRow--
if currentSelectedRow >= 0 {
img.table.Select(currentSelectedRow, -1)
}
}
} }

View File

@ -29,6 +29,7 @@ const (
// InfoBar implements the info bar primitive. // InfoBar implements the info bar primitive.
type InfoBar struct { type InfoBar struct {
*tview.Box *tview.Box
table *tview.Table table *tview.Table
title string title string
connStatus registry.ConnStatus connStatus registry.ConnStatus
@ -163,8 +164,9 @@ func (info *InfoBar) UpdateSystemUsageInfo(memUsage float64, swapUsage float64)
// UpdateConnStatus updates connection status value. // UpdateConnStatus updates connection status value.
func (info *InfoBar) UpdateConnStatus(status registry.ConnStatus) { func (info *InfoBar) UpdateConnStatus(status registry.ConnStatus) {
var connStatus string
info.connStatus = status info.connStatus = status
connStatus := ""
switch info.connStatus { switch info.connStatus {
case registry.ConnectionStatusConnected: case registry.ConnectionStatusConnected:
@ -180,8 +182,8 @@ func (info *InfoBar) UpdateConnStatus(status registry.ConnStatus) {
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
func (info *InfoBar) Draw(screen tcell.Screen) { func (info *InfoBar) Draw(screen tcell.Screen) {
info.Box.DrawForSubclass(screen, info) info.DrawForSubclass(screen, info)
info.Box.SetBorder(false) info.SetBorder(false)
x, y, width, height := info.GetInnerRect() x, y, width, height := info.GetInnerRect()

View File

@ -8,6 +8,7 @@ import (
"github.com/containers/podman-tui/pdcs/networks" "github.com/containers/podman-tui/pdcs/networks"
"github.com/containers/podman-tui/ui/dialogs" "github.com/containers/podman-tui/ui/dialogs"
"github.com/containers/podman-tui/ui/style" "github.com/containers/podman-tui/ui/style"
"github.com/containers/podman-tui/ui/utils"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -21,7 +22,7 @@ func (nets *Networks) runCommand(cmd string) {
nets.cdisconnect() nets.cdisconnect()
case "inspect": case "inspect":
nets.inspect() nets.inspect()
case "prune": //nolint:goconst case utils.PruneCommandLabel:
nets.cprune() nets.cprune()
case "rm": case "rm":
nets.rm() nets.rm()
@ -75,15 +76,15 @@ func (nets *Networks) connect() {
nets.progressDialog.SetTitle("podman network connect") nets.progressDialog.SetTitle("podman network connect")
nets.progressDialog.Display() nets.progressDialog.Display()
if err := networks.Connect(connectOptions); err != nil { err := networks.Connect(connectOptions)
nets.progressDialog.Hide()
nets.displayError("NETWORK CONNECT ERROR", err)
nets.appFocusHandler()
return nets.progressDialog.Hide()
if err != nil {
nets.displayError("NETWORK CONNECT ERROR", err)
} }
nets.progressDialog.Hide() nets.appFocusHandler()
} }
go connect() go connect()
@ -101,8 +102,10 @@ func (nets *Networks) cdisconnect() {
nets.progressDialog.Display() nets.progressDialog.Display()
cntListReport, err := containers.List() cntListReport, err := containers.List()
if err != nil {
nets.progressDialog.Hide() nets.progressDialog.Hide()
if err != nil {
nets.displayError("NETWORK DISCONNECT ERROR", err) nets.displayError("NETWORK DISCONNECT ERROR", err)
nets.appFocusHandler() nets.appFocusHandler()
@ -113,7 +116,6 @@ func (nets *Networks) cdisconnect() {
nets.disconnectDialog.SetNetworkInfo(netID, netName) nets.disconnectDialog.SetNetworkInfo(netID, netName)
nets.disconnectDialog.SetContainers(cntListReport) nets.disconnectDialog.SetContainers(cntListReport)
nets.progressDialog.Hide()
nets.disconnectDialog.Display() nets.disconnectDialog.Display()
nets.appFocusHandler() nets.appFocusHandler()
} }
@ -129,15 +131,15 @@ func (nets *Networks) disconnect() {
nets.progressDialog.SetTitle("podman network disconnect") nets.progressDialog.SetTitle("podman network disconnect")
nets.progressDialog.Display() nets.progressDialog.Display()
if err := networks.Disconnect(networkName, containerID); err != nil { err := networks.Disconnect(networkName, containerID)
nets.progressDialog.Hide()
nets.displayError("NETWORK DISCONNECT ERROR", err)
nets.appFocusHandler()
return nets.progressDialog.Hide()
if err != nil {
nets.displayError("NETWORK DISCONNECT ERROR", err)
} }
nets.progressDialog.Hide() nets.appFocusHandler()
} }
go disconnect() go disconnect()
@ -182,7 +184,7 @@ func (nets *Networks) inspect() {
func (nets *Networks) cprune() { func (nets *Networks) cprune() {
nets.confirmDialog.SetTitle("podman network prune") nets.confirmDialog.SetTitle("podman network prune")
nets.confirmData = "prune" nets.confirmData = utils.PruneCommandLabel
nets.confirmDialog.SetText("Are you sure you want to remove all un used network ?") nets.confirmDialog.SetText("Are you sure you want to remove all un used network ?")
nets.confirmDialog.Display() nets.confirmDialog.Display()
@ -193,16 +195,16 @@ func (nets *Networks) prune() {
nets.progressDialog.Display() nets.progressDialog.Display()
prune := func() { prune := func() {
if err := networks.Prune(); err != nil { err := networks.Prune()
nets.progressDialog.Hide()
nets.displayError("NETWORK PRUNE ERROR", err)
nets.appFocusHandler()
return nets.progressDialog.Hide()
if err != nil {
nets.displayError("NETWORK PRUNE ERROR", err)
} }
nets.appFocusHandler()
nets.UpdateData() nets.UpdateData()
nets.progressDialog.Hide()
} }
go prune() go prune()
@ -242,11 +244,9 @@ func (nets *Networks) remove() {
title := fmt.Sprintf("NETWORK (%s) REMOVE ERROR", nets.selectedID) title := fmt.Sprintf("NETWORK (%s) REMOVE ERROR", nets.selectedID)
nets.displayError(title, err) nets.displayError(title, err)
nets.appFocusHandler()
return
} }
nets.appFocusHandler()
nets.UpdateData() nets.UpdateData()
} }

View File

@ -2,14 +2,29 @@ package networks
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"github.com/containers/common/libnetwork/types"
"github.com/containers/podman-tui/pdcs/networks" "github.com/containers/podman-tui/pdcs/networks"
"github.com/containers/podman-tui/ui/style" "github.com/containers/podman-tui/ui/style"
"github.com/rivo/tview" "github.com/rivo/tview"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
// SortView sorts data view called from sort dialog.
func (nets *Networks) SortView(option string, ascending bool) {
log.Debug().Msgf("view: networks sort by %s", option)
nets.networkList.mu.Lock()
defer nets.networkList.mu.Unlock()
nets.networkList.sortBy = option
nets.networkList.ascending = ascending
sort.Sort(netsListSorted{nets.networkList.report, option, ascending})
}
// UpdateData retrieves networks list data. // UpdateData retrieves networks list data.
func (nets *Networks) UpdateData() { func (nets *Networks) UpdateData() {
netList, err := networks.List() netList, err := networks.List()
@ -22,10 +37,12 @@ func (nets *Networks) UpdateData() {
nets.networkList.mu.Lock() nets.networkList.mu.Lock()
defer nets.networkList.mu.Unlock() defer nets.networkList.mu.Unlock()
sort.Sort(netsListSorted{netList, nets.networkList.sortBy, nets.networkList.ascending})
nets.networkList.report = netList nets.networkList.report = netList
} }
func (nets *Networks) getData() [][]string { func (nets *Networks) getData() []types.Network {
nets.networkList.mu.Lock() nets.networkList.mu.Lock()
defer nets.networkList.mu.Unlock() defer nets.networkList.mu.Unlock()
@ -59,3 +76,31 @@ func (nets *Networks) ClearData() {
nets.table.SetTitle(fmt.Sprintf("[::b]%s[0]", strings.ToUpper(nets.title))) nets.table.SetTitle(fmt.Sprintf("[::b]%s[0]", strings.ToUpper(nets.title)))
} }
type lprSort []types.Network
func (a lprSort) Len() int { return len(a) }
func (a lprSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type netsListSorted struct {
lprSort
option string
ascending bool
}
func (a netsListSorted) Less(i, j int) bool {
if a.option == "driver" {
if a.ascending {
return a.lprSort[i].Driver < a.lprSort[j].Driver
}
return a.lprSort[i].Driver > a.lprSort[j].Driver
}
if a.ascending {
return a.lprSort[i].Name < a.lprSort[j].Name
}
return a.lprSort[i].Name > a.lprSort[j].Name
}

View File

@ -6,8 +6,8 @@ import (
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
func (nets *Networks) Draw(screen tcell.Screen) { func (nets *Networks) Draw(screen tcell.Screen) {
nets.Box.DrawForSubclass(screen, nets) nets.DrawForSubclass(screen, nets)
nets.Box.SetBorder(false) nets.SetBorder(false)
netViewX, netViewY, netViewW, netViewH := nets.GetInnerRect() netViewX, netViewY, netViewW, netViewH := nets.GetInnerRect()
@ -19,70 +19,12 @@ func (nets *Networks) Draw(screen tcell.Screen) {
x, y, width, height := nets.table.GetInnerRect() x, y, width, height := nets.table.GetInnerRect()
// error dialog for _, dialog := range nets.getInnerDialogs() {
if nets.errorDialog.IsDisplay() { if dialog.IsDisplay() {
nets.errorDialog.SetRect(x, y, width, height) dialog.SetRect(x, y, width, height)
nets.errorDialog.Draw(screen) dialog.Draw(screen)
return return
} }
// command dialog
if nets.cmdDialog.IsDisplay() {
nets.cmdDialog.SetRect(x, y, width, height)
nets.cmdDialog.Draw(screen)
return
}
// create dialog
if nets.createDialog.IsDisplay() {
nets.createDialog.SetRect(x, y, width, height)
nets.createDialog.Draw(screen)
return
}
// connect dialog
if nets.connectDialog.IsDisplay() {
nets.connectDialog.SetRect(x, y, width, height)
nets.connectDialog.Draw(screen)
return
}
// disconnect dialog
if nets.disconnectDialog.IsDisplay() {
nets.disconnectDialog.SetRect(x, y, width, height)
nets.disconnectDialog.Draw(screen)
return
}
// message dialog
if nets.messageDialog.IsDisplay() {
if nets.messageDialog.IsDisplayFullSize() {
nets.messageDialog.SetRect(netViewX, netViewY, netViewW, netViewH)
} else {
nets.messageDialog.SetRect(x, y, width, height+1)
}
nets.messageDialog.Draw(screen)
return
}
// confirm dialog
if nets.confirmDialog.IsDisplay() {
nets.confirmDialog.SetRect(x, y, width, height)
nets.confirmDialog.Draw(screen)
return
}
// progress dialog
if nets.progressDialog.IsDisplay() {
nets.progressDialog.SetRect(x, y, width, height)
nets.progressDialog.Draw(screen)
} }
} }

View File

@ -8,7 +8,7 @@ import (
) )
// InputHandler returns the handler for this primitive. // InputHandler returns the handler for this primitive.
func (nets *Networks) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { //nolint:gocognit,lll,cyclop func (nets *Networks) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { //nolint:cyclop
return nets.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { return nets.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
log.Debug().Msgf("view: networks event %v received", event) log.Debug().Msgf("view: networks event %v received", event)
@ -16,53 +16,12 @@ func (nets *Networks) InputHandler() func(event *tcell.EventKey, setFocus func(p
return return
} }
// error dialog handler for _, dialog := range nets.getInnerDialogs() {
if nets.errorDialog.HasFocus() { if dialog.HasFocus() {
if errorDialogHandler := nets.errorDialog.InputHandler(); errorDialogHandler != nil { if dialogHandler := dialog.InputHandler(); dialogHandler != nil {
errorDialogHandler(event, setFocus) dialogHandler(event, setFocus)
} }
} }
// message dialog handler
if nets.messageDialog.HasFocus() {
if messageDialogHandler := nets.messageDialog.InputHandler(); messageDialogHandler != nil {
messageDialogHandler(event, setFocus)
}
}
// create dialog handler
if nets.createDialog.HasFocus() {
if createDialogHandler := nets.createDialog.InputHandler(); createDialogHandler != nil {
createDialogHandler(event, setFocus)
}
}
// connect dialog handler
if nets.connectDialog.HasFocus() {
if connectDialogHandler := nets.connectDialog.InputHandler(); connectDialogHandler != nil {
connectDialogHandler(event, setFocus)
}
}
// disconnect dialog handler
if nets.disconnectDialog.HasFocus() {
if disconnectDialogHandler := nets.disconnectDialog.InputHandler(); disconnectDialogHandler != nil {
disconnectDialogHandler(event, setFocus)
}
}
// confirm dialog handler
if nets.confirmDialog.HasFocus() {
if confirmDialogHandler := nets.confirmDialog.InputHandler(); confirmDialogHandler != nil {
confirmDialogHandler(event, setFocus)
}
}
// command dialog handler
if nets.cmdDialog.HasFocus() {
if cmdHandler := nets.cmdDialog.InputHandler(); cmdHandler != nil {
cmdHandler(event, setFocus)
}
} }
// table handlers // table handlers
@ -79,6 +38,14 @@ func (nets *Networks) InputHandler() func(event *tcell.EventKey, setFocus func(p
return return
} }
// display sort menu
if event.Rune() == utils.SortMenuKey.Rune() {
nets.sortDialog.Display()
setFocus(nets)
return
}
if event.Key() == utils.DeleteKey.EventKey() { if event.Key() == utils.DeleteKey.EventKey() {
nets.rm() nets.rm()
setFocus(nets) setFocus(nets)

View File

@ -32,6 +32,7 @@ const (
// NetworkConnectDialog implements network connect dialog primitive. // NetworkConnectDialog implements network connect dialog primitive.
type NetworkConnectDialog struct { type NetworkConnectDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
network *tview.InputField network *tview.InputField
container *tview.DropDown container *tview.DropDown
@ -322,9 +323,9 @@ func (d *NetworkConnectDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
@ -350,21 +351,6 @@ func (d *NetworkConnectDialog) SetCancelFunc(handler func()) *NetworkConnectDial
return d return d
} }
func (d *NetworkConnectDialog) setFocusElement() {
switch d.focusElement {
case netConnectContainerFocus:
d.focusElement = netConnectAliasesFocus
case netConnectAliasesFocus:
d.focusElement = netConnectAliasesIPv4Focus
case netConnectAliasesIPv4Focus:
d.focusElement = netConnectAliasesIPv6Focus
case netConnectAliasesIPv6Focus:
d.focusElement = netConnectMacAddrFocus
case netConnectMacAddrFocus:
d.focusElement = netConnectFormFocus
}
}
// SetNetworkInfo sets selected network name in connect dialog. // SetNetworkInfo sets selected network name in connect dialog.
func (d *NetworkConnectDialog) SetNetworkInfo(id, name string) { func (d *NetworkConnectDialog) SetNetworkInfo(id, name string) {
d.networkName = name d.networkName = name
@ -400,3 +386,18 @@ func (d *NetworkConnectDialog) GetConnectOptions() networks.NetworkConnect {
return connectOptions return connectOptions
} }
func (d *NetworkConnectDialog) setFocusElement() {
switch d.focusElement {
case netConnectContainerFocus:
d.focusElement = netConnectAliasesFocus
case netConnectAliasesFocus:
d.focusElement = netConnectAliasesIPv4Focus
case netConnectAliasesIPv4Focus:
d.focusElement = netConnectAliasesIPv6Focus
case netConnectAliasesIPv6Focus:
d.focusElement = netConnectMacAddrFocus
case netConnectMacAddrFocus:
d.focusElement = netConnectFormFocus
}
}

View File

@ -42,6 +42,7 @@ const (
// NetworkCreateDialog implements network create dialog. // NetworkCreateDialog implements network create dialog.
type NetworkCreateDialog struct { type NetworkCreateDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
categoryLabels []string categoryLabels []string
categories *tview.TextView categories *tview.TextView
@ -204,49 +205,6 @@ func NewNetworkCreateDialog() *NetworkCreateDialog {
return &netDialog return &netDialog
} }
func (d *NetworkCreateDialog) setupLayout() {
bgColor := style.DialogBgColor
// basic info page
d.basicInfoPage.SetDirection(tview.FlexRow)
d.basicInfoPage.AddItem(d.networkNameField, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.networkLabelsField, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.networkInternalCheckBox, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.networkDriverField, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.networkDriverOptionsField, 1, 0, true)
d.basicInfoPage.SetBackgroundColor(bgColor)
// ip settings page
d.ipSettingsPage.SetDirection(tview.FlexRow)
d.ipSettingsPage.AddItem(d.networkIpv6CheckBox, 1, 0, true)
d.ipSettingsPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.ipSettingsPage.AddItem(d.networkGatewayField, 1, 0, true)
d.ipSettingsPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.ipSettingsPage.AddItem(d.networkIPRangeField, 1, 0, true)
d.ipSettingsPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.ipSettingsPage.AddItem(d.networkSubnetField, 1, 0, true)
d.ipSettingsPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.ipSettingsPage.AddItem(d.networkDisableDNSCheckBox, 1, 0, true)
d.ipSettingsPage.SetBackgroundColor(bgColor)
// adding category pages
d.categoryPages.AddPage(d.categoryLabels[basicInfoPageIndex], d.basicInfoPage, true, true)
d.categoryPages.AddPage(d.categoryLabels[ipSettingsPageIndex], d.ipSettingsPage, true, true)
// add it to layout.
_, layoutWidth := utils.AlignStringListWidth(d.categoryLabels)
layout := tview.NewFlex().SetDirection(tview.FlexColumn)
layout.AddItem(d.categories, layoutWidth+6, 0, true) //nolint:mnd
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 *NetworkCreateDialog) Display() { func (d *NetworkCreateDialog) Display() {
d.display = true d.display = true
@ -434,8 +392,8 @@ func (d *NetworkCreateDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
@ -461,6 +419,115 @@ func (d *NetworkCreateDialog) SetCreateFunc(handler func()) *NetworkCreateDialog
return d return d
} }
// NetworkCreateOptions returns new network options.
func (d *NetworkCreateDialog) NetworkCreateOptions() networks.CreateOptions { //nolint:cyclop
var (
labels = make(map[string]string)
options = make(map[string]string)
subnets []string
gateways []string
ipranges []string
)
for _, label := range strings.Split(d.networkLabelsField.GetText(), " ") {
if label != "" {
split := strings.Split(label, "=")
if len(split) == 2 { //nolint:mnd
key := split[0]
value := split[1]
if key != "" && value != "" {
labels[key] = value
}
}
}
}
for _, option := range strings.Split(d.networkDriverOptionsField.GetText(), " ") {
if option != "" {
split := strings.Split(option, "=")
if len(split) == 2 { //nolint:mnd
key := split[0]
value := split[1]
if key != "" && value != "" {
options[key] = value
}
}
}
}
if strings.Trim(d.networkGatewayField.GetText(), " ") != "" {
gateways = strings.Split(d.networkGatewayField.GetText(), " ")
}
if strings.Trim(d.networkSubnetField.GetText(), " ") != "" {
subnets = strings.Split(d.networkSubnetField.GetText(), " ")
}
if strings.Trim(d.networkIPRangeField.GetText(), " ") != "" {
ipranges = strings.Split(d.networkIPRangeField.GetText(), " ")
}
opts := networks.CreateOptions{
Name: strings.TrimSpace(d.networkNameField.GetText()),
Labels: labels,
Internal: d.networkInternalCheckBox.IsChecked(),
Drivers: strings.TrimSpace(d.networkDriverField.GetText()),
DriversOptions: options,
IPv6: d.networkIpv6CheckBox.IsChecked(),
Gateways: gateways,
Subnets: subnets,
IPRanges: ipranges,
DisableDNS: d.networkDisableDNSCheckBox.IsChecked(),
}
return opts
}
func (d *NetworkCreateDialog) setupLayout() {
bgColor := style.DialogBgColor
// basic info page
d.basicInfoPage.SetDirection(tview.FlexRow)
d.basicInfoPage.AddItem(d.networkNameField, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.networkLabelsField, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.networkInternalCheckBox, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.networkDriverField, 1, 0, true)
d.basicInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.basicInfoPage.AddItem(d.networkDriverOptionsField, 1, 0, true)
d.basicInfoPage.SetBackgroundColor(bgColor)
// ip settings page
d.ipSettingsPage.SetDirection(tview.FlexRow)
d.ipSettingsPage.AddItem(d.networkIpv6CheckBox, 1, 0, true)
d.ipSettingsPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.ipSettingsPage.AddItem(d.networkGatewayField, 1, 0, true)
d.ipSettingsPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.ipSettingsPage.AddItem(d.networkIPRangeField, 1, 0, true)
d.ipSettingsPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.ipSettingsPage.AddItem(d.networkSubnetField, 1, 0, true)
d.ipSettingsPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.ipSettingsPage.AddItem(d.networkDisableDNSCheckBox, 1, 0, true)
d.ipSettingsPage.SetBackgroundColor(bgColor)
// adding category pages
d.categoryPages.AddPage(d.categoryLabels[basicInfoPageIndex], d.basicInfoPage, true, true)
d.categoryPages.AddPage(d.categoryLabels[ipSettingsPageIndex], d.ipSettingsPage, true, true)
// add it to layout.
_, layoutWidth := utils.AlignStringListWidth(d.categoryLabels)
layout := tview.NewFlex().SetDirection(tview.FlexColumn)
layout.AddItem(d.categories, layoutWidth+6, 0, true) //nolint:mnd
layout.AddItem(d.categoryPages, 0, 1, true)
layout.SetBackgroundColor(bgColor)
d.layout.AddItem(layout, 0, 1, true)
}
func (d *NetworkCreateDialog) setActiveCategory(index int) { func (d *NetworkCreateDialog) setActiveCategory(index int) {
fgColor := style.DialogFgColor fgColor := style.DialogFgColor
bgColor := style.ButtonBgColor bgColor := style.ButtonBgColor
@ -584,69 +651,3 @@ func (d *NetworkCreateDialog) setIPSettingsPageNextFocus() {
d.focusElement = formFocus d.focusElement = formFocus
} }
// NetworkCreateOptions returns new network options.
func (d *NetworkCreateDialog) NetworkCreateOptions() networks.CreateOptions { //nolint:cyclop
var (
labels = make(map[string]string)
options = make(map[string]string)
subnets []string
gateways []string
ipranges []string
)
for _, label := range strings.Split(d.networkLabelsField.GetText(), " ") {
if label != "" {
split := strings.Split(label, "=")
if len(split) == 2 { //nolint:mnd
key := split[0]
value := split[1]
if key != "" && value != "" {
labels[key] = value
}
}
}
}
for _, option := range strings.Split(d.networkDriverOptionsField.GetText(), " ") {
if option != "" {
split := strings.Split(option, "=")
if len(split) == 2 { //nolint:mnd
key := split[0]
value := split[1]
if key != "" && value != "" {
options[key] = value
}
}
}
}
if strings.Trim(d.networkGatewayField.GetText(), " ") != "" {
gateways = strings.Split(d.networkGatewayField.GetText(), " ")
}
if strings.Trim(d.networkSubnetField.GetText(), " ") != "" {
subnets = strings.Split(d.networkSubnetField.GetText(), " ")
}
if strings.Trim(d.networkIPRangeField.GetText(), " ") != "" {
ipranges = strings.Split(d.networkIPRangeField.GetText(), " ")
}
opts := networks.CreateOptions{
Name: strings.TrimSpace(d.networkNameField.GetText()),
Labels: labels,
Internal: d.networkInternalCheckBox.IsChecked(),
Drivers: strings.TrimSpace(d.networkDriverField.GetText()),
DriversOptions: options,
IPv6: d.networkIpv6CheckBox.IsChecked(),
Gateways: gateways,
Subnets: subnets,
IPRanges: ipranges,
DisableDNS: d.networkDisableDNSCheckBox.IsChecked(),
}
return opts
}

View File

@ -26,6 +26,7 @@ const (
// NetworkDisconnectDialog implements network disconnect dialog primitive. // NetworkDisconnectDialog implements network disconnect dialog primitive.
type NetworkDisconnectDialog struct { type NetworkDisconnectDialog struct {
*tview.Box *tview.Box
layout *tview.Flex layout *tview.Flex
network *tview.InputField network *tview.InputField
container *tview.DropDown container *tview.DropDown
@ -224,9 +225,9 @@ func (d *NetworkDisconnectDialog) Draw(screen tcell.Screen) {
return return
} }
d.Box.DrawForSubclass(screen, d) d.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect() x, y, width, height := d.GetInnerRect()
d.layout.SetRect(x, y, width, height) d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen) d.layout.Draw(screen)
@ -264,12 +265,6 @@ func (d *NetworkDisconnectDialog) SetContainers(cntList []entities.ListContainer
d.container.SetOptions(containers, nil) d.container.SetOptions(containers, nil)
} }
func (d *NetworkDisconnectDialog) setFocusElement() {
if d.focusElement == netDisconnectContainerFocus {
d.focusElement = netConnectFormFocus
}
}
// SetNetworkInfo sets selected network name in disconnect dialog. // SetNetworkInfo sets selected network name in disconnect dialog.
func (d *NetworkDisconnectDialog) SetNetworkInfo(id string, name string) { func (d *NetworkDisconnectDialog) SetNetworkInfo(id string, name string) {
d.networkName = name d.networkName = name
@ -285,3 +280,9 @@ func (d *NetworkDisconnectDialog) GetDisconnectOptions() (string, string) {
return d.networkName, container return d.networkName, container
} }
func (d *NetworkDisconnectDialog) setFocusElement() {
if d.focusElement == netDisconnectContainerFocus {
d.focusElement = netConnectFormFocus
}
}

View File

@ -6,9 +6,11 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/containers/common/libnetwork/types"
"github.com/containers/podman-tui/ui/dialogs" "github.com/containers/podman-tui/ui/dialogs"
"github.com/containers/podman-tui/ui/networks/netdialogs" "github.com/containers/podman-tui/ui/networks/netdialogs"
"github.com/containers/podman-tui/ui/style" "github.com/containers/podman-tui/ui/style"
"github.com/containers/podman-tui/ui/utils"
"github.com/rivo/tview" "github.com/rivo/tview"
) )
@ -28,6 +30,7 @@ var (
// Networks implemnents the Networks page primitive. // Networks implemnents the Networks page primitive.
type Networks struct { type Networks struct {
*tview.Box *tview.Box
title string title string
headers []string headers []string
table *tview.Table table *tview.Table
@ -36,6 +39,7 @@ type Networks struct {
confirmDialog *dialogs.ConfirmDialog confirmDialog *dialogs.ConfirmDialog
cmdDialog *dialogs.CommandDialog cmdDialog *dialogs.CommandDialog
messageDialog *dialogs.MessageDialog messageDialog *dialogs.MessageDialog
sortDialog *dialogs.SortDialog
createDialog *netdialogs.NetworkCreateDialog createDialog *netdialogs.NetworkCreateDialog
connectDialog *netdialogs.NetworkConnectDialog connectDialog *netdialogs.NetworkConnectDialog
disconnectDialog *netdialogs.NetworkDisconnectDialog disconnectDialog *netdialogs.NetworkDisconnectDialog
@ -47,7 +51,9 @@ type Networks struct {
type networkListReport struct { type networkListReport struct {
mu sync.Mutex mu sync.Mutex
report [][]string report []types.Network
sortBy string
ascending bool
} }
// NewNetworks returns nets page view. // NewNetworks returns nets page view.
@ -60,9 +66,11 @@ func NewNetworks() *Networks {
progressDialog: dialogs.NewProgressDialog(), progressDialog: dialogs.NewProgressDialog(),
confirmDialog: dialogs.NewConfirmDialog(), confirmDialog: dialogs.NewConfirmDialog(),
messageDialog: dialogs.NewMessageDialog(""), messageDialog: dialogs.NewMessageDialog(""),
sortDialog: dialogs.NewSortDialog([]string{"name", "driver"}, 0),
createDialog: netdialogs.NewNetworkCreateDialog(), createDialog: netdialogs.NewNetworkCreateDialog(),
connectDialog: netdialogs.NewNetworkConnectDialog(), connectDialog: netdialogs.NewNetworkConnectDialog(),
disconnectDialog: netdialogs.NewNetworkDisconnectDialog(), disconnectDialog: netdialogs.NewNetworkDisconnectDialog(),
networkList: networkListReport{sortBy: "name", ascending: true},
} }
nets.cmdDialog = dialogs.NewCommandDialog([][]string{ nets.cmdDialog = dialogs.NewCommandDialog([][]string{
@ -114,7 +122,7 @@ func NewNetworks() *Networks {
nets.confirmDialog.Hide() nets.confirmDialog.Hide()
switch nets.confirmData { switch nets.confirmData {
case "prune": case utils.PruneCommandLabel:
nets.prune() nets.prune()
case "rm": case "rm":
nets.remove() nets.remove()
@ -143,6 +151,10 @@ func NewNetworks() *Networks {
nets.disconnectDialog.SetCancelFunc(nets.disconnectDialog.Hide) nets.disconnectDialog.SetCancelFunc(nets.disconnectDialog.Hide)
nets.disconnectDialog.SetDisconnectFunc(nets.disconnect) nets.disconnectDialog.SetDisconnectFunc(nets.disconnect)
// set sort dialog functions
nets.sortDialog.SetCancelFunc(nets.sortDialog.Hide)
nets.sortDialog.SetSelectFunc(nets.SortView)
return nets return nets
} }
@ -157,24 +169,12 @@ func (nets *Networks) GetTitle() string {
} }
// HasFocus returns whether or not this primitive has focus. // HasFocus returns whether or not this primitive has focus.
func (nets *Networks) HasFocus() bool { //nolint:cyclop func (nets *Networks) HasFocus() bool {
if nets.table.HasFocus() || nets.errorDialog.HasFocus() { if nets.SubDialogHasFocus() {
return true return true
} }
if nets.cmdDialog.HasFocus() || nets.messageDialog.IsDisplay() { if nets.table.HasFocus() || nets.Box.HasFocus() {
return true
}
if nets.progressDialog.HasFocus() || nets.confirmDialog.IsDisplay() {
return true
}
if nets.createDialog.HasFocus() || nets.connectDialog.HasFocus() {
return true
}
if nets.disconnectDialog.HasFocus() || nets.Box.HasFocus() {
return true return true
} }
@ -183,20 +183,10 @@ func (nets *Networks) HasFocus() bool { //nolint:cyclop
// SubDialogHasFocus returns whether or not sub dialog primitive has focus. // SubDialogHasFocus returns whether or not sub dialog primitive has focus.
func (nets *Networks) SubDialogHasFocus() bool { func (nets *Networks) SubDialogHasFocus() bool {
if nets.createDialog.HasFocus() || nets.errorDialog.HasFocus() { for _, dialog := range nets.getInnerDialogs() {
if dialog.HasFocus() {
return true return true
} }
if nets.cmdDialog.HasFocus() || nets.messageDialog.IsDisplay() {
return true
}
if nets.progressDialog.HasFocus() || nets.confirmDialog.IsDisplay() {
return true
}
if nets.connectDialog.HasFocus() || nets.disconnectDialog.HasFocus() {
return true
} }
return false return false
@ -204,58 +194,38 @@ func (nets *Networks) SubDialogHasFocus() bool {
// Focus is called when this primitive receives focus. // Focus is called when this primitive receives focus.
func (nets *Networks) Focus(delegate func(p tview.Primitive)) { func (nets *Networks) Focus(delegate func(p tview.Primitive)) {
// error dialog
if nets.errorDialog.IsDisplay() { if nets.errorDialog.IsDisplay() {
delegate(nets.errorDialog) delegate(nets.errorDialog)
return return
} }
// command dialog
if nets.cmdDialog.IsDisplay() {
delegate(nets.cmdDialog)
return
}
// message dialog
if nets.messageDialog.IsDisplay() {
delegate(nets.messageDialog)
return
}
// confirm dialog
if nets.confirmDialog.IsDisplay() { if nets.confirmDialog.IsDisplay() {
delegate(nets.confirmDialog) delegate(nets.confirmDialog)
return return
} }
// create dialog for _, dialog := range nets.getInnerDialogs() {
if nets.createDialog.IsDisplay() { if dialog.IsDisplay() {
delegate(nets.createDialog) delegate(dialog)
return return
} }
// connect dialog
if nets.connectDialog.IsDisplay() {
delegate(nets.connectDialog)
return
}
// disconnect dialog
if nets.disconnectDialog.IsDisplay() {
delegate(nets.disconnectDialog)
return
} }
delegate(nets.table) delegate(nets.table)
} }
// HideAllDialogs hides all sub dialogs.
func (nets *Networks) HideAllDialogs() {
for _, dialog := range nets.getInnerDialogs() {
if dialog.IsDisplay() {
dialog.Hide()
}
}
}
func (nets *Networks) getSelectedItem() (string, string) { func (nets *Networks) getSelectedItem() (string, string) {
if nets.table.GetRowCount() <= 1 { if nets.table.GetRowCount() <= 1 {
return "", "" return "", ""
@ -268,37 +238,18 @@ func (nets *Networks) getSelectedItem() (string, string) {
return netID, netName return netID, netName
} }
// HideAllDialogs hides all sub dialogs. func (nets *Networks) getInnerDialogs() []utils.UIDialog {
func (nets *Networks) HideAllDialogs() { dialogs := []utils.UIDialog{
if nets.errorDialog.IsDisplay() { nets.errorDialog,
nets.errorDialog.Hide() nets.progressDialog,
nets.confirmDialog,
nets.cmdDialog,
nets.messageDialog,
nets.connectDialog,
nets.createDialog,
nets.disconnectDialog,
nets.sortDialog,
} }
if nets.progressDialog.IsDisplay() { return dialogs
nets.progressDialog.Hide()
}
if nets.confirmDialog.IsDisplay() {
nets.confirmDialog.Hide()
}
if nets.cmdDialog.IsDisplay() {
nets.cmdDialog.Hide()
}
if nets.messageDialog.IsDisplay() {
nets.messageDialog.Hide()
}
if nets.createDialog.IsDisplay() {
nets.createDialog.Hide()
}
if nets.connectDialog.IsDisplay() {
nets.connectDialog.Hide()
}
if nets.disconnectDialog.IsDisplay() {
nets.disconnectDialog.Hide()
}
} }

View File

@ -24,34 +24,38 @@ func (nets *Networks) refresh(_ int) {
SetSelectable(false)) SetSelectable(false))
} }
currentSelectedRow, _ := nets.table.GetSelection()
rowIndex := 1 rowIndex := 1
netList := nets.getData() netList := nets.getData()
nets.table.SetTitle(fmt.Sprintf("[::b]%s[%d]", strings.ToUpper(nets.title), len(netList))) nets.table.SetTitle(fmt.Sprintf("[::b]%s[%d]", strings.ToUpper(nets.title), len(netList)))
for i := range netList { for _, net := range netList {
netID := netList[i][0]
netName := netList[i][1]
netDriver := netList[i][2]
// name column // name column
nets.table.SetCell(rowIndex, viewNetworkNameColIndex, nets.table.SetCell(rowIndex, viewNetworkNameColIndex,
tview.NewTableCell(netID[:12]). tview.NewTableCell(net.ID[:12]).
SetExpansion(expand). SetExpansion(expand).
SetAlign(alignment)) SetAlign(alignment))
// version column // version column
nets.table.SetCell(rowIndex, viewNetworkVersionColIndex, nets.table.SetCell(rowIndex, viewNetworkVersionColIndex,
tview.NewTableCell(netName). tview.NewTableCell(net.Name).
SetExpansion(expand). SetExpansion(expand).
SetAlign(alignment)) SetAlign(alignment))
// plugins at column // plugins at column
nets.table.SetCell(rowIndex, viewNetworkPluginColIndex, nets.table.SetCell(rowIndex, viewNetworkPluginColIndex,
tview.NewTableCell(netDriver). tview.NewTableCell(net.Driver).
SetExpansion(expand). SetExpansion(expand).
SetAlign(alignment)) SetAlign(alignment))
rowIndex++ rowIndex++
} }
if currentSelectedRow > len(netList) {
currentSelectedRow--
if currentSelectedRow >= 0 {
nets.table.Select(currentSelectedRow, -1)
}
}
} }

View File

@ -7,6 +7,7 @@ import (
ppods "github.com/containers/podman-tui/pdcs/pods" ppods "github.com/containers/podman-tui/pdcs/pods"
"github.com/containers/podman-tui/ui/dialogs" "github.com/containers/podman-tui/ui/dialogs"
"github.com/containers/podman-tui/ui/style" "github.com/containers/podman-tui/ui/style"
"github.com/containers/podman-tui/ui/utils"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -20,9 +21,9 @@ func (p *Pods) runCommand(cmd string) { //nolint:cyclop
p.kill() p.kill()
case "pause": case "pause":
p.pause() p.pause()
case "prune": //nolint:goconst case utils.PruneCommandLabel:
p.confirmDialog.SetTitle("podman pod prune") p.confirmDialog.SetTitle("podman pod prune")
p.confirmData = "prune" p.confirmData = utils.PruneCommandLabel
p.confirmDialog.SetText("Are you sure you want to remove all stopped pods ?") p.confirmDialog.SetText("Are you sure you want to remove all stopped pods ?")
p.confirmDialog.Display() p.confirmDialog.Display()
case "restart": case "restart":

View File

@ -2,6 +2,8 @@ package pods
import ( import (
"fmt" "fmt"
"sort"
"strconv"
"strings" "strings"
ppods "github.com/containers/podman-tui/pdcs/pods" ppods "github.com/containers/podman-tui/pdcs/pods"
@ -11,6 +13,18 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
// SortView sorts data view called from sort dialog.
func (pods *Pods) SortView(option string, ascending bool) {
log.Debug().Msgf("view: pods sort by %s", option)
pods.podsList.mu.Lock()
defer pods.podsList.mu.Unlock()
pods.podsList.sortBy = option
pods.podsList.ascending = ascending
sort.Sort(containerListSorted{pods.podsList.report, option, ascending})
}
// UpdateData retrieves pods list data. // UpdateData retrieves pods list data.
func (pods *Pods) UpdateData() { func (pods *Pods) UpdateData() {
podList, err := ppods.List() podList, err := ppods.List()
@ -25,6 +39,7 @@ func (pods *Pods) UpdateData() {
pods.podsList.mu.Lock() pods.podsList.mu.Lock()
defer pods.podsList.mu.Unlock() defer pods.podsList.mu.Unlock()
sort.Sort(containerListSorted{podList, pods.podsList.sortBy, pods.podsList.ascending})
pods.podsList.report = podList pods.podsList.report = podList
} }
@ -38,7 +53,7 @@ func (pods *Pods) getData() []*entities.ListPodsReport {
} }
// ClearData clears table data. // ClearData clears table data.
func (pods *Pods) ClearData() { //nolint:stylecheck func (pods *Pods) ClearData() {
pods.podsList.mu.Lock() pods.podsList.mu.Lock()
defer pods.podsList.mu.Unlock() defer pods.podsList.mu.Unlock()
@ -62,3 +77,47 @@ func (pods *Pods) ClearData() { //nolint:stylecheck
pods.table.SetTitle(fmt.Sprintf("[::b]%s[0]", strings.ToUpper(pods.title))) pods.table.SetTitle(fmt.Sprintf("[::b]%s[0]", strings.ToUpper(pods.title)))
} }
type lprSort []*entities.ListPodsReport
func (a lprSort) Len() int { return len(a) }
func (a lprSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type containerListSorted struct {
lprSort
option string
ascending bool
}
func (a containerListSorted) Less(i, j int) bool {
switch a.option {
case "# of containers":
iNumOfCnt := strconv.Itoa(len(a.lprSort[i].Containers))
jNumOfCnt := strconv.Itoa(len(a.lprSort[j].Containers))
if a.ascending {
return iNumOfCnt < jNumOfCnt
}
return iNumOfCnt > jNumOfCnt
case "status":
if a.ascending {
return a.lprSort[i].Status < a.lprSort[j].Status
}
return a.lprSort[i].Status > a.lprSort[j].Status
case "created":
if a.ascending {
return a.lprSort[i].Created.After(a.lprSort[j].Created)
}
return a.lprSort[i].Created.Before(a.lprSort[j].Created)
}
if a.ascending {
return a.lprSort[i].Name < a.lprSort[j].Name
}
return a.lprSort[i].Name > a.lprSort[j].Name
}

View File

@ -5,9 +5,9 @@ import (
) )
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
func (pods *Pods) Draw(screen tcell.Screen) { func (pods *Pods) Draw(screen tcell.Screen) { //nolint:cyclop
pods.Box.DrawForSubclass(screen, pods) pods.DrawForSubclass(screen, pods)
pods.Box.SetBorder(false) pods.SetBorder(false)
podViewX, podViewY, podViewW, podViewH := pods.GetInnerRect() podViewX, podViewY, podViewW, podViewH := pods.GetInnerRect()
@ -78,11 +78,19 @@ func (pods *Pods) Draw(screen tcell.Screen) {
return return
} }
// stats dialogs // stats dialog
if pods.statsDialog.IsDisplay() { if pods.statsDialog.IsDisplay() {
pods.statsDialog.SetRect(podViewX, podViewY, podViewW, podViewH) pods.statsDialog.SetRect(podViewX, podViewY, podViewW, podViewH)
pods.statsDialog.Draw(screen) pods.statsDialog.Draw(screen)
return return
} }
// sort dialog
if pods.sortDialog.IsDisplay() {
pods.sortDialog.SetRect(podViewX, podViewY, podViewW, podViewH)
pods.sortDialog.Draw(screen)
return
}
} }

View File

@ -65,6 +65,13 @@ func (pods *Pods) InputHandler() func(event *tcell.EventKey, setFocus func(p tvi
} }
} }
// pod sort dialog handler
if pods.sortDialog.HasFocus() {
if podsSortDialogHandler := pods.sortDialog.InputHandler(); podsSortDialogHandler != nil {
podsSortDialogHandler(event, setFocus)
}
}
// table handlers // table handlers
if pods.table.HasFocus() { //nolint:nestif if pods.table.HasFocus() { //nolint:nestif
pods.selectedID, _ = pods.getSelectedItem() pods.selectedID, _ = pods.getSelectedItem()
@ -79,6 +86,14 @@ func (pods *Pods) InputHandler() func(event *tcell.EventKey, setFocus func(p tvi
return return
} }
// display sort menu
if event.Rune() == utils.SortMenuKey.Rune() {
pods.sortDialog.Display()
setFocus(pods)
return
}
if event.Key() == utils.DeleteKey.EventKey() { if event.Key() == utils.DeleteKey.EventKey() {
pods.rm() pods.rm()
setFocus(pods) setFocus(pods)

Some files were not shown because too many files have changed in this diff Show More