Merge branch 'master' into mwhudson/snap-screen
This commit is contained in:
commit
919b292800
|
@ -20,7 +20,6 @@ import os
|
|||
import signal
|
||||
import sys
|
||||
|
||||
from subiquitycore.i18n import *
|
||||
from subiquitycore.log import setup_logger
|
||||
from subiquitycore import __version__ as VERSION
|
||||
from subiquitycore.core import ApplicationError
|
||||
|
|
|
@ -14,3 +14,5 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" Console-Conf """
|
||||
|
||||
import subiquitycore.i18n
|
||||
|
|
|
@ -55,7 +55,7 @@ def host_key_fingerprints():
|
|||
log.debug("sshd -T failed %r", config['err'])
|
||||
return []
|
||||
keyfiles = []
|
||||
for line in config.output.splitlines():
|
||||
for line in config.stdout.splitlines():
|
||||
if line.startswith('hostkey '):
|
||||
keyfiles.append(line.split(None, 1)[1])
|
||||
info = []
|
||||
|
|
8
setup.py
8
setup.py
|
@ -29,7 +29,11 @@ from DistUtilsExtra.command import build_i18n
|
|||
import os
|
||||
import sys
|
||||
|
||||
import subiquitycore
|
||||
with open(os.path.join(os.path.dirname(__file__), 'subiquitycore', '__init__.py')) as init:
|
||||
lines = [line for line in init if 'i18n' not in line]
|
||||
ns = {}
|
||||
exec('\n'.join(lines), ns)
|
||||
version = ns['__version__']
|
||||
|
||||
if sys.argv[-1] == 'clean':
|
||||
print("Cleaning up ...")
|
||||
|
@ -37,7 +41,7 @@ if sys.argv[-1] == 'clean':
|
|||
sys.exit()
|
||||
|
||||
setup(name='subiquity',
|
||||
version=subiquitycore.__version__,
|
||||
version=version,
|
||||
description="Ubuntu Server Installer",
|
||||
long_description=__doc__,
|
||||
author='Canonical Engineering',
|
||||
|
|
|
@ -16,3 +16,5 @@
|
|||
""" Subiquity """
|
||||
|
||||
__version__ = "0.0.5"
|
||||
|
||||
import subiquitycore.i18n
|
||||
|
|
|
@ -55,10 +55,6 @@ class FilesystemController(BaseController):
|
|||
self.model.probe() # probe before we complete
|
||||
|
||||
def default(self):
|
||||
title = _("Filesystem setup")
|
||||
footer = (_("Choose guided or manual partitioning"))
|
||||
self.ui.set_header(title)
|
||||
self.ui.set_footer(footer)
|
||||
self.ui.set_body(GuidedFilesystemView(self))
|
||||
if self.answers['guided']:
|
||||
self.guided()
|
||||
|
@ -66,19 +62,11 @@ class FilesystemController(BaseController):
|
|||
self.manual()
|
||||
|
||||
def manual(self):
|
||||
title = _("Filesystem setup")
|
||||
footer = (_("Select available disks to format and mount"))
|
||||
self.ui.set_header(title)
|
||||
self.ui.set_footer(footer)
|
||||
self.ui.set_body(FilesystemView(self.model, self))
|
||||
if self.answers['guided']:
|
||||
self.finish()
|
||||
|
||||
def guided(self):
|
||||
title = _("Filesystem setup")
|
||||
footer = (_("Choose the installation target"))
|
||||
self.ui.set_header(title)
|
||||
self.ui.set_footer(footer)
|
||||
v = GuidedDiskSelectionView(self.model, self)
|
||||
self.ui.set_body(v)
|
||||
if self.answers['guided']:
|
||||
|
@ -94,14 +82,6 @@ class FilesystemController(BaseController):
|
|||
def cancel(self):
|
||||
self.signal.emit_signal('prev-screen')
|
||||
|
||||
def filesystem_error(self, error_fname):
|
||||
title = _("Filesystem error")
|
||||
footer = (_("Error while installing Ubuntu"))
|
||||
error_msg = _("Failed to obtain write permissions to /tmp")
|
||||
self.ui.set_header(title)
|
||||
self.ui.set_footer(footer)
|
||||
self.ui.set_body(ErrorView(self.signal, error_msg))
|
||||
|
||||
def finish(self):
|
||||
# start curtin install in background
|
||||
self.signal.emit_signal('installprogress:filesystem-config-done')
|
||||
|
@ -111,26 +91,17 @@ class FilesystemController(BaseController):
|
|||
# Filesystem/Disk partition -----------------------------------------------
|
||||
def partition_disk(self, disk):
|
||||
log.debug("In disk partition view, using {} as the disk.".format(disk.label))
|
||||
title = (_("Partition, format, and mount {}").format(disk.label))
|
||||
footer = (_("Partition the disk, or format the entire device "
|
||||
"without partitions"))
|
||||
self.ui.set_header(title)
|
||||
self.ui.set_footer(footer)
|
||||
dp_view = DiskPartitionView(self.model, self, disk)
|
||||
|
||||
self.ui.set_body(dp_view)
|
||||
|
||||
def add_disk_partition(self, disk):
|
||||
log.debug("Adding partition to {}".format(disk))
|
||||
footer = _("Select whole disk, or partition, to format and mount.")
|
||||
self.ui.set_footer(footer)
|
||||
adp_view = PartitionView(self.model, self, disk)
|
||||
self.ui.set_body(adp_view)
|
||||
|
||||
def edit_partition(self, disk, partition):
|
||||
log.debug("Editing partition {}".format(partition))
|
||||
footer = _("Edit partition details format and mount.")
|
||||
self.ui.set_footer(footer)
|
||||
adp_view = PartitionView(self.model, self, disk, partition)
|
||||
self.ui.set_body(adp_view)
|
||||
|
||||
|
@ -295,23 +266,11 @@ class FilesystemController(BaseController):
|
|||
|
||||
def format_entire(self, disk):
|
||||
log.debug("format_entire {}".format(disk.label))
|
||||
header = (_("Format and/or mount {}").format(disk.label))
|
||||
footer = _("Format or mount whole disk.")
|
||||
self.ui.set_header(header)
|
||||
self.ui.set_footer(footer)
|
||||
afv_view = FormatEntireView(self.model, self, disk, lambda : self.partition_disk(disk))
|
||||
self.ui.set_body(afv_view)
|
||||
|
||||
def format_mount_partition(self, partition):
|
||||
log.debug("format_mount_partition {}".format(partition))
|
||||
if partition.fs() is not None:
|
||||
header = (_("Mount partition {} of {}").format(partition._number, partition.device.label))
|
||||
footer = _("Mount partition.")
|
||||
else:
|
||||
header = (_("Format and mount partition {} of {}").format(partition._number, partition.device.label))
|
||||
footer = _("Format and mount partition.")
|
||||
self.ui.set_header(header)
|
||||
self.ui.set_footer(footer)
|
||||
afv_view = FormatEntireView(self.model, self, partition, self.manual)
|
||||
self.ui.set_body(afv_view)
|
||||
|
||||
|
@ -380,8 +339,6 @@ class FilesystemController(BaseController):
|
|||
result = template.format(**dinfo)
|
||||
log.debug('calling DiskInfoView()')
|
||||
disk_info_view = DiskInfoView(self.model, self, disk, result)
|
||||
footer = _('Select next or previous disks with n and p')
|
||||
self.ui.set_footer(footer)
|
||||
self.ui.set_body(disk_info_view)
|
||||
|
||||
def is_uefi(self):
|
||||
|
|
|
@ -36,9 +36,6 @@ class IdentityController(BaseController):
|
|||
self.answers = self.all_answers.get('Identity', {})
|
||||
|
||||
def default(self):
|
||||
title = _("Profile setup")
|
||||
excerpt = _("Enter the username and password (or ssh identity) you will use to log in to the system.")
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_body(IdentityView(self.model, self, self.opts))
|
||||
if 'realname' in self.answers and \
|
||||
'username' in self.answers and \
|
||||
|
@ -70,6 +67,8 @@ class IdentityController(BaseController):
|
|||
|
||||
def _bg_fetch_ssh_keys(self, user_spec, proc, ssh_import_id):
|
||||
stdout, stderr = proc.communicate()
|
||||
stdout = stdout.decode('utf-8', errors='replace')
|
||||
stderr = stderr.decode('utf-8', errors='replace')
|
||||
log.debug("ssh-import-id exited with code %s", proc.returncode)
|
||||
if proc != self._fetching_proc:
|
||||
log.debug("_fetch_ssh_keys cancelled")
|
||||
|
|
|
@ -32,16 +32,6 @@ class InstallpathController(BaseController):
|
|||
self.answers = self.all_answers.get("Installpath", {})
|
||||
|
||||
def installpath(self):
|
||||
title = "Ubuntu %s"%(lsb_release.get_distro_information()['RELEASE'],)
|
||||
excerpt = _("Welcome to Ubuntu! The world's favourite platform "
|
||||
"for clouds, clusters, and amazing internet things. "
|
||||
"This is the installer for Ubuntu on servers and "
|
||||
"internet devices.")
|
||||
footer = _("Use UP, DOWN arrow keys, and ENTER, to "
|
||||
"navigate options")
|
||||
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_footer(footer)
|
||||
self.ui.set_body(InstallpathView(self.model, self))
|
||||
if 'path' in self.answers:
|
||||
path = self.answers['path']
|
||||
|
@ -75,9 +65,7 @@ class InstallpathController(BaseController):
|
|||
"individually, MAAS turns your bare metal into an elastic "
|
||||
"cloud-like resource. \n\nFor further details, see https://maas.io/."
|
||||
)
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_footer("")
|
||||
self.ui.set_body(MAASView(self.model, self))
|
||||
self.ui.set_body(MAASView(self.model, self, title, excerpt))
|
||||
|
||||
def install_maas_rack(self):
|
||||
# show cack questions, seed model
|
||||
|
@ -89,9 +77,7 @@ class InstallpathController(BaseController):
|
|||
"region controller."
|
||||
)
|
||||
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_footer("")
|
||||
self.ui.set_body(MAASView(self.model, self))
|
||||
self.ui.set_body(MAASView(self.model, self, title, excerpt))
|
||||
|
||||
def setup_maas(self, result):
|
||||
self.model.update(result)
|
||||
|
|
|
@ -398,14 +398,14 @@ class InstallProgressController(BaseController):
|
|||
|
||||
def default(self):
|
||||
self.progress_view_showing = True
|
||||
self.ui.set_body(self.progress_view)
|
||||
if self.install_state == InstallState.RUNNING:
|
||||
self.ui.set_header(_("Installing system"))
|
||||
self.ui.set_footer(_("Thank you for using Ubuntu!"))
|
||||
self.progress_view.title = _("Installing system")
|
||||
self.progress_view.footer = _("Thank you for using Ubuntu!")
|
||||
elif self.install_state == InstallState.DONE:
|
||||
self.ui.set_header(_("Install complete!"))
|
||||
self.ui.set_footer(_("Thank you for using Ubuntu!"))
|
||||
self.progress_view.title = _("Install complete!")
|
||||
self.progress_view.footer = _("Thank you for using Ubuntu!")
|
||||
elif self.install_state == InstallState.ERROR:
|
||||
self.ui.set_header(_('An error occurred during installation'))
|
||||
self.ui.set_footer(_('Please report this error in Launchpad'))
|
||||
self.progress_view.title = _('An error occurred during installation')
|
||||
self.progress_view.footer = _('Please report this error in Launchpad')
|
||||
self.ui.set_body(self.progress_view)
|
||||
|
||||
|
|
|
@ -45,14 +45,6 @@ class KeyboardController(BaseController):
|
|||
def default(self):
|
||||
if self.model.current_lang is None:
|
||||
self.model.load_language('C')
|
||||
title = _("Keyboard configuration")
|
||||
if self.opts.run_on_serial:
|
||||
excerpt = _('Please select the layout of the keyboard directly attached to the system, if any.')
|
||||
else:
|
||||
excerpt = _('Please select your keyboard layout below, or select "Identify keyboard" to detect your layout automatically.')
|
||||
footer = _("Use UP, DOWN and ENTER keys to select your keyboard.")
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_footer(footer)
|
||||
view = KeyboardView(self.model, self, self.opts)
|
||||
self.ui.set_body(view)
|
||||
if 'layout' in self.answers:
|
||||
|
|
|
@ -31,9 +31,6 @@ class ProxyController(BaseController):
|
|||
self.answers = self.all_answers.get('Proxy', {})
|
||||
|
||||
def default(self):
|
||||
title = _("Configure proxy")
|
||||
excerpt = _("If this system requires a proxy to connect to the internet, enter its details here.")
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_body(ProxyView(self.model, self))
|
||||
if 'proxy' in self.answers:
|
||||
self.done(self.answers['proxy'])
|
||||
|
|
|
@ -30,11 +30,6 @@ class WelcomeController(BaseController):
|
|||
log.debug("Welcome: answers=%s", self.answers)
|
||||
|
||||
def default(self):
|
||||
title = "Willkommen! Bienvenue! Welcome! Добро пожаловать! Welkom!"
|
||||
excerpt = _("Please choose your preferred language")
|
||||
footer = _("Use UP, DOWN and ENTER keys to select your language.")
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_footer(footer)
|
||||
view = WelcomeView(self.model, self)
|
||||
self.ui.set_body(view)
|
||||
if 'lang' in self.answers:
|
||||
|
|
|
@ -26,11 +26,15 @@ log = logging.getLogger('subiquity.ui.filesystem.disk_info')
|
|||
|
||||
|
||||
class DiskInfoView(BaseView):
|
||||
|
||||
footer = _('Select next or previous disks with n and p')
|
||||
|
||||
def __init__(self, model, controller, disk, hdinfo):
|
||||
log.debug('DiskInfoView: {}'.format(disk))
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
self.disk = disk
|
||||
self.title = _("Information on {}").format(disk.label)
|
||||
hdinfo = hdinfo.split("\n")
|
||||
body = []
|
||||
for h in hdinfo:
|
||||
|
|
|
@ -28,10 +28,14 @@ log = logging.getLogger('subiquity.ui.filesystem.disk_partition')
|
|||
|
||||
|
||||
class DiskPartitionView(BaseView):
|
||||
footer = (_("Partition the disk, or format the entire device "
|
||||
"without partitions"))
|
||||
|
||||
def __init__(self, model, controller, disk):
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
self.disk = disk
|
||||
self.title = _("Partition, format, and mount {}").format(disk.label)
|
||||
|
||||
self.body = Pile([
|
||||
('pack', Text("")),
|
||||
|
|
|
@ -75,6 +75,9 @@ class FilesystemConfirmation(Stretchy):
|
|||
|
||||
|
||||
class FilesystemView(BaseView):
|
||||
title = _("Filesystem setup")
|
||||
footer = _("Select available disks to format and mount")
|
||||
|
||||
def __init__(self, model, controller):
|
||||
log.debug('FileSystemView init start()')
|
||||
self.model = model
|
||||
|
@ -99,7 +102,7 @@ class FilesystemView(BaseView):
|
|||
#]
|
||||
|
||||
self.lb = Padding.center_95(ListBox(body))
|
||||
self.footer = Pile([
|
||||
bottom = Pile([
|
||||
Text(""),
|
||||
self._build_buttons(),
|
||||
Text(""),
|
||||
|
@ -107,7 +110,7 @@ class FilesystemView(BaseView):
|
|||
self.frame = Pile([
|
||||
('pack', Text("")),
|
||||
self.lb,
|
||||
('pack', self.footer)])
|
||||
('pack', bottom)])
|
||||
if self.model.can_install():
|
||||
self.frame.focus_position = 2
|
||||
super().__init__(self.frame)
|
||||
|
|
|
@ -43,6 +43,9 @@ review and modify the results.""")
|
|||
|
||||
class GuidedFilesystemView(BaseView):
|
||||
|
||||
title = _("Filesystem setup")
|
||||
footer = _("Choose guided or manual partitioning")
|
||||
|
||||
def __init__(self, controller):
|
||||
self.controller = controller
|
||||
guided = ok_btn(_("Use An Entire Disk"), on_press=self.guided)
|
||||
|
@ -68,6 +71,9 @@ class GuidedFilesystemView(BaseView):
|
|||
|
||||
class GuidedDiskSelectionView(BaseView):
|
||||
|
||||
title = _("Filesystem setup")
|
||||
footer = (_("Choose the installation target"))
|
||||
|
||||
def __init__(self, model, controller):
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
|
|
|
@ -35,6 +35,7 @@ from subiquitycore.view import BaseView
|
|||
|
||||
from subiquity.models.filesystem import (
|
||||
align_up,
|
||||
Disk,
|
||||
FilesystemModel,
|
||||
HUMAN_UNITS,
|
||||
dehumanize_size,
|
||||
|
@ -185,11 +186,13 @@ class PartitionView(PartitionFormatView):
|
|||
self.controller = controller
|
||||
self.disk = disk
|
||||
self.partition = partition
|
||||
self.title = _("Partition, format, and mount {}").format(disk.label)
|
||||
|
||||
max_size = disk.free
|
||||
initial = {}
|
||||
if partition is None:
|
||||
label = _("Create")
|
||||
self.footer = _("Enter partition details, format and mount.")
|
||||
else:
|
||||
max_size += partition.size
|
||||
initial['size'] = humanize_size(partition.size)
|
||||
|
@ -197,6 +200,7 @@ class PartitionView(PartitionFormatView):
|
|||
label = None
|
||||
initial['mount'] = None
|
||||
else:
|
||||
self.footer = _("Edit partition details, format and mount.")
|
||||
label = _("Save")
|
||||
super().__init__(max_size, partition, initial, lambda : self.controller.partition_disk(disk), focus_buttons=label is None)
|
||||
if label is not None:
|
||||
|
@ -247,10 +251,18 @@ class PartitionView(PartitionFormatView):
|
|||
|
||||
|
||||
class FormatEntireView(PartitionFormatView):
|
||||
|
||||
def __init__(self, model, controller, volume, back):
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
self.volume = volume
|
||||
if isinstance(volume, Disk):
|
||||
self.title = _("Format and/or mount {}").format(disk.label)
|
||||
self.footer = _("Format or mount whole disk.")
|
||||
else:
|
||||
self.title = _("Partition, format, and mount {}").format(volume.device.label)
|
||||
self.footer = _("Edit partition details, format and mount.")
|
||||
|
||||
super().__init__(None, volume, {}, back)
|
||||
|
||||
def done(self, form):
|
||||
|
|
|
@ -29,6 +29,9 @@ from subiquitycore.ui.buttons import (
|
|||
ok_btn,
|
||||
other_btn,
|
||||
)
|
||||
from subiquitycore.ui.container import (
|
||||
ListBox,
|
||||
)
|
||||
from subiquitycore.ui.interactive import (
|
||||
PasswordEditor,
|
||||
StringEditor,
|
||||
|
@ -257,6 +260,9 @@ class FetchingSSHKeysFailed(Stretchy):
|
|||
self.parent.remove_overlay()
|
||||
|
||||
class IdentityView(BaseView):
|
||||
title = _("Profile setup")
|
||||
excerpt = _("Enter the username and password (or ssh identity) you will use to log in to the system.")
|
||||
|
||||
def __init__(self, model, controller, opts):
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
|
@ -270,12 +276,14 @@ class IdentityView(BaseView):
|
|||
connect_signal(self.form.ssh_import_id.widget, 'select', self._select_ssh_import_id)
|
||||
self.form.import_username.enabled = False
|
||||
|
||||
|
||||
self.form_rows = ListBox(self.form.as_rows())
|
||||
super().__init__(
|
||||
screen(
|
||||
self.form.as_rows(),
|
||||
button_pile([self.form.done_btn]),
|
||||
self.form_rows,
|
||||
[self.form.done_btn],
|
||||
excerpt=_(self.excerpt),
|
||||
focus_buttons=False))
|
||||
self.form_rows = self._w[1]
|
||||
|
||||
def _check_password(self, sender, new_text):
|
||||
password = self.form.password.value
|
||||
|
|
|
@ -21,11 +21,13 @@ Provides high level options for Ubuntu install
|
|||
import binascii
|
||||
import logging
|
||||
import re
|
||||
from urwid import connect_signal, Text
|
||||
|
||||
import lsb_release
|
||||
|
||||
from urwid import connect_signal
|
||||
|
||||
from subiquitycore.ui.buttons import back_btn, forward_btn
|
||||
from subiquitycore.ui.utils import Padding, button_pile
|
||||
from subiquitycore.ui.container import ListBox, Pile
|
||||
from subiquitycore.ui.utils import screen
|
||||
from subiquitycore.view import BaseView
|
||||
from subiquitycore.ui.interactive import (
|
||||
PasswordEditor,
|
||||
|
@ -43,19 +45,24 @@ log = logging.getLogger('subiquity.installpath')
|
|||
|
||||
|
||||
class InstallpathView(BaseView):
|
||||
title = "Ubuntu {}"
|
||||
|
||||
excerpt = _("Welcome to Ubuntu! The world's favourite platform "
|
||||
"for clouds, clusters, and amazing internet things. "
|
||||
"This is the installer for Ubuntu on servers and "
|
||||
"internet devices.")
|
||||
footer = _("Use UP, DOWN arrow keys, and ENTER, to "
|
||||
"navigate options")
|
||||
|
||||
def __init__(self, model, controller):
|
||||
self.title = self.title.format(lsb_release.get_distro_information()['RELEASE'])
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
self.items = []
|
||||
back = back_btn(_("Back"), on_press=self.cancel)
|
||||
self.body = [
|
||||
('pack', Text("")),
|
||||
Padding.center_79(self._build_choices()),
|
||||
('pack', Text("")),
|
||||
('pack', button_pile([back])),
|
||||
('pack', Text("")),
|
||||
]
|
||||
super().__init__(Pile(self.body))
|
||||
super().__init__(screen(
|
||||
self._build_choices(), [back],
|
||||
focus_buttons=False, excerpt=_(self.excerpt)))
|
||||
|
||||
def _build_choices(self):
|
||||
choices = []
|
||||
|
@ -64,7 +71,7 @@ class InstallpathView(BaseView):
|
|||
choices.append(
|
||||
forward_btn(
|
||||
label=label, on_press=self.confirm, user_arg=path))
|
||||
return ListBox(choices)
|
||||
return choices
|
||||
|
||||
def confirm(self, sender, path):
|
||||
self.controller.choose_path(path)
|
||||
|
@ -149,11 +156,12 @@ class RackForm(Form):
|
|||
|
||||
class MAASView(BaseView):
|
||||
|
||||
def __init__(self, model, controller):
|
||||
def __init__(self, model, controller, title, excerpt):
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
self.signal = controller.signal
|
||||
self.items = []
|
||||
self.title = title
|
||||
|
||||
if self.model.path == "maas_region":
|
||||
self.form = RegionForm()
|
||||
|
@ -165,7 +173,7 @@ class MAASView(BaseView):
|
|||
connect_signal(self.form, 'submit', self.done)
|
||||
connect_signal(self.form, 'cancel', self.cancel)
|
||||
|
||||
super().__init__(self.form.as_screen(focus_buttons=False))
|
||||
super().__init__(self.form.as_screen(focus_buttons=False, excerpt=excerpt))
|
||||
|
||||
def done(self, result):
|
||||
log.debug("User input: {}".format(result.as_data()))
|
||||
|
|
|
@ -356,6 +356,9 @@ class KeyboardForm(Form):
|
|||
|
||||
class KeyboardView(BaseView):
|
||||
|
||||
title = _("Keyboard configuration")
|
||||
footer = _("Use UP, DOWN and ENTER keys to select your keyboard.")
|
||||
|
||||
def __init__(self, model, controller, opts):
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
|
@ -378,6 +381,11 @@ class KeyboardView(BaseView):
|
|||
# Don't crash on pre-existing invalid config.
|
||||
pass
|
||||
|
||||
if self.opts.run_on_serial:
|
||||
excerpt = _('Please select the layout of the keyboard directly attached to the system, if any.')
|
||||
else:
|
||||
excerpt = _('Please select your keyboard layout below, or select "Identify keyboard" to detect your layout automatically.')
|
||||
|
||||
lb_contents = self.form.as_rows()
|
||||
if not self.opts.run_on_serial:
|
||||
lb_contents.extend([
|
||||
|
@ -385,7 +393,7 @@ class KeyboardView(BaseView):
|
|||
button_pile([
|
||||
other_btn(label=_("Identify keyboard"), on_press=self.detect)]),
|
||||
])
|
||||
super().__init__(screen(lb_contents, self.form.buttons))
|
||||
super().__init__(screen(lb_contents, self.form.buttons, excerpt=excerpt))
|
||||
|
||||
def detect(self, sender):
|
||||
detector = Detector(self)
|
||||
|
|
|
@ -45,6 +45,9 @@ class ProxyForm(Form):
|
|||
|
||||
class ProxyView(BaseView):
|
||||
|
||||
title = _("Configure proxy")
|
||||
excerpt = _("If this system requires a proxy to connect to the internet, enter its details here.")
|
||||
|
||||
def __init__(self, model, controller):
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
|
@ -54,7 +57,7 @@ class ProxyView(BaseView):
|
|||
connect_signal(self.form, 'submit', self.done)
|
||||
connect_signal(self.form, 'cancel', self.cancel)
|
||||
|
||||
super().__init__(self.form.as_screen())
|
||||
super().__init__(self.form.as_screen(excerpt=_(self.excerpt)))
|
||||
|
||||
def done(self, result):
|
||||
log.debug("User input: {}".format(result.as_data()))
|
||||
|
|
|
@ -30,11 +30,15 @@ log = logging.getLogger("subiquity.views.welcome")
|
|||
|
||||
|
||||
class WelcomeView(BaseView):
|
||||
title = "Willkommen! Bienvenue! Welcome! Добро пожаловать! Welkom!"
|
||||
footer = _("Use UP, DOWN and ENTER keys to select your language.")
|
||||
|
||||
def __init__(self, model, controller):
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
super().__init__(Pile([
|
||||
('pack', Text("")),
|
||||
('pack', Padding.center_79(Text(_("Please choose your preferred language")))),
|
||||
('pack', Text("")),
|
||||
Padding.center_50(self._build_model_inputs()),
|
||||
('pack', Text("")),
|
||||
|
|
|
@ -16,3 +16,5 @@
|
|||
""" SubiquityCore """
|
||||
|
||||
__version__ = "0.0.5"
|
||||
|
||||
import subiquitycore.i18n
|
||||
|
|
|
@ -225,13 +225,6 @@ class NetworkController(BaseController, TaskWatcher):
|
|||
self.signal.emit_signal('prev-screen')
|
||||
|
||||
def default(self):
|
||||
title = _("Network connections")
|
||||
excerpt = _("Configure at least one interface this server can use to talk to "
|
||||
"other machines, and which preferably provides sufficient access for "
|
||||
"updates.")
|
||||
footer = _("Select an interface to configure it or select Done to continue")
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_footer(footer)
|
||||
self.ui.set_body(NetworkView(self.model, self))
|
||||
if self.answers.get('accept-default', False):
|
||||
self.network_finish(self.model.render())
|
||||
|
@ -315,35 +308,27 @@ class NetworkController(BaseController, TaskWatcher):
|
|||
self.signal.emit_signal('next-screen')
|
||||
|
||||
def set_default_v4_route(self):
|
||||
self.ui.set_header("Default route")
|
||||
#self.ui.set_header("Default route")
|
||||
self.ui.set_body(NetworkSetDefaultRouteView(self.model, socket.AF_INET, self))
|
||||
|
||||
def set_default_v6_route(self):
|
||||
self.ui.set_header("Default route")
|
||||
#self.ui.set_header("Default route")
|
||||
self.ui.set_body(NetworkSetDefaultRouteView(self.model, socket.AF_INET6, self))
|
||||
|
||||
def bond_interfaces(self):
|
||||
self.ui.set_header("Bond interfaces")
|
||||
#self.ui.set_header("Bond interfaces")
|
||||
self.ui.set_body(NetworkBondInterfacesView(self.model, self))
|
||||
|
||||
def network_configure_interface(self, iface):
|
||||
self.ui.set_header(_("Network interface {}").format(iface))
|
||||
self.ui.set_footer("")
|
||||
self.ui.set_body(NetworkConfigureInterfaceView(self.model, self, iface))
|
||||
|
||||
def network_configure_ipv4_interface(self, iface):
|
||||
self.ui.set_header(_("Network interface {} manual IPv4 configuration").format(iface))
|
||||
self.ui.set_footer("")
|
||||
self.ui.set_body(NetworkConfigureIPv4InterfaceView(self.model, self, iface))
|
||||
|
||||
def network_configure_wlan_interface(self, iface):
|
||||
self.ui.set_header(_("Network interface {} WIFI configuration").format(iface))
|
||||
self.ui.set_footer("")
|
||||
self.ui.set_body(NetworkConfigureWLANView(self.model, self, iface))
|
||||
|
||||
def network_configure_ipv6_interface(self, iface):
|
||||
self.ui.set_header(_("Network interface {} manual IPv6 configuration").format(iface))
|
||||
self.ui.set_footer("")
|
||||
self.ui.set_body(NetworkConfigureIPv6InterfaceView(self.model, self, iface))
|
||||
|
||||
def install_network_driver(self):
|
||||
|
|
|
@ -27,19 +27,14 @@ class Header(WidgetWrap):
|
|||
:returns: Header()
|
||||
"""
|
||||
|
||||
def __init__(self, title=None, excerpt=None):
|
||||
widgets = [Text("")]
|
||||
if title is not None:
|
||||
widgets.append(
|
||||
Padding.center_79(Text(title)))
|
||||
widgets.append(Text(""))
|
||||
widgets = [Color.frame_header(Pile(widgets))]
|
||||
if excerpt is not None:
|
||||
widgets.extend([
|
||||
Text(""),
|
||||
Padding.center_79(Text(excerpt)),
|
||||
])
|
||||
super().__init__(Pile(widgets))
|
||||
def __init__(self, title):
|
||||
super().__init__(
|
||||
Color.frame_header(
|
||||
Pile([
|
||||
Text(""),
|
||||
Padding.center_79(Text(title)),
|
||||
Text(""),
|
||||
])))
|
||||
|
||||
|
||||
class StepsProgressBar(ProgressBar):
|
||||
|
|
|
@ -28,7 +28,6 @@ from urwid import (
|
|||
WidgetWrap,
|
||||
)
|
||||
|
||||
from subiquitycore.i18n import *
|
||||
from subiquitycore.ui.buttons import cancel_btn, done_btn
|
||||
from subiquitycore.ui.container import Columns, Pile
|
||||
from subiquitycore.ui.interactive import (
|
||||
|
@ -362,8 +361,10 @@ class Form(object, metaclass=MetaForm):
|
|||
del rows[-1:]
|
||||
return rows
|
||||
|
||||
def as_screen(self, focus_buttons=True):
|
||||
return screen(self.as_rows(), self.buttons, focus_buttons=focus_buttons)
|
||||
def as_screen(self, focus_buttons=True, excerpt=None):
|
||||
return screen(
|
||||
self.as_rows(), self.buttons,
|
||||
focus_buttons=focus_buttons, excerpt=excerpt)
|
||||
|
||||
def validated(self):
|
||||
in_error = False
|
||||
|
|
|
@ -27,7 +27,7 @@ log = logging.getLogger('subiquitycore.ui.frame')
|
|||
class SubiquityUI(WidgetWrap):
|
||||
|
||||
def __init__(self):
|
||||
self.header = Header()
|
||||
self.header = Header("")
|
||||
self.body = Body()
|
||||
self.footer = Footer("", 0, 1)
|
||||
self.frame = Frame(self.body, header=self.header, footer=self.footer)
|
||||
|
@ -38,11 +38,13 @@ class SubiquityUI(WidgetWrap):
|
|||
def keypress(self, size, key):
|
||||
return super().keypress(size, key)
|
||||
|
||||
def set_header(self, title=None, excerpt=None):
|
||||
self.frame.header = Header(title, excerpt)
|
||||
def set_header(self, title=None):
|
||||
self.frame.header = Header(title)
|
||||
|
||||
def set_footer(self, message):
|
||||
self.frame.footer = Footer(message, self.progress_current, self.progress_completion)
|
||||
|
||||
def set_body(self, widget):
|
||||
self.set_header(_(widget.title))
|
||||
self.frame.body = widget
|
||||
self.set_footer(_(widget.footer))
|
||||
|
|
|
@ -208,6 +208,8 @@ def screen(rows, buttons, focus_buttons=True, excerpt=None):
|
|||
|
||||
The commonest screen layout in subiquity is:
|
||||
|
||||
[ 1 line padding (optional) ]
|
||||
excerpt (optional)
|
||||
[ 1 line padding ]
|
||||
Listbox()
|
||||
[ 1 line padding ]
|
||||
|
@ -218,21 +220,23 @@ def screen(rows, buttons, focus_buttons=True, excerpt=None):
|
|||
"""
|
||||
if isinstance(rows, list):
|
||||
rows = ListBox(rows)
|
||||
body = []
|
||||
if isinstance(buttons, list):
|
||||
buttons = button_pile(buttons)
|
||||
excerpt_rows = []
|
||||
if excerpt is not None:
|
||||
body = [
|
||||
excerpt_rows = [
|
||||
('pack', Text("")),
|
||||
('pack', Padding.center_79(Text(excerpt))),
|
||||
('pack', Text(excerpt)),
|
||||
]
|
||||
body.extend([
|
||||
body = [
|
||||
('pack', Text("")),
|
||||
Padding.center_79(rows),
|
||||
rows,
|
||||
('pack', Text("")),
|
||||
('pack', buttons),
|
||||
('pack', Text("")),
|
||||
])
|
||||
screen = Pile(body)
|
||||
]
|
||||
pile = Pile(excerpt_rows + body)
|
||||
if focus_buttons:
|
||||
screen.focus_position = 3 + 2*bool(excerpt)
|
||||
return screen
|
||||
pile.focus_position = len(excerpt_rows) + 3
|
||||
return Padding.center_79(pile)
|
||||
|
||||
|
|
|
@ -107,6 +107,12 @@ def _build_gateway_ip_info_for_version(dev, version):
|
|||
|
||||
|
||||
class NetworkView(BaseView):
|
||||
title = _("Network connections")
|
||||
excerpt = _("Configure at least one interface this server can use to talk to "
|
||||
"other machines, and which preferably provides sufficient access for "
|
||||
"updates.")
|
||||
footer = _("Select an interface to configure it or select Done to continue")
|
||||
|
||||
def __init__(self, model, controller):
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
|
@ -117,17 +123,19 @@ class NetworkView(BaseView):
|
|||
Padding.center_79(self.additional_options),
|
||||
Padding.line_break(""),
|
||||
])
|
||||
self.footer = Pile([
|
||||
self.bottom = Pile([
|
||||
Text(""),
|
||||
self._build_buttons(),
|
||||
Text(""),
|
||||
])
|
||||
self.error_showing = False
|
||||
self.frame = Pile([
|
||||
('pack', Text("")),
|
||||
('pack', Padding.center_79(Text(_(self.excerpt)))),
|
||||
('pack', Text("")),
|
||||
Padding.center_90(self.listbox),
|
||||
('pack', self.footer)])
|
||||
self.frame.focus_position = 2
|
||||
('pack', self.bottom)])
|
||||
self.frame.focus_position = 4
|
||||
super().__init__(self.frame)
|
||||
|
||||
def _build_buttons(self):
|
||||
|
@ -251,9 +259,9 @@ class NetworkView(BaseView):
|
|||
|
||||
def show_network_error(self, action, info=None):
|
||||
self.error_showing = True
|
||||
self.footer.contents[0:0] = [
|
||||
(Text(""), self.footer.options()),
|
||||
(Color.info_error(self.error), self.footer.options()),
|
||||
self.bottom.contents[0:0] = [
|
||||
(Text(""), self.bottom.options()),
|
||||
(Color.info_error(self.error), self.bottom.options()),
|
||||
]
|
||||
if action == 'stop-networkd':
|
||||
exc = info[0]
|
||||
|
@ -274,7 +282,7 @@ class NetworkView(BaseView):
|
|||
|
||||
def done(self, result):
|
||||
if self.error_showing:
|
||||
self.footer.contents[0:2] = []
|
||||
self.bottom.contents[0:2] = []
|
||||
self.controller.network_finish(self.model.render())
|
||||
|
||||
def cancel(self, button=None):
|
||||
|
|
|
@ -28,10 +28,12 @@ log = logging.getLogger('subiquitycore.network.network_configure_interface')
|
|||
choice_btn = _stylized_button("", "", "menu")
|
||||
|
||||
class NetworkConfigureInterfaceView(BaseView):
|
||||
|
||||
def __init__(self, model, controller, name):
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
self.dev = self.model.get_netdev_by_name(name)
|
||||
self.title = _("Network interface {}").format(name)
|
||||
self._build_widgets()
|
||||
super().__init__(Pile([
|
||||
('pack', Text("")),
|
||||
|
|
|
@ -114,6 +114,7 @@ class BaseNetworkConfigureManualView(BaseView):
|
|||
self.model = model
|
||||
self.controller = controller
|
||||
self.dev = self.model.get_netdev_by_name(name)
|
||||
self.title = _("Network interface {} manual IPv{} configuration").format(name, self.ip_version)
|
||||
self.is_gateway = False
|
||||
self.form = NetworkConfigForm(self.ip_version)
|
||||
connect_signal(self.form, 'submit', self.done)
|
||||
|
|
|
@ -54,6 +54,7 @@ class NetworkConfigureWLANView(BaseView):
|
|||
self.model = model
|
||||
self.controller = controller
|
||||
self.dev = self.model.get_netdev_by_name(name)
|
||||
self.title = _("Network interface {} WIFI configuration").format(name)
|
||||
|
||||
self.form = WLANForm()
|
||||
|
||||
|
|
|
@ -110,9 +110,16 @@ def run_command(cmd, *, input=None, stdout=subprocess.PIPE, stderr=subprocess.PI
|
|||
"""
|
||||
if input is None:
|
||||
kw['stdin'] = subprocess.DEVNULL
|
||||
else:
|
||||
input = input.encode(encoding)
|
||||
log.debug("run_command called: %s", cmd)
|
||||
try:
|
||||
cp = subprocess.run(cmd, input=input, stdout=stdout, stderr=stderr, encoding=encoding, env=_clean_env(env), **kw)
|
||||
cp = subprocess.run(cmd, input=input, stdout=stdout, stderr=stderr, env=_clean_env(env), **kw)
|
||||
if encoding:
|
||||
if isinstance(cp.stdout, bytes):
|
||||
cp.stdout = cp.stdout.decode(encoding)
|
||||
if isinstance(cp.stderr, bytes):
|
||||
cp.stderr = cp.stderr.decode(encoding)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log.debug("run_command %s", str(e))
|
||||
raise
|
||||
|
@ -127,7 +134,7 @@ def start_command(cmd, *, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stde
|
|||
We never ever want a subprocess to inherit our file descriptors!
|
||||
"""
|
||||
log.debug('start_command called: %s', cmd)
|
||||
return subprocess.Popen(cmd, stdin=stdin, stdout=stdout, stderr=stderr, encoding=encoding, env=_clean_env(env), **kw)
|
||||
return subprocess.Popen(cmd, stdin=stdin, stdout=stdout, stderr=stderr, env=_clean_env(env), **kw)
|
||||
|
||||
|
||||
# FIXME: replace with passlib and update package deps
|
||||
|
|
|
@ -25,6 +25,8 @@ from urwid import Columns, Overlay, Pile, Text, WidgetWrap
|
|||
|
||||
class BaseView(WidgetWrap):
|
||||
|
||||
footer = ""
|
||||
|
||||
def show_overlay(self, overlay_widget, **kw):
|
||||
args = dict(
|
||||
align='center',
|
||||
|
|
Loading…
Reference in New Issue