mirror of
https://github.com/clearlinux/clr-installer.git
synced 2026-09-06 05:31:58 +00:00
Remove the code which was attempting to do the work of many existing, more reliable disk partitioning tools. The code was getting overly complex to handle the very high number of options and corner cases. The installer will rely on external tools which do a better job. This will parse the media for advanced partition labels. Show the user the details of what will change and be used for the installation. Signed-off-by: Mark D Horn <mark.d.horn@intel.com>
473 lines
12 KiB
Go
473 lines
12 KiB
Go
// Copyright © 2019 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/clearlinux/clr-installer/model"
|
|
|
|
"github.com/VladimirMarkelov/clui"
|
|
term "github.com/nsf/termbox-go"
|
|
)
|
|
|
|
// BasePage is the common implementation for the TUI frontend
|
|
// other pages will inherit this base page behaviours
|
|
type BasePage struct {
|
|
tui *Tui // the Tui frontend reference
|
|
window *clui.Window // the page window
|
|
content *clui.Frame // the main content frame
|
|
cFrame *clui.Frame // control frame
|
|
cancelBtn *SimpleButton // cancel button
|
|
backBtn *SimpleButton // back button
|
|
confirmBtn *SimpleButton // confirm button
|
|
activated clui.Control // activated control
|
|
menuTitle string // the title used to show on main menu
|
|
done bool // marks if an item is completed
|
|
id int // the page id
|
|
data interface{} // arbitrary page context data
|
|
action int // indicates if the user has performed a navigation action
|
|
required bool // marks if an item is required for the install
|
|
menuButton *MenuButton
|
|
}
|
|
|
|
// Page defines the methods a Page must implement
|
|
type Page interface {
|
|
GetID() int
|
|
IsRequired() bool
|
|
GetWindow() *clui.Window
|
|
GetActivated() clui.Control
|
|
GetMenuTitle() string
|
|
SetData(data interface{})
|
|
GetData() interface{}
|
|
SetDone(done bool) bool
|
|
GetDone() bool
|
|
Activate()
|
|
DeActivate()
|
|
GetConfigDefinition() int
|
|
GetConfiguredValue() string
|
|
SetMenuButton(mb *MenuButton)
|
|
GetMenuButton() *MenuButton
|
|
}
|
|
|
|
const (
|
|
// WindowWidth is our desired terminal width
|
|
WindowWidth = 80
|
|
// WindowHeight is our desired terminal width
|
|
WindowHeight = 24
|
|
|
|
// ContentHeight is content frame height
|
|
ContentHeight = 15
|
|
|
|
// AutoSize is shortcut for clui.AutoSize flag
|
|
AutoSize = clui.AutoSize
|
|
|
|
// Fixed is shortcut for clui.Fixed flag
|
|
Fixed = clui.Fixed
|
|
|
|
// BorderNone is shortcut for clui.BorderNone flag
|
|
BorderNone = clui.BorderNone
|
|
|
|
// AlignLeft is shortcut for clui.AlignLeft flag
|
|
AlignLeft = clui.AlignLeft
|
|
|
|
// AlignRight is shortcut for clui.AlignRight flag
|
|
AlignRight = clui.AlignRight
|
|
|
|
// NoButtons mask defines a common Page will not set any control button
|
|
NoButtons = 0
|
|
|
|
// BackButton mask defines a common Page will have a back button
|
|
BackButton = 1 << 1
|
|
|
|
// ConfirmButton mask defines a common Page will have Confirm button
|
|
ConfirmButton = 1 << 2
|
|
|
|
// CancelButton mask defines a common Page will have a cancel button
|
|
CancelButton = 1 << 3
|
|
|
|
// AllButtons mask defines a common Page will have both Back and Confirm buttons
|
|
AllButtons = BackButton | ConfirmButton
|
|
|
|
// TuiPageMenu is the id for menu page
|
|
TuiPageMenu = iota
|
|
|
|
// TuiPageInstall is the id for install page
|
|
TuiPageInstall
|
|
|
|
// TuiPageLanguage is the id for language page
|
|
TuiPageLanguage
|
|
|
|
// TuiPageKeyboard is the id for keyboard page
|
|
TuiPageKeyboard
|
|
|
|
// TuiPageMediaConfig is the id for media configuration menu
|
|
TuiPageMediaConfig
|
|
|
|
// TuiPageNetwork is the id for network configuration page
|
|
TuiPageNetwork
|
|
|
|
// TuiPageProxy is the id for the proxy configuration page
|
|
TuiPageProxy
|
|
|
|
// TuiPageNetworkValidate is the id for the network validation page
|
|
TuiPageNetworkValidate
|
|
|
|
// TuiPageInterface is the id for the network interface configuration page
|
|
TuiPageInterface
|
|
|
|
// TuiPageBundle is the id for the bundle selection page
|
|
TuiPageBundle
|
|
|
|
// TuiPageTelemetry is the id for the telemetry enabling screen
|
|
TuiPageTelemetry
|
|
|
|
// TuiPageTimezone is the id for the timezone selection page
|
|
TuiPageTimezone
|
|
|
|
// TuiPageUserManager is the id for the user management page
|
|
TuiPageUserManager
|
|
|
|
// TuiPageUseradd is the id for the user add page
|
|
TuiPageUseradd
|
|
|
|
// TuiPageKernelCMDLine is the id for the kernel command line page
|
|
TuiPageKernelCMDLine
|
|
|
|
// TuiPageKernel is the id for the kernel selection page
|
|
TuiPageKernel
|
|
|
|
// TuiPageSwupdMirror is the id for the swupd mirror page
|
|
TuiPageSwupdMirror
|
|
|
|
// TuiPageHostname is the id for the hostname page
|
|
TuiPageHostname
|
|
|
|
// TuiPageAutoUpdate is the id for the Auto Update Enablement page
|
|
TuiPageAutoUpdate
|
|
|
|
// TuiPageSaveConfig is the id for the save YAML configuration file page
|
|
TuiPageSaveConfig
|
|
|
|
// ConfigDefinedByUser is used to determine a configuration was interactively
|
|
// defined by the user
|
|
ConfigDefinedByUser = iota
|
|
|
|
// ConfigDefinedByConfig is used to determine a configuration was defined by
|
|
// a configuration file
|
|
ConfigDefinedByConfig
|
|
|
|
// ConfigNotDefined is used to determine no configuration was provided yet
|
|
ConfigNotDefined
|
|
|
|
// ActionBackButton indicates the user has pressed back button
|
|
ActionBackButton = iota
|
|
|
|
// ActionConfirmButton indicates the user has pressed confirm button
|
|
ActionConfirmButton
|
|
|
|
// ActionCancelButton indicates the user has pressed cancel button
|
|
ActionCancelButton
|
|
|
|
// ActionNone indicates no action has been performed
|
|
ActionNone
|
|
)
|
|
const (
|
|
columnSpacer = ` `
|
|
columnWidthDefault = 10
|
|
rowDividor = `_`
|
|
)
|
|
|
|
type columnInfo struct {
|
|
title string
|
|
rightJustify bool
|
|
minWidth int
|
|
format string
|
|
width int
|
|
}
|
|
|
|
// given the columnInfo type, return the length and fmt string
|
|
func getColumnFormat(info columnInfo) (int, string) {
|
|
l := len(info.title)
|
|
if info.minWidth > l {
|
|
l = info.minWidth
|
|
}
|
|
justify := "-"
|
|
if info.rightJustify {
|
|
justify = ""
|
|
}
|
|
|
|
// Ensure we only write the max width
|
|
return l, fmt.Sprintf("%%%s%d.%ds", justify, l, l)
|
|
}
|
|
|
|
// Is the page id a PopUp page
|
|
func isPopUpPage(id int) bool {
|
|
if id == TuiPageNetworkValidate || id == TuiPageSaveConfig {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// SetMenuButton sets the page's menu control
|
|
func (page *BasePage) SetMenuButton(mb *MenuButton) {
|
|
page.menuButton = mb
|
|
}
|
|
|
|
// GetMenuButton returns the page's menu control
|
|
func (page *BasePage) GetMenuButton() *MenuButton {
|
|
return page.menuButton
|
|
}
|
|
|
|
// GetConfiguredValue Returns the string representation of currently value set
|
|
func (page *BasePage) GetConfiguredValue() string {
|
|
return "Unknown value"
|
|
}
|
|
|
|
// GetConfigDefinition is a stub implementation
|
|
// the real implementation must check with the model and return:
|
|
// + ConfigDefinedByUser: if the configuration was interactively defined by the user
|
|
// + ConfigDefinedByConfig: if the configuration was provided by a config file
|
|
// + ConfigNotDefined: if none of the above apply
|
|
func (page *BasePage) GetConfigDefinition() int {
|
|
return ConfigNotDefined
|
|
}
|
|
|
|
// DeActivate is a stub implementation for the DeActivate method of Page interface
|
|
func (page *BasePage) DeActivate() {}
|
|
|
|
// Activate is a stub implementation for the Activate method of Page interface
|
|
func (page *BasePage) Activate() {}
|
|
|
|
// SetDone sets the page's done flag
|
|
func (page *BasePage) SetDone(done bool) bool {
|
|
page.done = done
|
|
return true
|
|
}
|
|
|
|
// GotoPage transitions between 2 pages
|
|
func (page *BasePage) GotoPage(id int) {
|
|
page.tui.gotoPage(id, page.tui.currPage)
|
|
}
|
|
|
|
// Panic write an error to the tui panicked channel - we'll deal the error, stop clui
|
|
// mainloop and nicely panic() the application
|
|
func (page *BasePage) Panic(err error) {
|
|
page.tui.paniced <- err
|
|
}
|
|
|
|
// GetDone returns the current value of a page's done flag
|
|
func (page *BasePage) GetDone() bool {
|
|
return page.done
|
|
}
|
|
|
|
// GetData returns the current value of a page's data member
|
|
func (page *BasePage) GetData() interface{} {
|
|
return page.data
|
|
}
|
|
|
|
// SetData set the current value for the page's data member
|
|
func (page *BasePage) SetData(data interface{}) {
|
|
page.data = data
|
|
}
|
|
|
|
// GetMenuTitle returns the current page's title string
|
|
func (page *BasePage) GetMenuTitle() string {
|
|
return page.menuTitle
|
|
}
|
|
|
|
// GetActivated returns the control set as activated for a page
|
|
func (page *BasePage) GetActivated() clui.Control {
|
|
return page.activated
|
|
}
|
|
|
|
// GetWindow returns the current page's window control
|
|
func (page *BasePage) GetWindow() *clui.Window {
|
|
return page.window
|
|
}
|
|
|
|
// GetID returns the current page's identifier
|
|
func (page *BasePage) GetID() int {
|
|
return page.id
|
|
}
|
|
|
|
// IsRequired returns if this Page is required to be completed for the Install
|
|
func (page *BasePage) IsRequired() bool {
|
|
return page.required
|
|
}
|
|
|
|
// GetMenuStatus returns the menu button status id
|
|
func GetMenuStatus(item Page) int {
|
|
res := MenuButtonStatusDefault
|
|
|
|
if item.GetDone() {
|
|
res = MenuButtonStatusUserDefined
|
|
} else if item.GetConfigDefinition() == ConfigDefinedByConfig {
|
|
res = MenuButtonStatusAutoDetect
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func (page *BasePage) setupMenu(tui *Tui, id int, menuTitle string, btns int, returnID int) {
|
|
page.setup(tui, id, btns, returnID)
|
|
page.menuTitle = menuTitle
|
|
}
|
|
|
|
func (page *BasePage) setup(tui *Tui, id int, btns int, returnID int) {
|
|
page.action = ActionNone
|
|
page.id = id
|
|
page.tui = tui
|
|
|
|
page.newWindow()
|
|
page.window.SetPack(clui.Vertical)
|
|
|
|
page.content = clui.CreateFrame(page.window, AutoSize, ContentHeight,
|
|
BorderNone, clui.Fixed)
|
|
page.content.SetPack(clui.Vertical)
|
|
page.content.SetPaddings(2, 1)
|
|
|
|
page.cFrame = clui.CreateFrame(page.window, AutoSize, 1, BorderNone, Fixed)
|
|
page.cFrame.SetPack(clui.Horizontal)
|
|
page.cFrame.SetGaps(1, 1)
|
|
page.cFrame.SetPaddings(3, 0)
|
|
|
|
if btns&CancelButton == CancelButton {
|
|
page.newCancelButton(returnID)
|
|
}
|
|
|
|
if btns&BackButton == BackButton {
|
|
page.newBackButton(returnID)
|
|
}
|
|
|
|
if btns&ConfirmButton == ConfirmButton {
|
|
page.newConfirmButton(tui, returnID)
|
|
}
|
|
|
|
frm := clui.CreateFrame(page.window, AutoSize, 1, BorderNone, Fixed)
|
|
frm.SetPaddings(3, 1)
|
|
|
|
clui.CreateLabel(frm, AutoSize, 1,
|
|
"Use [Tab] or the arrow keys [Up and Down] to navigate", Fixed)
|
|
|
|
page.window.SetVisible(false)
|
|
|
|
// Escape-key cancel's this screen and returns
|
|
// same as the default 'Cancel' button
|
|
page.window.OnKeyDown(func(ev clui.Event, data interface{}) bool {
|
|
if ev.Key == term.KeyEsc {
|
|
if page.cancelBtn != nil {
|
|
page.cancelBtn.ProcessEvent(clui.Event{Type: clui.EventKey, Key: term.KeyEnter})
|
|
} else {
|
|
page.action = ActionCancelButton
|
|
page.GotoPage(returnID)
|
|
page.action = ActionNone
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}, nil)
|
|
}
|
|
|
|
func (page *BasePage) newWindow() {
|
|
sw, sh := clui.ScreenSize()
|
|
|
|
x := (sw - WindowWidth) / 2
|
|
y := (sh - WindowHeight) / 2
|
|
|
|
// Default all the windows to borderless
|
|
clui.WindowManager().SetBorder(clui.BorderNone)
|
|
title := " [Clear Linux* OS Installer"
|
|
if model.Version != model.DemoVersion {
|
|
title = title + " (" + model.Version + ")"
|
|
}
|
|
title = title + "] "
|
|
page.window = clui.AddWindow(x, y, WindowWidth, WindowHeight, title)
|
|
|
|
page.window.SetTitleButtons(0)
|
|
page.window.SetSizable(false)
|
|
page.window.SetMovable(false)
|
|
|
|
page.window.OnScreenResize(func(evt clui.Event) {
|
|
ww, wh := page.window.Size()
|
|
|
|
x := (evt.Width - ww) / 2
|
|
y := (evt.Height - wh) / 2
|
|
|
|
page.window.SetPos(x, y)
|
|
page.window.ResizeChildren()
|
|
page.window.PlaceChildren()
|
|
})
|
|
}
|
|
|
|
func (page *BasePage) newBackButton(pageID int) {
|
|
btn := CreateSimpleButton(page.cFrame, AutoSize, AutoSize, "< Main Menu", Fixed)
|
|
|
|
btn.OnClick(func(ev clui.Event) {
|
|
page.action = ActionBackButton
|
|
page.GotoPage(pageID)
|
|
page.action = ActionNone
|
|
})
|
|
|
|
page.backBtn = btn
|
|
}
|
|
|
|
func (page *BasePage) newCancelButton(pageID int) {
|
|
btn := CreateSimpleButton(page.cFrame, AutoSize, AutoSize, "Cancel", Fixed)
|
|
|
|
btn.OnClick(func(ev clui.Event) {
|
|
page.action = ActionCancelButton
|
|
page.GotoPage(pageID)
|
|
page.action = ActionNone
|
|
})
|
|
|
|
page.cancelBtn = btn
|
|
}
|
|
|
|
func (page *BasePage) newConfirmButton(tui *Tui, pageID int) {
|
|
btn := CreateSimpleButton(page.cFrame, AutoSize, AutoSize, "Confirm", Fixed)
|
|
|
|
btn.OnClick(func(ev clui.Event) {
|
|
if tui.currPage.SetDone(true) {
|
|
page.action = ActionConfirmButton
|
|
page.GotoPage(pageID)
|
|
page.action = ActionNone
|
|
}
|
|
})
|
|
page.confirmBtn = btn
|
|
}
|
|
|
|
func (page *BasePage) getModel() *model.SystemInstall {
|
|
return page.tui.model
|
|
}
|
|
|
|
func newEditField(frame *clui.Frame, validation bool, cb func(k term.Key, ch rune) bool) (*clui.EditField, *clui.Label) {
|
|
var label *clui.Label
|
|
|
|
height := 2
|
|
if validation {
|
|
height = 1
|
|
}
|
|
|
|
iframe := clui.CreateFrame(frame, 5, height, BorderNone, Fixed)
|
|
iframe.SetPack(clui.Vertical)
|
|
edit := clui.CreateEditField(iframe, 1, "", Fixed)
|
|
|
|
if validation {
|
|
label = clui.CreateLabel(iframe, AutoSize, 1, "", Fixed)
|
|
label.SetVisible(false)
|
|
label.SetBackColor(errorLabelBg)
|
|
label.SetTextColor(errorLabelFg)
|
|
}
|
|
|
|
if cb != nil {
|
|
edit.OnKeyPress(cb)
|
|
}
|
|
|
|
return edit, label
|
|
}
|