Upload files to "internal"

This commit is contained in:
2026-10-13 12:31:36 +00:00
commit b53597c288
5 changed files with 716 additions and 0 deletions
+282
View File
@@ -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 <repo>
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*
+326
View File
@@ -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() {}
+18
View File
@@ -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
)
+50
View File
@@ -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=
+40
View File
@@ -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)
}
}