add hanatui

This commit is contained in:
2026-05-20 11:13:14 +02:00
parent bb8ac5de08
commit 27cf1b6314
19 changed files with 2520 additions and 1 deletions
+313
View File
@@ -0,0 +1,313 @@
using HanaTui.Hana;
using Spectre.Console;
namespace HanaTui.Tui;
/// <summary>
/// Guided input forms for each operation.
/// Each method collects all needed parameters, then returns a typed params object.
/// Returns null if the user cancelled.
/// </summary>
public static class OperationForms
{
// -------------------------------------------------------------------------
// Shared helpers
// -------------------------------------------------------------------------
/// <summary>
/// Fetches the schema list with a spinner, then shows an arrow-key picker.
/// Returns the selected schema, or null if cancelled.
/// </summary>
private static string? PickSchema(string userKey, string title)
{
List<string> schemas = [];
string? error = null;
AnsiConsole.Status()
.Spinner(Spinner.Known.Dots)
.SpinnerStyle(Style.Parse("blue"))
.Start("[blue]Fetching schemas...[/]", _ =>
{
var (success, list, err) = HdbCliRunner.ListSchemas(userKey);
if (success)
schemas = list;
else
error = err;
});
if (error is not null)
AnsiConsole.MarkupLine($"[yellow][[WARN]] Could not fetch schemas: {Markup.Escape(error)}[/]");
var choices = new List<string>(schemas);
choices.Add("[dim][ Enter manually ][/]");
choices.Add("[dim][ Cancel ][/]");
var prompt = new SelectionPrompt<string>()
.Title($"[bold]{title}[/]")
.PageSize(15)
.HighlightStyle(Style.Parse("bold dodgerblue1"))
.AddChoices(choices);
var selected = AnsiConsole.Prompt(prompt);
if (selected.Contains("Cancel"))
return null;
if (selected.Contains("manually"))
{
var manual = AnsiConsole.Ask<string>("Enter schema name:").Trim();
return string.IsNullOrWhiteSpace(manual) ? null : manual;
}
return selected.Trim();
}
private static int PickThreads(string label = "Number of threads")
{
var maxThreads = Environment.ProcessorCount;
var defaultThreads = Math.Max(1, maxThreads / 2);
return AnsiConsole.Ask($"{label} [dim](default={defaultThreads}, max={maxThreads})[/]:",
defaultThreads);
}
private static bool ConfirmYesNo(string question, bool defaultYes = false)
{
var defaultLabel = defaultYes ? "Y/n" : "y/N";
var answer = AnsiConsole.Ask<string>($"{question} [dim]({defaultLabel})[/]:", defaultYes ? "y" : "n");
return answer.Trim().Equals("y", StringComparison.OrdinalIgnoreCase) ||
answer.Trim().Equals("yes", StringComparison.OrdinalIgnoreCase);
}
private static void PrintOperationHeader(string title)
{
AnsiConsole.Clear();
var rule = new Rule($"[bold dodgerblue1]{title}[/]").RuleStyle(Style.Parse("dodgerblue1"));
AnsiConsole.Write(rule);
AnsiConsole.WriteLine();
}
private static bool ShowSummaryAndConfirm(string title, Dictionary<string, string> fields)
{
AnsiConsole.WriteLine();
var table = new Table().BorderColor(Color.DodgerBlue1).Border(TableBorder.Rounded);
table.AddColumn("[bold]Parameter[/]");
table.AddColumn("[bold]Value[/]");
foreach (var (k, v) in fields)
table.AddRow($"[dim]{k}[/]", $"[yellow]{Markup.Escape(v)}[/]");
AnsiConsole.MarkupLine($"[bold]{title}[/]");
AnsiConsole.Write(table);
AnsiConsole.WriteLine();
return ConfirmYesNo("Proceed with this operation?", defaultYes: true);
}
// -------------------------------------------------------------------------
// Export
// -------------------------------------------------------------------------
public static ExportParams? ExportForm(string userKey)
{
PrintOperationHeader("Export Schema");
var schema = PickSchema(userKey, "Select schema to export:");
if (schema is null) return null;
var targetPath = AnsiConsole.Ask<string>("Target directory or file path [dim](.tar.gz for archive)[/]:").Trim();
if (string.IsNullOrWhiteSpace(targetPath)) return null;
var threads = PickThreads();
var compress = ConfirmYesNo("Compress output as .tar.gz?");
var confirmed = ShowSummaryAndConfirm("Export Summary", new Dictionary<string, string>
{
["Schema"] = schema,
["Target path"] = targetPath,
["Threads"] = threads.ToString(),
["Compress"] = compress ? "Yes" : "No",
});
if (!confirmed) return null;
return new ExportParams
{
Schema = schema,
TargetPath = targetPath,
Threads = threads,
Compress = compress,
};
}
// -------------------------------------------------------------------------
// Import
// -------------------------------------------------------------------------
public static ImportParams? ImportForm(string userKey, bool renameMode)
{
var title = renameMode ? "Import & Rename Schema" : "Import Schema";
PrintOperationHeader(title);
var sourceSchema = AnsiConsole.Ask<string>("Source schema name [dim](as it was exported)[/]:").Trim();
if (string.IsNullOrWhiteSpace(sourceSchema)) return null;
string? newSchemaName = null;
if (renameMode)
{
newSchemaName = AnsiConsole.Ask<string>("New target schema name:").Trim();
if (string.IsNullOrWhiteSpace(newSchemaName)) return null;
}
var sourcePath = AnsiConsole.Ask<string>("Source path [dim](directory or .tar.gz)[/]:").Trim();
if (string.IsNullOrWhiteSpace(sourcePath)) return null;
var threads = PickThreads();
var replace = ConfirmYesNo("Replace existing objects?");
var summary = new Dictionary<string, string>
{
["Source schema"] = sourceSchema,
["Source path"] = sourcePath,
["Threads"] = threads.ToString(),
["Replace"] = replace ? "Yes" : "No (IGNORE EXISTING)",
};
if (renameMode && newSchemaName is not null)
summary["New schema name"] = newSchemaName;
var confirmed = ShowSummaryAndConfirm($"{title} Summary", summary);
if (!confirmed) return null;
return new ImportParams
{
SourceSchema = sourceSchema,
NewSchemaName = newSchemaName,
SourcePath = sourcePath,
Threads = threads,
Replace = replace,
};
}
// -------------------------------------------------------------------------
// Copy
// -------------------------------------------------------------------------
public static CopyParams? CopyForm(string userKey)
{
PrintOperationHeader("Copy Schema");
var sourceSchema = PickSchema(userKey, "Select source schema:");
if (sourceSchema is null) return null;
var targetSchema = AnsiConsole.Ask<string>("Target schema name:").Trim();
if (string.IsNullOrWhiteSpace(targetSchema)) return null;
var tempPath = AnsiConsole.Ask<string>("Temporary export directory path:").Trim();
if (string.IsNullOrWhiteSpace(tempPath)) return null;
var threads = PickThreads();
var replace = ConfirmYesNo("Replace existing objects in target schema?");
var confirmed = ShowSummaryAndConfirm("Copy Schema Summary", new Dictionary<string, string>
{
["Source schema"] = sourceSchema,
["Target schema"] = targetSchema,
["Temp path"] = tempPath,
["Threads"] = threads.ToString(),
["Replace"] = replace ? "Yes" : "No",
});
if (!confirmed) return null;
return new CopyParams
{
SourceSchema = sourceSchema,
TargetSchema = targetSchema,
TempPath = tempPath,
Threads = threads,
Replace = replace,
};
}
// -------------------------------------------------------------------------
// Drop
// -------------------------------------------------------------------------
public static DropParams? DropForm(string userKey)
{
PrintOperationHeader("Drop Schema");
var schema = PickSchema(userKey, "Select schema to drop:");
if (schema is null) return null;
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine($"[bold red]WARNING:[/] You are about to [bold red]permanently drop[/] schema [bold yellow]{Markup.Escape(schema)}[/].");
AnsiConsole.MarkupLine("[red]This cannot be undone.[/]\n");
var confirm = AnsiConsole.Ask<string>($"Type [bold red]YES[/] to confirm dropping [yellow]{Markup.Escape(schema)}[/]:").Trim();
if (confirm != "YES")
{
AnsiConsole.MarkupLine("[yellow]Operation cancelled.[/]");
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("[dim]Press any key to return...[/]");
Console.ReadKey(intercept: true);
return null;
}
return new DropParams { Schema = schema };
}
// -------------------------------------------------------------------------
// Rename DB
// -------------------------------------------------------------------------
public static RenameDbParams? RenameDbForm(string userKey)
{
PrintOperationHeader("Rename Database (Company Name)");
var schema = PickSchema(userKey, "Select schema:");
if (schema is null) return null;
var newName = AnsiConsole.Ask<string>("New company name:").Trim();
if (string.IsNullOrWhiteSpace(newName)) return null;
var confirmed = ShowSummaryAndConfirm("Rename DB Summary", new Dictionary<string, string>
{
["Schema"] = schema,
["New company name"] = newName,
["Tables updated"] = "CINF, OADM",
});
if (!confirmed) return null;
return new RenameDbParams { Schema = schema, NewCompanyName = newName };
}
// -------------------------------------------------------------------------
// Backup
// -------------------------------------------------------------------------
public static BackupParams? BackupForm(string userKey)
{
PrintOperationHeader("Backup Tenant");
var targetPath = AnsiConsole.Ask<string>("Target directory path:").Trim();
if (string.IsNullOrWhiteSpace(targetPath)) return null;
var threads = PickThreads("Number of compression threads");
var compress = ConfirmYesNo("Compress backup as .tar.gz?");
var confirmed = ShowSummaryAndConfirm("Backup Summary", new Dictionary<string, string>
{
["Target path"] = targetPath,
["Threads"] = threads.ToString(),
["Compress"] = compress ? "Yes" : "No",
});
if (!confirmed) return null;
return new BackupParams
{
TargetPath = targetPath,
Threads = threads,
Compress = compress,
};
}
}