Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kubetui for openSUSE:Factory checked in at 2025-06-10 09:08:21 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kubetui (Old) and /work/SRC/openSUSE:Factory/.kubetui.new.19631 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kubetui" Tue Jun 10 09:08:21 2025 rev:11 rq:1284248 version:1.8.1 Changes: -------- --- /work/SRC/openSUSE:Factory/kubetui/kubetui.changes 2025-05-12 16:51:26.999149055 +0200 +++ /work/SRC/openSUSE:Factory/.kubetui.new.19631/kubetui.changes 2025-06-10 09:10:38.352714745 +0200 @@ -1,0 +2,40 @@ +Mon Jun 09 15:32:57 UTC 2025 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- Update to version 1.8.1: + * Fixed + - Updated README.md to document the --pod-columns feature + introduced in v1.8.0. + - Added usage examples for --pod-columns. + - Clarified behavior of the full keyword and required Name + column. + - No functional changes to the application. + * What's Changed + - Update docs by @sarub0b0 in #777 +- Update to version 1.8.0: + * Added + - --pod-columns flag to customize displayed pod columns + (name,status,age, etc.). + - Support for full keyword to show all available columns. + - ZSH/Bash completion for --pod-columns. + - Panic hook logging for better error traceability. + * Fixed + - Better error handling in internal column lookup logic. + * Dependencies + - Updated crates: flate2, clap, tokio, bitflags, ctrlc. + * What's Changed + - fix(deps): update rust crate clap to v4.5.38 by @renovate in + #768 + - fix(deps): update rust crate ctrlc to v3.4.7 by @renovate in + #769 + - fix(deps): update rust crate bitflags to v2.9.1 by @renovate + in #770 + - fix(deps): update rust crate tokio to v1.45.1 by @renovate in + #773 + - fix(deps): update rust crate clap to v4.5.39 by @renovate in + #774 + - fix(deps): update rust crate flate2 to v1.1.2 by @renovate in + #775 + - Add customizable pod columns via --pod-columns flag by + @sarub0b0 in #776 + +------------------------------------------------------------------- Old: ---- kubetui-1.7.1.obscpio New: ---- kubetui-1.8.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kubetui.spec ++++++ --- /var/tmp/diff_new_pack.n7lQKb/_old 2025-06-10 09:10:41.592848730 +0200 +++ /var/tmp/diff_new_pack.n7lQKb/_new 2025-06-10 09:10:41.592848730 +0200 @@ -17,7 +17,7 @@ Name: kubetui -Version: 1.7.1 +Version: 1.8.1 Release: 0 Summary: A terminal UI for Kubernetes License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.n7lQKb/_old 2025-06-10 09:10:41.640850716 +0200 +++ /var/tmp/diff_new_pack.n7lQKb/_new 2025-06-10 09:10:41.640850716 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/sarub0b0/kubetui.git</param> <param name="versionformat">@PARENT_TAG@</param> <param name="scm">git</param> - <param name="revision">v1.7.1</param> + <param name="revision">v1.8.1</param> <param name="match-tag">*</param> <param name="versionrewrite-pattern">v(\d+\.\d+\.\d+)</param> <param name="versionrewrite-replacement">\1</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.n7lQKb/_old 2025-06-10 09:10:41.660851543 +0200 +++ /var/tmp/diff_new_pack.n7lQKb/_new 2025-06-10 09:10:41.664851709 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/sarub0b0/kubetui.git</param> - <param name="changesrevision">95db426560374d95073205adf48d22708c583063</param></service></servicedata> + <param name="changesrevision">09111b9d4a84f78b5153a3e156acda72a0eb938a</param></service></servicedata> (No newline at EOF) ++++++ kubetui-1.7.1.obscpio -> kubetui-1.8.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/Cargo.lock new/kubetui-1.8.1/Cargo.lock --- old/kubetui-1.7.1/Cargo.lock 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/Cargo.lock 2025-06-08 19:42:14.000000000 +0200 @@ -181,9 +181,9 @@ [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -268,9 +268,9 @@ [[package]] name = "clap" -version = "4.5.37" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" dependencies = [ "clap_builder", "clap_derive", @@ -278,9 +278,9 @@ [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" dependencies = [ "anstream", "anstyle", @@ -474,9 +474,9 @@ [[package]] name = "ctrlc" -version = "3.4.6" +version = "3.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" dependencies = [ "nix", "windows-sys 0.59.0", @@ -624,7 +624,7 @@ checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -661,9 +661,9 @@ [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -1246,7 +1246,7 @@ [[package]] name = "kubetui" -version = "1.7.1" +version = "1.8.1" dependencies = [ "anyhow", "arboard", @@ -1453,9 +1453,9 @@ [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", @@ -2017,7 +2017,7 @@ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2030,7 +2030,7 @@ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2453,7 +2453,7 @@ "getrandom 0.3.2", "once_cell", "rustix 1.0.7", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2534,9 +2534,9 @@ [[package]] name = "tokio" -version = "1.45.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/Cargo.toml new/kubetui-1.8.1/Cargo.toml --- old/kubetui-1.7.1/Cargo.toml 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/Cargo.toml 2025-06-08 19:42:14.000000000 +0200 @@ -1,6 +1,6 @@ [package] name = "kubetui" -version = "1.7.1" +version = "1.8.1" authors = ["kosay <ekr59u...@gmail.com>"] edition = "2021" license = "MIT" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/README.md new/kubetui-1.8.1/README.md --- old/kubetui-1.7.1/README.md 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/README.md 2025-06-08 19:42:14.000000000 +0200 @@ -20,6 +20,8 @@ - [Using `cargo install`](#using-cargo-install) - [Downloading the binary](#downloading-the-binary) - [Usage](#usage) + - [Pod Column Customization](#pod-column-customization) + - [Shell Completion](#shell-completion) - [Custom Configuration](#custom-configuration) - [Log Query](#log-query) - [Usage Example](#usage-example) @@ -34,6 +36,7 @@ - [Table View](#table-view) - [Dialog](#dialog) - [Input Form](#input-form) + - [Container Logs View](#container-logs-view) - [Contributing](#contributing) - [License](#license) @@ -55,6 +58,7 @@ - **Pods List and Container Logs**: - View a list of pods and their container logs. - JSON logs display mode switching: toggle between pretty print and single-line display using the <kbd>f</kbd> or <kbd>p</kbd> keys. + - **(new) Customizable pod columns**: Use `--pod-columns` to control which columns are shown in the pod table view. Supports comma-separated lists or `full` to show all columns. - **ConfigMap and Secret Watching**: Monitor ConfigMaps and secrets, and decode their data. - **Network-related Resources**: Explore a list of network-related resources and their descriptions. - **Events Watching**: Stay updated with a real-time view of Kubernetes events. @@ -166,9 +170,25 @@ --config-file <CONFIG_FILE> Config file path -l, --logging Logging -n, --namespaces <NAMESPACES> Namespaces (e.g. -n val1,val2,val3 | -n val1 -n val2 -n val3) + --pod-columns <POD_COLUMNS> Comma-separated list of columns to show in pod table (e.g. name,status,ip). Use "full" to show all available columns [default: name,ready,status,age] -s, --split-direction <v|h> Window split direction [default: v] ``` +### Pod Column Customization + +Use `--pod-columns` to customize the columns displayed in the pod table. + +- Specify columns using a comma-separated list: + `--pod-columns=ready,status,age` + +- Use `full` to show all available columns: + `--pod-columns=full` + +Notes: +- The `Name` column is always included even if not specified. +- The `full` keyword cannot be combined with other columns. + + ### Shell Completion Kubetui supports shell completion for Bash and Zsh. You can enable the completion by adding the following to your shell configuration file: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/shell-completion/kubetui.bash new/kubetui-1.8.1/shell-completion/kubetui.bash --- old/kubetui-1.7.1/shell-completion/kubetui.bash 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/shell-completion/kubetui.bash 2025-06-08 19:42:14.000000000 +0200 @@ -14,6 +14,7 @@ "-c" "--context" "-C" "--kubeconfig" "--config-file" + "--pod-columns" "-l" "--logging" "-n" "--namespaces" "-s" "--split-direction" @@ -52,6 +53,10 @@ COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --pod-columns) + __kubetui_pod_columns "${cur}" + return 0 + ;; esac COMPREPLY=($(compgen -W "${options[*]}" -- "${cur}")) @@ -101,10 +106,91 @@ __kubetui_get_kubernetes_resources "context" } +__kubetui_pod_columns() { + local cur="$1" + + local all_values=(name ready status restarts age ip node nominatednode readinessgates full) + + local old_ifs="$IFS" + IFS=',' read -ra used <<<"$cur" + IFS="$old_ifs" + + local last="" + if [[ "$cur" == *, ]]; then + last="" + elif [[ ${#used[@]} -gt 0 ]]; then + last="${used[${#used[@]} - 1]}" + fi + + for u in "${used[@]}"; do + [[ "$u" == "full" ]] && return 0 + done + + # すでに何か指定済みの場合、候補から full を除外 + if [[ ${#used[@]} -gt 1 || "$cur" == *,* ]]; then + local filtered=() + for val in "${all_values[@]}"; do + [[ "$val" == "full" ]] && continue + filtered+=("$val") + done + all_values=("${filtered[@]}") + fi + + local candidates=() + for val in "${all_values[@]}"; do + local found=false + for u in "${used[@]}"; do + [[ "$u" == "$val" ]] && { + found=true + break + } + done + ! $found && candidates+=("$val") + done + + COMPREPLY=($(compgen -W "${candidates[*]}" -- "$last")) + + if [[ ${#used[@]} -gt 1 || "$cur" == *, ]]; then + __kubetui_debug "Used values: ${used[*]}, length: ${#used[@]}" + local -a prefix_parts + local last="${used[-1]}" + local exact_match=false + for v in "${all_values[@]}"; do + if [[ "$v" == "$last" ]]; then + exact_match=true + break + fi + done + + if $exact_match; then + prefix_parts=("${used[@]}") + last="" + else + prefix_parts=("${used[@]:0:${#used[@]}-1}") + last="${used[-1]}" + fi + + local old_ifs="$IFS" + IFS=, + local prefix="${prefix_parts[*]}" + IFS="$old_ifs" + + __kubetui_debug "Prefix: '${prefix}'" + for i in "${!COMPREPLY[@]}"; do + if [[ -n "$prefix" ]]; then + COMPREPLY[$i]="${prefix},${COMPREPLY[$i]}" + fi + done + + __kubetui_debug "Updated COMPREPLY: ${COMPREPLY[*]}" + fi + +} + if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then complete -F _kubetui -o nosort kubetui else complete -F _kubetui kubetui fi -# ex: ts=4 sw=4 et filetype=sh +# vim: ts=4 sw=4 sts=4 noet filetype=sh diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/shell-completion/kubetui.zsh new/kubetui-1.8.1/shell-completion/kubetui.zsh --- old/kubetui-1.7.1/shell-completion/kubetui.zsh 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/shell-completion/kubetui.zsh 2025-06-08 19:42:14.000000000 +0200 @@ -30,6 +30,7 @@ '(-h --help)'{-h,--help}'[Print help]' '(-V --version)'{-V,--version}'[Print version]' '(-A --all-namespaces)'\*{-n,--namespaces}'[Namespaces (e.g. -n val1,val2,val3 | -n val1 -n val2 -n val3)]:NAMESPACES:_sequence __kubetui_get_kubernetes_namespaces' + '--pod-columns[Comma-separated list of columns to show in pod table (e.g. name,status,ip). Use "full" to show all available columns.]:POD_COLUMNS:_sequence __kubetui_pod_columns' '--config-file[Config file path]:CONFIG_FILE:_files' ) @@ -86,6 +87,74 @@ __kubetui_get_kubernetes_resources "context" } +(( $+functions[__kubetui_pod_columns] )) || +__kubetui_pod_columns() { + local pod_columns_values=( + name + ready + status + restarts + age + ip + node + nominatednode + readinessgates + ) + + ## 入力済み値を取得(カンマで分割) + local cur="${words[CURRENT]##*,}" + local used=("${(s:,:)words[CURRENT]}") + local last_param="${used[-1]}" + + __kubetui_debug "Current value: ${cur}" + __kubetui_debug "Used values: ${used[*]}, length: ${#used[@]}" + __kubetui_debug "Last parameter: ${last_param}" + + # usedの要素数が0のときpod_columns_valuesにfullを追加 + if [[ ${#used[@]} -eq 1 ]]; then + pod_columns_values+=("full") + fi + + __kubetui_debug "Pod columns values: ${pod_columns_values[*]}" + + # `full` が入っていたら補完を無効化 + if [[ "${used[*]}" =~ "full" ]]; then + _message -e 'pod columns' "The 'full' option is already selected, no further columns can be added." + return + fi + + # 候補にない値が入力さている場合は、処理を停止 + if [[ -n "${cur}" && ! "${pod_columns_values[*]}" =~ "${cur}" ]]; then + _message -e 'pod columns' "Invalid pod column: '${cur}'." + return + fi + + ## 補完候補リストから used を除外 + local -a filtered_values=() + for val in "${pod_columns_values[@]}"; do + local is_used=false + + for used_val in "${used[@]}"; do + if [[ "${val}" == "${used_val}" ]] && [[ "$last_param" != "${used_val}" ]]; then + is_used=true + break + fi + done + + if [[ "${is_used}" == false ]]; then + filtered_values+=("${val}") + fi + done + + __kubetui_debug "Filtered values: ${filtered_values[*]}" + + if [[ -n "${filtered_values[*]}" ]]; then + _describe -t 'pod columns' 'pod columns' filtered_values + else + _message -e 'pod columns' "No more pod columns available to add." + fi +} + if [ "$funcstack[1]" = "_kubetui" ]; then _kubetui "$@" else diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/src/app.rs new/kubetui-1.8.1/src/app.rs --- old/kubetui-1.7.1/src/app.rs 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/src/app.rs 2025-06-08 19:42:14.000000000 +0200 @@ -29,6 +29,7 @@ let user_input = UserInput::new(tx_input.clone(), tx_shutdown.clone()); kube_worker_config.pod_config = PodConfig::from(config.theme.clone()); + kube_worker_config.pod_config.columns = cmd.pod_columns.columns; kube_worker_config.event_config = EventConfig::from(config.theme.clone()); kube_worker_config.api_config = ApiConfig::from(config.theme.clone()); kube_worker_config.apis_config = ApisConfig::from(config.theme.clone()); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/src/cmd/args/pod_columns.rs new/kubetui-1.8.1/src/cmd/args/pod_columns.rs --- old/kubetui-1.7.1/src/cmd/args/pod_columns.rs 1970-01-01 01:00:00.000000000 +0100 +++ new/kubetui-1.8.1/src/cmd/args/pod_columns.rs 2025-06-08 19:42:14.000000000 +0200 @@ -0,0 +1,222 @@ +use std::collections::HashMap; + +use anyhow::Result; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PodColumns { + pub columns: Vec<&'static str>, +} + +impl PodColumns { + #[allow(dead_code)] + pub fn new(columns: impl IntoIterator<Item = &'static str>) -> Self { + Self { + columns: columns.into_iter().collect(), + } + } +} + +const COLUMN_MAP: [(&str, &str); 9] = [ + ("name", "Name"), + ("ready", "Ready"), + ("status", "Status"), + ("restarts", "Restarts"), + ("age", "Age"), + ("ip", "IP"), + ("node", "Node"), + ("nominatednode", "Nominated Node"), + ("readinessgates", "Readiness Gates"), +]; + +fn valid_columns() -> String { + COLUMN_MAP + .iter() + .map(|(k, _)| *k) + .collect::<Vec<&str>>() + .join(", ") +} + +pub fn parse_pod_columns(input: &str) -> Result<PodColumns> { + let entries: Vec<&str> = input + .split(',') + .map(str::trim) + .filter(|s| !s.is_empty()) + .collect(); + + if entries.is_empty() { + return Err(anyhow::anyhow!("Columns list must not be empty",)); + } + + let has_full = entries.iter().any(|e| normalize_column(e) == "full"); + if has_full && entries.len() > 1 { + return Err(anyhow::anyhow!( + "Cannot specify 'full' with other columns. Use 'full' alone to get all columns." + )); + } + + if entries.len() == 1 && has_full { + return Ok(PodColumns { + columns: COLUMN_MAP.iter().map(|(_, v)| *v).collect::<Vec<&str>>(), + }); + } + + let column_map: HashMap<&str, &str> = COLUMN_MAP.into_iter().collect(); + + let mut result = Vec::new(); + + for column in entries { + let normalized = normalize_column(column); + + if let Some(&display_name) = column_map.get(normalized.as_str()) { + result.push(display_name); + } else { + return Err(anyhow::anyhow!( + "Invalid column name: {}. Valid options are: {}", + column, + valid_columns() + )); + } + } + + if !result.contains(&"Name") { + result.insert(0, "Name"); + } + + Ok(PodColumns { columns: result }) +} + +fn normalize_column(column: &str) -> String { + column.to_lowercase().replace([' ', '_', '-'], "") +} + +#[cfg(test)] +mod tests { + use super::*; + + mod parse_pod_columns { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn 空文字列を渡すとパニックする() { + let input = ""; + let result = parse_pod_columns(input); + assert!(result.is_err()); + } + + #[test] + fn フルを渡すと全カラムを返す() { + let input = "full"; + let actual = parse_pod_columns(input).unwrap(); + let expected: Vec<String> = COLUMN_MAP.iter().map(|(_, v)| v.to_string()).collect(); + assert_eq!(actual.columns, expected); + } + + #[test] + fn カンマ区切りのカラム名を渡すと対応するカラム名を返す() { + let input = "name, ready, status"; + let actual = parse_pod_columns(input).unwrap(); + let expected = vec!["Name", "Ready", "Status"]; + assert_eq!(actual.columns, expected); + } + + #[test] + fn カラム名に空白が含まれていても正しく処理される() { + let input = " name , ready , status "; + let actual = parse_pod_columns(input).unwrap(); + let expected = vec!["Name", "Ready", "Status"]; + assert_eq!(actual.columns, expected); + } + + #[test] + fn アンダースコアやハイフンを含むカラム名も正しく処理される() { + let input = "name, nominated_node, readiness-gates"; + let actual = parse_pod_columns(input).unwrap(); + let expected = vec!["Name", "Nominated Node", "Readiness Gates"]; + assert_eq!(actual.columns, expected); + } + + #[test] + fn 無効なカラム名が含まれているとエラーを返す() { + let input = "name, invalid_column"; + let result = parse_pod_columns(input); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid column name: invalid_column. Valid options are: name, ready, status, restarts, age, ip, node, nominatednode, readinessgates" + ); + } + + #[test] + #[allow(non_snake_case)] + fn Nameカラムが常に含まれる() { + let input = "ready, status"; + let actual = parse_pod_columns(input).unwrap(); + assert!(actual.columns.contains(&"Name")); + } + + #[test] + fn fullと他のカラムを同時に指定するとエラー() { + let input = "full, ready"; + let result = parse_pod_columns(input); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Cannot specify 'full' with other columns. Use 'full' alone to get all columns." + ); + } + + #[test] + #[allow(non_snake_case)] + fn full単体ならOK() { + let input = "full"; + let actual = parse_pod_columns(input).unwrap(); + let expected: Vec<&str> = COLUMN_MAP.iter().map(|(_, v)| *v).collect(); + assert_eq!(actual.columns, expected); + } + + #[test] + fn 空要素が含まれていても無視される() { + let input = "ready,,status"; + let actual = parse_pod_columns(input).unwrap(); + let expected = vec!["Name", "Ready", "Status"]; + assert_eq!(actual.columns, expected); + } + + #[test] + fn 空要素だけだとエラーになる() { + let input = ", , "; + let result = parse_pod_columns(input); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Columns list must not be empty" + ); + } + } + + mod normalize_column { + use super::*; + + #[test] + fn 空白を削除して小文字に変換する() { + let name = " Name "; + let actual = normalize_column(name); + assert_eq!(actual, "name"); + } + + #[test] + fn アンダースコアを削除して小文字に変換する() { + let name = "Nominated_Node"; + let actual = normalize_column(name); + assert_eq!(actual, "nominatednode"); + } + + #[test] + fn ハイフンを削除して小文字に変換する() { + let name = "Readiness-Gates"; + let actual = normalize_column(name); + assert_eq!(actual, "readinessgates"); + } + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/src/cmd/args.rs new/kubetui-1.8.1/src/cmd/args.rs --- old/kubetui-1.7.1/src/cmd/args.rs 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/src/cmd/args.rs 2025-06-08 19:42:14.000000000 +0200 @@ -1,5 +1,7 @@ mod all_namespaces; +mod pod_columns; mod split_direction; pub use all_namespaces::*; +pub use pod_columns::*; pub use split_direction::*; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/src/cmd/command.rs new/kubetui-1.8.1/src/cmd/command.rs --- old/kubetui-1.7.1/src/cmd/command.rs 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/src/cmd/command.rs 2025-06-08 19:42:14.000000000 +0200 @@ -6,7 +6,7 @@ use crate::{config::ConfigLoadOption, workers::kube::KubeWorkerConfig}; use super::{ - args::{AllNamespaces, SplitDirection}, + args::{parse_pod_columns, AllNamespaces, PodColumns, SplitDirection}, SubCommand, }; @@ -74,6 +74,14 @@ #[arg(long, display_order = 1000)] pub config_file: Option<PathBuf>, + /// Comma-separated list of columns to show in pod table (e.g. name,status,ip). Use "full" to show all available columns. + #[arg( + long, + value_parser = parse_pod_columns, + default_value = "name,ready,status,age", + display_order = 1000)] + pub pod_columns: PodColumns, + #[command(subcommand)] pub subcommand: Option<SubCommand>, } @@ -246,4 +254,47 @@ assert_eq!(cmd.unwrap_err().kind(), ErrorKind::ArgumentConflict) } } + + mod pod_columns { + use pretty_assertions::assert_eq; + + use crate::features::pod::kube::POD_DEFAULT_COLUMNS; + + use super::*; + + #[test] + fn デフォルトのカラムを設定する() { + let cmd = Command::try_parse_from(["kubetui"]).unwrap(); + assert_eq!(cmd.pod_columns, PodColumns::new(POD_DEFAULT_COLUMNS)); + } + + #[test] + fn フルを設定すると全カラムを設定する() { + let cmd = Command::try_parse_from(["kubetui", "--pod-columns=full"]).unwrap(); + assert_eq!( + cmd.pod_columns, + PodColumns::new([ + "Name", + "Ready", + "Status", + "Restarts", + "Age", + "IP", + "Node", + "Nominated Node", + "Readiness Gates" + ]) + ); + } + + #[test] + fn カンマ区切りでカラムを指定できる() { + let cmd = + Command::try_parse_from(["kubetui", "--pod-columns=name,ready,status"]).unwrap(); + assert_eq!( + cmd.pod_columns, + PodColumns::new(["Name", "Ready", "Status"]) + ); + } + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/src/config/theme.rs new/kubetui-1.8.1/src/config/theme.rs --- old/kubetui-1.7.1/src/config/theme.rs 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/src/config/theme.rs 2025-06-08 19:42:14.000000000 +0200 @@ -114,6 +114,7 @@ style: hi.style.into(), }) .collect(), + ..Default::default() } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/src/features/network/kube/network.rs new/kubetui-1.8.1/src/features/network/kube/network.rs --- old/kubetui-1.7.1/src/features/network/kube/network.rs 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/src/features/network/kube/network.rs 2025-06-08 19:42:14.000000000 +0200 @@ -340,7 +340,7 @@ ) -> Result<Vec<NetworkTableRow>> { let table = kind.fetch_table(client, ns).await?; - let indexes = table.find_indexes(target_columns); + let indexes = table.find_indexes(target_columns)?; let rows = table .rows diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/src/features/pod/kube/pod.rs new/kubetui-1.8.1/src/features/pod/kube/pod.rs --- old/kubetui-1.7.1/src/features/pod/kube/pod.rs 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/src/features/pod/kube/pod.rs 2025-06-08 19:42:14.000000000 +0200 @@ -19,9 +19,12 @@ workers::kube::{message::Kube, SharedTargetNamespaces, Worker, WorkerResult}, }; +pub const POD_DEFAULT_COLUMNS: [&str; 4] = ["Name", "Ready", "Status", "Age"]; + #[derive(Debug, Clone)] pub struct PodConfig { pub pod_highlight_rules: Vec<PodHighlightRule>, + pub columns: Vec<&'static str>, } impl Default for PodConfig { @@ -37,6 +40,7 @@ style: Style::default().fg(Color::Red), }, ], + columns: POD_DEFAULT_COLUMNS.into_iter().collect(), } } } @@ -99,18 +103,19 @@ let ok_only: Vec<KubeTableRow> = jobs?.into_iter().flatten().collect(); + let mut display_columns: Vec<String> = self + .config + .columns + .iter() + .map(|col| col.to_uppercase()) + .collect(); + + if namespaces.len() != 1 { + display_columns.insert(0, "NAMESPACE".to_string()); + } + let mut table = KubeTable { - header: if namespaces.len() == 1 { - ["NAME", "READY", "STATUS", "AGE"] - .iter() - .map(ToString::to_string) - .collect() - } else { - ["NAMESPACE", "NAME", "READY", "STATUS", "AGE"] - .iter() - .map(ToString::to_string) - .collect() - }, + header: display_columns, ..Default::default() }; @@ -124,25 +129,38 @@ namespaces: &[String], ) -> Result<Vec<Vec<KubeTableRow>>> { let insert_ns = insert_ns(namespaces); + + let name_index = self + .config + .columns + .iter() + .position(|&col| col == "Name") + .expect("Name column must be present in pod columns"); + + let status_index = self.config.columns.iter().position(|&col| col == "Status"); + try_join_all(namespaces.iter().map(|ns| { get_resource_per_namespace( &self.kube_client, format!("api/v1/namespaces/{}/{}", ns, "pods"), - &["Name", "Ready", "Status", "Age"], + self.config.columns.as_slice(), move |row: &TableRow, indexes: &[usize]| { let mut row: Vec<String> = indexes.iter().map(|i| row.cells[*i].to_string()).collect(); - let name = row[0].clone(); + let name = row[name_index].clone(); - let status = row[2].as_str(); + let color = if let Some(index) = status_index { + let status = row[index].as_str(); - let color = self - .config - .pod_highlight_rules - .iter() - .find(|rule| rule.status_regex.is_match(status)) - .map(|rule| style_to_ansi(rule.style)); + self.config + .pod_highlight_rules + .iter() + .find(|rule| rule.status_regex.is_match(status)) + .map(|rule| style_to_ansi(rule.style)) + } else { + None + }; if insert_ns { row.insert(0, ns.to_string()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/src/kube/apis/v1_table.rs new/kubetui-1.8.1/src/kube/apis/v1_table.rs --- old/kubetui-1.7.1/src/kube/apis/v1_table.rs 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/src/kube/apis/v1_table.rs 2025-06-08 19:42:14.000000000 +0200 @@ -164,11 +164,29 @@ .position(|cd| cd.name == target) } - pub fn find_indexes(&self, targets: &[&str]) -> Vec<usize> { - targets - .iter() - .filter_map(|target| self.find_index(target)) - .collect() + pub fn find_indexes(&self, targets: &[&str]) -> Result<Vec<usize>> { + let mut ret = Vec::with_capacity(targets.len()); + + for target in targets { + if let Some(index) = self.find_index(target) { + ret.push(index); + } else { + let cols = self + .column_definitions + .iter() + .map(|cd| cd.name.as_str()) + .collect::<Vec<&str>>() + .join(", "); + + anyhow::bail!( + "Column '{}' not found in table. Available columns: {}", + target, + cols + ); + } + } + + Ok(ret) } #[allow(dead_code)] @@ -317,11 +335,30 @@ let targets = vec!["A", "B", "C", "D"]; - assert_eq!(table.find_indexes(&targets), vec![0, 1, 2, 3]); + assert_eq!(table.find_indexes(&targets).unwrap(), vec![0, 1, 2, 3]); + } + + #[test] + fn indexes_with_error() { + let table = Table { + type_meta: Default::default(), + metadata: Default::default(), + column_definitions: vec![ + TableColumnDefinition { + name: "A".to_string(), + ..Default::default() + }, + TableColumnDefinition { + name: "B".to_string(), + ..Default::default() + }, + ], + rows: Default::default(), + }; - let targets = vec!["A", "B", "E"]; + let targets = vec!["A", "B", "C"]; - assert_eq!(table.find_indexes(&targets), vec![0, 1]); + assert!(table.find_indexes(&targets).is_err()); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/src/kube/table.rs new/kubetui-1.8.1/src/kube/table.rs --- old/kubetui-1.7.1/src/kube/table.rs 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/src/kube/table.rs 2025-06-08 19:42:14.000000000 +0200 @@ -84,7 +84,7 @@ { let table: Table = client.table_request(&path).await?; - let indexes = table.find_indexes(target_values); + let indexes = table.find_indexes(target_values)?; Ok(table .rows diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kubetui-1.7.1/src/message.rs new/kubetui-1.8.1/src/message.rs --- old/kubetui-1.7.1/src/message.rs 2025-05-10 07:49:18.000000000 +0200 +++ new/kubetui-1.8.1/src/message.rs 2025-06-08 19:42:14.000000000 +0200 @@ -49,6 +49,8 @@ let default_hook = panic::take_hook(); panic::set_hook(Box::new(move |info| { + logger!(error, "{}", info); + $t; default_hook(info); ++++++ kubetui.obsinfo ++++++ --- /var/tmp/diff_new_pack.n7lQKb/_old 2025-06-10 09:10:42.136871227 +0200 +++ /var/tmp/diff_new_pack.n7lQKb/_new 2025-06-10 09:10:42.140871392 +0200 @@ -1,5 +1,5 @@ name: kubetui -version: 1.7.1 -mtime: 1746856158 -commit: 95db426560374d95073205adf48d22708c583063 +version: 1.8.1 +mtime: 1749404534 +commit: 09111b9d4a84f78b5153a3e156acda72a0eb938a ++++++ vendor.tar.zst ++++++ /work/SRC/openSUSE:Factory/kubetui/vendor.tar.zst /work/SRC/openSUSE:Factory/.kubetui.new.19631/vendor.tar.zst differ: char 7, line 1