(bugfix): sort resource table ip columns as addresses

This commit is contained in:
Maksym Sadovnychyy 2026-08-20 15:28:39 +02:00
parent 1ed5962d21
commit a741640858
5 changed files with 84 additions and 4 deletions

View File

@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
## [Unreleased] ## [Unreleased]
## [0.2.1] - 2026-08-20
### Fixed
- Resource table IP columns (**Cluster IP**, **External IP**) sort as addresses, not text (`10.1.1.2` before `10.1.1.10`).
## [0.2.0] - 2026-08-20 ## [0.2.0] - 2026-08-20
### Added ### Added

View File

@ -1,8 +1,8 @@
# MaksIT.ClusterConsole # MaksIT.ClusterConsole
![Line Coverage](https://img.shields.io/badge/Line%20Coverage-50.9%25-yellowgreen) ![Line Coverage](https://img.shields.io/badge/Line%20Coverage-52.7%25-yellowgreen)
![Branch Coverage](https://img.shields.io/badge/Branch%20Coverage-41.5%25-yellowgreen) ![Branch Coverage](https://img.shields.io/badge/Branch%20Coverage-43.9%25-yellowgreen)
![Method Coverage](https://img.shields.io/badge/Method%20Coverage-56.7%25-yellowgreen) ![Method Coverage](https://img.shields.io/badge/Method%20Coverage-60.2%25-green)
![.NET](https://img.shields.io/badge/.NET-10-512BD4) ![.NET](https://img.shields.io/badge/.NET-10-512BD4)
![License](https://img.shields.io/badge/License-Apache%202.0-blue) ![License](https://img.shields.io/badge/License-Apache%202.0-blue)
![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20Linux-0078D6) ![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20Linux-0078D6)

View File

@ -3,7 +3,7 @@
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Version>0.2.0</Version> <Version>0.2.1</Version>
<Product>MaksIT.ClusterConsole</Product> <Product>MaksIT.ClusterConsole</Product>
<AssemblyTitle>MaksIT.ClusterConsole</AssemblyTitle> <AssemblyTitle>MaksIT.ClusterConsole</AssemblyTitle>
</PropertyGroup> </PropertyGroup>

View File

@ -1,3 +1,4 @@
using System.Net;
using System.Collections; using System.Collections;
using System.Globalization; using System.Globalization;
using MaksIT.ClusterConsole.Client; using MaksIT.ClusterConsole.Client;
@ -48,6 +49,7 @@ public static class ResourceColumnSort {
"Memory" => KubeQuantity.ToBytes(left).CompareTo(KubeQuantity.ToBytes(right)), "Memory" => KubeQuantity.ToBytes(left).CompareTo(KubeQuantity.ToBytes(right)),
"Replicas" or "Desired" or "Current" or "Min" or "Max" or "Port" => "Replicas" or "Desired" or "Current" or "Min" or "Max" or "Port" =>
ParseInt(left).CompareTo(ParseInt(right)), ParseInt(left).CompareTo(ParseInt(right)),
_ when IsIpHeader(header) => CompareIpList(left, right),
_ => string.Compare(left, right, StringComparison.OrdinalIgnoreCase) _ => string.Compare(left, right, StringComparison.OrdinalIgnoreCase)
}; };
} }
@ -83,6 +85,67 @@ public static class ResourceColumnSort {
return total; return total;
} }
private static bool IsIpHeader(string header) =>
header.Equals("IP", StringComparison.OrdinalIgnoreCase)
|| header.EndsWith(" IP", StringComparison.OrdinalIgnoreCase);
private static int CompareIpList(string left, string right) {
var leftParts = SplitIpCell(left);
var rightParts = SplitIpCell(right);
var count = Math.Min(leftParts.Length, rightParts.Length);
for (var i = 0; i < count; i++) {
var cmp = CompareIpToken(leftParts[i], rightParts[i]);
if (cmp != 0)
return cmp;
}
return leftParts.Length.CompareTo(rightParts.Length);
}
private static string[] SplitIpCell(string text) =>
text.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
private static int CompareIpToken(string left, string right) {
var leftMissing = IsMissingIp(left);
var rightMissing = IsMissingIp(right);
if (leftMissing && rightMissing)
return 0;
if (leftMissing)
return -1;
if (rightMissing)
return 1;
if (IPAddress.TryParse(left, out var leftIp)) {
if (IPAddress.TryParse(right, out var rightIp))
return CompareAddress(leftIp, rightIp);
return -1;
}
if (IPAddress.TryParse(right, out _))
return 1;
return string.Compare(left, right, StringComparison.OrdinalIgnoreCase);
}
private static bool IsMissingIp(string text) =>
text.Equals("None", StringComparison.OrdinalIgnoreCase)
|| text.Equals("<none>", StringComparison.OrdinalIgnoreCase);
private static int CompareAddress(IPAddress left, IPAddress right) {
var family = left.AddressFamily.CompareTo(right.AddressFamily);
if (family != 0)
return family;
Span<byte> leftBytes = stackalloc byte[16];
Span<byte> rightBytes = stackalloc byte[16];
if (!left.TryWriteBytes(leftBytes, out var leftLength)
|| !right.TryWriteBytes(rightBytes, out var rightLength))
return string.CompareOrdinal(left.ToString(), right.ToString());
return leftBytes[..leftLength].SequenceCompareTo(rightBytes[..rightLength]);
}
private static int CompareReady(string left, string right) { private static int CompareReady(string left, string right) {
ParseReady(left, out var leftReady, out var leftTotal); ParseReady(left, out var leftReady, out var leftTotal);
ParseReady(right, out var rightReady, out var rightTotal); ParseReady(right, out var rightReady, out var rightTotal);

View File

@ -29,6 +29,17 @@ public class ResourceTableTests {
Assert.True(ResourceColumnSort.Compare("Age", "2d", "5h") > 0); Assert.True(ResourceColumnSort.Compare("Age", "2d", "5h") > 0);
} }
[Fact]
public void Ip_columns_sort_as_addresses_not_text() {
Assert.True(ResourceColumnSort.Compare("Cluster IP", "10.1.1.2", "10.1.1.10") < 0);
Assert.True(ResourceColumnSort.Compare("Cluster IP", "10.1.1.10", "10.1.1.2") > 0);
Assert.Equal(0, ResourceColumnSort.Compare("External IP", "192.168.0.1", "192.168.0.1"));
Assert.True(ResourceColumnSort.Compare("Cluster IP", "None", "10.0.0.1") < 0);
Assert.True(ResourceColumnSort.Compare("External IP", "10.0.0.2,10.0.0.10", "10.0.0.2,10.0.0.3") > 0);
Assert.True(ResourceColumnSort.Compare("Cluster IP", "127.0.0.1", "::1") < 0);
Assert.True(ResourceColumnSort.Compare("Cluster IP", "2001:db8::1", "2001:db8::10") < 0);
}
[Fact] [Fact]
public void Ready_and_restarts_sort_numerically() { public void Ready_and_restarts_sort_numerically() {
Assert.True(ResourceColumnSort.Compare("Ready", "1/2", "2/2") < 0); Assert.True(ResourceColumnSort.Compare("Ready", "1/2", "2/2") < 0);