Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package ktop for openSUSE:Factory checked in at 2025-07-07 14:47:42 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ktop (Old) and /work/SRC/openSUSE:Factory/.ktop.new.1903 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ktop" Mon Jul 7 14:47:42 2025 rev:6 rq:1290967 version:0.4.1 Changes: -------- --- /work/SRC/openSUSE:Factory/ktop/ktop.changes 2024-07-26 16:16:23.731868086 +0200 +++ /work/SRC/openSUSE:Factory/.ktop.new.1903/ktop.changes 2025-07-07 14:47:43.439342808 +0200 @@ -1,0 +2,58 @@ +Mon Jul 07 06:01:40 UTC 2025 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- Update to version 0.4.1: + * Fix Homebrew cask release (#56) + +------------------------------------------------------------------- +Mon Jul 07 05:57:04 UTC 2025 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- Update to version 0.4.0: + * This release introduces new command-line options to filter + which columns are displayed in nodes and pods tables. + Users can now customize the view by specifying columns with + --node-columns and --pod-columns flags. + - Column Filtering + You can now customize which columns are displayed in the + nodes and pods tables. This is useful when you want to focus + on specific metrics or when working with limited screen + space. + To show only specific node columns: + ktop --node-columns NAME,CPU,MEM + To show only specific pod columns: + ktop --pod-columns NAMESPACE,POD,CPU,MEMORY + You can combine both filters: + ktop --node-columns NAME,CPU,MEM --pod-columns NAMESPACE,POD,STATUS + Available node columns: + - NAME + - STATUS + - AGE + - VERSION + - INT/EXT IPs + - OS/ARC + - PODS/IMGs + - DISK + - CPU + - MEM + Available pod columns: + - NAMESPACE + - POD + - READY + - STATUS + - RESTARTS + - AGE + - VOLS + - IP + - NODE + - CPU + - MEMORY + These enhancements improve the usability and customization of + ktop, allowing users to tailor the displayed information to + their needs. The updated automation scripts also ensure a + smoother release workflow. + * What's Changed + - Update CI/release scripts (#55) + - Add column filtering options for nodes and pods in ktop (#51) + - Bump golang.org/x/net from 0.36.0 to 0.38.0 + - Bump golang.org/x/net from 0.23.0 to 0.36.0 + +------------------------------------------------------------------- Old: ---- ktop-0.3.7.obscpio New: ---- ktop-0.4.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ktop.spec ++++++ --- /var/tmp/diff_new_pack.jx25YO/_old 2025-07-07 14:47:44.919404601 +0200 +++ /var/tmp/diff_new_pack.jx25YO/_new 2025-07-07 14:47:44.923404768 +0200 @@ -1,7 +1,7 @@ # # spec file for package ktop # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,10 +16,8 @@ # -%define __arch_install_post export NO_BRP_STRIP_DEBUG=true - Name: ktop -Version: 0.3.7 +Version: 0.4.1 Release: 0 Summary: A top-like tool for your Kubernetes clusters License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.jx25YO/_old 2025-07-07 14:47:44.963406438 +0200 +++ /var/tmp/diff_new_pack.jx25YO/_new 2025-07-07 14:47:44.967406605 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/vladimirvivien/ktop</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.3.7</param> + <param name="revision">v0.4.1</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">enable</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.jx25YO/_old 2025-07-07 14:47:45.019408776 +0200 +++ /var/tmp/diff_new_pack.jx25YO/_new 2025-07-07 14:47:45.027409109 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/vladimirvivien/ktop</param> - <param name="changesrevision">9ade4f20ee7a48f3c8ad871fb9db5a02519b65f5</param></service></servicedata> + <param name="changesrevision">5a4e6f7a9d4bd28ef200958d8d1c80d4250cf67f</param></service></servicedata> (No newline at EOF) ++++++ ktop-0.3.7.obscpio -> ktop-0.4.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ktop-0.3.7/.github/workflows/release.yaml new/ktop-0.4.1/.github/workflows/release.yaml --- old/ktop-0.3.7/.github/workflows/release.yaml 2024-07-26 01:48:40.000000000 +0200 +++ new/ktop-0.4.1/.github/workflows/release.yaml 1970-01-01 01:00:00.000000000 +0100 @@ -1,27 +0,0 @@ -name: ktop Release - -on: - push: - tags: - - 'v*.*.**' - -jobs: - go-release: - name: goreleaser-release - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: 'stable' - - name: Binary release - uses: goreleaser/goreleaser-action@v6 - with: - version: latest - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.RELEASER_SECRET }} - - name: Update new version in krew-index - uses: rajatjindal/krew-release-bot@v0.0.46 \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ktop-0.3.7/.gitignore new/ktop-0.4.1/.gitignore --- old/ktop-0.3.7/.gitignore 2024-07-26 01:48:40.000000000 +0200 +++ new/ktop-0.4.1/.gitignore 1970-01-01 01:00:00.000000000 +0100 @@ -1,8 +0,0 @@ -vendor -.idea -.vscode -debug -ktop -.build -.DS_Store -dist diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ktop-0.3.7/.goreleaser.yml new/ktop-0.4.1/.goreleaser.yml --- old/ktop-0.3.7/.goreleaser.yml 2024-07-26 01:48:40.000000000 +0200 +++ new/ktop-0.4.1/.goreleaser.yml 2025-07-05 00:43:45.000000000 +0200 @@ -60,20 +60,20 @@ archives: - id: ktop - builds: + ids: - ktop name_template: '{{ .Binary }}_{{ .Tag }}_{{ .Os }}_{{ .Arch }}{{ if .Arm}}v{{ .Arm }}{{ end }}' wrap_in_directory: false - format: tar.gz + formats: tar.gz files: - LICENSE - id: kubectl-ktop - builds: + ids: - kubectl-ktop name_template: '{{ .Binary }}_{{ .Tag }}_{{ .Os }}_{{ .Arch }}{{ if .Arm}}v{{ .Arm }}{{ end }}' wrap_in_directory: false - format: tar.gz + formats: tar.gz files: - LICENSE @@ -97,28 +97,29 @@ skip_upload: true # Homebrew -brews: +homebrew_casks: - name: ktop ids: - ktop + binary: ktop repository: owner: vladimirvivien name: homebrew-oss-tools branch: main commit_author: name: vladimirvivien - email: vladimir.viv...@gmail.com + email: 309126+vladimirviv...@users.noreply.github.com homepage: https://github.com/vladimirvivien/ktop description: A top-like tool to display kubernetes workload metrics license: "Apache-2.0 license" skip_upload: auto # ko-generated images - kos: - id: ko-ktop build: ktop - repository: ghcr.io/vladimirvivien/ktop + repositories: + - ghcr.io/vladimirvivien/ktop tags: - '{{.Version}}' - latest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ktop-0.3.7/README.md new/ktop-0.4.1/README.md --- old/ktop-0.3.7/README.md 2024-07-26 01:48:40.000000000 +0200 +++ new/ktop-0.4.1/README.md 2025-07-05 00:43:45.000000000 +0200 @@ -111,11 +111,14 @@ --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure --kubeconfig string Path to the kubeconfig file to use for CLI requests. -n, --namespace string If present, the namespace scope for this CLI request + --node-columns string Comma-separated list of node columns to display (e.g. 'NAME,CPU,MEM') + --pod-columns string Comma-separated list of pod columns to display (e.g. 'NAMESPACE,POD,STATUS') --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") -s, --server string The address and port of the Kubernetes API server + --show-all-columns If true, show all columns (default true) --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used --token string Bearer token for authentication to the API server - --user string The name of the kubeconfig user to use``` + --user string The name of the kubeconfig user to use ``` For instance, the following will show cluster information for workload resources associated with namespace `my-app` in context `web-cluster` using the default kubconfig file path: @@ -124,6 +127,53 @@ ktop --namespace my-app --context web-cluster ``` +### Column Filtering + +You can customize which columns are displayed in the nodes and pods tables. This is useful when you want to focus on specific metrics or when working with limited screen space. + +To show only specific node columns: + +``` +ktop --node-columns NAME,CPU,MEM +``` + +To show only specific pod columns: + +``` +ktop --pod-columns NAMESPACE,POD,CPU,MEMORY +``` + +You can combine both filters: + +``` +ktop --node-columns NAME,CPU,MEM --pod-columns NAMESPACE,POD,STATUS +``` + +Available node columns: +- NAME +- STATUS +- AGE +- VERSION +- INT/EXT IPs +- OS/ARC +- PODS/IMGs +- DISK +- CPU +- MEM + +Available pod columns: +- NAMESPACE +- POD +- READY +- STATUS +- RESTARTS +- AGE +- VOLS +- IP +- NODE +- CPU +- MEMORY + ## ktop metrics The ktop UI provides several metrics including a high-level summary of workload components installed on your cluster: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ktop-0.3.7/cmd/ktop.go new/ktop-0.4.1/cmd/ktop.go --- old/ktop-0.3.7/cmd/ktop.go 2024-07-26 01:48:40.000000000 +0200 +++ new/ktop-0.4.1/cmd/ktop.go 2025-07-05 00:43:45.000000000 +0200 @@ -31,12 +31,15 @@ ) type ktopCmdOptions struct { - namespace string - allNamespaces bool - context string - kubeconfig string - kubeFlags *genericclioptions.ConfigFlags - page string // future use + namespace string + allNamespaces bool + context string + kubeconfig string + kubeFlags *genericclioptions.ConfigFlags + page string // future use + nodeColumns string // comma-separated list of node columns to display + podColumns string // comma-separated list of pod columns to display + showAllColumns bool // show all columns } // NewKtopCmd returns a command for ktop @@ -60,6 +63,9 @@ }, } cmd.Flags().BoolVarP(&o.allNamespaces, "all-namespaces", "A", false, "If true, display metrics for all accessible namespaces") + cmd.Flags().StringVar(&o.nodeColumns, "node-columns", "", "Comma-separated list of node columns to display (e.g. 'NAME,CPU,MEM')") + cmd.Flags().StringVar(&o.podColumns, "pod-columns", "", "Comma-separated list of pod columns to display (e.g. 'NAMESPACE,POD,STATUS')") + cmd.Flags().BoolVar(&o.showAllColumns, "show-all-columns", true, "If true, show all columns (default)") o.kubeFlags.AddFlags(cmd.Flags()) return cmd } @@ -80,7 +86,22 @@ app := application.New(k8sC) app.WelcomeBanner() - app.AddPage(overview.New(app, "Overview")) + + // Process column options + nodeColumns := []string{} + if o.nodeColumns != "" { + nodeColumns = strings.Split(o.nodeColumns, ",") + o.showAllColumns = false + } + + podColumns := []string{} + if o.podColumns != "" { + podColumns = strings.Split(o.podColumns, ",") + o.showAllColumns = false + } + + // Create a new overview page with column options + app.AddPage(overview.NewWithColumnOptions(app, "Overview", o.showAllColumns, nodeColumns, podColumns)) if err := k8sC.AssertCoreAuthz(ctx); err != nil { return fmt.Errorf("ktop: %s", err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ktop-0.3.7/go.mod new/ktop-0.4.1/go.mod --- old/ktop-0.3.7/go.mod 2024-07-26 01:48:40.000000000 +0200 +++ new/ktop-0.4.1/go.mod 2025-07-05 00:43:45.000000000 +0200 @@ -61,12 +61,12 @@ github.com/stretchr/testify v1.7.0 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.23.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect @@ -82,4 +82,6 @@ sigs.k8s.io/yaml v1.2.0 // indirect ) -go 1.18 +go 1.24.0 + +toolchain go1.24.4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ktop-0.3.7/go.sum new/ktop-0.4.1/go.sum --- old/ktop-0.3.7/go.sum 2024-07-26 01:48:40.000000000 +0200 +++ new/ktop-0.4.1/go.sum 2025-07-05 00:43:45.000000000 +0200 @@ -426,8 +426,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -505,8 +505,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -585,14 +585,14 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -602,8 +602,8 @@ golang.org/x/text v0.3.5/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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ktop-0.3.7/views/overview/main_panel.go new/ktop-0.4.1/views/overview/main_panel.go --- old/ktop-0.3.7/views/overview/main_panel.go 2024-07-26 01:48:40.000000000 +0200 +++ new/ktop-0.4.1/views/overview/main_panel.go 2025-07-05 00:43:45.000000000 +0200 @@ -3,6 +3,7 @@ import ( "context" "fmt" + "strings" "time" "github.com/rivo/tview" @@ -21,29 +22,59 @@ nodePanel ui.Panel podPanel ui.Panel clusterSummaryPanel ui.Panel + showAllColumns bool + nodeColumns []string + podColumns []string } func New(app *application.Application, title string) *MainPanel { + return NewWithColumnOptions(app, title, true, nil, nil) +} + +func NewWithColumnOptions(app *application.Application, title string, showAllColumns bool, nodeColumns, podColumns []string) *MainPanel { ctrl := &MainPanel{ - app: app, - title: title, - refresh: app.Refresh, - selPanelIndex: -1, + app: app, + title: title, + refresh: app.Refresh, + selPanelIndex: -1, + showAllColumns: showAllColumns, + nodeColumns: nodeColumns, + podColumns: podColumns, } return ctrl } func (p *MainPanel) Layout(data interface{}) { + // Define the default columns + allNodeColumns := []string{"NAME", "STATUS", "AGE", "VERSION", "INT/EXT IPs", "OS/ARC", "PODS/IMGs", "DISK", "CPU", "MEM"} + allPodColumns := []string{"NAMESPACE", "POD", "READY", "STATUS", "RESTARTS", "AGE", "VOLS", "IP", "NODE", "CPU", "MEMORY"} + + // Use filtered columns if specified + nodeColumnsToDisplay := allNodeColumns + podColumnsToDisplay := allPodColumns + + if !p.showAllColumns { + if len(p.nodeColumns) > 0 { + // Filter node columns + nodeColumnsToDisplay = filterColumns(allNodeColumns, p.nodeColumns) + } + + if len(p.podColumns) > 0 { + // Filter pod columns + podColumnsToDisplay = filterColumns(allPodColumns, p.podColumns) + } + } + p.nodePanel = NewNodePanel(p.app, fmt.Sprintf(" %c Nodes ", ui.Icons.Factory)) - p.nodePanel.DrawHeader([]string{"NAME", "STATUS", "AGE", "VERSION", "INT/EXT IPs", "OS/ARC", "PODS/IMGs", "DISK", "CPU", "MEM"}) + p.nodePanel.DrawHeader(nodeColumnsToDisplay) p.clusterSummaryPanel = NewClusterSummaryPanel(p.app, fmt.Sprintf(" %c Cluster Summary ", ui.Icons.Thermometer)) p.clusterSummaryPanel.Layout(nil) p.clusterSummaryPanel.DrawHeader(nil) p.podPanel = NewPodPanel(p.app, fmt.Sprintf(" %c Pods ", ui.Icons.Package)) - p.podPanel.DrawHeader([]string{"NAMESPACE", "POD", "READY", "STATUS", "RESTARTS", "AGE", "VOLS", "IP", "NODE", "CPU", "MEMORY"}) + p.podPanel.DrawHeader(podColumnsToDisplay) p.children = []tview.Primitive{ p.clusterSummaryPanel.GetRootView(), @@ -123,3 +154,28 @@ } return nil } + +// filterColumns filters the allColumns based on the user-provided filterCols +// It returns a slice of columns that match the case-insensitive filter +func filterColumns(allColumns []string, filterCols []string) []string { + if len(filterCols) == 0 { + return allColumns + } + + result := []string{} + for _, col := range allColumns { + for _, filterCol := range filterCols { + if strings.EqualFold(col, filterCol) { + result = append(result, col) + break + } + } + } + + // If no matches found, return at least the first column (usually NAME) + if len(result) == 0 && len(allColumns) > 0 { + return []string{allColumns[0]} + } + + return result +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ktop-0.3.7/views/overview/node_panel.go new/ktop-0.4.1/views/overview/node_panel.go --- old/ktop-0.3.7/views/overview/node_panel.go 2024-07-26 01:48:40.000000000 +0200 +++ new/ktop-0.4.1/views/overview/node_panel.go 2025-07-05 00:43:45.000000000 +0200 @@ -18,7 +18,8 @@ children []tview.Primitive listCols []string list *tview.Table - laidout bool + laidout bool + colMap map[string]int // Maps column name to position index } func NewNodePanel(app *application.Application, title string) ui.Panel { @@ -58,7 +59,10 @@ panic(fmt.Sprintf("nodePanel.DrawHeader got unexpected data type %T", data)) } - // legend column + // Initialize a new column map + p.colMap = make(map[string]int) + + // Reserve index 0 for the legend column p.list.SetCell(0, 0, tview.NewTableCell(""). SetTextColor(tcell.ColorWhite). @@ -70,6 +74,8 @@ ) p.listCols = cols + + // Set column headers and build column map for i, col := range p.listCols { pos := i + 1 p.list.SetCell(0, pos, @@ -80,8 +86,10 @@ SetExpansion(100). SetSelectable(false), ) + + // Map column name to its position + p.colMap[col] = pos } - } func (p *nodePanel) DrawBody(data interface{}) { @@ -100,16 +108,17 @@ p.root.SetTitle(fmt.Sprintf("%s(%d) ", p.GetTitle(), len(nodes))) p.root.SetTitleAlign(tview.AlignLeft) - for i, node := range nodes { - i++ // offset for header-row + for rowIdx, node := range nodes { + rowIdx++ // offset for header-row + + // Always render the legend column controlLegend := "" if node.Controller { controlLegend = fmt.Sprintf("%c", ui.Icons.TrafficLight) } - - // legend + p.list.SetCell( - i, 0, + rowIdx, 0, &tview.TableCell{ Text: controlLegend, Color: tcell.ColorOrangeRed, @@ -117,128 +126,150 @@ NotSelectable: true, }, ) - - // name - p.list.SetCell( - i, 1, - &tview.TableCell{ - Text: node.Name, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 2, - &tview.TableCell{ - Text: node.Status, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 3, - &tview.TableCell{ - Text: node.TimeSinceStart, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 4, - &tview.TableCell{ - Text: node.KubeletVersion, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 5, - &tview.TableCell{ - Text: fmt.Sprintf("%s/%s", node.InternalIP, node.ExternalIP), - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 6, - &tview.TableCell{ - Text: fmt.Sprintf("%s/%s", node.OSImage, node.Architecture), - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 7, - &tview.TableCell{ - Text: fmt.Sprintf("%d/%d", node.PodsCount, node.ContainerImagesCount), - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - // Disk - p.list.SetCell( - i, 8, - &tview.TableCell{ - Text: fmt.Sprintf("%dGi", node.AllocatableStorageQty.ScaledValue(resource.Giga)), - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - if metricsDiabled { - cpuRatio = ui.GetRatio(float64(node.RequestedPodCpuQty.MilliValue()), float64(node.AllocatableCpuQty.MilliValue())) - cpuGraph = ui.BarGraph(10, cpuRatio, colorKeys) - cpuMetrics = fmt.Sprintf( - "[white][%s[white]] %dm/%dm (%1.0f%%)", - cpuGraph, node.RequestedPodCpuQty.MilliValue(), node.AllocatableCpuQty.MilliValue(), cpuRatio*100, - ) - - memRatio = ui.GetRatio(float64(node.RequestedPodMemQty.MilliValue()), float64(node.AllocatableMemQty.MilliValue())) - memGraph = ui.BarGraph(10, memRatio, colorKeys) - memMetrics = fmt.Sprintf( - "[white][%s[white]] %dGi/%dGi (%1.0f%%)", - memGraph, node.RequestedPodMemQty.ScaledValue(resource.Giga), node.AllocatableMemQty.ScaledValue(resource.Giga), memRatio*100, - ) - } else { - cpuRatio = ui.GetRatio(float64(node.UsageCpuQty.MilliValue()), float64(node.AllocatableCpuQty.MilliValue())) - cpuGraph = ui.BarGraph(10, cpuRatio, colorKeys) - cpuMetrics = fmt.Sprintf( - "[white][%s[white]] %dm/%dm (%1.0f%%)", - cpuGraph, node.UsageCpuQty.MilliValue(), node.AllocatableCpuQty.MilliValue(), cpuRatio*100, - ) - - memRatio = ui.GetRatio(float64(node.UsageMemQty.MilliValue()), float64(node.AllocatableMemQty.MilliValue())) - memGraph = ui.BarGraph(10, memRatio, colorKeys) - memMetrics = fmt.Sprintf( - "[white][%s[white]] %dGi/%dGi (%1.0f%%)", - memGraph, node.UsageMemQty.ScaledValue(resource.Giga), node.AllocatableMemQty.ScaledValue(resource.Giga), memRatio*100, - ) + + // Render each column that is included in the filtered view + for _, colName := range p.listCols { + colIdx, exists := p.colMap[colName] + if !exists { + continue + } + + switch colName { + case "NAME": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: node.Name, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "STATUS": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: node.Status, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "AGE": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: node.TimeSinceStart, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "VERSION": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: node.KubeletVersion, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "INT/EXT IPs": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: fmt.Sprintf("%s/%s", node.InternalIP, node.ExternalIP), + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "OS/ARC": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: fmt.Sprintf("%s/%s", node.OSImage, node.Architecture), + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "PODS/IMGs": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: fmt.Sprintf("%d/%d", node.PodsCount, node.ContainerImagesCount), + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "DISK": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: fmt.Sprintf("%dGi", node.AllocatableStorageQty.ScaledValue(resource.Giga)), + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "CPU": + // Calculate CPU metrics + if metricsDiabled { + cpuRatio = ui.GetRatio(float64(node.RequestedPodCpuQty.MilliValue()), float64(node.AllocatableCpuQty.MilliValue())) + cpuGraph = ui.BarGraph(10, cpuRatio, colorKeys) + cpuMetrics = fmt.Sprintf( + "[white][%s[white]] %dm/%dm (%1.0f%%)", + cpuGraph, node.RequestedPodCpuQty.MilliValue(), node.AllocatableCpuQty.MilliValue(), cpuRatio*100, + ) + } else { + cpuRatio = ui.GetRatio(float64(node.UsageCpuQty.MilliValue()), float64(node.AllocatableCpuQty.MilliValue())) + cpuGraph = ui.BarGraph(10, cpuRatio, colorKeys) + cpuMetrics = fmt.Sprintf( + "[white][%s[white]] %dm/%dm (%1.0f%%)", + cpuGraph, node.UsageCpuQty.MilliValue(), node.AllocatableCpuQty.MilliValue(), cpuRatio*100, + ) + } + + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: cpuMetrics, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "MEM": + // Calculate memory metrics + if metricsDiabled { + memRatio = ui.GetRatio(float64(node.RequestedPodMemQty.MilliValue()), float64(node.AllocatableMemQty.MilliValue())) + memGraph = ui.BarGraph(10, memRatio, colorKeys) + memMetrics = fmt.Sprintf( + "[white][%s[white]] %dGi/%dGi (%1.0f%%)", + memGraph, node.RequestedPodMemQty.ScaledValue(resource.Giga), node.AllocatableMemQty.ScaledValue(resource.Giga), memRatio*100, + ) + } else { + memRatio = ui.GetRatio(float64(node.UsageMemQty.MilliValue()), float64(node.AllocatableMemQty.MilliValue())) + memGraph = ui.BarGraph(10, memRatio, colorKeys) + memMetrics = fmt.Sprintf( + "[white][%s[white]] %dGi/%dGi (%1.0f%%)", + memGraph, node.UsageMemQty.ScaledValue(resource.Giga), node.AllocatableMemQty.ScaledValue(resource.Giga), memRatio*100, + ) + } + + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: memMetrics, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + } } - - p.list.SetCell( - i, 9, - &tview.TableCell{ - Text: cpuMetrics, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 10, - &tview.TableCell{ - Text: memMetrics, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) } } @@ -256,4 +287,4 @@ func (p *nodePanel) GetChildrenViews() []tview.Primitive { return p.children -} +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ktop-0.3.7/views/overview/pod_panel.go new/ktop-0.4.1/views/overview/pod_panel.go --- old/ktop-0.3.7/views/overview/pod_panel.go 2024-07-26 01:48:40.000000000 +0200 +++ new/ktop-0.4.1/views/overview/pod_panel.go 2025-07-05 00:43:45.000000000 +0200 @@ -18,7 +18,8 @@ children []tview.Primitive listCols []string list *tview.Table - laidout bool + laidout bool + colMap map[string]int // Maps column name to position index } func NewPodPanel(app *application.Application, title string) ui.Panel { @@ -61,7 +62,11 @@ panic(fmt.Sprintf("podPanel.DrawBody got unexpected data type %T", data)) } + // Initialize the column map + p.colMap = make(map[string]int) p.listCols = cols + + // Set column headers and build column map for i, col := range p.listCols { p.list.SetCell(0, i, tview.NewTableCell(col). @@ -71,6 +76,9 @@ SetExpansion(100). SetSelectable(false), ) + + // Map column name to position + p.colMap[col] = i } p.list.SetFixed(1, 0) } @@ -91,134 +99,171 @@ p.root.SetTitle(fmt.Sprintf("%s(%d) ", p.GetTitle(), len(pods))) p.root.SetTitleAlign(tview.AlignLeft) - for i, pod := range pods { - i++ // offset to n+1 - p.list.SetCell( - i, 0, - &tview.TableCell{ - Text: pod.Namespace, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 1, - &tview.TableCell{ - Text: pod.Name, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 2, - &tview.TableCell{ - Text: fmt.Sprintf("%d/%d", pod.ReadyContainers, pod.TotalContainers), - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 3, - &tview.TableCell{ - Text: pod.Status, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 4, - &tview.TableCell{ - Text: fmt.Sprintf("%d", pod.Restarts), - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 5, - &tview.TableCell{ - Text: pod.TimeSince, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - // Volume - p.list.SetCell( - i, 6, - &tview.TableCell{ - Text: fmt.Sprintf("%d/%d", pod.Volumes, pod.VolMounts), - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 7, - &tview.TableCell{ - Text: pod.IP, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 8, - &tview.TableCell{ - Text: pod.Node, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - if metricsDisabled { - cpuRatio = ui.GetRatio(float64(pod.PodRequestedCpuQty.MilliValue()), float64(pod.NodeAllocatableCpuQty.MilliValue())) - cpuGraph = ui.BarGraph(10, cpuRatio, colorKeys) - cpuMetrics = fmt.Sprintf( - "[white][%s[white]] %dm %02.1f%%", - cpuGraph, pod.PodRequestedCpuQty.MilliValue(), cpuRatio*100, - ) - - memRatio = ui.GetRatio(float64(pod.PodRequestedMemQty.MilliValue()), float64(pod.NodeAllocatableMemQty.MilliValue())) - memGraph = ui.BarGraph(10, memRatio, colorKeys) - memMetrics = fmt.Sprintf( - "[white][%s[white]] %dGi %02.1f%%", memGraph, pod.PodRequestedMemQty.ScaledValue(resource.Giga), memRatio*100, - ) - } else { - cpuRatio = ui.GetRatio(float64(pod.PodUsageCpuQty.MilliValue()), float64(pod.NodeAllocatableCpuQty.MilliValue())) - cpuGraph = ui.BarGraph(10, cpuRatio, colorKeys) - cpuMetrics = fmt.Sprintf("[white][%s[white]] %dm %02.1f%%", cpuGraph, pod.PodUsageCpuQty.MilliValue(), cpuRatio*100) - - memRatio = ui.GetRatio(float64(pod.PodUsageMemQty.MilliValue()), float64(pod.NodeUsageMemQty.MilliValue())) - memGraph = ui.BarGraph(10, memRatio, colorKeys) - memMetrics = fmt.Sprintf("[white][%s[white]] %dMi %02.1f%%", memGraph, pod.PodUsageMemQty.ScaledValue(resource.Mega), memRatio*100) + for rowIdx, pod := range pods { + rowIdx++ // offset for header row + + // Render each column that is included in the filtered view + for _, colName := range p.listCols { + colIdx, exists := p.colMap[colName] + if !exists { + continue + } + + switch colName { + case "NAMESPACE": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: pod.Namespace, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "POD": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: pod.Name, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "READY": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: fmt.Sprintf("%d/%d", pod.ReadyContainers, pod.TotalContainers), + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "STATUS": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: pod.Status, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "RESTARTS": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: fmt.Sprintf("%d", pod.Restarts), + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "AGE": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: pod.TimeSince, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "VOLS": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: fmt.Sprintf("%d", pod.Volumes), + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "IP": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: pod.IP, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "NODE": + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: pod.Node, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + + case "CPU": + if metricsDisabled { + // no CPU metrics + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: "unavailable", + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + } else { + cpuRatio = ui.GetRatio(float64(pod.PodUsageCpuQty.MilliValue()), float64(pod.PodRequestedCpuQty.MilliValue())) + cpuGraph = ui.BarGraph(10, cpuRatio, colorKeys) + cpuMetrics = fmt.Sprintf( + "[white][%s[white]] %dm/%dm (%1.0f%%)", + cpuGraph, pod.PodUsageCpuQty.MilliValue(), pod.PodRequestedCpuQty.MilliValue(), cpuRatio*100, + ) + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: cpuMetrics, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + } + + case "MEMORY": + if metricsDisabled { + // no Memory metrics + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: "unavailable", + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + } else { + memRatio = ui.GetRatio(float64(pod.PodUsageMemQty.Value()), float64(pod.PodRequestedMemQty.Value())) + memGraph = ui.BarGraph(10, memRatio, colorKeys) + memMetrics = fmt.Sprintf( + "[white][%s[white]] %dMi/%dMi (%1.0f%%)", + memGraph, + pod.PodUsageMemQty.ScaledValue(resource.Mega), + pod.PodRequestedMemQty.ScaledValue(resource.Mega), + memRatio*100, + ) + p.list.SetCell( + rowIdx, colIdx, + &tview.TableCell{ + Text: memMetrics, + Color: tcell.ColorYellow, + Align: tview.AlignLeft, + }, + ) + } + } } - - p.list.SetCell( - i, 9, - &tview.TableCell{ - Text: cpuMetrics, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) - - p.list.SetCell( - i, 10, - &tview.TableCell{ - Text: memMetrics, - Color: tcell.ColorYellow, - Align: tview.AlignLeft, - }, - ) } } -func (p *podPanel) DrawFooter(data interface{}) {} +func (p *podPanel) DrawFooter(_ interface{}) {} func (p *podPanel) Clear() { p.list.Clear() @@ -232,4 +277,4 @@ func (p *podPanel) GetChildrenViews() []tview.Primitive { return p.children -} +} \ No newline at end of file ++++++ ktop.obsinfo ++++++ --- /var/tmp/diff_new_pack.jx25YO/_old 2025-07-07 14:47:45.231417627 +0200 +++ /var/tmp/diff_new_pack.jx25YO/_new 2025-07-07 14:47:45.235417794 +0200 @@ -1,5 +1,5 @@ name: ktop -version: 0.3.7 -mtime: 1721951320 -commit: 9ade4f20ee7a48f3c8ad871fb9db5a02519b65f5 +version: 0.4.1 +mtime: 1751669025 +commit: 5a4e6f7a9d4bd28ef200958d8d1c80d4250cf67f ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/ktop/vendor.tar.gz /work/SRC/openSUSE:Factory/.ktop.new.1903/vendor.tar.gz differ: char 5, line 1