Add Windows SSH monitoring MVP #1

Merged
ochenstarik-ui merged 23 commits from agent/windows-ssh-monitoring into main 2026-07-16 15:25:19 +00:00
3 changed files with 26 additions and 10 deletions
Showing only changes of commit b19e3e8b31 - Show all commits

View file

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

View file

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

View file

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