Merge pull request #128 from CanonicalLtd/identity_ux_update
Identity ux update
This commit is contained in:
commit
2bd7c2a00a
|
@ -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')
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
|
@ -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)
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue