internal/rework-table (#74)

This commit is contained in:
T. von Dein
2026-07-07 07:29:03 +02:00
parent 92865c0fe2
commit c35ffa5e4b
26 changed files with 300 additions and 362 deletions

View File

@@ -113,11 +113,11 @@ func CcrRemoteInfo(conf *cfg.Config, index string) error {
table := printer.NewTable(conf, 2, 5)
table.Addheaders("ccr remote property", "value")
table.Entries = [][]string{
table.Entries = [][]any{
{"Remote Cluster", remote},
{"CCR Mode", mode},
{"Connected", fmt.Sprintf("%t", info.Connected)},
{"Num Proxy Sockets Connected", fmt.Sprintf("%d", info.NumProxySocketsConnected)},
{"Connected", info.Connected},
{"Num Proxy Sockets Connected", info.NumProxySocketsConnected},
{"Proxy Address", info.ProxyAddress},
}

View File

@@ -164,16 +164,16 @@ func CcrFollowerShow(conf *cfg.Config, index string) error {
table := printer.NewTable(conf, 2, 9)
table.Addheaders("ccr follower property", "value")
table.Entries = [][]string{
table.Entries = [][]any{
{"name", index},
{"remote_cluster", follower.RemoteCluster},
{"leader_checkpoint", fmt.Sprintf("%d", follower.LeaderGlobalCheckpoint)},
{"follower_checkpoint", fmt.Sprintf("%d", follower.FollowerGlobalCheckpoint)},
{"bytes_read", fmt.Sprintf("%d", follower.BytesRead)},
{"failed_read_requests", fmt.Sprintf("%d", follower.FailedReadRequests)},
{"failed_write_requests", fmt.Sprintf("%d", follower.FailedWriteRequests)},
{"successful_read_requests", fmt.Sprintf("%d", follower.SuccessfulReadRequests)},
{"successful_write_requests", fmt.Sprintf("%d", follower.SuccessfulWriteRequests)},
{"leader_checkpoint", follower.LeaderGlobalCheckpoint},
{"follower_checkpoint", follower.FollowerGlobalCheckpoint},
{"bytes_read", follower.BytesRead},
{"failed_read_requests", follower.FailedReadRequests},
{"failed_write_requests", follower.FailedWriteRequests},
{"successful_read_requests", follower.SuccessfulReadRequests},
{"successful_write_requests", follower.SuccessfulWriteRequests},
}
if err := table.Print(); err != nil {

View File

@@ -81,7 +81,7 @@ func ClusterList(conf *cfg.Config) error {
}
}
table.Entries[idx] = []string{name, cluster.Uri, reachableStr, current, errmsg}
table.Entries[idx] = []any{name, cluster.Uri, reachableStr, current, errmsg}
idx++
}
@@ -190,25 +190,25 @@ func ClusterStatus(conf *cfg.Config) error {
table := printer.NewTable(conf, 2, 7)
table.Addheaders(conf.DefaultCluster.Name, "status")
table.Entries = [][]string{
table.Entries = [][]any{
{"Cluster Name", res.health.ClusterName},
{"ES Status", printer.Colorize(conf, res.health.Status.Name, res.health.Status.Name)},
{"ES Version", res.info.Version.Int},
{"Is Leader", fmt.Sprintf("%t", isleader)},
{"Active Shards", fmt.Sprintf("%d", res.health.ActiveShards)},
{"Active Primary Shards", fmt.Sprintf("%d", res.health.ActivePrimaryShards)},
{"Unassigned Shards", fmt.Sprintf("%d", res.health.UnassignedShards)},
{"Unassigned Primary Shards", fmt.Sprintf("%d", res.health.UnassignedPrimaryShards)},
{"Pending Tasks", fmt.Sprintf("%d", res.health.NumberOfPendingTasks)},
{"Nodes", fmt.Sprintf("%d", res.health.NumberOfNodes)},
{"Red Indices", fmt.Sprintf("%d", redindices)},
{"Long Running Tasks", fmt.Sprintf("%d", longtasks)},
{"Is Leader", isleader},
{"Active Shards", res.health.ActiveShards},
{"Active Primary Shards", res.health.ActivePrimaryShards},
{"Unassigned Shards", res.health.UnassignedShards},
{"Unassigned Primary Shards", res.health.UnassignedPrimaryShards},
{"Pending Tasks", res.health.NumberOfPendingTasks},
{"Nodes", res.health.NumberOfNodes},
{"Red Indices", redindices},
{"Long Running Tasks", longtasks},
}
if !isleader && res.ccr != nil {
table.Entries = append(table.Entries, [][]string{
table.Entries = append(table.Entries, [][]any{
{"AutoFollow (success/failed indices)", ccrfollowing},
{"Followed Indices", fmt.Sprintf("%d", len(res.ccr.FollowStats.Indices))},
{"Followed Indices", len(res.ccr.FollowStats.Indices)},
}...)
}
@@ -219,15 +219,15 @@ func ClusterStatus(conf *cfg.Config) error {
if res.health.Status.Name != "green" {
for name, indicator := range res.healthreport.Indicators {
if indicator.Status != "green" {
table.Entries = append(table.Entries, []string{
table.Entries = append(table.Entries, []any{
printer.Colorize(conf, indicator.Status, "Bad health "+name), indicator.Symptom,
})
for _, diag := range indicator.Diagnosis {
table.Entries = append(table.Entries, []string{" -> cause", diag.Cause})
table.Entries = append(table.Entries, []any{" -> cause", diag.Cause})
for resource, items := range diag.AffectedResources {
table.Entries = append(table.Entries, []string{" -> affected " + resource, strings.Join(items, ",")})
table.Entries = append(table.Entries, []any{" -> affected " + resource, strings.Join(items, ",")})
}
}
}
@@ -255,13 +255,13 @@ func gatherClusterStats(conf *cfg.Config, clusterstats *clusterstats.Response, t
clusterstats.Nodes.Jvm.Versions[0].VmVersion}, " ")
}
table.Entries = append(table.Entries, [][]string{
{"Indicies", fmt.Sprintf("%d", clusterstats.Indices.Count)},
{"Docs", fmt.Sprintf("%d", clusterstats.Indices.Docs.Count)},
{"Total Size", humanize.Bytes(uint64(clusterstats.Indices.Docs.TotalSizeInBytes))},
{"Total Queries", fmt.Sprintf("%d", querycount)},
{"Shards Primaries", fmt.Sprintf("%d", clusterstats.Indices.Shards.Primaries)},
{"Shards Total", fmt.Sprintf("%d", clusterstats.Indices.Shards.Total)},
table.Entries = append(table.Entries, [][]any{
{"Indicies", clusterstats.Indices.Count},
{"Docs", clusterstats.Indices.Docs.Count},
{"Total Size", printer.Bytes(clusterstats.Indices.Docs.TotalSizeInBytes)},
{"Total Queries", "%d", querycount},
{"Shards Primaries", clusterstats.Indices.Shards.Primaries},
{"Shards Total", clusterstats.Indices.Shards.Total},
{"Storage", fmt.Sprintf(
"%s/%s",
humanize.Bytes(uint64(clusterstats.Indices.Store.SizeInBytes)),
@@ -272,11 +272,11 @@ func gatherClusterStats(conf *cfg.Config, clusterstats *clusterstats.Response, t
humanize.Bytes(uint64(clusterstats.Nodes.Jvm.Mem.HeapUsedInBytes)),
humanize.Bytes(uint64(clusterstats.Nodes.Jvm.Mem.HeapMaxInBytes)),
)},
{"JVM Threads", fmt.Sprintf("%d", clusterstats.Nodes.Jvm.Threads)},
{"JVM Threads", clusterstats.Nodes.Jvm.Threads},
{"JVM Version", vmversion},
{"CPUs", fmt.Sprintf("%d", clusterstats.Nodes.Os.AllocatedProcessors)},
{"CPUs", clusterstats.Nodes.Os.AllocatedProcessors},
{"CPU Usage", fmt.Sprintf("%d%%", clusterstats.Nodes.Process.Cpu.Percent)},
{"Open FDs", fmt.Sprintf("%d", clusterstats.Nodes.Process.OpenFileDescriptors.Avg)},
{"Open FDs", clusterstats.Nodes.Process.OpenFileDescriptors.Avg},
}...)
return table

View File

@@ -40,7 +40,7 @@ func ClusterSettingsList(conf *cfg.Config) error {
table := printer.NewTable(conf, 2, 0)
table.Addheaders("setting", "value")
entries := [][]string{}
entries := [][]any{}
settingshash := res.Persistent // == map[string]json.RawMessage
@@ -52,7 +52,7 @@ func ClusterSettingsList(conf *cfg.Config) error {
}
for topic, val := range settingshash {
entries = append(entries, []string{topic, string(val)})
entries = append(entries, []any{topic, string(val)})
}
table.Entries = entries

View File

@@ -72,27 +72,27 @@ func checkClusterStatus(conf *cfg.Config, leader, follower string) bool {
table.Addheaders("setting", "leader:"+leader, "follower:"+follower)
table.Entries = [][]string{
table.Entries = [][]any{
{"Cluster Name", status[leader].ClusterName, status[follower].ClusterName},
{"Cluster Status",
printer.Colorize(conf, status[leader].Status.Name, status[leader].Status.Name),
printer.Colorize(conf, status[follower].Status.Name, status[follower].Status.Name),
},
{"Active Shards",
fmt.Sprintf("%d", status[leader].ActiveShards),
fmt.Sprintf("%d", status[follower].ActiveShards),
status[leader].ActiveShards,
status[follower].ActiveShards,
},
{"Active Primary Shards",
fmt.Sprintf("%d", status[leader].ActivePrimaryShards),
fmt.Sprintf("%d", status[follower].ActivePrimaryShards),
status[leader].ActivePrimaryShards,
status[follower].ActivePrimaryShards,
},
{"Indicies",
fmt.Sprintf("%d", len(status[leader].Indices)),
fmt.Sprintf("%d", len(status[follower].Indices)),
len(status[leader].Indices),
len(status[follower].Indices),
},
{"Nodes",
fmt.Sprintf("%d", status[leader].NumberOfNodes),
fmt.Sprintf("%d", status[follower].NumberOfNodes),
status[leader].NumberOfNodes,
status[follower].NumberOfNodes,
},
}
@@ -112,7 +112,7 @@ func checkClusterStatus(conf *cfg.Config, leader, follower string) bool {
// finds indices on both clusters which have ilm errors
func findIlmErrors(conf *cfg.Config, leader, follower string) bool {
failed := map[string]map[string]string{}
failed := map[string]map[string]any{}
for _, cluster := range []string{leader, follower} {
ilm, err := conf.Clusters[cluster].ES().Ilm.ExplainLifecycle("_all").
@@ -123,13 +123,13 @@ func findIlmErrors(conf *cfg.Config, leader, follower string) bool {
return false
}
failed[cluster] = map[string]string{}
failed[cluster] = map[string]any{}
for name, ilmstate := range ilm.Indices {
count := ilmstate.(*types.LifecycleExplainManaged).FailedStepRetryCount
if count != nil && *count > 0 {
failed[cluster][name] = fmt.Sprintf("%d", *count)
failed[cluster][name] = *count
}
}
}
@@ -150,7 +150,7 @@ func findIlmErrors(conf *cfg.Config, leader, follower string) bool {
table.Addheaders("ilm errors on "+which, "errors")
for name, count := range failed[cluster] {
table.Entries[idx] = []string{name, count}
table.Entries[idx] = []any{name, count}
idx++
}
@@ -222,7 +222,7 @@ func findIndicesOnlyOnLeader(conf *cfg.Config, indices ClusterIndices, leader, f
for name, index := range indexOnlyOnLeader {
name := printer.Colorize(conf, "red", name)
table.Entries[idx] = []string{name, *index.DatasetSize, *index.DocsCount}
table.Entries[idx] = []any{name, *index.DatasetSize, *index.DocsCount}
idx++
}
@@ -270,7 +270,7 @@ func findOrphanedIndices(conf *cfg.Config, indices ClusterIndices, leader, follo
for name, index := range orphaned {
name := printer.Colorize(conf, "red", name)
table.Entries[idx] = []string{name, *index.DatasetSize, *index.DocsCount}
table.Entries[idx] = []any{name, *index.DatasetSize, *index.DocsCount}
idx++
}
@@ -304,7 +304,7 @@ func findFailedFollowerIndices(conf *cfg.Config, indices ClusterIndices, followe
for name, index := range red {
name := printer.Colorize(conf, "red", name)
table.Entries[idx] = []string{name, *index.DatasetSize, *index.DocsCount}
table.Entries[idx] = []any{name, *index.DatasetSize, *index.DocsCount}
idx++
}

View File

@@ -25,7 +25,6 @@ import (
"codeberg.org/scip/esctl/pkg/cfg"
"codeberg.org/scip/esctl/pkg/printer"
"github.com/dustin/go-humanize"
"github.com/elastic/go-elasticsearch/v9/typedapi/types"
)
@@ -73,13 +72,13 @@ func DatastreamList(conf *cfg.Config) error {
policy = *ds.IlmPolicy
}
table.Entries[idx] = []string{
table.Entries[idx] = []any{
name,
policy,
fmt.Sprintf("%t", ds.Hidden),
fmt.Sprintf("%t", *ds.System),
fmt.Sprintf("%t", *ds.Replicated),
fmt.Sprintf("%d", ds.Generation),
ds.Hidden,
*ds.System,
*ds.Replicated,
ds.Generation,
ds.TimestampField.Name,
}
@@ -156,18 +155,18 @@ func DatastreamShow(conf *cfg.Config, dsname string) error {
policy = *ds.IlmPolicy
}
table.Entries = [][]string{
table.Entries = [][]any{
{"name", name},
{"ilm policy", policy},
{"hidden", fmt.Sprintf("%t", ds.Hidden)},
{"system", fmt.Sprintf("%t", *ds.System)},
{"replicated", fmt.Sprintf("%t", *ds.Replicated)},
{"generation", fmt.Sprintf("%d", ds.Generation)},
{"hidden", ds.Hidden},
{"system", *ds.System},
{"replicated", *ds.Replicated},
{"generation", ds.Generation},
{"timestamp field", ds.TimestampField.Name},
{"backing indices", fmt.Sprintf("%d", stats.BackingIndices)},
{"size", humanize.Bytes(uint64(stats.TotalStoreSizeBytes))},
{"shards-failed", fmt.Sprintf("%d", stats.Shards_.Failed)},
{"shards-successful", fmt.Sprintf("%d", stats.Shards_.Successful)},
{"backing indices", stats.BackingIndices},
{"size", printer.Bytes(stats.TotalStoreSizeBytes)},
{"shards-failed", stats.Shards_.Failed},
{"shards-successful", stats.Shards_.Successful},
}
if err := table.Print(); err != nil {
@@ -183,10 +182,10 @@ func DatastreamShow(conf *cfg.Config, dsname string) error {
policy = *ds.IlmPolicy
}
table.Entries[idx] = []string{
table.Entries[idx] = []any{
index.IndexName,
index.IndexUuid,
fmt.Sprintf("%t", *index.PreferIlm),
*index.PreferIlm,
policy,
index.ManagedBy.Name,
}
@@ -235,10 +234,10 @@ func DatastreamRollover(conf *cfg.Config, ds string) error {
table := printer.NewTable(conf, 2, 5)
table.Addheaders("rollover response", "value")
table.Entries = [][]string{
{"acknowledged", fmt.Sprintf("%t", res.Acknowledged)},
{"rolled over", fmt.Sprintf("%t", res.RolledOver)},
{"shards acknowledged", fmt.Sprintf("%t", res.ShardsAcknowledged)},
table.Entries = [][]any{
{"acknowledged", res.Acknowledged},
{"rolled over", res.RolledOver},
{"shards acknowledged", res.ShardsAcknowledged},
{"old index", res.OldIndex},
{"new index", res.NewIndex},
}

View File

@@ -129,7 +129,7 @@ func IlmShow(conf *cfg.Config, policy string) error {
table := printer.NewTable(conf, 2, 5)
table.Addheaders("ilm policy setting", "value")
table.Entries = [][]string{
table.Entries = [][]any{
{"policy", policy},
{"hot phase", ilmPhaseString(ilm.Policy.Phases.Hot, false)},
{"warm phase", ilmPhaseString(ilm.Policy.Phases.Warm, false)},
@@ -291,17 +291,17 @@ func IlmExplain(conf *cfg.Config, index string) error {
return fmt.Errorf("failed to unmarshal step info: %w", err)
}
table.Entries = [][]string{
table.Entries = [][]any{
{"index", ilm.Index},
{"ilm policy", *ilm.Policy},
{"action", *ilm.Action},
{"age", fmt.Sprintf("%s", ilm.Age)},
{"managed", fmt.Sprintf("%t", ilm.Managed)},
{"age", ilm.Age},
{"managed", ilm.Managed},
{"phase", *ilm.Phase},
{"phase execution", ilmPhaseString(ilm.PhaseExecution.PhaseDefinition, false)},
{"step", *ilm.Step},
{"failed step", *ilm.FailedStep},
{"failed step retry count", fmt.Sprintf("%d", ilm.FailedStepRetryCount)},
{"failed step retry count", ilm.FailedStepRetryCount},
}
if err := table.Print(); err != nil {

View File

@@ -119,13 +119,13 @@ func IlmForecastList(conf *cfg.Config, filter string) error {
virtualAge := virtualAge(&phase)
row := []string{
row := []any{
phase.index,
humanize.Bytes(uint64(phase.size)),
printer.Bytes(phase.size),
formatDuration(phase.age),
formatDuration(virtualAge),
formatDuration(phase.minage),
humanize.Bytes(uint64(phase.minsize)),
printer.Bytes(phase.minsize),
phase.currentPhase,
phase.nextPhase,
}

View File

@@ -118,7 +118,7 @@ func IndexList(conf *cfg.Config) error {
for idx, index := range list {
name := printer.Colorize(conf, *index.Health, *index.Index)
table.Entries[idx] = []string{name, *index.DatasetSize, *index.DocsCount}
table.Entries[idx] = []any{name, *index.DatasetSize, *index.DocsCount}
if idx == size-1 {
break
@@ -156,18 +156,18 @@ func IndexShow(conf *cfg.Config, indexpattern string) error {
created := time.Unix(ts/1000, 0)
table.Entries = [][]string{
table.Entries = [][]any{
{"name", name},
{"replicas", *index.Settings.Index.NumberOfReplicas},
{"shards", *index.Settings.Index.NumberOfShards},
{"created", created.Format("2006-01-02 15:04:05")},
{"created", created},
{"uuid", *index.Settings.Index.Uuid},
{"version", *index.Settings.Index.Version.Created},
{"fields", strings.Join(fields, ",")},
{"fields", fields},
}
if index.Settings.Index.Lifecycle != nil {
table.Entries = append(table.Entries, [][]string{
table.Entries = append(table.Entries, [][]any{
{"ilm policy", *index.Settings.Index.Lifecycle.Name},
{"ilm rollover alias", *index.Settings.Index.Lifecycle.RolloverAlias},
}...)
@@ -283,14 +283,14 @@ func IndexAllocation(conf *cfg.Config, index string) error {
roles[idx] = role.Name
}
table.Entries = [][]string{
table.Entries = [][]any{
{"Index", index},
{"Current node", currentNode.Name},
{"Current k8s node", currentNode.Attributes["k8s_node_name"]},
{"Current node address", currentNode.TransportAddress},
{"Current node id", currentNode.Id},
{"Current node weight", fmt.Sprintf("%d", currentNode.WeightRanking)},
{"Current node roles", strings.Join(roles, ",")},
{"Current node weight", currentNode.WeightRanking},
{"Current node roles", roles},
{"Can rebalance cluster", res.CanRebalanceCluster.Name},
{"Can rebalance to another node", res.CanRebalanceToOtherNode.Name},
{"Can remain on current node", res.CanRemainOnCurrentNode.Name},
@@ -344,10 +344,11 @@ func IndexFields(conf *cfg.Config, index string) error {
continue
}
table.Entries = append(table.Entries, []string{name, fieldtype,
fmt.Sprintf("%t", caps.Searchable),
fmt.Sprintf("%t", caps.Aggregatable),
fmt.Sprintf("%t", *caps.MetadataField)})
table.AddRow(name, fieldtype,
caps.Searchable,
caps.Aggregatable,
*caps.MetadataField)
break
}

View File

@@ -78,7 +78,7 @@ func IndexAliasList(conf *cfg.Config) error {
idx := 0
for index, aliases := range aliaslist {
table.Entries[idx] = []string{
table.Entries[idx] = []any{
index,
strings.Join(aliases, ","),
}
@@ -117,10 +117,10 @@ func IndexAliasRollover(conf *cfg.Config, alias string) error {
table := printer.NewTable(conf, 2, 5)
table.Addheaders("rollover response", "value")
table.Entries = [][]string{
{"acknowledged", fmt.Sprintf("%t", res.Acknowledged)},
{"rolled over", fmt.Sprintf("%t", res.RolledOver)},
{"shards acknowledged", fmt.Sprintf("%t", res.ShardsAcknowledged)},
table.Entries = [][]any{
{"acknowledged", res.Acknowledged},
{"rolled over", res.RolledOver},
{"shards acknowledged", res.ShardsAcknowledged},
{"old index", res.OldIndex},
{"new index", res.NewIndex},
}

View File

@@ -51,11 +51,7 @@ func IndexTemplateList(conf *cfg.Config) error {
return fmt.Errorf("failed to unmarshal meta json data: %w", err)
}
table.Entries[idx] = []string{
tpl.Name,
string(desc),
fmt.Sprintf("%d", tpl.IndexTemplate.Priority),
}
table.Entries[idx] = []any{tpl.Name, desc, tpl.IndexTemplate.Priority}
}
table.Sort()
@@ -94,13 +90,13 @@ func IndexTemplateShow(conf *cfg.Config, tplname string) error {
aliases = append(aliases, alias)
}
table.Entries = [][]string{
table.Entries = [][]any{
{"name", tpl.Name},
{"description", string(desc)},
{"index patterns", strings.Join(tpl.IndexTemplate.IndexPatterns, ",")},
{"composed of", strings.Join(tpl.IndexTemplate.ComposedOf, ",")},
{"data stream enabled", fmt.Sprintf("%t", hasds)},
{"aliases", strings.Join(aliases, ",")},
{"description", desc},
{"index patterns", tpl.IndexTemplate.IndexPatterns},
{"composed of", tpl.IndexTemplate.ComposedOf},
{"data stream enabled", hasds},
{"aliases", aliases},
}
if err := table.Print(); err != nil {
@@ -136,9 +132,7 @@ func IndexTemplateShow(conf *cfg.Config, tplname string) error {
typeval = val.Type
}
table.Entries = append(table.Entries, []string{
name, typeval,
})
table.AddRow(name, typeval)
}
fmt.Println()
@@ -414,12 +408,7 @@ func rolloverAliasIndexTemplate(conf *cfg.Config, name string) error {
return err
}
table.Entries = append(table.Entries, []string{
alias,
fmt.Sprintf("%t", res.RolledOver),
fmt.Sprintf("%t", res.Acknowledged),
res.NewIndex,
})
table.AddRow(alias, res.RolledOver, res.Acknowledged, res.NewIndex)
if err := table.Print(); err != nil {
return err

View File

@@ -52,7 +52,7 @@ func getIndexTemplateSettings(conf *cfg.Config, tplname string, table *printer.T
tpl := data.IndexTemplates[0].IndexTemplate.Template.Settings
for topic, val := range tpl {
table.Entries = append(table.Entries, []string{topic, fmt.Sprintf("%v", val)})
table.AddRow(topic, val)
}
return nil

View File

@@ -54,11 +54,11 @@ func LicenseShow(conf *cfg.Config) error {
maxunits = fmt.Sprintf("%d", *lic.MaxResourceUnits)
}
table.Entries = [][]string{
table.Entries = [][]any{
{"UID", lic.Uid},
{"Issued to", lic.IssuedTo},
{"Expires", expire},
{"Issued", lic.IssueDate.(string)},
{"Issued", lic.IssueDate},
{"Max nodes", maxnodes},
{"Max resource units", maxunits},
{"Type", lic.Type.Name},

View File

@@ -41,14 +41,14 @@ func NodeList(conf *cfg.Config) error {
table.Addheaders("name", "ip", "load1m", "load5m", "load15m", "ram %", "heap %")
for idx, node := range nodes {
table.Entries[idx] = []string{
table.Entries[idx] = []any{
*node.Name,
*node.Ip,
*node.Load1M,
*node.Load5M,
*node.Load15M,
node.RamPercent.(string),
node.HeapPercent.(string),
node.RamPercent,
node.HeapPercent,
}
}
@@ -113,25 +113,25 @@ func NodeShow(conf *cfg.Config, nodename string) error {
k8snode := info.Attributes["k8s_node_name"]
table.Entries = [][]string{
table.Entries = [][]any{
{"Id", id},
{"Name", nodename},
{"Kubernetes node", k8snode},
{"Ip address", info.Ip},
{"Node rank", *stat.AdaptiveSelection[id].Rank},
{"JVM", info.Jvm.VmName + " " + info.Jvm.Version},
{"JVM Started", time.UnixMilli(info.Jvm.StartTimeInMillis).String()},
{"JVM Started", time.UnixMilli(info.Jvm.StartTimeInMillis)},
{"OS", info.Os.PrettyName + " " + info.Os.Version},
{"Node roles", strings.Join(roles, ",")},
{"Node roles", roles},
{"Node version", info.Version},
{"HTTP clients", fmt.Sprintf("%d", *stat.Http.CurrentOpen)},
{"CPUs", fmt.Sprintf("%d", *info.Os.AllocatedProcessors)},
{"HTTP clients", *stat.Http.CurrentOpen},
{"CPUs", *info.Os.AllocatedProcessors},
{"Load 15m/5m/1m", fmt.Sprintf("%.2f/%.2f/%.2f",
stat.Os.Cpu.LoadAverage["15m"],
stat.Os.Cpu.LoadAverage["5m"],
stat.Os.Cpu.LoadAverage["1m"],
)},
{"Open FD's", fmt.Sprintf("%d", *stat.Process.OpenFileDescriptors)},
{"Open FD's", *stat.Process.OpenFileDescriptors},
{"Response time avg", fmt.Sprintf("%dns", *stat.AdaptiveSelection[id].AvgResponseTimeNs)},
{"Memory usage (used/avail)",
humanize.Bytes(uint64(*stat.Os.Mem.UsedInBytes)) + " / " + humanize.Bytes(uint64(*stat.Os.Mem.TotalInBytes))},
@@ -139,7 +139,7 @@ func NodeShow(conf *cfg.Config, nodename string) error {
if len(stat.Fs.Data) > 0 {
fs := stat.Fs.Data[0]
table.Entries = append(table.Entries, [][]string{
table.Entries = append(table.Entries, [][]any{
{"Storage usage (used/avail)",
humanize.Bytes(uint64(*fs.AvailableInBytes)) + " / " + humanize.Bytes(uint64(*fs.TotalInBytes))},
{"Storage mount", *fs.Mount},

View File

@@ -20,7 +20,6 @@ import (
"context"
"fmt"
"log/slog"
"strings"
"codeberg.org/scip/esctl/pkg/cfg"
"codeberg.org/scip/esctl/pkg/printer"
@@ -58,10 +57,10 @@ func RoleList(conf *cfg.Config) error {
idx := 0
for name, role := range res {
table.Entries[idx] = []string{
table.Entries[idx] = []any{
name,
fmt.Sprintf("%d", len(role.Cluster)),
fmt.Sprintf("%d", len(role.Indices)),
role.Cluster,
role.Indices,
}
idx++
}
@@ -140,10 +139,8 @@ func roleRemoteClusters(conf *cfg.Config, role types.Role) error {
perms = append(perms, perm.Name)
}
table.Entries[idx] = []string{
strings.Join(priv.Clusters, ","),
strings.Join(perms, ","),
}
table.Entries[idx] = []any{priv.Clusters, perms}
idx++
}
@@ -160,7 +157,7 @@ func roleClusters(conf *cfg.Config, role types.Role) error {
table.Addheaders("cluster rights")
idx := 0
for _, cluster := range role.Cluster {
table.Entries[idx] = []string{cluster.Name}
table.Entries[idx] = []any{cluster.Name}
idx++
}
@@ -182,11 +179,7 @@ func roleRemoteIndices(conf *cfg.Config, role types.Role) error {
perms = append(perms, perm.Name)
}
table.Entries[idx] = []string{
strings.Join(priv.Names, ", "),
strings.Join(perms, ", "),
fmt.Sprintf("%t", *priv.AllowRestrictedIndices),
}
table.Entries[idx] = []any{priv.Names, perms, *priv.AllowRestrictedIndices}
idx++
}
@@ -209,11 +202,7 @@ func roleIndices(conf *cfg.Config, role types.Role) error {
perms = append(perms, perm.Name)
}
table.Entries[idx] = []string{
strings.Join(priv.Names, ", "),
strings.Join(perms, ", "),
fmt.Sprintf("%t", *priv.AllowRestrictedIndices),
}
table.Entries[idx] = []any{priv.Names, perms, *priv.AllowRestrictedIndices}
idx++
}
@@ -231,10 +220,10 @@ func roleApplications(conf *cfg.Config, role types.Role) error {
table.Addheaders("application", "privileges", "resources")
idx := 0
for _, priv := range role.Applications {
table.Entries[idx] = []string{
table.Entries[idx] = []any{
priv.Application,
strings.Join(priv.Privileges, ", "),
strings.Join(priv.Resources, ", "),
priv.Privileges,
priv.Resources,
}
idx++

View File

@@ -229,7 +229,7 @@ func RoleDiff(conf *cfg.Config, csvfile, role string) error {
defined = printer.Colorize(conf, "red", "undefined")
}
table.Entries[idx] = []string{
table.Entries[idx] = []any{
row.name,
deployed,
defined,

View File

@@ -107,7 +107,7 @@ func printShards(conf *cfg.Config, shardlist shards.Response) error {
for idx, shard := range shardlist {
name := colorzizeShard(conf, *shard.State, *shard.Index)
table.Entries[idx] = []string{
table.Entries[idx] = []any{
name,
*shard.Shard,
resolvePrirep(*shard.Prirep),

View File

@@ -88,7 +88,7 @@ func SnapshotList(conf *cfg.Config) error {
table.Addheaders("name", "index", "start", "orphaned", "status")
for idx, snap := range snapshots {
table.Entries[idx] = []string{
table.Entries[idx] = []any{
snap.Name,
snap.Forindex,
snap.Start,
@@ -122,20 +122,20 @@ func SnapshotShow(conf *cfg.Config, snapshot string) error {
snap := res.Snapshots[0]
table.Entries = [][]string{
table.Entries = [][]any{
{"snapshot", snapshot},
{"uuid", snap.Uuid},
{"repository", *snap.Repository},
{"version_id", fmt.Sprintf("%d", snap.VersionId)},
{"version_id", snap.VersionId},
{"version", *snap.Version},
{"include_global_state", fmt.Sprintf("%t", *snap.IncludeGlobalState)},
{"include_global_state", *snap.IncludeGlobalState},
{"state", *snap.State},
{"start_time", fmt.Sprintf("%s", snap.StartTime)},
{"end_time", fmt.Sprintf("%s", snap.EndTime)},
{"duration_in_millis", fmt.Sprintf("%d", *snap.DurationInMillis)},
{"shards-total", fmt.Sprintf("%d", snap.Shards.Total)},
{"shards-failed", fmt.Sprintf("%d", snap.Shards.Failed)},
{"shards-successful", fmt.Sprintf("%d", snap.Shards.Successful)},
{"start_time", snap.StartTime},
{"end_time", snap.EndTime},
{"duration_in_millis", *snap.DurationInMillis},
{"shards-total", snap.Shards.Total},
{"shards-failed", snap.Shards.Failed},
{"shards-successful", snap.Shards.Successful},
}
if err := table.Print(); err != nil {

View File

@@ -49,10 +49,10 @@ func TaskList(conf *cfg.Config) error {
ts = time.Unix(int64(epc/1000), 0)
}
table.Entries[idx] = []string{
table.Entries[idx] = []any{
*task.TaskId,
*task.Action,
ts.Format("2006-01-02T15:04:05"),
ts,
*task.RunningTime,
*task.Node,
*task.Type,

31
pkg/printer/bytes.go Normal file
View File

@@ -0,0 +1,31 @@
/*
Copyright © 2026 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package printer
import "github.com/dustin/go-humanize"
type ByteSize struct {
size uint64
}
func Bytes(size int64) ByteSize {
return ByteSize{size: uint64(size)}
}
func (b *ByteSize) String() string {
return humanize.Bytes(b.size)
}

97
pkg/printer/cast.go Normal file
View File

@@ -0,0 +1,97 @@
/*
Copyright © 2026 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package printer
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/elastic/go-elasticsearch/v9/typedapi/types"
)
func any2string(in any) string {
switch val := in.(type) {
case string:
return val
case bool:
return strconv.FormatBool(val)
case int64:
return strconv.FormatInt(val, 10)
case int:
return strconv.Itoa(val)
case float64:
return fmt.Sprintf("%.2f", val)
case []string:
return strings.Join(val, ",")
case ByteSize:
return val.String()
case time.Time:
return val.Format("2006-01-02 15:04:05")
case []byte:
return string(val)
case types.DateTime, types.Percentage:
return val.(string)
case nil:
return "null"
}
return ""
}
func (data *Table) preprocessRows() {
if data.processed {
// only do it once
return
}
// convert entries to strings
data.rows = make([][]string, len(data.Entries))
for rowidx, entries := range data.Entries {
data.rows[rowidx] = make([]string, len(entries))
for colidx, entry := range data.Entries[rowidx] {
data.rows[rowidx][colidx] = any2string(entry)
}
}
// determine header lenght's
for idx, head := range data.Headers {
data.lenHeaders[idx] = visibleLen(head)
}
// determine max width per column
for _, entries := range data.rows {
currentWidth := 0
for idx, entry := range entries {
length := visibleLen(entry)
if data.lenHeaders[idx] < length {
if length > currentWidth+data.maxwidth {
data.lenHeaders[idx] = data.maxwidth - currentWidth
} else {
data.lenHeaders[idx] = length
}
}
currentWidth += data.lenHeaders[idx]
}
}
data.processed = true
}

View File

@@ -25,18 +25,17 @@ import (
"strings"
"codeberg.org/scip/esctl/pkg/cfg"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"github.com/seeruk/go-wordwrap"
"gopkg.in/yaml.v3"
)
type Table struct {
Mode string // table, markdown, json
Mode string // tsv, json, yaml
Headers []string
Entries [][]string
Entries [][]any
rows [][]string // representation used for printing
processed bool
lenHeaders []int
alignInts bool
maxwidth int
@@ -46,7 +45,7 @@ func NewTable(conf *cfg.Config, columns, rows int) *Table {
table := Table{Mode: conf.Output, maxwidth: cfg.GetTermWidth()}
table.Headers = make([]string, columns)
table.Entries = make([][]string, rows)
table.Entries = make([][]any, rows)
table.lenHeaders = make([]int, columns)
table.alignInts = conf.AlignInts
@@ -62,7 +61,7 @@ func NewTableEmpty(conf *cfg.Config) *Table {
func (table *Table) WithHeaders(headers ...string) *Table {
count := len(headers)
table.Entries = [][]string{}
table.Entries = [][]any{}
table.lenHeaders = make([]int, count)
table.Headers = make([]string, count)
@@ -73,8 +72,6 @@ func (table *Table) WithHeaders(headers ...string) *Table {
func (data *Table) Print() error {
switch data.Mode {
case "markdown", "md":
return data.PrintMarkdown()
case "json":
return data.PrintJSON()
case "yaml":
@@ -89,11 +86,11 @@ var (
)
// needed for json and yaml output
func (data *Table) toMap() []map[string]string {
raw := make([]map[string]string, len(data.Entries))
func (data *Table) toMap() []map[string]any {
raw := make([]map[string]any, len(data.Entries))
for idx, entries := range data.Entries {
raw[idx] = make(map[string]string, len(data.Headers))
raw[idx] = make(map[string]any, len(data.Headers))
for eidx, entry := range entries {
raw[idx][data.Headers[eidx]] = entry
}
@@ -128,31 +125,9 @@ func (data *Table) PrintJSON() error {
return nil
}
// Well. tablewriter should be able to print in TSV mode, but it
// doesn't work because of the various ansic color escapes we're
// using. Therefore we do it the hard way and use our own TSV printer.
func (data *Table) PrintTSV() error {
// determine lenght's
for idx, head := range data.Headers {
data.lenHeaders[idx] = visibleLen(head)
}
for _, entries := range data.Entries {
currentWidth := 0
for idx, entry := range entries {
length := visibleLen(entry)
if data.lenHeaders[idx] < length {
if length > currentWidth+data.maxwidth {
data.lenHeaders[idx] = data.maxwidth - currentWidth
} else {
data.lenHeaders[idx] = length
}
}
currentWidth += data.lenHeaders[idx]
}
}
// length's, convert cell types
data.preprocessRows()
// output headers
for idx, header := range data.Headers {
@@ -169,7 +144,7 @@ func (data *Table) PrintTSV() error {
}
fmt.Println()
for _, entries := range data.Entries {
for _, entries := range data.rows {
currentWidth := 0
for idx, entry := range entries {
@@ -213,130 +188,16 @@ func (data *Table) PrintTSV() error {
return nil
}
func (data *Table) PrintTabularFail() error {
OFS := " "
tableString := &strings.Builder{}
styleTSV := tw.NewSymbolCustom("space").WithColumn("\t")
table := tablewriter.NewTable(tableString,
tablewriter.WithRenderer(
renderer.NewBlueprint(tw.Rendition{
Borders: tw.BorderNone,
Symbols: styleTSV,
Settings: tw.Settings{
Separators: tw.SeparatorsNone,
Lines: tw.LinesNone,
},
})),
tablewriter.WithConfig(tablewriter.Config{
Header: tw.CellConfig{
Formatting: tw.CellFormatting{
AutoFormat: tw.Off,
},
Padding: tw.CellPadding{Global: tw.Padding{Left: "", Right: OFS}},
},
Row: tw.CellConfig{
Formatting: tw.CellFormatting{
AutoWrap: tw.WrapNone,
Alignment: tw.AlignLeft,
},
Padding: tw.CellPadding{Global: tw.Padding{Right: OFS}},
},
Debug: true,
}),
)
table.Header(data.Headers)
if err := table.Bulk(data.Entries); err != nil {
return fmt.Errorf("failed to add data to table renderer: %s", err)
}
if err := table.Render(); err != nil {
return fmt.Errorf("failed to render table: %s", err)
}
// we need to trim our output here, because tablewriter appends
// excess whitespace to our rows.
cleanedString := &strings.Builder{}
for _, row := range strings.Split(strings.TrimSpace(tableString.String()), "\n") {
cleanedString.WriteString(strings.TrimSpace(row))
cleanedString.WriteString("\n")
}
fmt.Println(cleanedString.String())
return nil
}
func (data *Table) PrintMarkdown() error {
tableString := &strings.Builder{}
table := tablewriter.NewTable(tableString,
tablewriter.WithRenderer(
renderer.NewBlueprint(
tw.Rendition{
Borders: tw.Border{
Left: tw.On,
Right: tw.On,
Top: tw.Off,
Bottom: tw.Off,
},
Settings: tw.Settings{
Separators: tw.Separators{
ShowHeader: tw.On,
ShowFooter: tw.Off,
BetweenRows: tw.Off,
BetweenColumns: 0,
},
},
Symbols: tw.NewSymbols(tw.StyleMarkdown),
})),
tablewriter.WithConfig(
tablewriter.Config{
Header: tw.CellConfig{
Formatting: tw.CellFormatting{
Alignment: tw.AlignLeft,
AutoFormat: tw.Off,
},
},
Row: tw.CellConfig{
Formatting: tw.CellFormatting{
Alignment: tw.AlignLeft,
},
},
},
),
)
table.Header(data.Headers)
if err := table.Bulk(data.Entries); err != nil {
return fmt.Errorf("failed to add data to table renderer: %s", err)
}
if err := table.Render(); err != nil {
return fmt.Errorf("failed to render table: %s", err)
}
fmt.Println(tableString.String())
return nil
}
func (data *Table) Sort() {
// sanity checks
if len(data.Entries) == 0 {
return
}
sort.Slice(data.Entries, func(i, j int) bool {
return data.Entries[i][0] < data.Entries[j][0]
data.preprocessRows()
sort.Slice(data.rows, func(i, j int) bool {
return data.rows[i][0] < data.rows[j][0]
})
}
@@ -351,7 +212,7 @@ func (data *Table) Addheaders(headers ...string) {
}
}
func (data *Table) AddRow(fields ...string) {
func (data *Table) AddRow(fields ...any) {
data.Entries = append(data.Entries, fields)
}