Merge pull request #1331 from ogayot/ubuntu-pro-visual-no-ubuntu-one
ubuntu-pro: update screens to match current design mockups
This commit is contained in:
commit
0fa8c54296
|
@ -18,6 +18,8 @@ import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Callable, List, Optional
|
from typing import Callable, List, Optional
|
||||||
|
|
||||||
|
from urwid import Widget
|
||||||
|
|
||||||
from subiquitycore.async_helpers import schedule_task
|
from subiquitycore.async_helpers import schedule_task
|
||||||
|
|
||||||
from subiquity.client.controller import SubiquityTuiController
|
from subiquity.client.controller import SubiquityTuiController
|
||||||
|
@ -26,7 +28,12 @@ from subiquity.common.types import (
|
||||||
UbuntuProCheckTokenStatus as TokenStatus,
|
UbuntuProCheckTokenStatus as TokenStatus,
|
||||||
UbuntuProService,
|
UbuntuProService,
|
||||||
)
|
)
|
||||||
from subiquity.ui.views.ubuntu_pro import UbuntuProView
|
from subiquity.ui.views.ubuntu_pro import (
|
||||||
|
UbuntuProView,
|
||||||
|
UpgradeYesNoForm,
|
||||||
|
UpgradeModeForm,
|
||||||
|
TokenAddedWidget,
|
||||||
|
)
|
||||||
|
|
||||||
from subiquitycore.lsb_release import lsb_release
|
from subiquitycore.lsb_release import lsb_release
|
||||||
from subiquitycore.tuicontroller import Skip
|
from subiquitycore.tuicontroller import Skip
|
||||||
|
@ -58,9 +65,66 @@ class UbuntuProController(SubiquityTuiController):
|
||||||
ubuntu_pro_info = await self.endpoint.GET()
|
ubuntu_pro_info = await self.endpoint.GET()
|
||||||
return UbuntuProView(self, ubuntu_pro_info.token)
|
return UbuntuProView(self, ubuntu_pro_info.token)
|
||||||
|
|
||||||
def run_answers(self) -> None:
|
async def run_answers(self) -> None:
|
||||||
if "token" in self.answers:
|
""" Interact with the UI to go through the pre-attach process if
|
||||||
self.done(self.answers["token"])
|
requested. """
|
||||||
|
if "token" not in self.answers:
|
||||||
|
return
|
||||||
|
|
||||||
|
from subiquitycore.testing.view_helpers import (
|
||||||
|
click,
|
||||||
|
enter_data,
|
||||||
|
find_button_matching,
|
||||||
|
find_with_pred,
|
||||||
|
keypress,
|
||||||
|
)
|
||||||
|
|
||||||
|
view = self.app.ui.body
|
||||||
|
|
||||||
|
def run_yes_no_screen(skip: bool) -> None:
|
||||||
|
if skip:
|
||||||
|
radio = view.upgrade_yes_no_form.skip.widget
|
||||||
|
else:
|
||||||
|
radio = view.upgrade_yes_no_form.upgrade.widget
|
||||||
|
|
||||||
|
keypress(radio, key="enter")
|
||||||
|
click(find_button_matching(view, UpgradeYesNoForm.ok_label))
|
||||||
|
|
||||||
|
def run_token_screen(token: str) -> None:
|
||||||
|
keypress(view.upgrade_mode_form.with_contract_token.widget,
|
||||||
|
key="enter")
|
||||||
|
data = {"with_contract_token_subform": {"token": token}}
|
||||||
|
# TODO: add this point, it would be good to trigger the validation
|
||||||
|
# code for the token field.
|
||||||
|
enter_data(view.upgrade_mode_form, data)
|
||||||
|
click(find_button_matching(view, UpgradeModeForm.ok_label))
|
||||||
|
|
||||||
|
async def run_token_added_overlay() -> None:
|
||||||
|
def is_token_added_overlay(widget: Widget) -> bool:
|
||||||
|
try:
|
||||||
|
if widget._text == f" {TokenAddedWidget.title} ":
|
||||||
|
return True
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Wait until the "Token added successfully" overlay is shown.
|
||||||
|
while not find_with_pred(view, is_token_added_overlay):
|
||||||
|
await asyncio.sleep(.2)
|
||||||
|
|
||||||
|
click(find_button_matching(view, TokenAddedWidget.done_label))
|
||||||
|
|
||||||
|
def run_services_screen() -> None:
|
||||||
|
click(find_button_matching(view._w,
|
||||||
|
UbuntuProView.services_done_label))
|
||||||
|
|
||||||
|
if not self.answers["token"]:
|
||||||
|
run_yes_no_screen(skip=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
run_yes_no_screen(skip=False)
|
||||||
|
run_token_screen(self.answers["token"])
|
||||||
|
await run_token_added_overlay()
|
||||||
|
run_services_screen()
|
||||||
|
|
||||||
def check_token(self, token: str,
|
def check_token(self, token: str,
|
||||||
on_success: Callable[[List[UbuntuProService]], None],
|
on_success: Callable[[List[UbuntuProService]], None],
|
||||||
|
@ -70,6 +134,7 @@ class UbuntuProController(SubiquityTuiController):
|
||||||
async def inner() -> None:
|
async def inner() -> None:
|
||||||
answer = await self.endpoint.check_token.GET(token)
|
answer = await self.endpoint.check_token.GET(token)
|
||||||
if answer.status == TokenStatus.VALID_TOKEN:
|
if answer.status == TokenStatus.VALID_TOKEN:
|
||||||
|
await self.endpoint.POST(UbuntuProInfo(token=token))
|
||||||
on_success(answer.subscription.services)
|
on_success(answer.subscription.services)
|
||||||
else:
|
else:
|
||||||
on_failure(answer.status)
|
on_failure(answer.status)
|
||||||
|
@ -85,6 +150,12 @@ class UbuntuProController(SubiquityTuiController):
|
||||||
self.app.prev_screen()
|
self.app.prev_screen()
|
||||||
|
|
||||||
def done(self, token: str) -> None:
|
def done(self, token: str) -> None:
|
||||||
|
""" Submit the token and move on to the next screen. """
|
||||||
self.app.next_screen(
|
self.app.next_screen(
|
||||||
self.endpoint.POST(UbuntuProInfo(token=token))
|
self.endpoint.POST(UbuntuProInfo(token=token))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def next_screen(self) -> None:
|
||||||
|
""" Move on to the next screen. Assume the token should not be
|
||||||
|
submitted (or has already been submitted). """
|
||||||
|
self.app.next_screen()
|
||||||
|
|
|
@ -79,12 +79,16 @@ class MockedUAInterfaceStrategy(UAInterfaceStrategy):
|
||||||
UA token. No actual query is done to the UA servers in this
|
UA token. No actual query is done to the UA servers in this
|
||||||
implementation. Instead, we create a response based on the following
|
implementation. Instead, we create a response based on the following
|
||||||
rules:
|
rules:
|
||||||
|
* Empty tokens are considered invalid.
|
||||||
* Tokens starting with "x" will be considered expired.
|
* Tokens starting with "x" will be considered expired.
|
||||||
* Tokens starting with "i" will be considered invalid.
|
* Tokens starting with "i" will be considered invalid.
|
||||||
* Tokens starting with "f" will generate an internal error.
|
* Tokens starting with "f" will generate an internal error.
|
||||||
"""
|
"""
|
||||||
await asyncio.sleep(1 / self.scale_factor)
|
await asyncio.sleep(1 / self.scale_factor)
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
raise InvalidTokenError(token)
|
||||||
|
|
||||||
if token[0] == "x":
|
if token[0] == "x":
|
||||||
path = "examples/uaclient-status-expired.json"
|
path = "examples/uaclient-status-expired.json"
|
||||||
elif token[0] == "i":
|
elif token[0] == "i":
|
||||||
|
@ -118,6 +122,11 @@ class UAClientUAInterfaceStrategy(UAInterfaceStrategy):
|
||||||
UA token. The information will be queried using the UA client
|
UA token. The information will be queried using the UA client
|
||||||
executable passed to the initializer.
|
executable passed to the initializer.
|
||||||
"""
|
"""
|
||||||
|
if not token:
|
||||||
|
# u-a-c does not produce the expected output when the contract
|
||||||
|
# token is empty ; so let's not call it at all.
|
||||||
|
raise InvalidTokenError(token)
|
||||||
|
|
||||||
command = tuple(self.executable) + (
|
command = tuple(self.executable) + (
|
||||||
"status",
|
"status",
|
||||||
"--format", "json",
|
"--format", "json",
|
||||||
|
|
|
@ -16,9 +16,10 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import List
|
from typing import Callable, List
|
||||||
|
|
||||||
from urwid import (
|
from urwid import (
|
||||||
|
Columns,
|
||||||
connect_signal,
|
connect_signal,
|
||||||
LineBox,
|
LineBox,
|
||||||
Text,
|
Text,
|
||||||
|
@ -34,15 +35,21 @@ from subiquitycore.ui.buttons import (
|
||||||
back_btn,
|
back_btn,
|
||||||
cancel_btn,
|
cancel_btn,
|
||||||
done_btn,
|
done_btn,
|
||||||
|
menu_btn,
|
||||||
ok_btn,
|
ok_btn,
|
||||||
)
|
)
|
||||||
from subiquitycore.ui.container import (
|
from subiquitycore.ui.container import (
|
||||||
|
ListBox,
|
||||||
Pile,
|
Pile,
|
||||||
WidgetWrap,
|
WidgetWrap,
|
||||||
)
|
)
|
||||||
from subiquitycore.ui.form import (
|
from subiquitycore.ui.form import (
|
||||||
Form,
|
Form,
|
||||||
|
SubForm,
|
||||||
|
SubFormField,
|
||||||
|
NO_HELP,
|
||||||
simple_field,
|
simple_field,
|
||||||
|
RadioButtonField,
|
||||||
WantsToKnowFormField,
|
WantsToKnowFormField,
|
||||||
)
|
)
|
||||||
from subiquitycore.ui.spinner import (
|
from subiquitycore.ui.spinner import (
|
||||||
|
@ -53,7 +60,7 @@ from subiquitycore.ui.stretchy import (
|
||||||
)
|
)
|
||||||
from subiquitycore.ui.utils import (
|
from subiquitycore.ui.utils import (
|
||||||
button_pile,
|
button_pile,
|
||||||
SomethingFailed,
|
screen,
|
||||||
)
|
)
|
||||||
|
|
||||||
from subiquitycore.ui.interactive import StringEditor
|
from subiquitycore.ui.interactive import StringEditor
|
||||||
|
@ -61,18 +68,13 @@ from subiquitycore.ui.interactive import StringEditor
|
||||||
|
|
||||||
log = logging.getLogger('subiquity.ui.views.ubuntu_pro')
|
log = logging.getLogger('subiquity.ui.views.ubuntu_pro')
|
||||||
|
|
||||||
ua_help = _("If you want to enroll this system using your Ubuntu Pro "
|
|
||||||
"subscription, enter your Ubuntu Pro token here. "
|
|
||||||
"Otherwise, leave this blank.")
|
|
||||||
|
|
||||||
|
class ContractTokenEditor(StringEditor, WantsToKnowFormField):
|
||||||
class UATokenEditor(StringEditor, WantsToKnowFormField):
|
|
||||||
""" Represent a text-box editor for the Ubuntu Pro Token. """
|
""" Represent a text-box editor for the Ubuntu Pro Token. """
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
""" Initialize the text-field editor for UA token. """
|
""" Initialize the text-field editor for UA token. """
|
||||||
self.valid_char_pat = r"[a-zA-Z0-9]"
|
self.valid_char_pat = r"[1-9A-HJ-NP-Za-km-z]"
|
||||||
self.error_invalid_char = _("The only characters permitted in this "
|
self.error_invalid_char = _("'{}' is not a valid character.")
|
||||||
"field are alphanumeric characters.")
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def valid_char(self, ch: str) -> bool:
|
def valid_char(self, ch: str) -> bool:
|
||||||
|
@ -81,23 +83,95 @@ class UATokenEditor(StringEditor, WantsToKnowFormField):
|
||||||
"""
|
"""
|
||||||
if len(ch) == 1 and not re.match(self.valid_char_pat, ch):
|
if len(ch) == 1 and not re.match(self.valid_char_pat, ch):
|
||||||
self.bff.in_error = True
|
self.bff.in_error = True
|
||||||
self.bff.show_extra(("info_error", self.error_invalid_char))
|
self.bff.show_extra(
|
||||||
|
("info_error", self.error_invalid_char.format(ch)))
|
||||||
return False
|
return False
|
||||||
return super().valid_char(ch)
|
return super().valid_char(ch)
|
||||||
|
|
||||||
|
|
||||||
class UbuntuProForm(Form):
|
class ContractTokenForm(SubForm):
|
||||||
|
""" Represents a sub-form requesting Ubuntu Pro token.
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
| Token: C123456789ABCDEF |
|
||||||
|
| This is your Ubuntu Pro token |
|
||||||
|
+---------------------------------------------------------+
|
||||||
"""
|
"""
|
||||||
Represents a form requesting Ubuntu Pro information
|
ContractTokenField = simple_field(ContractTokenEditor)
|
||||||
|
|
||||||
|
token = ContractTokenField(
|
||||||
|
_("Token:"),
|
||||||
|
help=_("This is your Ubuntu Pro token"))
|
||||||
|
|
||||||
|
def validate_token(self):
|
||||||
|
""" Return an error if the token input does not match the expected
|
||||||
|
format. """
|
||||||
|
if not 24 <= len(self.token.value) <= 30:
|
||||||
|
return _("Invalid token: should be between 24 and 30 characters")
|
||||||
|
|
||||||
|
|
||||||
|
class UpgradeModeForm(Form):
|
||||||
|
""" Represents a form requesting the Ubuntu Pro credentials.
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
| (X) Add token manually |
|
||||||
|
| Token: C123456789ABCDEF |
|
||||||
|
| This is your Ubuntu Pro token |
|
||||||
|
| |
|
||||||
|
| [ Continue ] |
|
||||||
|
| [ Back ] |
|
||||||
|
+---------------------------------------------------------+
|
||||||
"""
|
"""
|
||||||
cancel_label = _("Back")
|
cancel_label = _("Back")
|
||||||
|
ok_label = _("Continue")
|
||||||
|
group: List[RadioButtonField] = []
|
||||||
|
|
||||||
UATokenField = simple_field(UATokenEditor)
|
with_contract_token = RadioButtonField(
|
||||||
|
group, _("Add token manually"),
|
||||||
|
help=NO_HELP)
|
||||||
|
with_contract_token_subform = SubFormField(
|
||||||
|
ContractTokenForm, "", help=NO_HELP)
|
||||||
|
|
||||||
token = UATokenField(_("Ubuntu Pro token:"), help=ua_help)
|
def __init__(self, initial) -> None:
|
||||||
|
""" Initializer that configures the callback to run when the radio is
|
||||||
|
checked/unchecked. Since there is a single radio for now, it can
|
||||||
|
only be unchecked programmatically. """
|
||||||
|
super().__init__(initial)
|
||||||
|
connect_signal(self.with_contract_token.widget,
|
||||||
|
'change', self._toggle_contract_token_input)
|
||||||
|
|
||||||
|
def _toggle_contract_token_input(self, sender, new_value):
|
||||||
|
""" Enable/disable the sub-form that requests the contract token. """
|
||||||
|
self.with_contract_token_subform.enabled = new_value
|
||||||
|
|
||||||
|
|
||||||
class CheckingUAToken(WidgetWrap):
|
class UpgradeYesNoForm(Form):
|
||||||
|
""" Represents a form asking if we want to upgrade to Ubuntu Pro.
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
| (X) Upgrade to Ubuntu Pro |
|
||||||
|
| |
|
||||||
|
| ( ) Do this later |
|
||||||
|
| |
|
||||||
|
| You can always enable Ubuntu Pro later via the |
|
||||||
|
| 'pro attach' command. |
|
||||||
|
| |
|
||||||
|
| [ Continue ] |
|
||||||
|
| [ Back ] |
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
"""
|
||||||
|
|
||||||
|
cancel_label = _("Back")
|
||||||
|
ok_label = _("Continue")
|
||||||
|
group: List[RadioButtonField] = []
|
||||||
|
|
||||||
|
upgrade = RadioButtonField(
|
||||||
|
group, _("Upgrade to Ubuntu Pro"),
|
||||||
|
help=NO_HELP)
|
||||||
|
skip = RadioButtonField(
|
||||||
|
group, _("Do this later"),
|
||||||
|
help="\n" + _("You can always enable Ubuntu Pro later via the"
|
||||||
|
" 'pro attach' command."))
|
||||||
|
|
||||||
|
|
||||||
|
class CheckingContractToken(WidgetWrap):
|
||||||
""" Widget displaying a loading animation while checking ubuntu pro
|
""" Widget displaying a loading animation while checking ubuntu pro
|
||||||
subscription. """
|
subscription. """
|
||||||
def __init__(self, parent: BaseView):
|
def __init__(self, parent: BaseView):
|
||||||
|
@ -125,44 +199,223 @@ class CheckingUAToken(WidgetWrap):
|
||||||
class UbuntuProView(BaseView):
|
class UbuntuProView(BaseView):
|
||||||
""" Represent the view of the Ubuntu Pro configuration. """
|
""" Represent the view of the Ubuntu Pro configuration. """
|
||||||
|
|
||||||
title = _("Enable Ubuntu Pro")
|
title = _("Upgrade to Ubuntu Pro")
|
||||||
excerpt = _("Enter your Ubuntu Pro token if you want to enroll "
|
services_done_label = _("Continue")
|
||||||
"this system.")
|
|
||||||
|
|
||||||
def __init__(self, controller, token: str):
|
def __init__(self, controller, token: str):
|
||||||
""" Initialize the view with the default value for the token. """
|
""" Initialize the view with the default value for the token. """
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
|
||||||
self.form = UbuntuProForm(initial={"token": token})
|
self.upgrade_yes_no_form = UpgradeYesNoForm(initial={
|
||||||
|
"skip": not token,
|
||||||
|
"upgrade": bool(token),
|
||||||
|
})
|
||||||
|
self.upgrade_mode_form = UpgradeModeForm(initial={
|
||||||
|
"with_contract_token_subform": {"token": token},
|
||||||
|
})
|
||||||
|
|
||||||
def on_cancel(_: UbuntuProForm):
|
def on_upgrade_yes_no_cancel(unused: UpgradeYesNoForm):
|
||||||
|
""" Function to call when hitting Done from the upgrade/skip
|
||||||
|
screen. """
|
||||||
self.cancel()
|
self.cancel()
|
||||||
|
|
||||||
connect_signal(self.form, 'submit', self.done)
|
def on_upgrade_mode_cancel(unused: UpgradeModeForm):
|
||||||
connect_signal(self.form, 'cancel', on_cancel)
|
""" Function to call when hitting Back from the contract token
|
||||||
|
form. """
|
||||||
|
self._w = self.upgrade_yes_no_screen()
|
||||||
|
|
||||||
super().__init__(self.form.as_screen(excerpt=_(self.excerpt)))
|
connect_signal(self.upgrade_yes_no_form,
|
||||||
|
'submit', self.upgrade_yes_no_done)
|
||||||
|
connect_signal(self.upgrade_yes_no_form,
|
||||||
|
'cancel', on_upgrade_yes_no_cancel)
|
||||||
|
connect_signal(self.upgrade_mode_form,
|
||||||
|
'submit', self.upgrade_mode_done)
|
||||||
|
connect_signal(self.upgrade_mode_form,
|
||||||
|
'cancel', on_upgrade_mode_cancel)
|
||||||
|
|
||||||
def done(self, form: UbuntuProForm) -> None:
|
super().__init__(self.upgrade_yes_no_screen())
|
||||||
""" If no token was supplied, move on to the next screen.
|
|
||||||
If a token was provided, open the loading dialog and
|
def upgrade_mode_screen(self) -> Widget:
|
||||||
asynchronously check if the token is valid. """
|
""" Return a screen that asks the user for his information (e.g.,
|
||||||
token: str = form.token.value
|
contract token).
|
||||||
if token:
|
+---------------------------------------------------------+
|
||||||
|
| To upgrade to Ubuntu Pro, you can enter your token |
|
||||||
|
| manually. |
|
||||||
|
| |
|
||||||
|
| [ How to Register -> ] |
|
||||||
|
| |
|
||||||
|
| (X) Add token manually |
|
||||||
|
| Token: C123456789ABCDEF |
|
||||||
|
| This is your Ubuntu Pro token |
|
||||||
|
| |
|
||||||
|
| [ Continue ] |
|
||||||
|
| [ Back ] |
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
"""
|
||||||
|
|
||||||
|
excerpt = _("To upgrade to Ubuntu Pro, you can enter your token"
|
||||||
|
" manually.")
|
||||||
|
|
||||||
|
how_to_register_btn = menu_btn(
|
||||||
|
_("How to Register"),
|
||||||
|
on_press=lambda unused: self.show_how_to_register()
|
||||||
|
)
|
||||||
|
bp = button_pile([how_to_register_btn])
|
||||||
|
bp.align = "left"
|
||||||
|
rows = [
|
||||||
|
bp,
|
||||||
|
Text(""),
|
||||||
|
] + self.upgrade_mode_form.as_rows()
|
||||||
|
return screen(
|
||||||
|
ListBox(rows),
|
||||||
|
self.upgrade_mode_form.buttons,
|
||||||
|
excerpt=excerpt,
|
||||||
|
focus_buttons=True)
|
||||||
|
|
||||||
|
def upgrade_yes_no_screen(self) -> Widget:
|
||||||
|
""" Return a screen that asks the user to skip or upgrade.
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
| Upgrade this machine to Ubuntu Pro or skip this step. |
|
||||||
|
| |
|
||||||
|
| [ About Ubuntu Pro -> ] |
|
||||||
|
| |
|
||||||
|
| ( ) Upgrade to Ubuntu Pro |
|
||||||
|
| |
|
||||||
|
| (X) Do this later |
|
||||||
|
| You can always enable Ubuntu Pro later via the |
|
||||||
|
| 'pro attach' command. |
|
||||||
|
| |
|
||||||
|
| [ Continue ] |
|
||||||
|
| [ Back ] |
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
"""
|
||||||
|
|
||||||
|
excerpt = _("Upgrade this machine to Ubuntu Pro or skip this step.")
|
||||||
|
|
||||||
|
about_pro_btn = menu_btn(
|
||||||
|
_("About Ubuntu Pro"),
|
||||||
|
on_press=lambda unused: self.show_about_ubuntu_pro())
|
||||||
|
|
||||||
|
bp = button_pile([about_pro_btn])
|
||||||
|
bp.align = "left"
|
||||||
|
rows = [
|
||||||
|
bp,
|
||||||
|
Text(""),
|
||||||
|
] + self.upgrade_yes_no_form.as_rows()
|
||||||
|
return screen(
|
||||||
|
ListBox(rows),
|
||||||
|
self.upgrade_yes_no_form.buttons,
|
||||||
|
excerpt=excerpt,
|
||||||
|
focus_buttons=True)
|
||||||
|
|
||||||
|
def services_screen(self, services) -> Widget:
|
||||||
|
"""
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
| List of your enabled services: |
|
||||||
|
| |
|
||||||
|
| * ... |
|
||||||
|
| * ... |
|
||||||
|
| |
|
||||||
|
| Other available services: |
|
||||||
|
| |
|
||||||
|
| * ... |
|
||||||
|
| * ... |
|
||||||
|
| |
|
||||||
|
| If you want to change the default enablements for your |
|
||||||
|
| token, you can do so via the ubuntu.com/pro web |
|
||||||
|
| interface. Alternatively, you can change enabled |
|
||||||
|
| services using the `pro' command-line tool once the |
|
||||||
|
| installation is finished. |
|
||||||
|
| |
|
||||||
|
| [ Continue ] |
|
||||||
|
| [ Back ] |
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
"""
|
||||||
|
auto_enabled = [svc for svc in services if svc.auto_enabled]
|
||||||
|
can_be_enabled = [svc for svc in services if not svc.auto_enabled]
|
||||||
|
|
||||||
|
svc_rows: List[Widget] = []
|
||||||
|
|
||||||
|
if auto_enabled:
|
||||||
|
svc_rows.append(Text(_("List of your enabled services:")))
|
||||||
|
svc_rows.append(Text(""))
|
||||||
|
svc_rows.extend(
|
||||||
|
[Text(f" * {svc.description}") for svc in auto_enabled])
|
||||||
|
|
||||||
|
if can_be_enabled:
|
||||||
|
if auto_enabled:
|
||||||
|
# available here means activable
|
||||||
|
svc_rows.append(Text(""))
|
||||||
|
svc_rows.append(Text(_("Other available services:")))
|
||||||
|
else:
|
||||||
|
svc_rows.append(Text(_("Available services:")))
|
||||||
|
svc_rows.append(Text(""))
|
||||||
|
svc_rows.extend(
|
||||||
|
[Text(f" * {svc.description}") for svc in can_be_enabled])
|
||||||
|
|
||||||
|
def on_continue() -> None:
|
||||||
|
self.controller.next_screen()
|
||||||
|
|
||||||
|
def on_back() -> None:
|
||||||
|
self._w = self.upgrade_yes_no_screen()
|
||||||
|
|
||||||
|
back_button = back_btn(
|
||||||
|
label=_("Back"),
|
||||||
|
on_press=lambda unused: on_back())
|
||||||
|
continue_button = done_btn(
|
||||||
|
label=self.__class__.services_done_label,
|
||||||
|
on_press=lambda unused: on_continue())
|
||||||
|
|
||||||
|
widgets: List[Widget] = [
|
||||||
|
Text(""),
|
||||||
|
Pile(svc_rows),
|
||||||
|
Text(""),
|
||||||
|
Text(_("If you want to change the default enablements for your"
|
||||||
|
" token, you can do so via the ubuntu.com/pro web"
|
||||||
|
" interface. Alternatively you can change enabled services"
|
||||||
|
" using the `pro` command-line tool once the installation"
|
||||||
|
" is finished.")),
|
||||||
|
Text(""),
|
||||||
|
]
|
||||||
|
|
||||||
|
return screen(
|
||||||
|
ListBox(widgets),
|
||||||
|
buttons=[continue_button, back_button],
|
||||||
|
excerpt=None,
|
||||||
|
focus_buttons=True)
|
||||||
|
|
||||||
|
def upgrade_mode_done(self, form: UpgradeModeForm) -> None:
|
||||||
|
""" Open the loading dialog and asynchronously check if the token is
|
||||||
|
valid. """
|
||||||
def on_success(services: List[UbuntuProService]) -> None:
|
def on_success(services: List[UbuntuProService]) -> None:
|
||||||
|
def show_services() -> None:
|
||||||
self.remove_overlay()
|
self.remove_overlay()
|
||||||
self.show_activable_services(services)
|
self.show_activable_services(services)
|
||||||
|
|
||||||
|
self.remove_overlay()
|
||||||
|
widget = TokenAddedWidget(
|
||||||
|
parent=self,
|
||||||
|
on_continue=show_services)
|
||||||
|
self.show_stretchy_overlay(widget)
|
||||||
|
|
||||||
def on_failure(status: UbuntuProCheckTokenStatus) -> None:
|
def on_failure(status: UbuntuProCheckTokenStatus) -> None:
|
||||||
self.remove_overlay()
|
self.remove_overlay()
|
||||||
|
token_field = form.with_contract_token_subform.widget.form.token
|
||||||
if status == UbuntuProCheckTokenStatus.INVALID_TOKEN:
|
if status == UbuntuProCheckTokenStatus.INVALID_TOKEN:
|
||||||
self.show_invalid_token()
|
self.show_invalid_token()
|
||||||
|
token_field.in_error = True
|
||||||
|
token_field.show_extra(("info_error", "Invalid token"))
|
||||||
|
form.validated()
|
||||||
elif status == UbuntuProCheckTokenStatus.EXPIRED_TOKEN:
|
elif status == UbuntuProCheckTokenStatus.EXPIRED_TOKEN:
|
||||||
self.show_expired_token()
|
self.show_expired_token()
|
||||||
|
token_field.in_error = True
|
||||||
|
token_field.show_extra(("info_error", "Expired token"))
|
||||||
|
form.validated()
|
||||||
elif status == UbuntuProCheckTokenStatus.UNKNOWN_ERROR:
|
elif status == UbuntuProCheckTokenStatus.UNKNOWN_ERROR:
|
||||||
self.show_unknown_error()
|
self.show_unknown_error()
|
||||||
|
|
||||||
checking_token_overlay = CheckingUAToken(self)
|
token: str = form.with_contract_token_subform.value["token"]
|
||||||
|
checking_token_overlay = CheckingContractToken(self)
|
||||||
self.show_overlay(checking_token_overlay,
|
self.show_overlay(checking_token_overlay,
|
||||||
width=checking_token_overlay.width,
|
width=checking_token_overlay.width,
|
||||||
min_width=None)
|
min_width=None)
|
||||||
|
@ -170,31 +423,37 @@ class UbuntuProView(BaseView):
|
||||||
self.controller.check_token(token,
|
self.controller.check_token(token,
|
||||||
on_success=on_success,
|
on_success=on_success,
|
||||||
on_failure=on_failure)
|
on_failure=on_failure)
|
||||||
|
|
||||||
|
def upgrade_yes_no_done(self, form: UpgradeYesNoForm) -> None:
|
||||||
|
""" If skip is selected, move on to the next screen.
|
||||||
|
Otherwise, show the form requesting the contract token. """
|
||||||
|
if form.skip.value:
|
||||||
|
self.controller.done("")
|
||||||
else:
|
else:
|
||||||
self.controller.done(token)
|
self._w = self.upgrade_mode_screen()
|
||||||
|
|
||||||
def cancel(self) -> None:
|
def cancel(self) -> None:
|
||||||
""" Called when the user presses the Back button. """
|
""" Called when the user presses the Back button. """
|
||||||
self.controller.cancel()
|
self.controller.cancel()
|
||||||
|
|
||||||
|
def show_about_ubuntu_pro(self) -> None:
|
||||||
|
""" Display an overlay that shows information about Ubuntu Pro. """
|
||||||
|
self.show_stretchy_overlay(AboutProWidget(self))
|
||||||
|
|
||||||
|
def show_how_to_register(self) -> None:
|
||||||
|
""" Display an overlay that shows instructions to register to
|
||||||
|
Ubuntu Pro. """
|
||||||
|
self.show_stretchy_overlay(HowToRegisterWidget(self))
|
||||||
|
|
||||||
def show_invalid_token(self) -> None:
|
def show_invalid_token(self) -> None:
|
||||||
""" Display an overlay that indicates that the user-supplied token is
|
""" Display an overlay that indicates that the user-supplied token is
|
||||||
invalid. """
|
invalid. """
|
||||||
self.show_stretchy_overlay(
|
self.show_stretchy_overlay(InvalidTokenWidget(self))
|
||||||
SomethingFailed(self,
|
|
||||||
"Invalid token.",
|
|
||||||
"The Ubuntu Pro token that you provided"
|
|
||||||
" is invalid. Please make sure that you typed"
|
|
||||||
" your token correctly."))
|
|
||||||
|
|
||||||
def show_expired_token(self) -> None:
|
def show_expired_token(self) -> None:
|
||||||
""" Display an overlay that indicates that the user-supplied token has
|
""" Display an overlay that indicates that the user-supplied token has
|
||||||
expired. """
|
expired. """
|
||||||
self.show_stretchy_overlay(
|
self.show_stretchy_overlay(ExpiredTokenWidget(self))
|
||||||
SomethingFailed(self,
|
|
||||||
"Token expired.",
|
|
||||||
"The Ubuntu Pro token that you provided"
|
|
||||||
" has expired. Please use a different token."))
|
|
||||||
|
|
||||||
def show_unknown_error(self) -> None:
|
def show_unknown_error(self) -> None:
|
||||||
""" Display an overlay that indicates that we were unable to retrieve
|
""" Display an overlay that indicates that we were unable to retrieve
|
||||||
|
@ -206,47 +465,224 @@ class UbuntuProView(BaseView):
|
||||||
|
|
||||||
def show_activable_services(self,
|
def show_activable_services(self,
|
||||||
services: List[UbuntuProService]) -> None:
|
services: List[UbuntuProService]) -> None:
|
||||||
""" Display an overlay with the list of services that can be enabled
|
""" Display a screen with the list of services that can be enabled
|
||||||
via Ubuntu Pro subscription. After the user confirms, we will
|
via Ubuntu Pro subscription. After the user confirms, we will
|
||||||
quit the current view and move on. """
|
quit the current view and move on. """
|
||||||
self.show_stretchy_overlay(ShowServicesWidget(self, services))
|
self._w = self.services_screen(services)
|
||||||
|
|
||||||
|
|
||||||
class ShowServicesWidget(Stretchy):
|
class ExpiredTokenWidget(Stretchy):
|
||||||
""" Widget to show the activable services for UA subscription. """
|
""" Widget that shows that the supplied token is expired.
|
||||||
|
|
||||||
|
+--------------------- Expired token ---------------------+
|
||||||
|
| |
|
||||||
|
| Your token has expired. Please use another token to |
|
||||||
|
| continue. |
|
||||||
|
| |
|
||||||
|
| [ Okay ] |
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
"""
|
||||||
|
def __init__(self, parent: BaseView) -> None:
|
||||||
|
""" Initializes the widget. """
|
||||||
|
self.parent = parent
|
||||||
|
cont = done_btn(label=_("Okay"), on_press=lambda unused: self.close())
|
||||||
|
widgets = [
|
||||||
|
Text(_("Your token has expired. Please use another token"
|
||||||
|
" to continue.")),
|
||||||
|
Text(""),
|
||||||
|
button_pile([cont]),
|
||||||
|
]
|
||||||
|
super().__init__("Expired token", widgets,
|
||||||
|
stretchy_index=0, focus_index=2)
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
""" Close the overlay. """
|
||||||
|
self.parent.remove_overlay()
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidTokenWidget(Stretchy):
|
||||||
|
""" Widget that shows that the supplied token is invalid.
|
||||||
|
|
||||||
|
+--------------------- Invalid token ---------------------+
|
||||||
|
| |
|
||||||
|
| Your token could not be verified. Please ensure it is |
|
||||||
|
| correct and try again. |
|
||||||
|
| |
|
||||||
|
| [ Okay ] |
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
"""
|
||||||
|
def __init__(self, parent: BaseView) -> None:
|
||||||
|
""" Initializes the widget. """
|
||||||
|
self.parent = parent
|
||||||
|
cont = done_btn(label=_("Okay"), on_press=lambda unused: self.close())
|
||||||
|
widgets = [
|
||||||
|
Text(_("Your token could not be verified. Please ensure it is"
|
||||||
|
" correct and try again.")),
|
||||||
|
Text(""),
|
||||||
|
button_pile([cont]),
|
||||||
|
]
|
||||||
|
super().__init__("Invalid token", widgets,
|
||||||
|
stretchy_index=0, focus_index=2)
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
""" Close the overlay. """
|
||||||
|
self.parent.remove_overlay()
|
||||||
|
|
||||||
|
|
||||||
|
class TokenAddedWidget(Stretchy):
|
||||||
|
""" Widget that shows that the supplied token is valid and was "added".
|
||||||
|
+---------------- Token added successfully ---------------+
|
||||||
|
| |
|
||||||
|
| Your token has been added successfully and your |
|
||||||
|
| subscription configuration will be applied at the first |
|
||||||
|
| boot. |
|
||||||
|
| |
|
||||||
|
| [ Continue ] |
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
"""
|
||||||
|
title = _("Token added successfully")
|
||||||
|
done_label = _("Continue")
|
||||||
|
|
||||||
def __init__(self, parent: UbuntuProView,
|
def __init__(self, parent: UbuntuProView,
|
||||||
services: List[UbuntuProService]) -> None:
|
on_continue: Callable[[], None]) -> None:
|
||||||
""" Initializes the widget by including the list of services as a
|
""" Initializes the widget. """
|
||||||
bullet-point list. """
|
self.parent = parent
|
||||||
|
cont = done_btn(
|
||||||
|
label=self.__class__.done_label,
|
||||||
|
on_press=lambda unused: on_continue())
|
||||||
|
widgets = [
|
||||||
|
Text(_("Your token has been added successfully and your"
|
||||||
|
" subscription configuration will be applied at the first"
|
||||||
|
" boot.")),
|
||||||
|
Text(""),
|
||||||
|
button_pile([cont]),
|
||||||
|
]
|
||||||
|
super().__init__(self.__class__.title, widgets,
|
||||||
|
stretchy_index=0, focus_index=2)
|
||||||
|
|
||||||
|
|
||||||
|
class AboutProWidget(Stretchy):
|
||||||
|
""" Widget showing some information about what Ubuntu Pro offers.
|
||||||
|
+------------------- About Ubuntu Pro --------------------+
|
||||||
|
| |
|
||||||
|
| Ubuntu Pro subscription gives you access to multiple |
|
||||||
|
| security & compliance services, including: |
|
||||||
|
| |
|
||||||
|
| • Security patching for over 30.000 packages, with a |
|
||||||
|
| focus on High and Critical CVEs (extended from 2.500) |
|
||||||
|
| • ... |
|
||||||
|
| • ... |
|
||||||
|
| |
|
||||||
|
| Ubuntu Pro is free for personal use on up to 3 machines.|
|
||||||
|
| More information on ubuntu.com/pro |
|
||||||
|
| |
|
||||||
|
| [ Continue ] |
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
"""
|
||||||
|
def __init__(self, parent: UbuntuProView) -> None:
|
||||||
|
""" Initializes the widget."""
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
|
||||||
ok = ok_btn(label=_("OK"), on_press=self.ok)
|
ok = ok_btn(label=_("Continue"), on_press=lambda unused: self.close())
|
||||||
|
|
||||||
title = _("Activable Services")
|
title = _("About Ubuntu Pro")
|
||||||
header = _("List of services that are activable through your "
|
header = _("Ubuntu Pro subscription gives you access to multiple"
|
||||||
"Ubuntu Pro subscription:")
|
" security & compliance services, including:")
|
||||||
|
|
||||||
|
services = [
|
||||||
|
_("Security patching for over 30.000 packages, with a focus on"
|
||||||
|
" High and Critical CVEs (extended from 2.500)"),
|
||||||
|
_("10 years of security Maintenance (extended from 5 years)"),
|
||||||
|
_("Kernel Livepatch service for increased uptime and security"),
|
||||||
|
_("Ubuntu Security Guide for hardening profiles, including CIS"
|
||||||
|
" and DISA-STIG"),
|
||||||
|
_("FIPS 140-2 NIST-certified crypto-modules for FedRAMP"
|
||||||
|
" compliance"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def itemize(item: str, marker: str = "•") -> Columns:
|
||||||
|
""" Return the text specified in a Text widget prepended with a
|
||||||
|
bullet point / marker. If the text is too long to fit in a single
|
||||||
|
line, the continuation lines are indented as shown below:
|
||||||
|
+---------------------------+
|
||||||
|
| * This is an example of |
|
||||||
|
| what such element would |
|
||||||
|
| look like. |
|
||||||
|
+---------------------------+
|
||||||
|
"""
|
||||||
|
return Columns(
|
||||||
|
[(len(marker), Text(marker)), Text(item)], dividechars=1)
|
||||||
|
|
||||||
widgets: List[Widget] = [
|
widgets: List[Widget] = [
|
||||||
Text(header),
|
Text(header),
|
||||||
Text(""),
|
Text(""),
|
||||||
Pile([Text(f"* {svc.description}") for svc in services]),
|
Pile([itemize(svc) for svc in services]),
|
||||||
Text(""),
|
Text(""),
|
||||||
Text("Once the installation has finished, you can enable these "
|
Text(_("Ubuntu Pro is free for personal use on up to 3"
|
||||||
"services using the `ua` command-line tool."),
|
" machines.")),
|
||||||
|
Text(_("More information on ubuntu.com/pro")),
|
||||||
Text(""),
|
Text(""),
|
||||||
button_pile([ok]),
|
button_pile([ok]),
|
||||||
]
|
]
|
||||||
|
|
||||||
super().__init__(title, widgets, 2, 6)
|
super().__init__(title, widgets, stretchy_index=2, focus_index=7)
|
||||||
|
|
||||||
def ok(self, sender) -> None:
|
def close(self) -> None:
|
||||||
""" Close the overlay and submit the token. """
|
""" Close the overlay. """
|
||||||
self.parent.controller.done(self.parent.form.token.value)
|
self.parent.remove_overlay()
|
||||||
|
|
||||||
|
|
||||||
|
class HowToRegisterWidget(Stretchy):
|
||||||
|
""" Widget showing some instructions to register to Ubuntu Pro.
|
||||||
|
+-------------------- How to register --------------------+
|
||||||
|
| |
|
||||||
|
| You can register for a free Ubuntu One account and get |
|
||||||
|
| a personal token for up to 3 machines. |
|
||||||
|
| |
|
||||||
|
| To register an account, visit ubuntu.com/pro on another |
|
||||||
|
| device. |
|
||||||
|
| |
|
||||||
|
| [ Continue ] |
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
"""
|
||||||
|
def __init__(self, parent: UbuntuProView) -> None:
|
||||||
|
""" Initializes the widget."""
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
ok = ok_btn(label=_("Continue"), on_press=lambda unused: self.close())
|
||||||
|
|
||||||
|
title = _("How to register")
|
||||||
|
header = _("You can register for a free Ubuntu One account and get a"
|
||||||
|
" personal token for up to 3 machines.")
|
||||||
|
|
||||||
|
widgets: List[Widget] = [
|
||||||
|
Text(header),
|
||||||
|
Text(""),
|
||||||
|
Text("To register an account, visit ubuntu.com/pro on another"
|
||||||
|
" device."),
|
||||||
|
Text(""),
|
||||||
|
button_pile([ok]),
|
||||||
|
]
|
||||||
|
|
||||||
|
super().__init__(title, widgets, stretchy_index=2, focus_index=4)
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
""" Close the overlay. """
|
||||||
|
self.parent.remove_overlay()
|
||||||
|
|
||||||
|
|
||||||
class ContinueAnywayWidget(Stretchy):
|
class ContinueAnywayWidget(Stretchy):
|
||||||
""" Widget that requests the user if he wants to go back or continue
|
""" Widget that requests the user if he wants to go back or continue
|
||||||
anyway. """
|
anyway.
|
||||||
|
+--------------------- Unknown error ---------------------+
|
||||||
|
| |
|
||||||
|
| Unable to check your subscription information. Do you |
|
||||||
|
| want to go back or continue anyway? |
|
||||||
|
| |
|
||||||
|
| [ Back ] |
|
||||||
|
| [ Continue anyway ] |
|
||||||
|
+---------------------------------------------------------+
|
||||||
|
"""
|
||||||
def __init__(self, parent: UbuntuProView) -> None:
|
def __init__(self, parent: UbuntuProView) -> None:
|
||||||
""" Initializes the widget by showing two buttons, one to go back and
|
""" Initializes the widget by showing two buttons, one to go back and
|
||||||
one to move forward anyway. """
|
one to move forward anyway. """
|
||||||
|
@ -267,4 +703,5 @@ class ContinueAnywayWidget(Stretchy):
|
||||||
|
|
||||||
def cont(self, sender) -> None:
|
def cont(self, sender) -> None:
|
||||||
""" Move on to the next screen. """
|
""" Move on to the next screen. """
|
||||||
self.parent.controller.done(self.parent.form.token.value)
|
subform = self.parent.upgrade_mode_form.with_contract_token_subform
|
||||||
|
self.parent.controller.done(subform.value["token"])
|
||||||
|
|
Loading…
Reference in New Issue