Feature enhancements:
- Add network controller/view/model - Add route manager to switch views during installer - Add logging support Signed-off-by: Adam Stokes <adam.stokes@ubuntu.com>
This commit is contained in:
commit
73d5b9b3b2
|
@ -58,3 +58,4 @@ target/
|
|||
|
||||
# subiquity installer images
|
||||
installer/*.img
|
||||
logs/*
|
||||
|
|
|
@ -16,10 +16,11 @@
|
|||
|
||||
import argparse
|
||||
import sys
|
||||
import urwid
|
||||
import urwid.curses_display
|
||||
from subiquity.palette import STYLES, STYLES_MONO
|
||||
from subiquity.controllers.welcome import WelcomeController
|
||||
import logging
|
||||
from subiquity.log import setup_logger
|
||||
from subiquity import __version__ as VERSION
|
||||
from subiquity.controllers import BaseController as Subiquity
|
||||
from subiquity.ui.frame import SubiquityUI
|
||||
|
||||
|
||||
def parse_options(argv):
|
||||
|
@ -34,20 +35,15 @@ def parse_options(argv):
|
|||
|
||||
def main():
|
||||
opts = parse_options(sys.argv[1:])
|
||||
if opts.run_on_serial:
|
||||
screen = urwid.curses_display.Screen()
|
||||
screen.register_palette(STYLES_MONO)
|
||||
else:
|
||||
screen = urwid.raw_display.Screen()
|
||||
screen.set_mouse_tracking(False)
|
||||
screen.set_terminal_properties(256)
|
||||
screen.register_palette(STYLES)
|
||||
setup_logger()
|
||||
logger = logging.getLogger('subiquity')
|
||||
logger.info("Starting SUbiquity v{}".format(VERSION))
|
||||
logger.info("Arguments passed: {}".format(sys.argv))
|
||||
|
||||
def unhandled_input(key):
|
||||
if key in ('Q', 'q', 'esc'):
|
||||
raise urwid.ExitMainLoop()
|
||||
urwid.MainLoop(WelcomeController().show(), screen=screen,
|
||||
unhandled_input=unhandled_input).run()
|
||||
ui = SubiquityUI()
|
||||
|
||||
subiquity_interface = Subiquity(ui, opts)
|
||||
subiquity_interface.run()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -13,31 +13,93 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import logging
|
||||
import urwid
|
||||
import urwid.curses_display
|
||||
from subiquity.routes import Routes
|
||||
from subiquity.palette import STYLES, STYLES_MONO
|
||||
|
||||
|
||||
class BaseController(metaclass=ABCMeta):
|
||||
controller_name = None
|
||||
log = logging.getLogger('subiquity.controller')
|
||||
|
||||
@classmethod
|
||||
def name(cls):
|
||||
if cls.controller_name:
|
||||
return cls.controller_name
|
||||
return cls.__name__.lower()
|
||||
|
||||
@abstractmethod
|
||||
def show(self):
|
||||
""" Implements show action for the controller
|
||||
|
||||
Renders the View for controller.
|
||||
"""
|
||||
class BaseControllerError(Exception):
|
||||
""" Basecontroller exception """
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def finish(self):
|
||||
""" Implements finish action for controller.
|
||||
|
||||
This handles any callback data/procedures required
|
||||
to move to the next controller or end the install.
|
||||
"""
|
||||
pass
|
||||
class BaseController:
|
||||
def __init__(self, ui, opts):
|
||||
self.ui = ui
|
||||
self.opts = opts
|
||||
|
||||
def next_controller(self, *args, **kwds):
|
||||
controller = Routes.next()
|
||||
controller(self).show(*args, **kwds)
|
||||
|
||||
def prev_controller(self, *args, **kwds):
|
||||
controller = Routes.prev()
|
||||
controller(self).show(*args, **kwds)
|
||||
|
||||
def redraw_screen(self):
|
||||
if hasattr(self, 'loop'):
|
||||
try:
|
||||
self.loop.draw_screen()
|
||||
except AssertionError as e:
|
||||
log.critical(e)
|
||||
|
||||
def set_alarm_in(self, interval, cb):
|
||||
self.loop.set_alarm_in(interval, cb)
|
||||
return
|
||||
|
||||
def update(self, *args, **kwds):
|
||||
route = Routes.current_idx()
|
||||
if route == 0:
|
||||
self.begin()
|
||||
self.set_alarm_in(1, self.update)
|
||||
|
||||
def exit(self):
|
||||
raise urwid.ExitMainLoop()
|
||||
|
||||
def header_hotkeys(self, key):
|
||||
if key in ['q', 'Q']:
|
||||
self.exit()
|
||||
|
||||
def set_body(self, w):
|
||||
self.ui.set_body(w)
|
||||
self.redraw_screen()
|
||||
|
||||
def set_header(self, title, excerpt):
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.redraw_screen()
|
||||
|
||||
def set_footer(self, message):
|
||||
self.ui.set_footer(message)
|
||||
self.redraw_screen()
|
||||
|
||||
def run(self):
|
||||
if not hasattr(self, 'loop'):
|
||||
if self.opts.run_on_serial:
|
||||
screen = urwid.raw_display.Screen()
|
||||
screen.register_palette(STYLES_MONO)
|
||||
else:
|
||||
screen = urwid.raw_display.Screen()
|
||||
screen.set_mouse_tracking(False)
|
||||
screen.set_terminal_properties(256)
|
||||
screen.register_palette(STYLES)
|
||||
|
||||
self.loop = urwid.MainLoop(self.ui, screen=screen,
|
||||
unhandled_input=self.header_hotkeys)
|
||||
|
||||
try:
|
||||
self.begin()
|
||||
self.loop.run()
|
||||
except:
|
||||
log.exception("Exception in controller.run():")
|
||||
raise
|
||||
|
||||
def begin(self):
|
||||
""" Initializes the first controller for installation """
|
||||
Routes.reset()
|
||||
initial_controller = Routes.first()
|
||||
initial_controller(self).show()
|
||||
|
|
|
@ -13,18 +13,36 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from subiquity.controllers import BaseController
|
||||
from subiquity.controllers.policy import ControllerPolicy
|
||||
from subiquity.views.installpath import InstallpathView
|
||||
from subiquity.models.welcome import InstallpathModel
|
||||
from subiquity.models.installpath import InstallpathModel
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
log = logging.getLogger('subiquity.installpath')
|
||||
|
||||
|
||||
class InstallpathController(BaseController):
|
||||
class InstallpathController(ControllerPolicy):
|
||||
"""InstallpathController"""
|
||||
controller_name = "Ubuntu Install selection"
|
||||
|
||||
def show(self):
|
||||
title = "15.10"
|
||||
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 show(self, *args, **kwds):
|
||||
log.debug("Loading install path controller")
|
||||
self.ui.set_header(self.title, self.excerpt)
|
||||
self.ui.set_footer(self.footer)
|
||||
model = InstallpathModel()
|
||||
return InstallpathView(model, self.finish)
|
||||
self.ui.set_body(InstallpathView(model, self.finish))
|
||||
return
|
||||
|
||||
def finish(self, install_selection):
|
||||
raise SystemExit("Install selection: {}".format(install_selection))
|
||||
def finish(self, install_selection=None):
|
||||
# subprocess.check_call("/usr/local/bin/curtin_wrap.sh")
|
||||
return self.ui.next_controller()
|
||||
|
||||
__controller_class__ = InstallpathController
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# Copyright 2015 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from subiquity.controllers.policy import ControllerPolicy
|
||||
from subiquity.views.network import NetworkView
|
||||
from subiquity.models.network import NetworkModel
|
||||
import logging
|
||||
|
||||
log = logging.getLogger('subiquity.network')
|
||||
|
||||
|
||||
class NetworkController(ControllerPolicy):
|
||||
"""InstallpathController"""
|
||||
|
||||
title = "Network connections"
|
||||
excerpt = ("Configure at least the main interface this server will "
|
||||
"use to talk to other machines, and preferably provide "
|
||||
"sufficient access for updates.")
|
||||
|
||||
footer = ("Additional networking info here")
|
||||
|
||||
def show(self, *args, **kwds):
|
||||
self.ui.set_header(self.title, self.excerpt)
|
||||
self.ui.set_footer(self.footer)
|
||||
model = NetworkModel()
|
||||
self.ui.set_body(NetworkView(model, self.finish))
|
||||
return
|
||||
|
||||
def finish(self, interface=None):
|
||||
log.info("Network Interface choosen: {}".format(interface))
|
||||
return self.ui.exit()
|
||||
|
||||
__controller_class__ = NetworkController
|
|
@ -0,0 +1,41 @@
|
|||
# Copyright 2015 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" Controller policy """
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class ControllerPolicy(metaclass=ABCMeta):
|
||||
|
||||
def __init__(self, ui):
|
||||
self.ui = ui
|
||||
|
||||
@abstractmethod
|
||||
def show(self, *args, **kwds):
|
||||
""" Implements show action for the controller
|
||||
|
||||
Renders the View for controller.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def finish(self):
|
||||
""" Implements finish action for controller.
|
||||
|
||||
This handles any callback data/procedures required
|
||||
to move to the next controller or end the install.
|
||||
"""
|
||||
pass
|
|
@ -13,20 +13,34 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from subiquity.controllers import BaseController
|
||||
from subiquity.controllers.policy import ControllerPolicy
|
||||
from subiquity.views.welcome import WelcomeView
|
||||
from subiquity.models.welcome import WelcomeModel
|
||||
import subprocess
|
||||
import logging
|
||||
|
||||
|
||||
class WelcomeController(BaseController):
|
||||
log = logging.getLogger('subiquity.controllers.welcome')
|
||||
|
||||
|
||||
class WelcomeController(ControllerPolicy):
|
||||
"""WelcomeController"""
|
||||
controller_name = "Welcome to Ubuntu"
|
||||
title = "Wilkommen! Bienvenue! Welcome! Zdrastvutie! Welkom!"
|
||||
excerpt = "Please choose your preferred language"
|
||||
footer = ("Use UP, DOWN arrow keys, and ENTER, to "
|
||||
"select your language.")
|
||||
|
||||
def show(self):
|
||||
model = WelcomeModel()
|
||||
return WelcomeView(model, self.finish)
|
||||
def show(self, *args, **kwds):
|
||||
self.ui.set_header(self.title, self.excerpt)
|
||||
self.ui.set_footer(self.footer)
|
||||
self.ui.set_body(WelcomeView(WelcomeModel, self.finish))
|
||||
return
|
||||
|
||||
def finish(self, code, val):
|
||||
subprocess.check_call("/usr/local/bin/curtin_wrap.sh")
|
||||
raise SystemExit("Saw res: {}, val: {}".format(code, val))
|
||||
def finish(self, language=None):
|
||||
if language is None:
|
||||
raise SystemExit("No language selected, exiting as there are no "
|
||||
"more previous controllers to render.")
|
||||
WelcomeModel.selected_language = language
|
||||
log.debug("Welcome Model: {}".format(WelcomeModel()))
|
||||
return self.ui.next_controller()
|
||||
|
||||
__controller_class__ = WelcomeController
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# Copyright 2015 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import os
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
|
||||
|
||||
def setup_logger(name=__name__):
|
||||
LOGDIR = "logs"
|
||||
LOGFILE = os.path.join(LOGDIR, "debug.log")
|
||||
if not os.path.isdir(LOGDIR):
|
||||
os.makedirs(LOGDIR)
|
||||
log = TimedRotatingFileHandler(LOGFILE,
|
||||
when='D',
|
||||
interval=1,
|
||||
backupCount=7)
|
||||
log.setLevel('DEBUG')
|
||||
log.setFormatter(logging.Formatter(
|
||||
"%(asctime)s "
|
||||
"%(name)s:%(lineno)d %(message)s",
|
||||
datefmt='%m/%d %H:%M'))
|
||||
log_filter = logging.Filter(name='subiquity')
|
||||
log.addFilter(log_filter)
|
||||
|
||||
logger = logging.getLogger('')
|
||||
logger.setLevel('DEBUG')
|
||||
logger.addHandler(log)
|
||||
return logger
|
|
@ -0,0 +1,40 @@
|
|||
# Copyright 2015 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" Filesystem Model
|
||||
|
||||
Provides storage device selection and additional storage
|
||||
configuration.
|
||||
|
||||
"""
|
||||
|
||||
from subiquity import models
|
||||
|
||||
|
||||
class FilesystemModel(models.Model):
|
||||
""" Model representing storage options
|
||||
"""
|
||||
|
||||
available_disks = ['/dev/sda',
|
||||
'/dev/sdb',
|
||||
'/dev/sdc',
|
||||
'/dev/sdd',
|
||||
'/dev/sde']
|
||||
|
||||
additional_options = ['Connecti iSCSI network disk',
|
||||
'Connect Ceph network disk',
|
||||
'Create volume group (LVM2)',
|
||||
'Create software RAID (MD)',
|
||||
'Setup hierarchichal storage (bcache)']
|
|
@ -0,0 +1,35 @@
|
|||
# Copyright 2015 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" Network Model
|
||||
|
||||
Provides network device listings and extended network information
|
||||
|
||||
"""
|
||||
|
||||
from subiquity import models
|
||||
|
||||
|
||||
class NetworkModel(models.Model):
|
||||
""" Model representing network interfaces
|
||||
"""
|
||||
|
||||
interfaces = ['em1',
|
||||
'em2',
|
||||
'bond0']
|
||||
|
||||
additional_options = ['Set default route',
|
||||
'Bond interfaces',
|
||||
'Install network driver']
|
|
@ -28,3 +28,6 @@ class WelcomeModel(models.Model):
|
|||
|
||||
supported_languages = ['English', 'Belgian', 'German', 'Italian']
|
||||
selected_language = None
|
||||
|
||||
def __repr__(self):
|
||||
return "<Selected: {}>".format(self.selected_language)
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
# Copyright 2015 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from subiquity.controllers.welcome import WelcomeController
|
||||
from subiquity.controllers.installpath import InstallpathController
|
||||
from subiquity.controllers.network import NetworkController
|
||||
|
||||
|
||||
class RoutesError(Exception):
|
||||
""" Error in routes """
|
||||
pass
|
||||
|
||||
|
||||
class Routes:
|
||||
""" Defines application routes and maps to their controller
|
||||
|
||||
Routes are inserted top down from start to finish. Maintaining
|
||||
this order is required for routing to work.
|
||||
"""
|
||||
routes = [WelcomeController,
|
||||
InstallpathController,
|
||||
NetworkController]
|
||||
current_route_idx = 0
|
||||
|
||||
@classmethod
|
||||
def route(cls, idx):
|
||||
""" Include route listing in controllers """
|
||||
try:
|
||||
_route = cls.routes[idx]
|
||||
except IndexError:
|
||||
raise RoutesError("Failed to load Route at index: {}".format(idx))
|
||||
return _route
|
||||
|
||||
@classmethod
|
||||
def current_idx(cls):
|
||||
""" Returns current route index """
|
||||
return cls.current_route_idx
|
||||
|
||||
@classmethod
|
||||
def reset(cls):
|
||||
""" Resets current route """
|
||||
cls.current_route_idx = 0
|
||||
|
||||
@classmethod
|
||||
def first(cls):
|
||||
""" first controller/start of install """
|
||||
return cls.route(0)
|
||||
|
||||
@classmethod
|
||||
def last(cls):
|
||||
""" end of install, last controller """
|
||||
return cls.route(-1)
|
||||
|
||||
@classmethod
|
||||
def next(cls):
|
||||
cls.current_route_idx = cls.current_route_idx + 1
|
||||
return cls.route(cls.current_route_idx)
|
||||
|
||||
@classmethod
|
||||
def prev(cls):
|
||||
cls.current_route_idx = cls.current_route_idx - 1
|
||||
return cls.route(cls.current_route_idx)
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
from urwid import WidgetWrap, Pile, Text
|
||||
from subiquity.ui.utils import Padding, Color
|
||||
from subiquity.ui.lists import SimpleList
|
||||
|
||||
|
||||
class Header(WidgetWrap):
|
||||
|
@ -26,12 +27,9 @@ class Header(WidgetWrap):
|
|||
:returns: Header()
|
||||
"""
|
||||
|
||||
title = "Ubuntu Server Installer"
|
||||
excerpt = ""
|
||||
|
||||
def __init__(self):
|
||||
title_widget = Padding.push_10(Color.body(Text(self.title)))
|
||||
excerpt_widget = Padding.push_10(Color.body(Text(self.excerpt)))
|
||||
def __init__(self, title="Ubuntu Server Installer", excerpt=""):
|
||||
title_widget = Padding.center_79(Color.body(Text(title)))
|
||||
excerpt_widget = Padding.center_79(Color.body(Text(excerpt)))
|
||||
pile = Pile([Text(""),
|
||||
title_widget,
|
||||
Text(""),
|
||||
|
@ -47,20 +45,23 @@ class Footer(WidgetWrap):
|
|||
|
||||
"""
|
||||
|
||||
message = ""
|
||||
|
||||
def __init__(self):
|
||||
border = Text("")
|
||||
message_widget = Padding.push_10(Color.body(Text(self.message)))
|
||||
status = Pile([border, message_widget])
|
||||
def __init__(self, message=""):
|
||||
message_widget = Padding.center_79(Color.body(Text(message)))
|
||||
status = Pile([Padding.line_break(""), message_widget])
|
||||
super().__init__(status)
|
||||
|
||||
|
||||
class Body(WidgetWrap):
|
||||
""" Body widget
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.text = [
|
||||
Text("Welcome to the Ubuntu Server Installation", align="center")
|
||||
text = [
|
||||
Padding.line_break(""),
|
||||
Padding.center_79(
|
||||
Text("Welcome to the Ubuntu Server Installation",
|
||||
align="center")),
|
||||
Padding.line_break("")
|
||||
]
|
||||
super().__init__(Pile(self.text))
|
||||
w = (SimpleList(text))
|
||||
super().__init__(w)
|
||||
|
|
|
@ -17,11 +17,25 @@
|
|||
|
||||
from urwid import Frame, WidgetWrap
|
||||
from subiquity.ui.anchors import Header, Footer, Body
|
||||
import logging
|
||||
|
||||
|
||||
class BaseFrame(WidgetWrap):
|
||||
def __init__(self):
|
||||
_frame = Frame(Body(),
|
||||
Header(),
|
||||
Footer())
|
||||
super().__init__(_frame)
|
||||
log = logging.getLogger('subiquity.ui.frame')
|
||||
|
||||
|
||||
class SubiquityUI(WidgetWrap):
|
||||
def __init__(self, header=None, body=None, footer=None):
|
||||
self.header = header if header else Header()
|
||||
self.body = body if body else Body()
|
||||
self.footer = footer if footer else Footer()
|
||||
self.frame = Frame(self.body, header=self.header, footer=self.footer)
|
||||
super().__init__(self.frame)
|
||||
|
||||
def set_header(self, title, excerpt):
|
||||
self.frame.header = Header(title, excerpt)
|
||||
|
||||
def set_footer(self, message):
|
||||
self.frame.footer = Footer(message)
|
||||
|
||||
def set_body(self, widget):
|
||||
self.frame.body = widget
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from urwid import ListBox, SimpleListWalker, WidgetWrap, Button
|
||||
from urwid import ListBox, SimpleListWalker, WidgetWrap
|
||||
|
||||
|
||||
class SimpleList(WidgetWrap):
|
||||
|
@ -22,6 +22,6 @@ class SimpleList(WidgetWrap):
|
|||
super().__init__(self._build_widget())
|
||||
|
||||
def _build_widget(self):
|
||||
lw = SimpleListWalker([Button(x) for x in self.contents])
|
||||
lw = SimpleListWalker([x for x in self.contents])
|
||||
|
||||
return ListBox(lw)
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
""" UI utilities """
|
||||
|
||||
from urwid import Padding as _Padding
|
||||
from urwid import AttrMap
|
||||
from urwid import AttrMap, Text
|
||||
from functools import partialmethod
|
||||
from subiquity.palette import STYLES
|
||||
|
||||
|
@ -108,7 +108,7 @@ class Padding:
|
|||
"width of 10 columns"))
|
||||
|
||||
"""
|
||||
pass
|
||||
line_break = partialmethod(Text)
|
||||
|
||||
|
||||
def apply_style_map(cls):
|
||||
|
|
|
@ -13,32 +13,28 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter)
|
||||
from subiquity.ui.lists import SimpleList
|
||||
from subiquity.ui.anchors import Header, Footer, Body # NOQA
|
||||
from subiquity.ui.buttons import cancel_btn
|
||||
from subiquity.ui.buttons import confirm_btn, cancel_btn
|
||||
from subiquity.ui.utils import Padding, Color
|
||||
|
||||
|
||||
log = logging.getLogger('subiquity.installpathView')
|
||||
|
||||
|
||||
class InstallpathView(WidgetWrap):
|
||||
def __init__(self, model, cb=None):
|
||||
Header.title = "15.10"
|
||||
Header.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.message = ("Use UP, DOWN arrow keys, and ENTER, to "
|
||||
"navigate options..")
|
||||
def __init__(self, model, cb):
|
||||
log.debug("In install path view")
|
||||
self.model = model
|
||||
self.cb = cb
|
||||
self.items = []
|
||||
self.layout = [
|
||||
Header(),
|
||||
self.body = [
|
||||
Padding.center_79(self._build_model_inputs()),
|
||||
Padding.line_break(""),
|
||||
Padding.center_20(self._build_buttons()),
|
||||
Footer()
|
||||
]
|
||||
super().__init__(ListBox(self.layout))
|
||||
super().__init__(ListBox(self.body))
|
||||
|
||||
def _build_buttons(self):
|
||||
self.buttons = [
|
||||
|
@ -48,16 +44,17 @@ class InstallpathView(WidgetWrap):
|
|||
return Pile(self.buttons)
|
||||
|
||||
def _build_model_inputs(self):
|
||||
sl = SimpleList(self.model.install_paths)
|
||||
return BoxAdapter(sl,
|
||||
height=len(self.model.install_paths))
|
||||
sl = []
|
||||
for ipath in self.model.install_paths:
|
||||
sl.append(Color.button_primary(confirm_btn(label=ipath,
|
||||
on_press=self.confirm),
|
||||
focus_map='button_primary focus'))
|
||||
|
||||
return BoxAdapter(SimpleList(sl),
|
||||
height=len(sl))
|
||||
|
||||
def confirm(self, button):
|
||||
if self.cb is not None:
|
||||
return self.cb(True, 'Moving to next controller.')
|
||||
return self.cb(button.label)
|
||||
|
||||
def cancel(self, button):
|
||||
if self.cb is None:
|
||||
raise SystemExit('Cancelled.')
|
||||
else:
|
||||
return self.cb(False, 'Cancelled with callback.')
|
||||
return self.cb(None)
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
# Copyright 2015 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter, Text)
|
||||
from subiquity.ui.lists import SimpleList
|
||||
from subiquity.ui.buttons import confirm_btn, cancel_btn
|
||||
from subiquity.ui.utils import Padding, Color
|
||||
|
||||
|
||||
log = logging.getLogger('subiquity.networkView')
|
||||
|
||||
|
||||
class NetworkView(WidgetWrap):
|
||||
def __init__(self, model, cb):
|
||||
self.model = model
|
||||
self.cb = cb
|
||||
self.items = []
|
||||
self.body = [
|
||||
Padding.center_79(self._build_model_inputs()),
|
||||
Padding.line_break(""),
|
||||
Padding.center_79(self._build_additional_options()),
|
||||
Padding.line_break(""),
|
||||
Padding.center_20(self._build_buttons()),
|
||||
]
|
||||
super().__init__(ListBox(self.body))
|
||||
|
||||
def _build_buttons(self):
|
||||
buttons = [
|
||||
Color.button_secondary(cancel_btn(on_press=self.cancel),
|
||||
focus_map='button_secondary focus'),
|
||||
]
|
||||
return Pile(buttons)
|
||||
|
||||
def _build_model_inputs(self):
|
||||
sl = []
|
||||
for iface in self.model.interfaces:
|
||||
sl.append(Color.button_primary(confirm_btn(label=iface,
|
||||
on_press=self.confirm),
|
||||
focus_map='button_primary focus'))
|
||||
sl.append(Padding.push_10(Text("Adapter info")))
|
||||
|
||||
return BoxAdapter(SimpleList(sl),
|
||||
height=len(sl))
|
||||
|
||||
def _build_additional_options(self):
|
||||
opts = []
|
||||
for opt in self.model.additional_options:
|
||||
opts.append(
|
||||
Color.button_secondary(confirm_btn(label=opt,
|
||||
on_press=self.confirm),
|
||||
focus_map='button_secondary focus'))
|
||||
return Pile(opts)
|
||||
|
||||
def confirm(self, button):
|
||||
return self.cb(button.label)
|
||||
|
||||
def cancel(self, button):
|
||||
return self.cb(None)
|
|
@ -15,48 +15,41 @@
|
|||
|
||||
from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter)
|
||||
from subiquity.ui.lists import SimpleList
|
||||
from subiquity.ui.anchors import Header, Footer, Body # NOQA
|
||||
from subiquity.ui.buttons import confirm_btn, cancel_btn
|
||||
from subiquity.ui.utils import Padding, Color
|
||||
|
||||
|
||||
class WelcomeView(WidgetWrap):
|
||||
def __init__(self, model, cb=None):
|
||||
Header.title = "Wilkommen! Bienvenue! Welcome! Zdrastvutie! Welkom!"
|
||||
Header.excerpt = "Please choose your preferred language"
|
||||
Footer.message = ("Use UP, DOWN arrow keys, and ENTER, to "
|
||||
"select your language.")
|
||||
def __init__(self, model, cb):
|
||||
self.model = model
|
||||
self.cb = cb
|
||||
self.items = []
|
||||
self.layout = [
|
||||
Header(),
|
||||
self.body = [
|
||||
Padding.center_79(self._build_model_inputs()),
|
||||
Padding.line_break(""),
|
||||
Padding.center_20(self._build_buttons()),
|
||||
Footer()
|
||||
]
|
||||
super().__init__(ListBox(self.layout))
|
||||
super().__init__(ListBox(self.body))
|
||||
|
||||
def _build_buttons(self):
|
||||
self.buttons = [
|
||||
Color.button_primary(confirm_btn(on_press=self.confirm),
|
||||
focus_map='button_primary focus'),
|
||||
Color.button_secondary(cancel_btn(on_press=self.cancel),
|
||||
focus_map='button_secondary focus'),
|
||||
]
|
||||
return Pile(self.buttons)
|
||||
|
||||
def _build_model_inputs(self):
|
||||
sl = SimpleList(self.model.supported_languages)
|
||||
return BoxAdapter(sl,
|
||||
height=len(self.model.supported_languages))
|
||||
sl = []
|
||||
for lang in self.model.supported_languages:
|
||||
sl.append(Color.button_primary(
|
||||
confirm_btn(label=lang, on_press=self.confirm),
|
||||
focus_map="button_primary focus"))
|
||||
|
||||
return BoxAdapter(SimpleList(sl),
|
||||
height=len(sl))
|
||||
|
||||
def confirm(self, button):
|
||||
if self.cb is not None:
|
||||
return self.cb(True, 'Moving to next controller.')
|
||||
return self.cb(button.label)
|
||||
|
||||
def cancel(self, button):
|
||||
if self.cb is None:
|
||||
raise SystemExit('Cancelled.')
|
||||
else:
|
||||
return self.cb(False, 'Cancelled with callback.')
|
||||
return self.cb(None)
|
||||
|
|
Loading…
Reference in New Issue