8.8 KiB
8.8 KiB
HANA TUI - Implementation Plan
Overview
A single-binary AOT-compiled C# TUI for managing SAP HANA schemas. Built on .NET 10
with Spectre.Console for rich UI, running against the existing hdbsql/hdbuserstore
tools already installed on the system.
Project Setup
Update hanatui.csproj:
<PublishAot>true</PublishAot><AllowUnsafeBlocks>true</AllowUnsafeBlocks><InvariantGlobalization>true</InvariantGlobalization><StripSymbols>true</StripSymbols>- NuGet:
Spectre.Console(core only, no.Cli)
Build command:
dotnet publish -r linux-x64 -c Release -o bin/publish
Output: bin/publish/hanatui (~15-20 MB single native binary, no runtime dependency)
File Structure
hanatui/
├── hanatui.csproj
├── PLAN.md
├── Program.cs
└── src/
├── Hana/
│ ├── HdbClientLocator.cs # Finds hdbclient path (/usr/sap/hdbclient etc.)
│ ├── HdbCliRunner.cs # Spawns hdbsql/hdbuserstore, streams output
│ ├── HdbUserstoreKey.cs # Model: key name, host, port, tenant, user
│ ├── SchemaService.cs # Lists schemas, builds/runs SQL queries
│ └── SqlQueryBuilder.cs # Builds EXPORT/IMPORT/DROP/BACKUP SQL strings
├── System/
│ ├── SystemStats.cs # Reads /proc/stat + /proc/meminfo, computes deltas
│ └── CpuSample.cs # Struct for CPU snapshot (used for delta calc)
└── Tui/
├── KeySelectionScreen.cs # Startup: arrow-key key picker
├── MainMenuScreen.cs # Main menu with current key shown in header
├── OperationForms.cs # Per-op guided input forms (Export/Import/etc.)
├── TaskRunnerScreen.cs # Live split-panel: stats left, log right
└── Components/
├── StatsPanel.cs # CPU bars per core + total, RAM bar, Swap bar
└── LogPanel.cs # Scrolling timestamped log lines
Screen Flow
[Launch]
└─> KeySelectionScreen
hdbuserstore list → parse keys → arrow-key picker (or manual entry)
└─> MainMenuScreen (shows active key + host in header)
└─> OperationForms (per operation)
1. Fetch schemas (spinner while loading)
2. Schema picker (arrow-key selectable list)
3. Additional inputs (path, threads, flags)
4. Confirmation summary before running
└─> TaskRunnerScreen
┌──────────────────┬───────────────────┐
│ SYSTEM STATS │ OPERATION LOG │
│ CPU bars │ streaming lines │
│ Total [████░░] │ with timestamps │
│ Core0 [███░░░] │ │
│ Core1 [█████░] │ │
│ ... │ │
│ RAM bar │ │
│ [██████░░] 58% │ │
│ 12.4 / 21.3 GB │ │
│ Swap bar │ │
│ [██░░░░░░] 18% │ │
│ Elapsed: 4m 32s │ │
└──────────────────┴───────────────────┘
[q] → "Press Q again to abort"
[q again] → SIGTERM → wait 5s → SIGKILL
On complete/abort:
- Stats stop refreshing, log stays visible
- "Returning in 10s... [any key to stay]"
- If key pressed → cancel countdown, stay on result screen
- Enter/Esc → back to main menu
Operations
| # | Operation | Key inputs | Notes |
|---|---|---|---|
| 1 | Export Schema | Schema (picker), target path, threads, compress | Shells pigz if available |
| 2 | Import Schema | Source schema name, source path, threads, replace | Decompresses .tar.gz if needed |
| 3 | Import & Rename | Source name, new name, source path, threads, replace | Adds RENAME clause |
| 4 | Copy Schema | Schema (picker), target name, temp path, threads, replace | Export then Import-Rename |
| 5 | Drop Schema | Schema (picker), type YES to confirm | Destructive — extra confirm |
| 6 | Rename DB | Schema (picker), new company name | Updates CINF + OADM tables |
| 7 | Backup Tenant | Target path, threads, compress | BACKUP DATA USING FILE |
System Stats Details
| Metric | Source | Method |
|---|---|---|
| CPU % total | /proc/stat |
Delta between two reads 500ms apart |
| CPU % core N | /proc/stat |
Per-core lines (cpu0, cpu1, ...) |
| RAM total | /proc/meminfo |
MemTotal |
| RAM used | /proc/meminfo |
MemTotal - MemAvailable |
| Swap total | /proc/meminfo |
SwapTotal |
| Swap used | /proc/meminfo |
SwapTotal - SwapFree |
- Refresh interval: 800ms via
System.Threading.Timer - Bar width adapts to terminal width
- Bar format:
[████████░░] 82%
Abort Behavior
- First
qpress during running task:- Shows warning in log panel:
[WARN] Press Q again to abort the operation
- Shows warning in log panel:
- Second
qpress within ~3 seconds:- Sends SIGTERM to
hdbsqlchild process - Waits up to 5 seconds for graceful exit
- If still running after 5s: sends SIGKILL
- Logs each step to the log panel
- Sends SIGTERM to
Post-Operation Return Behavior
- After success or abort: stats panel freezes, log remains
- Footer shows:
Returning to menu in 10s... [any key to stay] - If any key pressed: countdown cancels, footer changes to
[Enter/Esc] Return to menu - User reads the result at their own pace
AOT Compatibility
| Concern | Mitigation |
|---|---|
| No runtime reflection | All code uses concrete types, no Activator |
| Spectre.Console prompts | SelectionPrompt, TextPrompt — AOT-safe |
| Spectre.Console Live/Layout | AnsiConsole.Live() — AOT-compatible |
| Process spawning | System.Diagnostics.Process — fully AOT-safe |
| /proc file reading | File.ReadAllText — no issues |
| Compression | Process (pigz/tar) — no native .NET compression |
| No JsonSerializer | Not needed |
| No dynamic/Activator | Enforced throughout |
Key Discovery (hdbuserstore)
Command: hdbuserstore list
Sample output:
DATA FILE : /home/user/.hdb/hostname/SSFS_HDB.DAT
KEY FILE : /home/user/.hdb/hostname/SSFS_HDB.KEY
KEY CRONKEY
ENV : hostname:30015@NDB
USER: SYSTEM
KEY DEVKEY
ENV : devhost:30015@DEV
USER: SYSTEM
Parse: lines starting with KEY → key name. Following ENV : and USER: lines → details.
SQL Queries Used
-- List schemas
SELECT SCHEMA_NAME FROM SCHEMAS
WHERE SCHEMA_OWNER = 'SYSTEM'
AND SCHEMA_NAME NOT IN ('_SYS_SECURITY', 'IFSERV', 'B1if', 'SYSTEM', 'RSP');
-- Export
EXPORT "SCHEMA"."*" AS BINARY INTO '/path' WITH REPLACE THREADS N NO DEPENDENCIES;
-- Import
IMPORT "SCHEMA"."*" AS BINARY FROM '/path' WITH IGNORE EXISTING THREADS N;
-- Import-Rename
IMPORT "SCHEMA"."*" AS BINARY FROM '/path'
WITH IGNORE EXISTING RENAME SCHEMA "OLD" TO "NEW" THREADS N;
-- Drop
DROP SCHEMA "SCHEMA" CASCADE;
-- Rename company
UPDATE "SCHEMA".CINF SET "CompnyName" = 'NAME';
UPDATE "SCHEMA".OADM SET "CompnyName" = 'NAME', "PrintHeadr" = 'NAME';
-- Backup tenant
BACKUP DATA USING FILE ('/path/prefix');
-- Get tenant name
SELECT DATABASE_NAME FROM SYS.M_DATABASES;