Upload files to "internal"
This commit is contained in:
@@ -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*
|
||||
@@ -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() {}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user