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:
Olivier Gayot 2022-07-05 16:12:15 +02:00 committed by GitHub
commit 0fa8c54296
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 599 additions and 82 deletions

View File

@ -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()

View File

@ -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",

View File

@ -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"])