first commit
This commit is contained in:
149
Tui/FirewallTui.cs
Normal file
149
Tui/FirewallTui.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using HanaToolbox.Config;
|
||||
using HanaToolbox.Logging;
|
||||
using HanaToolbox.Services;
|
||||
using HanaToolbox.Services.Interfaces;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace HanaToolbox.Tui;
|
||||
|
||||
/// <summary>
|
||||
/// Interactive Firewall TUI using Spectre.Console.
|
||||
/// Mirrors the firewalld.sh interactive flow.
|
||||
/// </summary>
|
||||
public sealed class FirewallTui(
|
||||
FirewallService firewallService,
|
||||
AppLogger _logger)
|
||||
{
|
||||
public async Task<FirewallConfig?> RunAsync(
|
||||
FirewallConfig current, CancellationToken ct = default)
|
||||
{
|
||||
AnsiConsole.Clear();
|
||||
AnsiConsole.Write(new Rule("[cyan]SAP B1 Firewall Configurator[/]").RuleStyle("cyan"));
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// Flush question
|
||||
var flush = AnsiConsole.Confirm("Flush (remove) all current firewall rules before applying? [creates clean slate]",
|
||||
defaultValue: current.FlushBeforeApply);
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// Per-service configuration
|
||||
var entries = new List<FirewallServiceEntry>();
|
||||
foreach (var svc in current.Services)
|
||||
{
|
||||
AnsiConsole.Write(new Rule($"[green]{svc.Name}[/] [grey]({string.Join(", ", svc.Ports)})[/]").RuleStyle("grey"));
|
||||
|
||||
var choice = AnsiConsole.Prompt(
|
||||
new SelectionPrompt<string>()
|
||||
.Title("Access rule:")
|
||||
.AddChoices("Allow from ANYWHERE (Public)", "Restrict to SPECIFIC IPs", "Skip / Block")
|
||||
.HighlightStyle("cyan"));
|
||||
|
||||
var decision = choice switch
|
||||
{
|
||||
"Allow from ANYWHERE (Public)" => FirewallDecision.All,
|
||||
"Restrict to SPECIFIC IPs" => FirewallDecision.Ip,
|
||||
_ => FirewallDecision.Skip
|
||||
};
|
||||
|
||||
var ips = new List<string>();
|
||||
if (decision == FirewallDecision.Ip)
|
||||
{
|
||||
var existing = string.Join(", ", svc.AllowedIps);
|
||||
var ipInput = AnsiConsole.Prompt(
|
||||
new TextPrompt<string>("Enter IPs or subnets (comma-separated):")
|
||||
.DefaultValue(existing.Length > 0 ? existing : "192.168.1.1")
|
||||
.AllowEmpty());
|
||||
|
||||
ips = ipInput
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(i => i.Trim())
|
||||
.Where(i => i.Length > 0)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
entries.Add(new FirewallServiceEntry
|
||||
{
|
||||
Name = svc.Name,
|
||||
Ports = svc.Ports,
|
||||
Decision = decision,
|
||||
AllowedIps = ips
|
||||
});
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
|
||||
// Summary table
|
||||
AnsiConsole.Write(new Rule("[yellow]Summary[/]").RuleStyle("yellow"));
|
||||
var table = new Table()
|
||||
.AddColumn("Service")
|
||||
.AddColumn("Action")
|
||||
.AddColumn("Details");
|
||||
|
||||
foreach (var e in entries)
|
||||
{
|
||||
var (action, details) = e.Decision switch
|
||||
{
|
||||
FirewallDecision.All => ("[red]Open Public[/]", "0.0.0.0/0"),
|
||||
FirewallDecision.Ip => ("[green]Restricted[/]", string.Join(", ", e.AllowedIps)),
|
||||
_ => ("[grey]Blocked/Skip[/]", "-")
|
||||
};
|
||||
table.AddRow(e.Name.Length > 35 ? e.Name[..35] : e.Name, action, details);
|
||||
}
|
||||
AnsiConsole.Write(table);
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
if (!AnsiConsole.Confirm("Apply and save these rules?", defaultValue: true))
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]Aborted.[/]");
|
||||
return null;
|
||||
}
|
||||
|
||||
var updated = new FirewallConfig
|
||||
{
|
||||
FlushBeforeApply = flush,
|
||||
Services = entries,
|
||||
Enabled = current.Enabled,
|
||||
ScheduleHour = current.ScheduleHour,
|
||||
ScheduleMinute = current.ScheduleMinute
|
||||
};
|
||||
|
||||
// Apply rules
|
||||
await firewallService.ApplyAsync(updated, ct);
|
||||
|
||||
// Safety revert window
|
||||
AnsiConsole.MarkupLine("[yellow]Rules applied. You have 15 seconds to confirm your connection still works.[/]");
|
||||
AnsiConsole.MarkupLine("Press [cyan]ENTER[/] to keep changes permanently, or wait to auto-revert.");
|
||||
|
||||
var confirmed = await WaitForConfirmAsync(15, ct);
|
||||
if (!confirmed)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[red]Timeout — reverting firewall to permanent config...[/]");
|
||||
await AnsiConsole.Status().StartAsync("Reverting...", async _ =>
|
||||
{
|
||||
await Task.Run(() =>
|
||||
System.Diagnostics.Process.Start("/bin/bash", "-c firewall-cmd --reload")?.WaitForExit(), ct);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
AnsiConsole.MarkupLine("[green]Changes confirmed and saved permanently.[/]");
|
||||
return updated;
|
||||
}
|
||||
|
||||
private static async Task<bool> WaitForConfirmAsync(int seconds, CancellationToken ct)
|
||||
{
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(seconds));
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Run(() => Console.ReadLine(), cts.Token);
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user