Files
clr-installer/tui/user_manager.go
T
Reagan Lopez 4c1f7a9dfe gui: Introduces localization
- Modifies the screen flow to first launch the Welcome (Language) page
- Adds localization package "gotext"
- Enables localization for "en_US", "zh_CN" and "es_MX"
- Modifies the strings to use localized version

Signed-off-by: Reagan Lopez <reagan.lopez@intel.com>
2019-04-19 15:04:57 -07:00

477 lines
12 KiB
Go

// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: GPL-3.0-only
package tui
import (
"fmt"
"sort"
"strings"
"github.com/VladimirMarkelov/clui"
"github.com/clearlinux/clr-installer/log"
"github.com/clearlinux/clr-installer/user"
)
// SelectedUser holds the shared date between the User Manager page
// and the user configuration page
type SelectedUser struct {
user *user.User
definedUsers []string
addMode bool
}
const (
userManagerTitle = "Manage User"
)
const (
userColumnLogin = iota
userColumnUserName
userColumnPassword
userColumnAdmin
userColumnCount
)
var (
userColumns []columnInfo
)
func init() {
userColumns = make([]columnInfo, userColumnCount)
userColumns[userColumnLogin].title = "Login"
userColumns[userColumnLogin].minWidth = 12
userColumns[userColumnUserName].title = "User Name"
userColumns[userColumnUserName].minWidth = -1 // This column get all free space
userColumns[userColumnPassword].title = "Password"
userColumns[userColumnPassword].minWidth = 8
userColumns[userColumnAdmin].title = "Admin"
userColumns[userColumnAdmin].minWidth = 5
}
// UserManagerPage is the Page implementation for the disk partitioning menu page
type UserManagerPage struct {
BasePage
scrollingFrame *clui.Frame // content scrolling frame
columnFormat string
users []*user.User
usersChanged bool
rowFrames []*clui.Frame
activeRow *clui.Frame
activeUser *SimpleButton
activeLogin string
}
// GetConfiguredValue Returns the string representation of currently value set
func (page *UserManagerPage) GetConfiguredValue() string {
users := page.getModel().Users
res := []string{}
if len(users) == 0 {
return "No users added"
}
for _, curr := range users {
tks := []string{curr.Login}
if curr.Admin {
tks = append(tks, "admin")
}
res = append(res, strings.Join(tks, ":"))
}
return strings.Join(res, ", ")
}
// GetConfigDefinition returns if the config was interactively defined by the user,
// was loaded from a config file or if the config is not set.
func (page *UserManagerPage) GetConfigDefinition() int {
users := page.getModel().Users
if len(users) == 0 {
return ConfigNotDefined
}
return ConfigDefinedByUser
}
// SetDone put list of users into the model and sets the page as done
func (page *UserManagerPage) SetDone(done bool) bool {
page.getModel().RemoveAllUsers()
for _, user := range page.users {
page.getModel().AddUser(user)
}
page.usersChanged = false
page.users = nil
// TODO start using new API page.GotoPage() when finished merging
// disk pages
page.tui.gotoPage(TuiPageMenu, page)
return false
}
// Activate updates the UI elements with the most current list of block devices
func (page *UserManagerPage) Activate() {
// We have new or updated user data
if page.data != nil {
data := page.data.(*SelectedUser)
// Make sure the data is valid before we add
if data.user.Login != "" {
page.addUser(data.user)
page.activeLogin = data.user.Login
page.data = nil
} else {
page.activeLogin = ""
}
} else {
// We are entering from the main menu and need to copy the existing users
page.removeAllUsers()
for _, user := range page.getModel().Users {
page.addUser(user)
}
// Since we are restore, there
page.usersChanged = false
}
page.redrawRows()
if page.activeUser != nil {
page.activated = page.activeUser
page.scrollToRow(page.activeRow)
}
if len(page.users) > 0 || page.usersChanged {
page.confirmBtn.SetEnabled(true)
} else {
page.confirmBtn.SetEnabled(false)
}
clui.RefreshScreen()
}
func (page *UserManagerPage) scrollToRow(rowFrame *clui.Frame) {
_, cy, _, ch := page.scrollingFrame.Clipper()
vx, vy := page.scrollingFrame.Pos()
_, ry := rowFrame.Pos()
_, rh := rowFrame.Size()
if ry+rh > cy+ch {
diff := (cy + ch) - (ry + rh)
ty := vy + diff
page.scrollingFrame.ScrollTo(vx, ty)
} else if ry < cy {
page.scrollingFrame.ScrollTo(vx, cy)
}
}
func (page *UserManagerPage) redrawRows() {
for _, curr := range page.rowFrames {
curr.Destroy()
}
page.rowFrames = []*clui.Frame{}
if len(page.users) > 0 {
for _, user := range page.users {
if err := page.addUserRow(user); err != nil {
page.Panic(err)
}
}
} else {
rowFrame := clui.CreateFrame(page.scrollingFrame, 1, AutoSize, clui.BorderNone, clui.Fixed)
rowFrame.SetPack(clui.Vertical)
_ = clui.CreateLabel(rowFrame, 2, 1, "*** No Custom Users Defined ***", clui.Fixed)
page.rowFrames = append(page.rowFrames, rowFrame)
page.scrollToRow(rowFrame)
}
clui.RefreshScreen()
}
// The user manager page gives the user the option define additional accounts
// to be created on the target system
func newUserManagerPage(tui *Tui) (Page, error) {
page := &UserManagerPage{
BasePage: BasePage{},
}
page.setupMenu(tui, TuiPageUserManager, userManagerTitle, CancelButton|ConfirmButton, TuiPageMenu)
cWidth, cHeight := page.content.Size()
// Calculate the Scrollable frame area
sWidth := cWidth - (2 * 2) // Buffer of 2 characters from each side
sHeight := cHeight + 1 // Add back the blank line from content to menu buttons
// Top label for the page
lbl := clui.CreateLabel(page.content, 2, 2, userManagerTitle, clui.Fixed)
lbl.SetPaddings(0, 2)
_, lHeight := lbl.Size()
sHeight -= lHeight // Remove the label from total height
remainingColumns := sWidth - 2
allFree := -1
for i, info := range userColumns {
// Should this column get all extra space?
if info.minWidth == -1 {
if allFree == -1 {
allFree = i
continue // Do not format this column
} else {
log.Warning("More than one user info column set for all free space: %s", info.title)
info.minWidth = columnWidthDefault
}
}
l, format := getColumnFormat(info)
userColumns[i].format = format
userColumns[i].width = l
remainingColumns -= l
}
// remove the column spacers
remainingColumns -= ((len(userColumns) - 1) * len(columnSpacer))
// If we had a column which get the remaining space
if allFree != -1 {
userColumns[allFree].minWidth = remainingColumns
userColumns[allFree].width = remainingColumns
_, userColumns[allFree].format = getColumnFormat(userColumns[allFree])
}
// Build the Header Title and full row format string
titles := []interface{}{""} // need to use an interface for Sprintf
formats := []string{}
dividers := []interface{}{""} // need to use an interface for Sprintf
for _, info := range userColumns {
titles = append(titles, info.title)
formats = append(formats, info.format)
dividers = append(dividers, strings.Repeat(rowDividor, info.width))
}
titles = titles[1:] // pop the first empty string
page.columnFormat = strings.Join(formats, columnSpacer)
dividers = dividers[1:] // pop the first empty string
// Create the frame for the header label
headerFrame := clui.CreateFrame(page.content, sWidth, 1, clui.BorderNone, clui.Fixed)
headerFrame.SetPack(clui.Vertical)
headerFrame.SetPaddings(1, 0)
columnsTitle := fmt.Sprintf(page.columnFormat, titles...)
columnsLabel := clui.CreateLabel(headerFrame, AutoSize, 1, columnsTitle, clui.Fixed)
columnsLabel.SetPaddings(0, 0)
_, lHeight = columnsLabel.Size()
sHeight -= lHeight // Remove the label from total height
columnsDividors := fmt.Sprintf(page.columnFormat, dividers...)
columnsDividorLabel := clui.CreateLabel(headerFrame, AutoSize, 1, columnsDividors, clui.Fixed)
columnsDividorLabel.SetPaddings(0, 0)
_, lHeight = columnsDividorLabel.Size()
sHeight -= lHeight // Remove the label from total height
page.scrollingFrame = clui.CreateFrame(page.content, sWidth, sHeight, clui.BorderNone, clui.Fixed)
page.scrollingFrame.SetPack(clui.Vertical)
page.scrollingFrame.SetScrollable(true)
page.scrollingFrame.SetGaps(0, 1)
page.scrollingFrame.SetPaddings(1, 0)
// Setup the cancel button with a warning
page.cancelBtn.OnClick(func(ev clui.Event) {
if page.usersChanged {
message := "User data modified and will be lost!\n\nDiscard user changes?"
if dialog, err := CreateConfirmCancelDialogBox(message); err == nil {
dialog.OnClose(func() {
if dialog.Confirmed {
page.data = nil
page.GotoPage(TuiPageMenu)
log.Debug("Warning: DATA LOSS: Cancel button " + message)
}
})
}
} else {
page.data = nil
page.GotoPage(TuiPageMenu)
}
})
// Add an Add User button
addButton := CreateSimpleButton(page.cFrame, AutoSize, 1, "Add New User", Fixed)
addButton.OnClick(func(ev clui.Event) {
user := &user.User{}
users := []string{}
for _, cur := range page.users {
users = append(users, cur.Login)
}
page.data = &SelectedUser{user: user, definedUsers: users, addMode: true}
page.GotoPage(TuiPageUseradd)
})
// Add a Revert button
revertBtn := CreateSimpleButton(page.cFrame, AutoSize, AutoSize, "Revert", Fixed)
revertBtn.OnClick(func(ev clui.Event) {
if page.usersChanged {
message := "User data modified and will be lost!\n\nDiscard user changes?"
if dialog, err := CreateConfirmCancelDialogBox(message); err == nil {
dialog.OnClose(func() {
if dialog.Confirmed {
page.removeAllUsers()
for _, user := range page.getModel().Users {
page.addUser(user)
}
page.usersChanged = false
page.activeLogin = ""
// Clear last selected row as it might be removed
page.data = nil
log.Debug("Warning: DATA LOSS: Revert button " + message)
page.GotoPage(TuiPageUserManager)
}
})
}
} else {
page.data = nil
page.activeLogin = ""
page.GotoPage(TuiPageUserManager)
}
})
page.activated = addButton
return page, nil
}
func (page *UserManagerPage) addUserRow(user *user.User) error {
rowFrame := clui.CreateFrame(page.scrollingFrame, 1, 1, clui.BorderNone, clui.Fixed)
rowFrame.SetPack(clui.Vertical)
password := ""
if user.Password != "" {
password = "********"
}
admin := ""
if user.Admin {
admin = " X"
}
userTitle := fmt.Sprintf(page.columnFormat,
user.Login, user.UserName, password, admin)
userButton := CreateSimpleButton(rowFrame, 1, 1, userTitle, Fixed)
userButton.SetAlign(AlignLeft)
userButton.OnClick(func(ev clui.Event) {
// Remove the user to be modified as any part of the data could change
page.removeUser(user)
users := []string{}
for _, cur := range page.users {
users = append(users, cur.Login)
}
page.data = &SelectedUser{user: user, definedUsers: users, addMode: false}
page.GotoPage(TuiPageUseradd)
})
page.rowFrames = append(page.rowFrames, rowFrame)
userButton.OnActive(func(active bool) {
if active {
page.scrollToRow(rowFrame)
}
})
rowFrame.OnActive(func(active bool) {
if active {
page.scrollToRow(rowFrame)
}
})
// We do not have an active user, so default to the current
if page.activeLogin == "" {
page.activeLogin = user.Login
page.activeUser = userButton
page.activeRow = rowFrame
} else if user.Login == page.activeLogin {
// Set Active User button and Row Frame
page.activeUser = userButton
page.activeRow = rowFrame
}
return nil
}
// Sorting Interface
// Users is a list of User
type Users []*user.User
// Len is require to find the length of the Users list
func (users Users) Len() int { return len(users) }
// Swap is require for swapping two positions in Users list
func (users Users) Swap(a, b int) { users[a], users[b] = users[b], users[a] }
// ByLogin implements sort.Interface for sorting by the for Login field
type ByLogin struct{ Users }
// Less is require comparison function of the Users list for ByLogin
func (users ByLogin) Less(a, b int) bool { return users.Users[a].Login < users.Users[b].Login }
// ByUserName implements sort.Interface for sorting by the for UserName field
type ByUserName struct{ Users }
// Less is require comparison function of the Users list for ByUserName
func (users ByUserName) Less(a, b int) bool { return users.Users[a].UserName < users.Users[b].UserName }
func (page *UserManagerPage) addUser(addUser *user.User) {
page.usersChanged = true
newUser := &user.User{
Login: addUser.Login,
UserName: addUser.UserName,
Password: addUser.Password,
Admin: addUser.Admin,
}
page.users = append(page.users, newUser)
// Now sort by Login
sort.Sort(ByLogin{page.users})
}
func (page *UserManagerPage) removeUser(user *user.User) {
page.usersChanged = true
if page.users == nil {
return
}
deleteIndex := -1
for i, cur := range page.users {
if cur.Equals(user) {
deleteIndex = i
break
}
}
if deleteIndex >= 0 {
copy(page.users[deleteIndex:], page.users[deleteIndex+1:])
page.users[len(page.users)-1] = nil
page.users = page.users[:len(page.users)-1]
} else {
log.Warning("Attempting to remove user '%s', but not found in active list.", user.Login)
}
}
func (page *UserManagerPage) removeAllUsers() {
page.users = []*user.User{}
}