commit b53597c2887b2b5554901f6c9375cef407f37d3d Author: Faiz Kaysan Hidayat Date: Tue Oct 13 12:31:36 2026 +0000 Upload files to "internal" diff --git a/internal/README.md b/internal/README.md new file mode 100644 index 0000000..12511bb --- /dev/null +++ b/internal/README.md @@ -0,0 +1,282 @@ +# Nebara OS TUI — Visual Baseline v0.1 + +Native terminal management interface for the **Nebara S3GX Appliance**, implemented in Go using `tview` + `tcell/v2`. + +> [!IMPORTANT] +> This is a **visual prototype** using mock appliance data only. +> No real system configuration, privileged operations, or hardware interaction occurs. + +--- + +## Overview + +Nebara OS is a common appliance operating environment for enterprise infrastructure products. +This prototype establishes the TUI visual foundation for the Nebara S3GX Appliance, +reproducing the Figma reference mockups as faithfully as a native terminal environment allows. + +The interface is intentionally minimal and industrial — inspired by enterprise console UIs like VMware ESXi DCUI. + +--- + +## Prerequisites + +| Requirement | Version | +|---|---| +| Go | ≥ 1.21 | +| Terminal | True-color 256+ color support | +| Recommended font | JetBrains Mono (any weight) | + +### Check Go version + +```bash +go version +``` + +--- + +## Build + +```bash +git clone +cd nebara-os +go mod tidy +go build -o nebara-tui ./cmd/nebara-tui +``` + +On Windows: + +```powershell +go build -o nebara-tui.exe ./cmd/nebara-tui +``` + +--- + +## Run + +```bash +# Warning state (default) — matches Figma Mock State A +./nebara-tui --mock-state=warning + +# Normal state — matches Figma Mock State B +./nebara-tui --mock-state=normal +``` + +Or via `go run`: + +```bash +go run ./cmd/nebara-tui --mock-state=warning +go run ./cmd/nebara-tui --mock-state=normal +``` + +--- + +## Mock States + +### State A — Warning (`--mock-state=warning`) + +| Field | Value | +|---|---| +| Product | Nebara S3GX Appliance | +| Version | 1.0.1 | +| Hostname | jkt-s3gx-lin-01.domain.local | +| Address | 10.249.23.13/26 | +| Uptime | 132d 12h 32m | +| Status | Healthy | +| Message | `[Warn] Update Available to version 2.1.4!` | +| Initial selection | Security & Access | + +### State B — Normal (`--mock-state=normal`) + +| Field | Value | +|---|---| +| Product | Nebara S3GX Appliance | +| Version | 2.1.4 | +| Hostname | sin-s3gx-lin-05.domain.local | +| Address | 10.175.15.11/25 | +| Uptime | 111d 14h 21m | +| Status | Healthy | +| Message | None | +| Initial selection | System Maintenance | + +--- + +## Keyboard Controls + +| Key | Action | +|---|---| +| `↑` / `↓` | Navigate menu items | +| `Enter` | Open submenu or confirm | +| `Esc` | Return to parent menu | +| `Home` | Jump to first item | +| `End` | Jump to last item | +| `F1` | Show keyboard shortcut help | +| `Ctrl+C` | Quit application | + +--- + +## Recommended Terminal Settings + +For the best visual experience: + +- **Font:** JetBrains Mono (Regular / Bold) — available from [jetbrains.com/lp/mono](https://www.jetbrains.com/lp/mono/) +- **Color support:** True-color (24-bit) — set `COLORTERM=truecolor` if needed +- **Background:** Black (`#000000`) — ensure terminal background matches +- **Minimum size:** 60 columns × 20 rows (warning shown if smaller) +- **Recommended size:** 120 columns × 40 rows or larger + +### Windows Terminal + +```json +{ + "font": { "face": "JetBrains Mono", "size": 14 }, + "colorScheme": "One Half Dark" +} +``` + +Set `COLORTERM=truecolor` in your shell profile for 24-bit colors. + +--- + +## Responsive Layout + +The layout adapts to terminal dimensions using percentage-based margins derived from the Figma reference (1920×1080, 80px outer margins ≈ 4.17% of width): + +| Terminal Size | Behavior | +|---|---| +| 192×54 and wider | Spacious; full Figma proportions | +| 120×40 | Normal desktop; full metadata | +| 100×30 | Compact; reduced whitespace | +| 80×24 | Minimum practical target; message row suppressed below 24 rows | +| Below 60×20 | Size warning overlay; no crash | + +Live resizing is handled; no restart required. + +--- + +## Project Structure + +``` +nebara-os/ +├── cmd/ +│ └── nebara-tui/ +│ └── main.go # CLI entry point, --mock-state flag +│ +├── internal/ +│ ├── app/ +│ │ ├── application.go # TUI wiring: layout, callbacks, modals +│ │ └── state.go # Runtime application state struct +│ │ +│ ├── model/ +│ │ ├── appliance.go # ApplianceInfo data model +│ │ ├── message.go # SystemMessage + severity types +│ │ └── menu.go # MenuItem tree + MainMenu() factory +│ │ +│ ├── ui/ +│ │ ├── theme/ +│ │ │ └── colors.go # Centralized color tokens & tcell styles +│ │ │ +│ │ ├── layout/ +│ │ │ └── responsive.go # Terminal→cell layout constraint calculator +│ │ │ +│ │ ├── components/ +│ │ │ ├── header.go # Header, ApplianceInfo, Message, Description, Footer +│ │ │ └── menu.go # Custom MenuWidget (full-width row highlight) +│ │ │ +│ │ └── navigation/ +│ │ └── navigator.go # Navigation stack (Enter/Back/Up/Down) +│ │ +│ └── mock/ +│ └── states.go # WarningState() and NormalState() factories +│ +├── go.mod +├── go.sum +└── README.md +``` + +--- + +## Design System + +### Colors + +| Token | Hex | Usage | +|---|---|---| +| Background | `#000000` | Main screen background | +| Primary | `#F5F5F5` | Default foreground text | +| Secondary | `#BBBBBB` | Labels, descriptions, borders | +| Selection BG | `#BBBBBB` | Selected menu row background | +| Selection FG | `#000000` | Selected menu row text | +| Warning | `#FFCC00` | Warning message text | +| Error | `#B30D18` | Error messages; logo "O" accent | +| Info | `#0088FF` | Informational messages | + +### Typography Hierarchy + +The terminal cannot control font family per-component. Hierarchy is expressed through: + +- **Bold** — product title, footer key labels +- **Regular** — metadata labels, values, menu items, descriptions +- **Color** — severity-classified messages, logo accent + +--- + +## Known Terminal Rendering Limitations + +| Figma Specification | Terminal Equivalent | +|---|---| +| Michroma 24px logo font | Terminal monospace font; layout preserved | +| JetBrains Mono Bold/Regular by size | Bold attribute; font configured per-terminal | +| 1px border (outside alignment) | Single-cell-width tview box border | +| Pixel-precise spacing | Cell-based proportional spacing | +| Shadow effects | Not reproducible in terminals | +| Rounded corners | Not applicable; square box characters used | +| True-color hex values | Full 24-bit color via tcell on supported terminals | + +--- + +## Testing + +```bash +go test ./... # Run all tests +go test ./... -v # Verbose output +go vet ./... # Static analysis +go fmt ./... # Format check +``` + +### Test Coverage + +| Package | Tests | +|---|---| +| `internal/mock` | Mock state correctness, message visibility | +| `internal/ui/layout` | Margin clamping, message visibility, compact behavior | +| `internal/ui/navigation` | All navigation transitions, stack depth, breadcrumbs | + +### Manual Validation Steps + +1. Launch both mock states and compare against reference screenshots +2. Navigate all 6 main menu items with ↑/↓ — verify description updates +3. Press Enter on each main menu item — verify submenu appears with correct title +4. Navigate submenu items — verify ↑/↓ work +5. Press Esc from submenu — verify return to main menu with original selection +6. Press Enter on a leaf (submenu item) — verify not-implemented modal appears +7. Press F1 — verify help modal appears +8. Resize terminal live — verify no flickering or corruption +9. Shrink below 60×20 — verify size warning appears + +--- + +## Future Integration Boundaries + +This prototype establishes the visual foundation. Future phases will: + +- Connect to a privileged management daemon via Unix socket or gRPC +- Add real `ApplianceInfo` collection (hostname, IP, uptime) without root +- Implement `System Configuration`, `Network Configuration`, etc. as real forms +- Support product-specific modules (UNFZFS, Tape, Backup) via adapter pattern +- Add authentication gate before main menu on production builds + +The TUI must never require root privileges. Privileged operations route through a separate daemon. + +--- + +*Nebara OS TUI — Visual Baseline v0.1 — Prototype only — Mock data only* diff --git a/internal/application.go b/internal/application.go new file mode 100644 index 0000000..95c809c --- /dev/null +++ b/internal/application.go @@ -0,0 +1,326 @@ +package app + +import ( + "github.com/gdamore/tcell/v2" + "github.com/nebara/nebara-os/internal/ui/components" + "github.com/nebara/nebara-os/internal/ui/layout" + "github.com/nebara/nebara-os/internal/ui/theme" + "github.com/rivo/tview" +) + +// Application is the root Nebara OS TUI application object. +// It owns the tview application, all UI components, and the application state. +type Application struct { + tviewApp *tview.Application + state *State + + // tview layout primitives + pages *tview.Pages + rootFlex *tview.Flex // full-screen outer flex + contentFlex *tview.Flex // inner vertical flex with margins applied + + // Named UI components + header *components.HeaderWidget + appInfo *components.ApplianceInfoWidget + msgWidget *components.MessageWidget + menuWidget *components.MenuWidget + description *components.DescriptionWidget + footer *components.FooterWidget + + // Current layout constraints + constraints layout.Constraints + + // Minimum-size overlay: when true we only show the size warning. + tooSmall bool +} + +// NewApplication constructs the complete TUI application from application state. +func NewApplication(state *State) *Application { + a := &Application{ + tviewApp: tview.NewApplication(), + state: state, + } + a.tviewApp.EnableMouse(false) + a.buildUI() + a.wireCallbacks() + a.syncDescription() + return a +} + +// Run starts the TUI event loop and blocks until the application exits. +func (a *Application) Run() error { + return a.tviewApp.Run() +} + +// buildUI constructs all widgets and assembles the layout. +func (a *Application) buildUI() { + s := a.state + + // --- Widgets --- + a.header = components.NewHeaderWidget(s.Appliance.Version, 8) + a.appInfo = components.NewApplianceInfoWidget(s.Appliance) + a.msgWidget = components.NewMessageWidget() + a.msgWidget.SetMessage(s.Message) + + // Determine initial inner padding for menu. + a.menuWidget = components.NewMenuWidget( + a.state.Navigator.Current().Items, + 2, + ) + a.menuWidget.SetSelectedIndex(a.state.Navigator.Current().SelectedIdx) + + a.description = components.NewDescriptionWidget() + a.footer = components.NewFooterWidget() + + // --- Assemble layout --- + a.contentFlex = a.buildContentFlex() + + // Outer flex: left margin | content | right margin + a.rootFlex = tview.NewFlex().SetDirection(tview.FlexColumn) + a.rootFlex.SetBackgroundColor(theme.ColorBackground) + + a.pages = tview.NewPages() + a.pages.SetBackgroundColor(theme.ColorBackground) + a.pages.AddPage("main", a.rootFlex, true, true) + + // Initial layout pass — will be recalculated on first draw/resize. + a.applyConstraints(layout.Calculate(120, 40)) + + a.tviewApp.SetRoot(a.pages, true) + a.tviewApp.SetFocus(a.menuWidget) + + // Handle terminal resize. + a.tviewApp.SetBeforeDrawFunc(func(screen tcell.Screen) bool { + tw, th := screen.Size() + c := layout.Calculate(tw, th) + if c.IsMinimum != a.tooSmall || + c.MarginH != a.constraints.MarginH || + c.MarginV != a.constraints.MarginV || + c.ContentWidth != a.constraints.ContentWidth { + a.applyConstraints(c) + } + return false + }) + + // Global key: Ctrl+C to quit, F1 for help. + a.tviewApp.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyCtrlC: + a.tviewApp.Stop() + return nil + case tcell.KeyF1: + a.showHelp() + return nil + } + return event + }) +} + +// buildContentFlex assembles the vertical content stack (without outer margins). +func (a *Application) buildContentFlex() *tview.Flex { + // Spacer utility. + spacer := func(rows int) tview.Primitive { + box := tview.NewBox().SetBackgroundColor(theme.ColorBackground) + _ = rows + return box + } + + // Determine menu height: 2 rows per item (text + blank) + 2 border rows + 2 inset rows. + menuHeight := len(a.state.Navigator.Current().Items)*2 + 4 + + // Message row: always 1 row tall (hidden by making it 0 when not visible). + msgHeight := 0 + if a.state.Message.IsVisible() { + msgHeight = 1 + } + + cf := tview.NewFlex().SetDirection(tview.FlexRow) + cf.SetBackgroundColor(theme.ColorBackground) + + // Row 0: top vertical margin (spacer). + cf.AddItem(spacer(1), 1, 0, false) // placeholder; overridden in applyConstraints + + // Row 1: Header. + cf.AddItem(a.header, 1, 0, false) + + // Row 2: blank line after header. + cf.AddItem(spacer(1), 1, 0, false) + + // Row 3: Appliance info block (product title + blank + 4 metadata rows = 6 rows). + cf.AddItem(a.appInfo, 6, 0, false) + + // Row 4: Message row (0 or 1 row). + cf.AddItem(a.msgWidget, msgHeight, 0, false) + + // Row 5: blank line before menu. + cf.AddItem(spacer(1), 1, 0, false) + + // Row 6: Navigation menu. + cf.AddItem(a.menuWidget, menuHeight, 0, false) + + // Row 7: Flexible whitespace — expands to consume remaining vertical space. + cf.AddItem(spacer(0), 0, 1, false) + + // Row 8: Contextual description. + cf.AddItem(a.description, 1, 0, false) + + // Row 9: blank between description and footer. + cf.AddItem(spacer(1), 1, 0, false) + + // Row 10: Footer. + cf.AddItem(a.footer, 1, 0, false) + + // Row 11: bottom vertical margin. + cf.AddItem(spacer(1), 1, 0, false) + + return cf +} + +// applyConstraints rebuilds margins and updates child widget parameters. +func (a *Application) applyConstraints(c layout.Constraints) { + a.constraints = c + a.tooSmall = c.IsMinimum + + // Clear and rebuild the outer flex. + a.rootFlex.Clear() + if c.IsMinimum { + // Show a minimal warning box. + warn := tview.NewBox().SetBackgroundColor(theme.ColorBackground) + a.rootFlex.AddItem(warn, 0, 1, false) + a.tviewApp.SetFocus(warn) + return + } + + // Update header margin. + a.header.SetMarginH(c.MarginH) + a.header.Refresh(c.TermWidth) + + // Update footer density. + a.footer.Refresh(c.TermWidth) + + // Update menu inner padding. + a.menuWidget.SetInnerPad(c.MenuInnerPad) + + // Update menu height based on current items. + menuItemCount := len(a.state.Navigator.Current().Items) + menuHeight := menuItemCount*2 + 4 + + // Message visibility. + msgHeight := 0 + if a.state.Message.IsVisible() && c.MessageVisible { + msgHeight = 1 + } + + // Rebuild content flex items with updated sizes. + // We rebuild the whole content flex to keep sizing in sync. + a.contentFlex = a.rebuildContentFlex(c, menuHeight, msgHeight) + + // Outer layout: left margin | content | right margin. + a.rootFlex.AddItem(tview.NewBox().SetBackgroundColor(theme.ColorBackground), c.MarginH, 0, false) + a.rootFlex.AddItem(a.contentFlex, 0, 1, true) + a.rootFlex.AddItem(tview.NewBox().SetBackgroundColor(theme.ColorBackground), c.MarginH, 0, false) + + a.tviewApp.SetFocus(a.menuWidget) +} + +// rebuildContentFlex constructs a fresh vertical flex with updated sizing. +func (a *Application) rebuildContentFlex(c layout.Constraints, menuHeight, msgHeight int) *tview.Flex { + spacer := func() *tview.Box { + return tview.NewBox().SetBackgroundColor(theme.ColorBackground) + } + + cf := tview.NewFlex().SetDirection(tview.FlexRow) + cf.SetBackgroundColor(theme.ColorBackground) + + cf.AddItem(spacer(), c.MarginV, 0, false) + cf.AddItem(a.header, 1, 0, false) + cf.AddItem(spacer(), 1, 0, false) + cf.AddItem(a.appInfo, 6, 0, false) + + if c.MessageVisible { + cf.AddItem(a.msgWidget, msgHeight, 0, false) + } + + cf.AddItem(spacer(), 1, 0, false) + cf.AddItem(a.menuWidget, menuHeight, 0, true) + cf.AddItem(spacer(), 0, 1, false) // flexible whitespace + cf.AddItem(a.description, 1, 0, false) + cf.AddItem(spacer(), 1, 0, false) + cf.AddItem(a.footer, 1, 0, false) + cf.AddItem(spacer(), c.MarginV, 0, false) + + return cf +} + +// wireCallbacks connects navigation events to UI updates. +func (a *Application) wireCallbacks() { + a.menuWidget.SetOnSelectionChanged(func(idx int) { + a.state.Navigator.Current().SelectedIdx = idx + a.syncDescription() + }) + + a.menuWidget.SetOnEnter(func(idx int) { + item := a.state.Navigator.SelectedItem() + if item == nil { + return + } + if !a.state.Navigator.Enter() { + // Leaf item — show not-implemented notice. + a.showNotImplemented(item.Label) + return + } + // Descended into submenu: refresh menu widget. + a.refreshMenuForCurrentLevel() + }) + + a.menuWidget.SetOnBack(func() { + if a.state.Navigator.Back() { + a.refreshMenuForCurrentLevel() + } + }) +} + +// refreshMenuForCurrentLevel updates the menu widget to reflect the current +// navigation level and recalculates layout. +func (a *Application) refreshMenuForCurrentLevel() { + cur := a.state.Navigator.Current() + a.menuWidget.SetItems(cur.Items) + a.menuWidget.SetSelectedIndex(cur.SelectedIdx) + a.syncDescription() + // Recalculate layout with new menu item count. + a.applyConstraints(a.constraints) +} + +// syncDescription updates the contextual description for the selected item. +func (a *Application) syncDescription() { + item := a.state.Navigator.SelectedItem() + if item == nil { + a.description.SetDescription("") + return + } + a.description.SetDescription(item.Description) +} + +// showNotImplemented displays the not-implemented modal. +func (a *Application) showNotImplemented(label string) { + modal := components.NewNotImplementedModal(label, func() { + a.pages.RemovePage("modal") + a.tviewApp.SetFocus(a.menuWidget) + }) + a.pages.AddPage("modal", modal, true, true) + a.tviewApp.SetFocus(modal) +} + +// showHelp displays the help modal. +func (a *Application) showHelp() { + helpModal := components.HelpModal(func() { + a.pages.RemovePage("help") + a.tviewApp.SetFocus(a.menuWidget) + }) + a.pages.AddPage("help", helpModal, true, true) + a.tviewApp.SetFocus(helpModal) +} + +// SetInnerPad exposes the inner padding setter on MenuWidget for use by app. +// (Defined here so app package can call it without modifying the component.) +func init() {} diff --git a/internal/go.mod b/internal/go.mod new file mode 100644 index 0000000..5efd8b4 --- /dev/null +++ b/internal/go.mod @@ -0,0 +1,18 @@ +module github.com/nebara/nebara-os + +go 1.21 + +require ( + github.com/gdamore/tcell/v2 v2.7.4 + github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57 +) + +require ( + github.com/gdamore/encoding v1.0.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/internal/go.sum b/internal/go.sum new file mode 100644 index 0000000..cd2f502 --- /dev/null +++ b/internal/go.sum @@ -0,0 +1,50 @@ +github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= +github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57 h1:LmsF7Fk5jyEDhJk0fYIqdWNuTxSyid2W42A0L2YWjGE= +github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/main.go b/internal/main.go new file mode 100644 index 0000000..be2d504 --- /dev/null +++ b/internal/main.go @@ -0,0 +1,40 @@ +// Nebara OS TUI — Visual Baseline v0.1 +// Native terminal management interface for the Nebara S3GX Appliance. +// +// Usage: +// +// go run ./cmd/nebara-tui [--mock-state=warning|normal] +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/nebara/nebara-os/internal/app" + "github.com/nebara/nebara-os/internal/mock" +) + +func main() { + stateFlag := flag.String("mock-state", "warning", "Mock state to display: 'warning' or 'normal'") + flag.Parse() + + var s mock.State + switch *stateFlag { + case "normal": + s = mock.NormalState() + case "warning": + s = mock.WarningState() + default: + fmt.Fprintf(os.Stderr, "Unknown mock state %q. Use 'warning' or 'normal'.\n", *stateFlag) + os.Exit(1) + } + + appState := app.NewStateFromMock(s) + application := app.NewApplication(appState) + + if err := application.Run(); err != nil { + fmt.Fprintf(os.Stderr, "fatal: %v\n", err) + os.Exit(1) + } +}