Confirm applied Link state in Windows client

This commit is contained in:
Ochenstarik 2026-07-16 19:02:05 +07:00
parent 0f6dd31646
commit b19e3e8b31
3 changed files with 26 additions and 10 deletions

View file

@ -51,9 +51,9 @@
- [x] политики по целевому `/32`, TCP/UDP и порту;
- [x] TTL и автоматическое истечение;
- [ ] состояния Connecting, Active, Disconnecting, Partial, Disabled и Failed;
- [ ] версия политики и подтверждение применения;
- [x] версия политики и подтверждение применения на Hub;
- [ ] обязательное отключение после reconnect;
- [ ] append-only аудит;
- [x] локальный append-only JSONL-аудит операций Link;
- [ ] интеграционные тесты kill switch и частичных отказов.
## Этап 5 — мониторинг и терминал

View file

@ -306,9 +306,10 @@ public sealed partial class MainPage : Page
continue;
}
var fields = line[5..].Split('|');
if (fields.Length >= 7
if (fields.Length >= 8
&& int.TryParse(fields[4], CultureInfo.InvariantCulture, out var port)
&& long.TryParse(fields[5], CultureInfo.InvariantCulture, out var expiresUnix))
&& long.TryParse(fields[5], CultureInfo.InvariantCulture, out var expiresUnix)
&& long.TryParse(fields[7], CultureInfo.InvariantCulture, out var version))
{
MeshLinks.Add(new MeshLinkViewModel(
fields[0],
@ -316,7 +317,9 @@ public sealed partial class MainPage : Page
fields[2],
fields[3],
port,
expiresUnix));
expiresUnix,
fields[6],
version));
}
}
@ -411,14 +414,21 @@ public sealed partial class MainPage : Page
? $"{protocol} {port} {ttlMinutes}"
: $"{protocol} {port}";
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
await _ssh.RunRestrictedCommandAsync(
var commandOutput = await _ssh.RunRestrictedCommandAsync(
hub.Profile,
$"mesh {action} {source.Name} {target.Name} {policyArguments}",
timeout.Token);
var confirmation = commandOutput
.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)
.FirstOrDefault(line => line.StartsWith("LINK_STATE=", StringComparison.Ordinal));
if (confirmation is null)
{
throw new InvalidOperationException("Hub не подтвердил фактическое состояние Link.");
}
await RefreshMeshAsync(showSuccess: false);
ShowInfo(
enable ? "Связь включена" : "Связь отключена",
$"{source.Name} → {target.Name} · {protocol.ToUpperInvariant()}/{port}",
enable ? "Hub подтвердил Active" : "Hub подтвердил Disabled",
$"{source.Name} → {target.Name} · {protocol.ToUpperInvariant()}/{port} · {confirmation[11..]}",
enable ? InfoBarSeverity.Success : InfoBarSeverity.Warning);
}
catch (Exception exception)

View file

@ -25,7 +25,9 @@ public sealed class MeshLinkViewModel
string cidr,
string protocol,
int port,
long expiresUnix)
long expiresUnix,
string state,
long version)
{
Source = source;
Target = target;
@ -33,6 +35,8 @@ public sealed class MeshLinkViewModel
Protocol = protocol;
Port = port;
ExpiresUnix = expiresUnix;
State = state;
Version = version;
}
public string Source { get; set; }
@ -41,8 +45,10 @@ public sealed class MeshLinkViewModel
public string Protocol { get; set; }
public int Port { get; set; }
public long ExpiresUnix { get; set; }
public string State { get; set; }
public long Version { get; set; }
public string ExpirationText => ExpiresUnix == 0
? "вручную"
: $"до {DateTimeOffset.FromUnixTimeSeconds(ExpiresUnix).ToLocalTime():dd.MM HH:mm}";
public string Label => $"{Source} → {Target} · {Protocol.ToUpperInvariant()}/{Port} · {Cidr} · {ExpirationText}";
public string Label => $"{Source} → {Target} · {Protocol.ToUpperInvariant()}/{Port} · {Cidr} · {ExpirationText} · {State} v{Version}";
}