Merge pull request #128 from CanonicalLtd/identity_ux_update

Identity ux update
This commit is contained in:
Adam Stokes 2015-11-20 17:17:13 -05:00
commit 2bd7c2a00a
5 changed files with 101 additions and 181 deletions

View File

@ -1,36 +0,0 @@
# Copyright 2015 Canonical, Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from subiquity.controller import ControllerPolicy
from subiquity.models import HostnameModel
from subiquity.ui.views import HostnameView
class HostnameController(ControllerPolicy):
def __init__(self, common):
super().__init__(common)
self.model = HostnameModel()
def hostname(self):
title = "Set hostname"
excerpt = ("Set the systems hostname")
footer = ""
self.ui.set_header(title, excerpt)
self.ui.set_footer(footer, 40)
self.ui.set_body(HostnameView(self.model, self.signal))
def hostname_finish(self, hostname):
# Do curtin hostname setup
self.signal.emit_signal('identity:show')

View File

@ -94,6 +94,8 @@ def curtin_userinfo_to_config(userinfo):
' passwd: {password}\\n' + \ ' passwd: {password}\\n' + \
' shell: /bin/bash\\n' + \ ' shell: /bin/bash\\n' + \
' groups: admin\\n' + \ ' groups: admin\\n' + \
' ssh_import_id: [{ssh_import_id}]\\n' + \
' hostname: {hostname}\\n' + \
' lock-passwd: false\\n' ' lock-passwd: false\\n'
return user_template.format(**userinfo) return user_template.format(**userinfo)

View File

@ -1,50 +0,0 @@
# Copyright 2015 Canonical, Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from subiquity.model import ModelPolicy
log = logging.getLogger('subiquity.models.hostname')
class HostnameModel(ModelPolicy):
""" Model representing system hostname
"""
prev_signal = ('Back to filesystem view',
'filesystem:show',
'filesystem')
signals = [
("Hostname View",
'hostname:show',
'hostname')
]
hostname_menu = []
def get_signals(self):
return self.signals
def get_menu(self):
return self.hostname_menu
def get_signal_by_name(self, selection):
for x, y, z in self.get_menu():
if x == selection:
return y
def __repr__(self):
return "<Hostname: {}>".format(self.hostname)

View File

@ -1,75 +0,0 @@
# Copyright 2015 Canonical, Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" Hostname
Sets system hostname
"""
import logging
from urwid import (Pile, Columns, Text, ListBox)
from subiquity.ui.buttons import done_btn, cancel_btn
from subiquity.ui.interactive import StringEditor
from subiquity.ui.utils import Padding, Color
from subiquity.view import ViewPolicy
log = logging.getLogger("subiquity.views.hostname")
class HostnameView(ViewPolicy):
def __init__(self, model, signal):
self.model = model
self.signal = signal
self.hostname = StringEditor(caption="")
body = [
Padding.center_50(self._build_model_inputs()),
Padding.line_break(""),
Padding.fixed_10(self._build_buttons()),
]
super().__init__(ListBox(body))
def _build_buttons(self):
cancel = cancel_btn(on_press=self.cancel)
done = done_btn(on_press=self.done)
buttons = [
Color.button(done, focus_map='button focus'),
Color.button(cancel, focus_map='button focus')
]
return Pile(buttons)
def _build_model_inputs(self):
sl = [
Columns(
[
("weight", 0.2, Text("Hostname", align="right")),
("weight", 0.3,
Color.string_input(self.hostname,
focus_map="string_input focus"))
],
)
]
return Pile(sl)
def done(self, result):
result = {
"hostname": self.hostname.value
}
self.signal.emit_signal('hostname:finish', result)
def cancel(self, button):
self.signal.emit_signal(self.model.get_previous_signal)

View File

@ -23,6 +23,7 @@ from urwid import (Pile, Columns, Text, ListBox)
from subiquity.ui.buttons import done_btn, cancel_btn from subiquity.ui.buttons import done_btn, cancel_btn
from subiquity.ui.interactive import (PasswordEditor, from subiquity.ui.interactive import (PasswordEditor,
RealnameEditor, RealnameEditor,
StringEditor,
UsernameEditor) UsernameEditor)
from subiquity.ui.utils import Padding, Color from subiquity.ui.utils import Padding, Color
from subiquity.view import ViewPolicy from subiquity.view import ViewPolicy
@ -31,8 +32,10 @@ from subiquity.curtin import curtin_write_postinst_config
log = logging.getLogger("subiquity.views.identity") log = logging.getLogger("subiquity.views.identity")
USERNAME_MAXLEN = 32 HOSTNAME_MAXLEN = 64
REALNAME_MAXLEN = 160 REALNAME_MAXLEN = 160
SSH_IMPORT_MAXLEN = 256 + 3 # account for lp: or gh:
USERNAME_MAXLEN = 32
class IdentityView(ViewPolicy): class IdentityView(ViewPolicy):
@ -41,15 +44,18 @@ class IdentityView(ViewPolicy):
self.signal = signal self.signal = signal
self.items = [] self.items = []
self.realname = RealnameEditor(caption="") self.realname = RealnameEditor(caption="")
self.hostname = UsernameEditor(caption="")
self.username = UsernameEditor(caption="") self.username = UsernameEditor(caption="")
self.password = PasswordEditor(caption="") self.password = PasswordEditor(caption="")
self.ssh_import_id = StringEditor(caption="")
self.ssh_import_confirmed = True
self.error = Text("", align="center") self.error = Text("", align="center")
self.confirm_password = PasswordEditor(caption="") self.confirm_password = PasswordEditor(caption="")
body = [ body = [
Padding.center_50(self._build_model_inputs()), Padding.center_90(self._build_model_inputs()),
Padding.line_break(""), Padding.line_break(""),
Padding.center_50(Color.info_error(self.error)), Padding.center_90(Color.info_error(self.error)),
Padding.line_break(""), Padding.line_break(""),
Padding.fixed_10(self._build_buttons()), Padding.fixed_10(self._build_buttons()),
] ]
@ -69,7 +75,7 @@ class IdentityView(ViewPolicy):
sl = [ sl = [
Columns( Columns(
[ [
("weight", 0.2, Text("Real Name", align="right")), ("weight", 0.2, Text("Your name:", align="right")),
("weight", 0.3, ("weight", 0.3,
Color.string_input(self.realname, Color.string_input(self.realname,
focus_map="string_input focus")) focus_map="string_input focus"))
@ -78,7 +84,26 @@ class IdentityView(ViewPolicy):
), ),
Columns( Columns(
[ [
("weight", 0.2, Text("Username", align="right")), ("weight", 0.2, Text("Your server's name:",
align="right")),
("weight", 0.3,
Color.string_input(self.hostname,
focus_map="string_input focus"))
],
dividechars=4
),
Columns(
[
("weight", 0.2, Text("", align="right")),
("weight", 0.3, Color.info_minor(
Text("The name it uses when it talks to "
"other computers", align="left"))),
],
dividechars=4
),
Columns(
[
("weight", 0.2, Text("Pick a username:", align="right")),
("weight", 0.3, ("weight", 0.3,
Color.string_input(self.username, Color.string_input(self.username,
focus_map="string_input focus")) focus_map="string_input focus"))
@ -87,7 +112,7 @@ class IdentityView(ViewPolicy):
), ),
Columns( Columns(
[ [
("weight", 0.2, Text("Password", align="right")), ("weight", 0.2, Text("Choose a password:", align="right")),
("weight", 0.3, ("weight", 0.3,
Color.string_input(self.password, Color.string_input(self.password,
focus_map="string_input focus")) focus_map="string_input focus"))
@ -96,29 +121,42 @@ class IdentityView(ViewPolicy):
), ),
Columns( Columns(
[ [
("weight", 0.2, Text("Confirm Password", align="right")), ("weight", 0.2, Text("Confirm your password:",
align="right")),
("weight", 0.3, ("weight", 0.3,
Color.string_input(self.confirm_password, Color.string_input(self.confirm_password,
focus_map="string_input focus")) focus_map="string_input focus"))
], ],
dividechars=4 dividechars=4
) ),
Columns(
[
("weight", 0.2, Text("Import SSH identity:",
align="right")),
("weight", 0.3,
Color.string_input(self.ssh_import_id,
focus_map="string_input focus"))
],
dividechars=4
),
Columns(
[
("weight", 0.2, Text("", align="right")),
("weight", 0.3, Color.info_minor(
Text("Input your SSH user id from "
"Launchpad (lp:username) or "
"Github (gh:username).",
align="left"))),
],
dividechars=4
),
] ]
return Pile(sl) return Pile(sl)
def done(self, result): def done(self, result):
if len(self.password.value) < 1: # check in display order:
self.error.set_text("Password must be set") # realname, hostname, username, password, ssh
self.password.value = ""
self.confirm_password.value = ""
return
if self.password.value != self.confirm_password.value:
self.error.set_text("Passwords do not match.")
self.password.value = ""
self.confirm_password.value = ""
return
if len(self.realname.value) < 1: if len(self.realname.value) < 1:
self.error.set_text("Realname missing.") self.error.set_text("Realname missing.")
self.realname.value = "" self.realname.value = ""
@ -130,6 +168,17 @@ class IdentityView(ViewPolicy):
self.realname.value = "" self.realname.value = ""
return return
if len(self.hostname.value) < 1:
self.error.set_text("Server name missing.")
self.hostname.value = ""
return
if len(self.hostname.value) > HOSTNAME_MAXLEN:
self.error.set_text("Server name too long, must be < " +
str(HOSTNAME_MAXLEN))
self.hostname.value = ""
return
if len(self.username.value) < 1: if len(self.username.value) < 1:
self.error.set_text("Username missing.") self.error.set_text("Username missing.")
self.username.value = "" self.username.value = ""
@ -141,15 +190,45 @@ class IdentityView(ViewPolicy):
self.username.value = "" self.username.value = ""
return return
if len(self.password.value) < 1:
self.error.set_text("Password must be set")
self.password.value = ""
self.confirm_password.value = ""
return
if self.password.value != self.confirm_password.value:
self.error.set_text("Passwords do not match.")
self.password.value = ""
self.confirm_password.value = ""
return
# ssh_id is optional
if len(self.ssh_import_id.value) > SSH_IMPORT_MAXLEN:
self.error.set_text("SSH id too long, must be < " +
str(SSH_IMPORT_MAXLEN))
self.ssh_import_id.value = ""
return
cpassword = self.model.encrypt_password(self.password.value) cpassword = self.model.encrypt_password(self.password.value)
log.debug("*crypted* User input: {} {} {}".format( log.debug("*crypted* User input: {} {} {}".format(
self.username.value, cpassword, cpassword)) self.username.value, cpassword, cpassword))
result = { result = {
"hostname": self.hostname.value,
"realname": self.realname.value, "realname": self.realname.value,
"username": self.username.value, "username": self.username.value,
"password": cpassword, "password": cpassword,
"confirm_password": cpassword, "confirm_password": cpassword,
} }
# if user specifed a value, allow user to validate fingerprint
if self.ssh_import_id.value:
if self.ssh_import_confirmed is True:
result.update({'ssh_import_id': self.ssh_import_id.value})
else:
self.emit_signal('identity:confirm-ssh-id',
self.ssh_import_id.value)
return
log.debug("User input: {}".format(result)) log.debug("User input: {}".format(result))
try: try:
curtin_write_postinst_config(result) curtin_write_postinst_config(result)