def build_widgets(self): self.message = Text("Please review available machines in MAAS", align="center") self.button_pile = Pile([]) self.main_pile = Pile([self.message, Divider(), self.button_pile]) return Filler(self.main_pile, valign="middle")
class DeployView(WidgetWrap): def __init__(self, display_controller, placement_controller, placement_view): self.display_controller = display_controller self.placement_controller = placement_controller self.placement_view = placement_view self.prev_status = None w = self.build_widgets() super().__init__(w) self.update() def selectable(self): return True def build_widgets(self): self.deploy_ok_msg = ("\u2713 All the required OpenStack services are " "placed on a machine, and you can now deploy.") self.deploy_button = AttrMap( Button("Deploy", on_press=self.do_deploy), 'button_primary', 'button_primary focus') self.deploy_grid = GridFlow([self.deploy_button], 10, 1, 0, 'center') self.unplaced_msg = "Some required services are still unassigned." self.main_pile = Pile([Divider()]) return self.main_pile def update(self): changed = self.prev_status != self.placement_controller.can_deploy() if self.placement_controller.can_deploy(): if changed: self.show_deploy_button() else: self.main_pile.contents[0] = (Divider(), self.main_pile.options()) if changed: self.display_controller.status_error_message(self.unplaced_msg) self.prev_status = self.placement_controller.can_deploy() def show_deploy_button(self): self.main_pile.contents[0] = (AttrMap(self.deploy_grid, 'deploy_highlight_start'), self.main_pile.options()) self.display_controller.status_info_message(self.deploy_ok_msg) def do_deploy(self, sender): self.placement_view.do_deploy_cb()
def build_widgets(self): widgets = [] if self.title: widgets = [Text(self.title), Divider(' ')] widgets += self.service_widgets self.service_pile = Pile(widgets) return self.service_pile
class Header(WidgetWrap): TITLE_TEXT = "Ubuntu OpenStack Installer - Dashboard" def __init__(self): self.text = Text(self.TITLE_TEXT) self.widget = Color.frame_header(self.text) self.pile = Pile([self.widget, Text("")]) self.set_show_add_units_hotkey(False) super().__init__(self.pile) def set_openstack_rel(self, release): self.text.set_text("{} ({})".format(self.TITLE_TEXT, release)) def set_show_add_units_hotkey(self, show): self.show_add_units = show self.update() def update(self): if self.show_add_units: add_unit_string = "(A)dd Services \N{BULLET}" else: add_unit_string = "" tw = Color.frame_subheader( Text(add_unit_string + " (H)elp \N{BULLET} " "(R)efresh \N{BULLET} (Q)uit", align="center") ) self.pile.contents[1] = (tw, self.pile.options())
def __init__(self, filename, title=None, timeout=10, interval=0.1):#{{{ self.filename = filename self.interval = interval self.timeout = timeout if title is None: title = "IMPRIMIENDO" msg = Text("Esperando la respuesta de la Impresora Fiscal ...", align='left') self.eta = Text("", align='left') self.dummy = Text("") self.dummy._selectable = True self.dummy.keypress = lambda s,k : None self.buttons = Columns([ AttrMap(Button("Seguir esperando", on_press=self.remove_buttons),'dialog.button', 'dialog.button.focus'), AttrMap(Button("Cancelar Impresion", on_press=lambda *w: self.quit()), 'dialog.button', 'dialog.button.focus'), ], dividechars=1) self.content = Pile([ msg, Divider(), self.eta, self.dummy, ]) self.__super.__init__( self.content, title=title, attr_style='dialog.waitfiscal', title_attr_style='dialog.waitfiscal.title', height=None, width=60, )
def build_widgets(self): self.action_button_cols = Columns([]) self.action_buttons = [] self.build_unselected_widgets() self.pile = Pile([self.unselected_columns]) return self.pile
def build_widgets(self, title_widgets): if title_widgets is None: title_widgets = [Text("Machines", align='center')] self.filter_edit_box = FilterBox(self.handle_filter_change) header_widgets = title_widgets + [Divider()] if self.show_filter_box: header_widgets += [self.filter_edit_box, Divider()] labels = ["ID", "Cores", "Memory (GiB)", "Storage (GiB)"] if self.show_assignments: labels += ["Assignments", ""] else: labels += [""] header_label_col = Columns([Text(m) for m in labels], dividechars=2) header_widgets.append(header_label_col) self.header_padding = len(header_widgets) self.add_new_button = AttrMap( PlainButton("Add New Machine", on_press=self.do_add_machine), 'button_secondary', 'button_secondary focus') self.add_new_cols = Columns([Text(s) for s in [' ', ' ', ' ', ' ', ' ']] + [self.add_new_button], dividechars=2) self.machine_pile = Pile(header_widgets + self.machine_widgets + [self.add_new_cols]) return self.machine_pile
def __init__(self, title=None, cls=None): if cls is None: cls = type('Dummy', (object,), {'__init__': lambda *a, **k: None, 'run': lambda *a: None}) self.cls = cls self._obj = None _edit_cancel = lambda *w: self.focus_button(1) self.codigo_box = MaeterCodeBox(max_length=12, align='right') connect_signal(self.codigo_box, 'focus-in', highlight_focus_in), connect_signal(self.codigo_box, 'edit-done', self.on_codigo_edit_done) connect_signal(self.codigo_box, 'edit-cancel', _edit_cancel) connect_signal(self.codigo_box, 'search-client', self.on_client_search) self.nombre = Text(u'') client_row = Columns([ ('fixed', 8, AttrMap(Text("Cliente", align='right'), 'dialog.selectdate.label')), ('fixed', 6, AttrMap(self.codigo_box, 'dialog.selectdate.input', 'dialog.selectdate.input.focus')), AttrMap(self.nombre, 'dialog.selectdate.label'), ], dividechars=1) self.content = Pile([ client_row, Divider(), ]) buttons = [("Continuar", self.run_list), ("Cancelar", self.quit)] self.__super.__init__(self.content, buttons, title=title or u'<Falta titulo>', height=None, width=60, attr_style='dialog.selectdate', title_attr_style='dialog.selectdate.title')
def build_widgets(self): self.button = MenuSelectButton("I AM A MACHINE", self.do_select) self.action_button_cols = Columns([]) self.action_buttons = [] self.pile = Pile([self.button]) return self.pile
def build_widgets(self): self.button = MenuSelectButton("I AM A SERVICE", self.do_select) self.action_button_cols = Columns([], dividechars=1) self.action_buttons = [] self.pile = Pile([self.button]) return self.pile
def __init__(self, walker, **kwargs): if not isinstance(walker, TreeListWalker): walker = TreeListWalker(walker) self._walker = walker self._lines = [] self.loadlines() logging.debug('lines:\n\n%s' % str(self._lines)) self._pile = Pile(self._lines) self.__super.__init__(self._pile)
def build_widgets(self): self.description_w = Text("Description Loading…") self.readme_w = Text("README Loading…") self.scale_edit = IntegerEditor(default=self.service.num_units) connect_signal(self.scale_edit._edit, 'change', self.handle_scale_changed) self.skip_rest_button = PlainButton( "Deploy all {} Remaining Applications with Bundle Defaults".format( self.n_remaining), self.do_skip_rest ) col = Columns( [ (6, Text('Units:', align='right')), (15, Color.string_input(self.scale_edit, focus_map='string_input focus')) ], dividechars=1 ) if self.n_remaining == 0: buttons = [Padding.right_50(Color.button_primary( PlainButton("Deploy and Continue", self.do_deploy), focus_map='button_primary focus'))] else: buttons = [ Padding.right_50(Color.button_primary( PlainButton( "Deploy and Configure Next Application", self.do_deploy), focus_map='button_primary focus')), Padding.right_50( Color.button_secondary( self.skip_rest_button, focus_map='button_secondary focus'))] ws = [Text("{} of {}: {}".format(self.idx+1, self.n_total, self.service.service_name.upper())), Padding.center(HR()), Padding.center(self.description_w, left=2), Padding.line_break(""), Padding.center(self.readme_w, left=2), Padding.center(HR())] if not self.service.subordinate: ws.append(Padding.left(col, left=1)) ws.append(Padding.line_break("")) ws += buttons self.pile = Pile(ws) return Padding.center_90(Filler(self.pile, valign="top"))
def build_widgets(self, title_widgets): if title_widgets is None: if len(self.constraints) > 0: cstr = " matching constraints" else: cstr = "" title_widgets = Text("Machines" + cstr, align="center") self.filter_edit_box = FilterBox(self.handle_filter_change) self.machine_pile = Pile([title_widgets, Divider(), self.filter_edit_box] + self.machine_widgets) return self.machine_pile
def build_widgets(self): self.deploy_ok_msg = ("\u2713 All the required OpenStack services are " "placed on a machine, and you can now deploy.") self.deploy_button = AttrMap( Button("Deploy", on_press=self.do_deploy), 'button_primary', 'button_primary focus') self.deploy_grid = GridFlow([self.deploy_button], 10, 1, 0, 'center') self.unplaced_msg = "Some required services are still unassigned." self.main_pile = Pile([Divider()]) return self.main_pile
def build_widgets(self): def has_services_p(m): pc = self.placement_controller n = sum([len(al) for at, al in pc.assignments_for_machine(m).items()]) return n > 0 clear_machine_func = self.placement_view.do_clear_machine show_chooser_func = self.placement_view.do_show_service_chooser self.open_maas_button = AttrMap(Button("Open in Browser", on_press=self.browse_maas), 'button_secondary', 'button_secondary focus') bc = self.placement_view.config.juju_env['bootstrap-config'] maasname = "'{}' <{}>".format(bc['name'], bc['maas-server']) maastitle = "Connected to MAAS {}".format(maasname) tw = Columns([Text(maastitle), Padding(self.open_maas_button, align='right', width=BUTTON_SIZE, right=2)]) self.machines_list = MachinesList(self.placement_controller, [(has_services_p, 'Clear All Services', clear_machine_func), (has_services_p, 'Remove Some Services', show_chooser_func)], show_hardware=True, title_widgets=tw) self.machines_list.update() self.machines_list_pile = Pile([self.machines_list, Divider()]) # placeholders replaced in update() with absolute indexes, so # if you change this list, check update(). pl = [Text(('body', "Machines {}".format(MetaScroll().get_text()[0])), align='center'), Divider(), Pile([]), # machines_list Divider()] self.main_pile = Pile(pl) return self.main_pile
def build_widgets(self): self.machines_list = MachinesList(self.placement_controller, self.display_controller, None, show_hardware=True, show_assignments=False, show_placeholders=False, show_only_ready=True, title_widgets=[]) self.machines_list.update() self.machines_list_pile = Pile([self.machines_list, Divider()]) return self.machines_list_pile
def build_widgets(self, title_widgets): if title_widgets is None: if len(self.constraints) > 0: cstr = " matching constraints" else: cstr = "" title_widgets = [Text("Machines" + cstr, align='center')] self.filter_edit_box = FilterBox(self.handle_filter_change) header_widgets = title_widgets + [Divider()] if self.show_filter_box: header_widgets.append(self.filter_edit_box) self.header_padding = len(header_widgets) self.machine_pile = Pile(header_widgets + self.machine_widgets) return self.machine_pile
def build_widgets(self): ws = [Text("Configure {}".format( self.application.service_name))] num_unit_ow = OptionWidget("Units", "int", "How many units to deploy.", self.application.orig_num_units, current_value=self.application.num_units, value_changed_callback=self.handle_scale) ws.append(num_unit_ow) ws += self.get_whitelisted_option_widgets() self.toggle_show_all_button_index = len(ws) + 1 self.toggle_show_all_button = PlainButton( "Show Advanced Configuration", self.do_toggle_show_all_config) ws += [HR(), Columns([('weight', 1, Text(" ")), (36, Color.button_secondary( self.toggle_show_all_button))])] self.pile = Pile(ws) return Padding.center_90(Filler(self.pile, valign="top"))
def __init__(self, **kwargs):#{{{ self.search_box = SearchBox() self.items_count = Text("", align='right') self.search_items = SimpleListWalker(self._null_list_item()) connect_signal(self.search_items, "modified", self._update_items_count) self.search_list = ListBox(self.search_items) connect_signal(self.search_box, "edit-done", self.on_edit_done) connect_signal(self.search_box, "edit-cancel", lambda w: self.quit()) connect_signal(self.search_box, "change", lambda sb, term: self.set_search_term(term)) self._constr = self.get_item_constructor() self.multiple_selection = kwargs.pop('multiple_selection', False) self._selected_items = OrderedSet([]) opts = { 'height': kwargs.get('height', None), 'width': kwargs.get('width', ('relative', 90)), 'title': kwargs.get('title', self.title), 'subtitle': kwargs.get('subtitle', self.subtitle), 'compact_header': kwargs.get('compact_header', True), } kwargs.update(opts) self.pile = Pile([ ('fixed', 15, AttrMap(self.search_list, 'dialog.search.item')), Columns([ AttrMap(self.search_box, 'dialog.search.input'), ('fixed', 1, AttrMap(Divider(), 'dialog.search.input')), ('fixed', 4, AttrMap(self.items_count, 'dialog.search.input')), ]), ], focus_item=0) self.__super.__init__(self.pile, **kwargs) self.attr_style = "dialog.search" self.title_attr_style = "dialog.search.title" self.subtitle_attr_style = "dialog.search.subtitle"
class Header(WidgetWrap): def __init__(self): self.title_widget = Color.frame_header(padding(Text(TITLE_TEXT))) self.pile = Pile([self.title_widget, Text("")]) self.set_show_add_units_hotkey(False) super().__init__(self.pile) def set_show_add_units_hotkey(self, show): self.show_add_units = show self.update() def update(self): if self.show_add_units: add_unit_string = '(A)dd Services \N{BULLET}' else: add_unit_string = '' tw = Color.frame_subheader(Text(add_unit_string + ' (H)elp \N{BULLET} ' '(R)efresh \N{BULLET} (Q)uit', align='center')) self.pile.contents[1] = (tw, self.pile.options())
def build_widgets(self, title_widgets): if title_widgets is None: if len(self.constraints) > 0: cstr = " matching constraints '{}'".format( constraints_from_dict(self.constraints)) else: cstr = "" title_widgets = [Text("Machines" + cstr, align='center')] self.filter_edit_box = FilterBox(self.handle_filter_change) header_widgets = title_widgets + [Divider()] if self.show_filter_box: header_widgets += [self.filter_edit_box, Divider()] labels = ["FQDN", "Cores", "Memory (GiB)", "Storage (GiB)", ""] header_label_col = Columns([Text(m) for m in labels]) header_widgets.append(header_label_col) self.header_padding = len(header_widgets) self.machine_pile = Pile(header_widgets + self.machine_widgets) return self.machine_pile
def _create_login_widget(self): self.username_entry = Edit(align='right') self.username_entry.keypress = self._username_keypress self.password_entry = Password(align='right') self.password_entry.keypress = self._password_keypress username_row = Columns([ ('fixed', 10, Text("Usuario:", align='right')), ('fixed', 10, self.username_entry), ]) password_row = Columns([ ('fixed', 10, Text("Clave:", align='right')), ('fixed', 10, self.password_entry), ]) self.pile = Pile([ username_row, Divider(), password_row, ], focus_item=0) self.login_widget = Filler(Columns([Divider(), self.pile, Divider()]))
def _build_widget(self): total_items = [Text( "Enter your {} credentials:".format(self.cloud.upper()))] total_items += [HR()] for k in self.input_items.keys(): display = k if k.startswith('_'): # Don't treat 'private' keys as input continue if k.startswith('@'): # Strip public, not storable attribute display = k[1:] col = Columns( [ ('weight', 0.5, Text(display, align='right')), Color.string_input(self.input_items[k], focus_map='string_input focus') ], dividechars=1 ) total_items.append(col) total_items.append(Padding.line_break("")) total_items.append(Text("")) self.pile = Pile(total_items) return Padding.center_60(Filler(self.pile, valign="top"))
def build_widgets(self): self.title = Text('') self.option_widgets = [] self.pile = Pile([Divider(), self.title] + self.option_widgets) return self.pile
def _add_item(self, item): if not self.pile: self.pile = Pile([item]) else: self.pile.contents.append((item, self.pile.options()))
class MachinesColumn(WidgetWrap): """Shows machines or a link to MAAS to add more""" def __init__(self, display_controller, placement_controller, placement_view): self.display_controller = display_controller self.placement_controller = placement_controller self.placement_view = placement_view w = self.build_widgets() super().__init__(w) self.update() def selectable(self): return True def build_widgets(self): def has_services_p(m): pc = self.placement_controller n = sum( [len(al) for at, al in pc.assignments_for_machine(m).items()]) return n > 0 clear_machine_func = self.placement_view.do_clear_machine show_chooser_func = self.placement_view.do_show_service_chooser self.open_maas_button = AttrMap( Button("Open in Browser", on_press=self.browse_maas), 'button_secondary', 'button_secondary focus') bc = self.placement_view.config.juju_env['bootstrap-config'] maasname = "'{}' <{}>".format(bc['name'], bc['maas-server']) maastitle = "Connected to MAAS {}".format(maasname) tw = Columns([ Text(maastitle), Padding(self.open_maas_button, align='right', width=BUTTON_SIZE, right=2) ]) self.machines_list = MachinesList( self.placement_controller, [(has_services_p, 'Clear All Services', clear_machine_func), (has_services_p, 'Remove Some Services', show_chooser_func)], show_hardware=True, title_widgets=tw) self.machines_list.update() self.machines_list_pile = Pile([self.machines_list, Divider()]) # placeholders replaced in update() with absolute indexes, so # if you change this list, check update(). pl = [ Text(('subheading', "Machines"), align='center'), Divider(), Pile([]), # machines_list Divider() ] self.main_pile = Pile(pl) return self.main_pile def update(self): self.machines_list.update() bc = self.placement_view.config.juju_env['bootstrap-config'] empty_maas_msg = ("There are no available machines.\n" "Open {} to add machines to " "'{}':".format(bc['maas-server'], bc['name'])) self.empty_maas_widgets = Pile([ Text([('error_icon', "\N{WARNING SIGN} "), empty_maas_msg], align='center'), Padding(self.open_maas_button, align='center', width=BUTTON_SIZE) ]) # 2 machines is the subordinate placeholder + juju default: if len(self.placement_controller.machines()) == 2: self.main_pile.contents[2] = (self.empty_maas_widgets, self.main_pile.options()) else: self.main_pile.contents[2] = (self.machines_list_pile, self.main_pile.options()) def browse_maas(self, sender): bc = self.placement_view.config.juju_env['bootstrap-config'] try: p = Popen(["sensible-browser", bc['maas-server']], stdout=PIPE, stderr=PIPE) outs, errs = p.communicate(timeout=5) except TimeoutExpired: # went five seconds without an error, so we assume it's # OK. Don't kill it, just let it go: return e = errs.decode('utf-8') msg = "Error opening '{}' in a browser:\n{}".format(bc['name'], e) w = InfoDialogWidget(msg, self.placement_view.remove_overlay) self.placement_view.show_overlay(w)
class MachinesColumn(WidgetWrap): """Shows machines or a link to MAAS to add more""" def __init__(self, display_controller, placement_controller, placement_view): self.display_controller = display_controller self.placement_controller = placement_controller self.placement_view = placement_view w = self.build_widgets() super().__init__(w) self.update() def selectable(self): return True def build_widgets(self): def has_services_p(m): pc = self.placement_controller n = sum([len(al) for at, al in pc.assignments_for_machine(m).items()]) return n > 0 clear_machine_func = self.placement_view.do_clear_machine show_chooser_func = self.placement_view.do_show_service_chooser self.open_maas_button = AttrMap(Button("Open in Browser", on_press=self.browse_maas), 'button_secondary', 'button_secondary focus') bc = self.placement_view.config.juju_env['bootstrap-config'] maasname = "'{}' <{}>".format(bc['name'], bc['maas-server']) maastitle = "Connected to MAAS {}".format(maasname) tw = Columns([Text(maastitle), Padding(self.open_maas_button, align='right', width=BUTTON_SIZE, right=2)]) self.machines_list = MachinesList(self.placement_controller, [(has_services_p, 'Clear All Services', clear_machine_func), (has_services_p, 'Remove Some Services', show_chooser_func)], show_hardware=True, title_widgets=tw) self.machines_list.update() self.machines_list_pile = Pile([self.machines_list, Divider()]) # placeholders replaced in update() with absolute indexes, so # if you change this list, check update(). pl = [Text(('body', "Machines {}".format(MetaScroll().get_text()[0])), align='center'), Divider(), Pile([]), # machines_list Divider()] self.main_pile = Pile(pl) return self.main_pile def update(self): self.machines_list.update() bc = self.placement_view.config.juju_env['bootstrap-config'] empty_maas_msg = ("There are no available machines.\n" "Open {} to add machines to " "'{}':".format(bc['maas-server'], bc['name'])) self.empty_maas_widgets = Pile([Text([('error_icon', "\N{WARNING SIGN} "), empty_maas_msg], align='center'), Padding(self.open_maas_button, align='center', width=BUTTON_SIZE)]) # 1 machine is the subordinate placeholder: if len(self.placement_controller.machines()) == 1: self.main_pile.contents[2] = (self.empty_maas_widgets, self.main_pile.options()) else: self.main_pile.contents[2] = (self.machines_list_pile, self.main_pile.options()) def browse_maas(self, sender): bc = self.placement_view.config.juju_env['bootstrap-config'] try: p = Popen(["sensible-browser", bc['maas-server']], stdout=PIPE, stderr=PIPE) outs, errs = p.communicate(timeout=5) except TimeoutExpired: # went five seconds without an error, so we assume it's # OK. Don't kill it, just let it go: return e = errs.decode('utf-8') msg = "Error opening '{}' in a browser:\n{}".format(bc['name'], e) w = InfoDialogWidget(msg, self.placement_view.remove_overlay) self.placement_view.show_overlay(w)
def exploreFieldSet(field_set, args, options={}): charset = getTerminalCharset() ui = urwid.curses_display.Screen() ui.register_palette(( ('focus', 'white', 'dark blue'), ('sep', 'white', 'dark red'), ('input', 'black', 'light gray'), )) msgs = [[], [], 0] hachoir_log.use_print = False def logger(level, prefix, text, ctxt): if ctxt is not None: c = [] if hasattr(ctxt, "_logger"): c[:0] = [ctxt._logger()] if issubclass(ctxt.__class__, Field): ctxt = ctxt["/"] name = logger.objects.get(ctxt) if name: c[:0] = [name] if c: text = "[%s] %s" % ('|'.join(c), text) if not isinstance(text, str): text = str(text, charset) msgs[0].append((level, prefix, text)) logger.objects = WeakKeyDictionary() hachoir_log.on_new_message = logger preload_fields = 1 + max(0, args.preload) log_count = [0, 0, 0] sep = Separator("log: %%u/%%u/%%u | %s " % "F1: help") sep.set_info(*tuple(log_count)) body = Tabbed(sep) help = ('help', ListBox([Text(getHelpMessage())])) logger.objects[field_set] = logger.objects[ field_set.stream] = name = 'root' body.append((name, TreeBox(charset, Node(field_set, None), preload_fields, args.path, options))) log = BoxAdapter(ListBox(msgs[1]), 0) log.selectable = lambda: False wrapped_sep = AttrWrap(sep, 'sep') footer = Pile([('flow', wrapped_sep), log]) # awful way to allow the user to hide the log widget log.render = lambda size, focus=False: BoxAdapter.render( log, size[:1], focus) footer.render = lambda arg, focus=False: Pile.render( footer, (arg[0], sep.rows(arg) + log.height), focus) top = Frame(body, None, footer) def input_enter(w): footer.widget_list[0] = w footer.set_focus(0) top.set_focus('footer') def input_leave(): footer.widget_list[0] = wrapped_sep footer.set_focus(0) top.set_focus('body') input = Input(input_enter, input_leave) def run(): msg = _resize = retry = 0 events = ("window resize", ) profile_display = args.profile_display while True: for e in events: try: if e == "window resize": size = ui.get_cols_rows() resize = log.height else: e = top.keypress(size, e) if e is None: pass elif e in ('f1', '?'): try: body.select(body.tabs.index(help)) except ValueError: body.append(help) resize = log.height elif e in ('esc', 'ctrl w'): body.close() if body.original_widget is None: return resize = log.height elif e == '+': if log.height: resize = log.height - 1 elif e == '-': resize = log.height + 1 elif e == 'q': return # except AssertionError: # hachoir_log.error(getBacktrace()) except NewTab_Stream as e: stream = e.field.getSubIStream() logger.objects[stream] = e = "%u/%s" % ( body.active, e.field.absolute_address) parser = guessParser(stream) if not parser: hachoir_log.error("No parser found for %s" % stream.source) else: logger.objects[parser] = e body.append((e, TreeBox(charset, Node(parser, None), preload_fields, None, options))) resize = log.height except NeedInput as e: input.do(*e.args) if profile_display: events = events[1:] break while True: if msgs[0]: for level, prefix, text in msgs[0]: log_count[level] += 1 txt = Text("[%u]%s %s" % (msg, prefix, text)) msg += 1 msgs[1].append(txt) _resize += txt.rows(size[:1]) if log.height < _resize and (resize is None or resize < _resize): resize = _resize log.set_focus(len(msgs[1]) - 1) sep.set_info(*tuple(log_count)) msgs[0] = [] if resize is not None: body.height = size[1] - sep.rows(size[:1]) - resize if body.height <= 0: resize += body.height - 1 body.height = 1 log.height = resize resize = None canvas = top.render(size, focus=True) if not msgs[0]: _resize = retry = 0 break assert not retry retry += 1 ui.draw_screen(size, canvas) msgs[2] = len(msgs[1]) if profile_display and events: continue while True: events = ui.get_input() if events: break try: ui.run_wrapper(run) except Exception: pending = [msg.get_text()[0] for msg in msgs[1][msgs[2]:]] + \ ["[*]%s %s" % (prefix, text) for level, prefix, text in msgs[0]] if pending: print("\nPending messages:\n" + '\n'.join(pending)) raise
class OptionsColumn(WidgetWrap): """UI to edit options of a service """ def __init__(self, display_controller, placement_controller, placement_view, metadata_controller): self.placement_controller = placement_controller self.metadata_controller = metadata_controller self.service = None self.filter_string = "" self.placement_view = placement_view w = self.build_widgets() super().__init__(w) self.update() def build_widgets(self): self.title = Text('') self.option_widgets = [] self.pile = Pile([Divider(), self.title] + self.option_widgets) return self.pile def refresh(self): self.set_service(self.service) def set_service(self, service): self.service = service self.metadata_controller.add_charm(service.csid.as_str_without_rev()) self.pile.contents = self.pile.contents[:2] self.option_widgets = [] def update(self): if self.service is None: return self.title.set_text(('body', "Edit Options for {}".format( self.service.service_name))) if len(self.option_widgets) == 0: if self.filter_string != "": self.title.set_text( ('body', "No options match '{}'".format(self.filter_string))) else: self.title.set_text(('body', "Loading Options...")) else: self.title.set_text(('body', "Edit Options: (Changes are " "saved immediately)")) mc = self.metadata_controller options = mc.get_options(self.service.csid.as_str_without_rev()) for opname, opdict in sorted(options.items()): if self.filter_string != "" and \ self.filter_string not in opname: self.remove_option_widget(opname) continue ow = self.find_option_widget(opname) if ow is None: ow = self.add_option_widget(opname, opdict) ow.update() # MMCC TODO set filterbox.set_info for w, _ in self.pile.contents[2:]: w.update() self.sort_option_widgets() def handle_filter_change(self, edit_button, userdata): self.filter_string = userdata self.update() def handle_edit(self, opname, value): self.placement_controller.set_option(self.service.service_name, opname, value) def find_option_widget(self, opname): return next((ow for ow in self.option_widgets if ow.name == opname), None) def add_option_widget(self, opname, opdict): cv = self.service.options.get(opname, None) ow = OptionWidget(opname, opdict['Type'], opdict['Description'], opdict['Default'], current_value=cv, value_changed_callback=self.handle_edit) self.option_widgets.append(ow) self.pile.contents.append((ow, self.pile.options())) return ow def remove_option_widget(self, opname): ow = self.find_option_widget(opname) if ow is None: return self.option_widgets.remove(ow) ow_idx = 0 for w, opts in self.pile.contents: if w == ow: break ow_idx += 1 c = self.pile.contents[:ow_idx] + \ self.pile.contents[ow_idx + 1:] self.pile.contents = c def focus_prev_or_top(self): # ? self.pile.focus_position = len(self.pile.contents) - 1 if len(self.pile.contents) <= 2: return pos = self.pile.focus_position if pos < 2: self.pile.focus_position = 2 def sort_option_widgets(self): def keyfunc(ow): return str(ow.name) self.option_widgets.sort(key=keyfunc) def wrappedkeyfunc(t): rw, options = t if isinstance(rw, OptionWidget): return keyfunc(rw) return 'A' self.pile.contents.sort(key=wrappedkeyfunc)
class JujuMachineWidget(WidgetWrap): """A widget displaying a machine and action buttons. juju_machine_id = juju id of the machine md = the machine dict application - the current application for which machines are being shown assign_cb - a function that takes a machine and assignmenttype to perform the button action unassign_cb - a function that takes a machine and removes assignments for the machine controller - a controller object that provides show_pin_chooser show_assignments - display info about which charms are assigned and what assignment type (LXC, KVM, etc) they have. show_pin - show button to pin juju machine to a maas machine """ def __init__(self, juju_machine_id, md, application, assign_cb, unassign_cb, controller, show_assignments=True, show_pin=False): self.juju_machine_id = juju_machine_id self.md = md self.application = application self.is_selected = False self.assign_cb = assign_cb self.unassign_cb = unassign_cb self.controller = controller self.show_assignments = show_assignments self.show_pin = show_pin self.all_assigned = False w = self.build_widgets() super().__init__(w) self.update() def selectable(self): return True def __repr__(self): return "jujumachinewidget #" + str(self.juju_machine_id) def build_widgets(self): self.action_button_cols = Columns([]) self.action_buttons = [] self.build_unselected_widgets() self.pile = Pile([self.unselected_columns]) return self.pile def build_unselected_widgets(self): cdict = juju.constraints_to_dict(self.md.get('constraints', '')) self.juju_machine_id_button = MenuSelectButton( '{:20s}'.format(self.juju_machine_id), self.show_pin_chooser) self.juju_machine_id_label = Text( "{:20s}".format(self.juju_machine_id)) self.cores_field = IntEdit('', cdict.get('cores', '')) connect_signal(self.cores_field, 'change', self.handle_cores_changed) self.mem_field = Edit('', cdict.get('mem', '')) connect_signal(self.mem_field, 'change', self.handle_mem_changed) self.disk_field = Edit('', cdict.get('root-disk', '')) connect_signal(self.disk_field, 'change', self.handle_disk_changed) if self.show_pin: machine_id_w = self.juju_machine_id_button else: machine_id_w = self.juju_machine_id_label cols = [machine_id_w, self.cores_field, self.mem_field, self.disk_field] cols = [AttrMap(w, 'string_input', 'string_input_focus') for w in cols] cols.append(Text("placeholder")) self.unselected_columns = Columns(cols, dividechars=2) self.update_assignments() return self.unselected_columns def update_assignments(self): assignments = [] mps = self.controller.get_all_assignments(self.juju_machine_id) if len(mps) > 0: if self.show_assignments: ad = defaultdict(list) for application, atype in mps: ad[atype].append(application) astr = " ".join(["{}{}".format( atype_to_label([atype])[0], ",".join([application.service_name for application in al])) for atype, al in ad.items()]) assignments.append(astr) else: if self.show_assignments: assignments.append("-") if any([application == self.application for application, _ in mps]): action = self.do_remove label = "Remove" else: action = self.do_select label = "Select" self.select_button = PlainButton(label, action) cols = [Text(s) for s in assignments] current_assignments = [a for a, _ in mps if a == self.application] if self.all_assigned and len(current_assignments) == 0: cols.append(Text("")) else: cols += [AttrMap(self.select_button, 'text', 'button_secondary focus')] opts = self.unselected_columns.options() self.unselected_columns.contents[4:] = [(w, opts) for w in cols] def update(self): self.update_action_buttons() if self.is_selected: self.update_selected() else: self.update_unselected() def update_selected(self): cn = self.application.service_name msg = Text(" Add {} to machine #{}:".format(cn, self.juju_machine_id)) self.pile.contents = [(msg, self.pile.options()), (self.action_button_cols, self.pile.options()), (Divider(), self.pile.options())] def update_unselected(self): if self.show_pin: pinned_machine = self.controller.get_pin(self.juju_machine_id) if pinned_machine: pin_label = " {} \N{PENCIL}".format(pinned_machine.hostname) else: pin_label = " \N{PENCIL}" self.juju_machine_id_button.set_label('{:20s}'.format( self.juju_machine_id + " " + pin_label)) else: self.juju_machine_id_label.set_text('{:20s}'.format( self.juju_machine_id)) self.pile.contents = [(self.unselected_columns, self.pile.options()), (Divider(), self.pile.options())] self.update_assignments() def update_action_buttons(self): all_actions = [(AssignmentType.BareMetal, 'Add as Bare Metal', self.select_baremetal), (AssignmentType.LXD, 'Add as LXD', self.select_lxd), (AssignmentType.KVM, 'Add as KVM', self.select_kvm)] sc = self.application if sc: allowed_set = set(sc.allowed_assignment_types) allowed_types = set([atype for atype, _, _ in all_actions]) allowed_types = allowed_types.intersection(allowed_set) else: allowed_types = set() # + 1 for the cancel button: if len(self.action_buttons) == len(allowed_types) + 1: return self.action_buttons = [AttrMap(PlainButton(label, on_press=func), 'button_secondary', 'button_secondary focus') for atype, label, func in all_actions if atype in allowed_types] self.action_buttons.append( AttrMap(PlainButton("Cancel", on_press=self.do_cancel), 'button_secondary', 'button_secondary focus')) opts = self.action_button_cols.options() self.action_button_cols.contents = [(b, opts) for b in self.action_buttons] def do_select(self, sender): self.is_selected = True self.update() self.pile.focus_position = 1 self.action_button_cols.focus_position = 0 def do_remove(self, sender): self.unassign_cb(self.juju_machine_id) def do_cancel(self, sender): self.is_selected = False self.update() self.pile.focus_position = 0 def _do_select_assignment(self, atype): self.assign_cb(self.juju_machine_id, atype) self.pile.focus_position = 0 self.is_selected = False self.update() def select_baremetal(self, sender): self._do_select_assignment(AssignmentType.BareMetal) def select_lxd(self, sender): self._do_select_assignment(AssignmentType.LXD) def select_kvm(self, sender): self._do_select_assignment(AssignmentType.KVM) def handle_cores_changed(self, sender, val): if val == '': self.md = self.controller.clear_constraint(self.juju_machine_id, 'cores') else: self.md = self.controller.set_constraint(self.juju_machine_id, 'cores', val) def _format_constraint(self, val): """Ensure that a constraint has a unit. bare numbers are treated as gigabytes""" try: return units.gb_to_human(float(val)) except ValueError: return val def handle_mem_changed(self, sender, val): if val == '': self.md = self.controller.clear_constraint(self.juju_machine_id, 'mem') else: self.md = self.controller.set_constraint( self.juju_machine_id, 'mem', self._format_constraint(val)) def handle_disk_changed(self, sender, val): if val == '': self.md = self.controller.clear_constraint(self.juju_machine_id, 'root-disk') else: self.md = self.controller.set_constraint( self.juju_machine_id, 'root-disk', self._format_constraint(val)) def show_pin_chooser(self, sender): self.controller.show_pin_chooser(self.juju_machine_id)
class SimpleServiceWidget(WidgetWrap): """A widget displaying a service as a button service - the service to display placement_controller - a PlacementController instance display_controller - a PlacerView instance callback - a function to be called when either of the buttons is pressed. The service will be passed to the function as userdata. show_placements - display the machine(s) currently assigned to host this service, both planned deployments (aka 'assignments', and already-deployed, called 'deployments'). """ def __init__(self, service, placement_controller, display_controller, show_placements=False): self.service = service self.placement_controller = placement_controller self.display_controller = display_controller self.show_placements = show_placements self.state = ServiceWidgetState.UNSELECTED w = self.build_widgets() super().__init__(w) self.update() def selectable(self): return True def build_widgets(self): self.button = MenuSelectButton("I AM A SERVICE", self.do_select) self.action_button_cols = Columns([], dividechars=1) self.action_buttons = [] self.pile = Pile([self.button]) return self.pile def get_markup(self): if self.service.subordinate: return [self.service.service_name + " (subordinate)\n " + self.service.charm_source], [] nr = self.service.required_num_units() pl = "s" if nr > 1 else "" title_markup = [self.service.service_name + "\n {} unit{}: ".format(nr, pl) + self.service.charm_source] info_markup = [] if not self.display_controller.has_maas: return title_markup, info_markup pd = self.placement_controller.get_assignments(self.service) nplaced = sum([len(pd[k]) for k in pd]) if nr-nplaced > 0: pl = "" if nr-nplaced > 1: pl = "s" info_str = (" {} unit{} will be auto-placed " "by Juju\n".format(nr-nplaced, pl)) info_markup.append(info_str) def string_for_placement_dict(d): if self.display_controller.has_maas: defstring = "Bare Metal (Default)" else: defstring = "LXD (Default)" s = [] for atype, ml in sorted(d.items()): if atype == AssignmentType.DEFAULT: aname = defstring else: aname = atype.name hostnames = [m.hostname for m in ml] s.append(" {}: {}".format(aname, ", ".join(hostnames))) if len(s) == 0: return [] return "\n".join(s) ad = self.placement_controller.get_assignments(self.service) info_markup += string_for_placement_dict(ad) return title_markup, info_markup def update_choosing(self): title_markup, _ = self.get_markup() msg = Padding(Text(title_markup), left=2, right=2, align='center') self.pile.contents = [(msg, self.pile.options()), (self.action_button_cols, self.pile.options()), (Divider(), self.pile.options())] def update_default(self): title_markup, info_markup = self.get_markup() self.button.set_label(title_markup + ["\n"] + info_markup) if self.state == ServiceWidgetState.SELECTED: b = AttrMap(self.button, 'deploy_highlight_start', 'button_secondary focus') else: b = AttrMap(self.button, 'text', 'button_secondary focus') self.pile.contents = [(b, self.pile.options()), (Divider(), self.pile.options())] def update(self): self.service = next((s for s in self.placement_controller.bundle.services if s.service_name == self.service.service_name), self.service) self.update_action_buttons() if self.state == ServiceWidgetState.CHOOSING: self.update_choosing() else: self.update_default() def keypress(self, size, key): if key == 'backspace': self.display_controller.remove_service(self.service) elif key == '+': if not self.service.subordinate: self.display_controller.scale_service(self.service, 1) elif key == '-': if not self.service.subordinate: self.display_controller.scale_service(self.service, -1) return super().keypress(size, key) def update_action_buttons(self): all_actions = [] if self.display_controller.has_maas: all_actions = [('Choose Placement', self.handle_placement_button_pressed)] all_actions += [('Edit Relations', self.handle_relation_button_pressed), ('Edit Options', self.handle_options_button_pressed)] self.action_buttons = [AttrMap(PlainButton(label, on_press=func), 'button_secondary', 'button_secondary focus') for label, func in all_actions] self.action_buttons.append(AttrMap( PlainButton("Cancel", on_press=self.do_cancel), 'button_secondary', 'button_secondary focus')) opts = self.action_button_cols.options() self.action_button_cols.contents = [(b, opts) for b in self.action_buttons] def do_select(self, sender): self.display_controller.clear_selections() if self.state == ServiceWidgetState.SELECTED: self.state = ServiceWidgetState.UNSELECTED self.display_controller.set_selected_service(None) else: self.display_controller.set_selected_service(self.service) self.state = ServiceWidgetState.CHOOSING self.pile.focus_position = 1 self.action_button_cols.focus_position = 0 self.update() def do_cancel(self, sender): self.state = ServiceWidgetState.UNSELECTED self.update() self.display_controller.show_default_view() self.pile.focus_position = 0 def handle_placement_button_pressed(self, sender): self.state = ServiceWidgetState.SELECTED self.update() self.display_controller.edit_placement() self.pile.focus_position = 0 def handle_relation_button_pressed(self, sender): self.state = ServiceWidgetState.SELECTED self.update() self.display_controller.edit_relations() self.pile.focus_position = 0 def handle_options_button_pressed(self, sender): self.state = ServiceWidgetState.SELECTED self.update() self.display_controller.edit_options() self.pile.focus_position = 0
def __init__(self, message, close_func): self.close_func = close_func button = Button("Close", self.do_close) box = LineBox(Pile([Text(message), button]), title="Info") super().__init__(box)
class MachinesList(WidgetWrap): """A list of machines with configurable action buttons for each machine. action - a function to call when the machine's button is pressed constraints - a dict of constraints to filter the machines list. only machines matching all the constraints will be shown. show_hardware - bool, whether or not to show the hardware details for each of the machines title_widgets - A Text Widget to be used in place of the default title. show_assignments - bool, whether or not to show the assignments for each of the machines. show_only_ready - bool, only show machines with a ready state. """ def __init__(self, controller, display_controller, constraints=None, show_hardware=False, title_widgets=None, show_assignments=True, show_placeholders=True, show_only_ready=False, show_filter_box=False): self.controller = controller self.display_controller = display_controller self.machine_widgets = [] if constraints is None: self.constraints = {} else: self.constraints = constraints self.show_hardware = show_hardware self.show_assignments = show_assignments self.show_placeholders = show_placeholders self.show_only_ready = show_only_ready self.show_filter_box = show_filter_box self.filter_string = "" w = self.build_widgets(title_widgets) self.update() super().__init__(w) def selectable(self): # overridden to ensure that we can arrow through the buttons # shouldn't be necessary according to documented behavior of # Pile & Columns, but discovered via trial & error. return True def build_widgets(self, title_widgets): if title_widgets is None: if len(self.constraints) > 0: cstr = " matching constraints" else: cstr = "" title_widgets = [Text("Machines" + cstr, align='center')] self.filter_edit_box = FilterBox(self.handle_filter_change) header_widgets = title_widgets + [Divider()] if self.show_filter_box: header_widgets.append(self.filter_edit_box) self.header_padding = len(header_widgets) self.machine_pile = Pile(header_widgets + self.machine_widgets) return self.machine_pile def handle_filter_change(self, edit_button, userdata): self.filter_string = userdata self.update() def find_machine_widget(self, m): return next((mw for mw in self.machine_widgets if mw.machine.instance_id == m.instance_id), None) def update(self): machines = self.controller.machines( include_placeholders=self.show_placeholders) if self.show_only_ready: machines = [m for m in machines if m.status == MaasMachineStatus.READY] for mw in self.machine_widgets: machine = next((m for m in machines if mw.machine.instance_id == m.instance_id), None) if machine is None: self.remove_machine(mw.machine) n_satisfying_machines = len(machines) def get_placement_filter_label(d): s = "" for atype, al in d.items(): s += " ".join(["{} {}".format(cc.service_name, cc.display_name) for cc in al]) return s for m in machines: if not satisfies(m, self.constraints)[0]: self.remove_machine(m) n_satisfying_machines -= 1 continue assignment_names = "" ad = self.controller.assignments_for_machine(m) assignment_names = get_placement_filter_label(ad) filter_label = "{} {}".format(m.filter_label(), assignment_names) if self.filter_string != "" and \ self.filter_string not in filter_label: self.remove_machine(m) continue mw = self.find_machine_widget(m) if mw is None: mw = self.add_machine_widget(m) mw.update() self.filter_edit_box.set_info(len(self.machine_widgets), n_satisfying_machines) self.sort_machine_widgets() def add_machine_widget(self, machine): mw = SimpleMachineWidget(machine, self.controller, self.display_controller, self.show_assignments) self.machine_widgets.append(mw) options = self.machine_pile.options() self.machine_pile.contents.append((mw, options)) # NOTE: see the +1: indexing in remove_machine if you re-add # this divider. it should then be +2. # self.machine_pile.contents.append((AttrMap(Padding(Divider('\u23bc'), # left=2, right=2), # 'label'), options)) return mw def remove_machine(self, machine): mw = self.find_machine_widget(machine) if mw is None: return self.machine_widgets.remove(mw) mw_idx = 0 for w, opts in self.machine_pile.contents: if w == mw: break mw_idx += 1 c = self.machine_pile.contents[:mw_idx] + \ self.machine_pile.contents[mw_idx + 1:] self.machine_pile.contents = c def sort_machine_widgets(self): def keyfunc(mw): m = mw.machine hwinfo = " ".join(map(str, [m.arch, m.cpu_cores, m.mem, m.storage])) if str(mw.machine.status) == 'ready': skey = 'A' else: skey = str(mw.machine.status) return skey + mw.machine.hostname + hwinfo self.machine_widgets.sort(key=keyfunc) def wrappedkeyfunc(t): mw, options = t if not isinstance(mw, SimpleMachineWidget): return 'A' return keyfunc(mw) self.machine_pile.contents.sort(key=wrappedkeyfunc) def focus_prev_or_top(self): self.update() try: if self.machine_pile.focus_position <= self.header_padding: self.machine_pile.focus_position = self.header_padding except IndexError: log.debug("index error in machines_list focus_top")
def __init__(self): self.text = Text(self.TITLE_TEXT) self.widget = Color.frame_header(self.text) self.pile = Pile([self.widget, Text("")]) self.set_show_add_units_hotkey(False) super().__init__(self.pile)
def add(self, key, heading): self.columns[key] = Pile([Color.column_header(Text(heading))])
class OptionsColumn(WidgetWrap): """UI to edit options of a service """ def __init__(self, display_controller, placement_controller, placement_view, metadata_controller): self.placement_controller = placement_controller self.metadata_controller = metadata_controller self.service = None self.filter_string = "" self.placement_view = placement_view w = self.build_widgets() super().__init__(w) self.update() def build_widgets(self): self.title = Text('') self.option_widgets = [] self.pile = Pile([Divider(), self.title] + self.option_widgets) return self.pile def refresh(self): self.set_service(self.service) def set_service(self, service): self.service = service self.metadata_controller.add_charm(service.csid.as_str_without_rev()) self.pile.contents = self.pile.contents[:2] self.option_widgets = [] def update(self): if self.service is None: return self.title.set_text( ('body', "Edit Options for {}".format(self.service.service_name))) if len(self.option_widgets) == 0: if self.filter_string != "": self.title.set_text( ('body', "No options match '{}'".format(self.filter_string))) else: self.title.set_text(('body', "Loading Options...")) else: self.title.set_text(('body', "Edit Options: (Changes are " "saved immediately)")) mc = self.metadata_controller options = mc.get_options(self.service.csid.as_str_without_rev()) for opname, opdict in sorted(options.items()): if self.filter_string != "" and \ self.filter_string not in opname: self.remove_option_widget(opname) continue ow = self.find_option_widget(opname) if ow is None: ow = self.add_option_widget(opname, opdict) ow.update() # MMCC TODO set filterbox.set_info for w, _ in self.pile.contents[2:]: w.update() self.sort_option_widgets() def handle_filter_change(self, edit_button, userdata): self.filter_string = userdata self.update() def handle_edit(self, opname, value): self.placement_controller.set_option(self.service.service_name, opname, value) def find_option_widget(self, opname): return next((ow for ow in self.option_widgets if ow.name == opname), None) def add_option_widget(self, opname, opdict): cv = self.service.options.get(opname, None) ow = OptionWidget(opname, opdict['Type'], opdict['Description'], opdict['Default'], current_value=cv, value_changed_callback=self.handle_edit) self.option_widgets.append(ow) self.pile.contents.append((ow, self.pile.options())) return ow def remove_option_widget(self, opname): ow = self.find_option_widget(opname) if ow is None: return self.option_widgets.remove(ow) ow_idx = 0 for w, opts in self.pile.contents: if w == ow: break ow_idx += 1 c = self.pile.contents[:ow_idx] + \ self.pile.contents[ow_idx + 1:] self.pile.contents = c def focus_prev_or_top(self): # ? self.pile.focus_position = len(self.pile.contents) - 1 if len(self.pile.contents) <= 2: return pos = self.pile.focus_position if pos < 2: self.pile.focus_position = 2 def sort_option_widgets(self): def keyfunc(ow): return str(ow.name) self.option_widgets.sort(key=keyfunc) def wrappedkeyfunc(t): rw, options = t if isinstance(rw, OptionWidget): return keyfunc(rw) return 'A' self.pile.contents.sort(key=wrappedkeyfunc)
def build_widgets(self): self.service_pile = Pile( [Text(self.title), Divider(' ')] + self.service_widgets) return self.service_pile
class CloudView(WidgetWrap): def __init__(self, app, public_clouds, custom_clouds, cb=None): self.app = app self.cb = cb self.public_clouds = public_clouds self.custom_clouds = custom_clouds self.config = self.app.config self.buttons_pile_selected = False self.pile = None self.pile_localhost_idx = None self.frame = Frame(body=Padding.center_80( Filler(self._build_widget(), valign='top')), footer=self._build_footer()) super().__init__(self.frame) def keypress(self, size, key): if key in ['tab', 'shift tab']: self._swap_focus() return super().keypress(size, key) def _swap_focus(self): if not self.buttons_pile_selected: self.buttons_pile_selected = True self.frame.focus_position = 'footer' self.buttons_pile.focus_position = 1 else: self.buttons_pile_selected = False self.frame.focus_position = 'body' def _build_buttons(self): cancel = menu_btn(on_press=self.cancel, label="\n QUIT\n") buttons = [ Padding.line_break(""), Color.menu_button(cancel, focus_map='button_primary focus'), ] self.buttons_pile = Pile(buttons) return self.buttons_pile def _build_footer(self): footer_pile = Pile([ Padding.line_break(""), Color.frame_footer( Columns([('fixed', 2, Text("")), ('fixed', 13, self._build_buttons())])) ]) return footer_pile def _get_localhost_widget_idx(self): """ Returns index in pile where localhost widget resides """ if self.pile_localhost_idx: return self.pile_localhost_idx else: for idx, item in enumerate(self.pile.contents): if hasattr(item[0], 'original_widget') and \ isinstance(item[0].original_widget, MenuSelectButton) and \ item[0].original_widget.get_label() == 'localhost': return idx def _enable_localhost_widget(self): """ Sets the proper widget for localhost availability """ idx = self._get_localhost_widget_idx() widget = Color.body(menu_btn(label=cloud_types.LOCALHOST, on_press=self.submit), focus_map='menu_button focus') self._update_pile_at_idx(idx, widget) del self.pile.contents[idx + 1] def _update_pile_at_idx(self, idx, item): """ In place updates a widget in the self.pile contents """ self.pile_localhost_idx = idx self.pile.contents[idx] = (item, self.pile.options()) def _add_item(self, item): if not self.pile: self.pile = Pile([item]) else: self.pile.contents.append((item, self.pile.options())) def _build_widget(self): if len(self.public_clouds) > 0: self._add_item(Text("Public Clouds")) self._add_item(HR()) for item in self.public_clouds: self._add_item( Color.body(menu_btn(label=item, on_press=self.submit), focus_map='menu_button focus')) self._add_item(Padding.line_break("")) if len(self.custom_clouds) > 0: self._add_item(Text("Your Clouds")) self._add_item(HR()) for item in self.custom_clouds: self._add_item( Color.body(menu_btn(label=item, on_press=self.submit), focus_map='menu_button focus')) self._add_item(Padding.line_break("")) new_clouds = juju.get_compatible_clouds( ['localhost', 'maas', 'vsphere']) if new_clouds: self._add_item(Text("Configure a New Cloud")) self._add_item(HR()) for item in sorted(new_clouds): if item == 'localhost': self._add_item( Color.info_context(menu_btn( label=cloud_types.LOCALHOST, on_press=None), focus_map='disabled_button')) self._add_item( Color.info_context( Padding.center_90( Text("LXD not found, please install with " "`sudo snap install lxd && lxd init` " "and wait for this message to disappear.") ))) else: self._add_item( Color.body(menu_btn(label=item, on_press=self.submit), focus_map='menu_button focus')) self.pile.focus_position = 2 return self.pile def submit(self, result): self.cb(result.label) def cancel(self, btn): EventLoop.exit(0)
class MachineWaitView(WidgetWrap): def __init__(self, display_controller, installer, config): self.display_controller = display_controller self.installer = installer self.config = config creds = self.config.getopt('maascreds') if os.getenv("FAKE_API_DATA"): self.maas_client = None self.maas_state = FakeMaasState() else: self.maas_client, self.maas_state = connect_to_maas(creds) self.spinner = Spinner(15, 4) w = self.build_widgets() super().__init__(w) self.update() def build_widgets(self): self.message = Text("Please review available machines in MAAS", align='center') self.button_pile = Pile([]) self.main_pile = Pile([self.message, Divider(), self.button_pile]) return Filler(self.main_pile, valign='middle') def keypress(self, size, key): key = {'tab': 'down', 'shift tab': 'up'}.get(key, key) return super().keypress(size, key) def scroll_down(self): pass def scroll_up(self): pass def selectable(self): return True def get_status(self): " returns (global_ok, [ok, condition])" self.maas_state.invalidate_nodes_cache() machines = self.maas_state.machines(state=MaasMachineStatus.READY) powerable_machines = [m for m in machines if m.power_type is not None] n_powerable = len(powerable_machines) conditions = [(n_powerable >= 1, "At least one machine enlisted with power " "control (currently {})".format(n_powerable))] global_ok = all([ok for ok, _ in conditions]) return global_ok, conditions def update(self): msg = ("Before continuing, ensure that at least one machine is " "enlisted into MAAS:") self.message = Text(self.spinner.next_frame() + ['\n', msg, '\n'], align='center') contents = [(self.message, self.main_pile.options())] global_ok, statuses = self.get_status() status_map = {True: ('success_icon', "\u2713 "), False: ('error_icon', "<!> ")} contents += [(Text([status_map[status], condition], align='center'), self.main_pile.options()) for status, condition in statuses] contents += [(Divider(), self.main_pile.options()), (self.button_pile, self.main_pile.options())] self.main_pile.contents = contents if not global_ok: b = AttrMap(SelectableIcon(" ( Can't Continue ) "), 'disabled_button', 'disabled_button_focus') else: b = AttrMap(Button("Continue", on_press=self.do_continue), 'button_primary', 'button_primary focus') cancel_b = AttrMap(Button("Cancel", on_press=self.do_cancel), 'button_secondary', 'button_secondary focus') self.button_pile.contents = [(Padding(cancel_b, width=24, align='center'), self.button_pile.options()), (Padding(b, width=24, align='center'), self.button_pile.options())] # ensure that the button is always focused: self.main_pile.focus_position = len(self.main_pile.contents) - 1 @utils.async def do_continue(self, *args, **kwargs): self.installer.do_install() def do_cancel(self, *args, **kwargs): raise SystemExit("Installation cancelled.")
def _build_model_inputs(self): sl = [ Columns([("weight", 0.2, Text("Your name:", align="right")), ("weight", 0.3, Color.string_input(self.realname, focus_map="string_input focus"))], dividechars=4), Columns( [("weight", 0.2, Text("Your server's name:", align="right")), ("weight", 0.3, Color.string_input(self.hostname, focus_map="string_input focus"))], dividechars=4), Columns([ ("weight", 0.2, Text("", align="right")), ("weight", 0.3, Color.info_minor( Text( "The name it uses when it talks to " "other computers", align="left"))), ], dividechars=4), Columns([("weight", 0.2, Text("Pick a username:"******"right")), ("weight", 0.3, Color.string_input(self.username, focus_map="string_input focus"))], dividechars=4), Columns( [("weight", 0.2, Text("Choose a password:"******"right")), ("weight", 0.3, Color.string_input(self.password, focus_map="string_input focus"))], dividechars=4), Columns([ ("weight", 0.2, Text("Confirm your password:"******"right")), ("weight", 0.3, Color.string_input(self.confirm_password, focus_map="string_input focus")) ], dividechars=4), Columns( [("weight", 0.2, Text("Import SSH identity:", align="right")), ("weight", 0.3, Color.string_input(self.ssh_import_id, focus_map="string_input focus"))], dividechars=4), Columns([ ("weight", 0.2, Text("", align="right")), ("weight", 0.3, Color.info_minor( Text( "Input your SSH user id from " "Ubuntu SSO (sso:email), " "Launchpad (lp:username) or " "Github (gh:username).", align="left"))), ], dividechars=4), ] return Pile(sl)
def _display_schedule(self, s, f, date, schedule=None): columns = list(self.__schedule_fields(s, f, date, schedule)) if columns: yield Pile([Text('Monitor'), Indent(Pile(columns))])
def _create_text(self): text = [] for line in self.BANNER: text.append(Text(line, align='center')) return Pile(text)
def build_widgets(self): self.title = Text('') self.pile = Pile([self.title]) return self.pile
class NetworkSetDefaultRouteView(BaseView): def __init__(self, model, family, signal): self.model = model self.family = family self.signal = signal self.default_gateway_w = None self.gateway_options = Pile(self._build_default_routes()) body = [ Padding.center_79(Text("Please set the default gateway:")), Padding.line_break(""), Padding.center_79(self.gateway_options), Padding.line_break(""), Padding.fixed_10(self._build_buttons()) ] super().__init__(ListBox(body)) def _build_default_routes(self): ''' iterate through interfaces collecting any uniq provider (aka, gateway) and associate the interface name with the gateway then generate a line per key in the gateway dict and display the keys. Upon selection of the gateway entry (ip) then we set model.set_default_gateway(ip) if manual is selected, then we update the second entry into a IPAddressEditor and accept the value, submitting it to the model. ''' providers = {} for iface in self.model.get_all_interfaces(): if self.family == netifaces.AF_INET: ip_providers = iface.ip4_providers elif self.family == netifaces.AF_INET6: ip_providers = iface.ip6_providers for provider in ip_providers: log.debug('ipv4 provider: {}'.format(provider)) gw = provider if gw in providers: providers[gw].append(iface.ifname) else: providers[gw] = [iface.ifname] log.debug('gateway providers: {}'.format(providers)) items = [] items.append( Padding.center_79( Color.menu_button(menu_btn(label="None", on_press=self.done), focus_map="menu_button focus"))) for (gw, ifaces) in providers.items(): if gw is None: continue items.append( Padding.center_79( Color.menu_button(menu_btn(label="{gw} ({ifaces})".format( gw=gw, ifaces=(",".join(ifaces))), on_press=self.done), focus_map="menu_button focus"))) items.append( Padding.center_79( Color.menu_button(menu_btn( label="Specify the default route manually", on_press=self.show_edit_default_route), focus_map="menu_button focus"))) return items def _build_buttons(self): cancel = cancel_btn(on_press=self.cancel) done = done_btn(on_press=self.done) buttons = [ Color.button(done, focus_map='button focus'), Color.button(cancel, focus_map='button focus') ] return Pile(buttons) def show_edit_default_route(self, btn): log.debug("Re-rendering specify default route") self.default_gateway_w = StringEditor( caption="Default gateway will be ") self.gateway_options.contents[-1] = (Padding.center_50( Color.string_input(self.default_gateway_w, focus_map="string_input focus")), self.gateway_options.options()) # self.signal.emit_signal('refresh') def done(self, result): log.debug("changing default gw: {}".format(result)) gw_func = None if self.family == netifaces.AF_INET: gw_func = self.model.set_default_v4_gateway elif self.family == netifaces.AF_INET6: gw_func = self.model.set_default_v6_gateway if self.default_gateway_w and self.default_gateway_w.value: try: gw_func(None, self.default_gateway_w.value) except ValueError: # FIXME: raise UX error message self.default_gateway_w.edit_text = "" else: gw_ip_from_label = result.label.split(" ")[0] log.debug("default gw entered: {}".format(gw_ip_from_label)) try: if gw_ip_from_label.startswith('None'): gw_func(None, None) else: gw_func(None, gw_ip_from_label) except ValueError: # FIXME: raise UX error message pass self.signal.prev_signal() def cancel(self, button): self.signal.prev_signal()
class CharmstoreColumn(WidgetWrap): def __init__(self, display_controller, placement_controller, placement_view, metadata_controller): self.placement_controller = placement_controller self.display_controller = display_controller self.placement_view = placement_view self.metadata_controller = metadata_controller self.state = CharmstoreColumnUIState.RELATED self.prev_state = None self.current_search_string = "" w = self.build_widgets() super().__init__(w) self._related_charms = [] self._bundle_results = [] self._charm_results = [] self._recommended_charms = [] self.loading = True self.update() def build_widgets(self): self.title = Text('') self.pile = Pile([self.title]) return self.pile def clear_search_results(self): self._bundle_results = [] self._charm_results = [] def handle_search_change(self, s): if s == "": self.state = CharmstoreColumnUIState.RELATED self.clear_search_results() else: self.state = CharmstoreColumnUIState.SEARCH_RESULTS self.current_search_string = s self.loading = True self.update() def remove_existing_charms(self, charms): existing_charms = [CharmStoreID(s.charm_source).as_str_without_rev() for s in self.placement_controller.bundle.services] return [c for c in charms if CharmStoreID(c['Id']).as_str_without_rev() not in existing_charms] def get_filtered_recommendations(self): opts = self.pile.options() if len(self._recommended_charms) == 0: mc = self.metadata_controller self._recommended_charms = mc.get_recommended_charms() return [(CharmWidget(d, self.do_add_charm, recommended=True), opts) for d in self.remove_existing_charms(self._recommended_charms)] def update(self): opts = self.pile.options() extra_widgets = [] recommended_widgets = [] if self.metadata_controller.loaded(): recommended_widgets = self.get_filtered_recommendations() if len(recommended_widgets) > 0: top_w = recommended_widgets[0][0] top_w.set_header("Recommended Charms") self.loading = False series = self.placement_controller.bundle.series bundle_widgets = [(BundleWidget(d, self.do_add_bundle), opts) for d in self._bundle_results if 'bundle-metadata' in d.get('Meta', {}) and 'Series' in d['Meta']['bundle-metadata'] and d['Meta']['bundle-metadata']['Series'] == series] if len(bundle_widgets) > 0: top_w = bundle_widgets[0][0] top_w.set_header("Bundles") filtered_charm_results = self.remove_existing_charms( self._charm_results) filtered_charm_results = filtered_charm_results[:10] charm_widgets = [(CharmWidget(d, self.do_add_charm), opts) for d in filtered_charm_results if 'charm-metadata' in d.get('Meta', {})] if len(charm_widgets) > 0: top_w = charm_widgets[0][0] top_w.set_header("Charms") if self.state == CharmstoreColumnUIState.RELATED: if self.loading: self.title.set_text("\nLoading Recommended " "and Popular Charms…") else: self.title.set_text("") extra_widgets = recommended_widgets else: if self.loading: msg = "\nSearching for '{}'…\n".format( self.current_search_string) self.title.set_text(msg) else: bn = len(self._bundle_results) cn = len(filtered_charm_results) if bn + cn == 0: advice = "" if len(self.current_search_string) < 3: advice = "Try a longer search string." msg = ("\nNo charms found matching '{}' " "{}".format(self.current_search_string, advice)) else: msg = ("\nShowing the top {} bundles and {} " "charms matching {}:" "".format(bn, cn, self.current_search_string)) self.title.set_text(msg) self.pile.contents[1:] = extra_widgets + bundle_widgets + charm_widgets def add_results(self, bundle_results, charm_results): self._bundle_results += bundle_results self._bundle_results = self._bundle_results[:5] self._charm_results += charm_results def focus_prev_or_top(self): if len(self.pile.contents) < 2: return if self.state == CharmstoreColumnUIState.RELATED and self.loading \ and len(self.pile.contents) > 2: self.pile.focus_position = 2 else: self.pile.focus_position = 1 def do_add_charm(self, charm_name, charm_dict): self.placement_view.do_add_charm(charm_name, charm_dict) self.update() def do_add_bundle(self, bundle_dict): self.placement_view.do_add_bundle(bundle_dict) def handle_error(self, e): msg = "Error searching for {}: {}".format(self.current_search_string, e) self.title.set_text(msg)
from urwid import MainLoop, SolidFill, Filler, Pile, Overlay, LineBox interior = Filler(Pile([])) window = LineBox(interior, title='Choose enviroment') background = SolidFill(' ') body = Overlay(window, background, 'center', 30, 'middle', 10) main_loop = MainLoop(body) main_loop.run()
def __init__(self): self.title_widget = AttrWrap(padding(Text(TITLE_TEXT)), "header_title") self.pile = Pile([self.title_widget, Text("")]) self.set_show_add_units_hotkey(False) super().__init__(self.pile)
def _display_date(self, s, f, date): columns = self.__fields(s, date) if columns: yield Pile([Text('Monitor'), Indent(Columns(columns))])
def build_info(self): items = [Text(self.msg)] return Pile(items)
class ServicesList(WidgetWrap): """A list of services (charm classes) with flexible display options. Note that not all combinations of display options make sense. YMMV. controller - a PlacementController actions - a list of tuples describing buttons. Passed to ServiceWidget. machine - a machine instance to query for constraint checking. If None, no constraint checking is done. If set, only services whose constraints are satisfied by 'machine' are shown. ignore_assigned - bool, whether or not to display services that have already been assigned to a machine (but not yet deployed) ignore_deployed - bool, whether or not to display services that have already been deployed deployed_only - bool, only show deployed services show_constraints - bool, whether or not to display the constraints for the various services show_type - string, one of 'all', 'required' or 'non-required', controls which charm states should be shown. default is 'all'. trace_updates - bool, enable verbose update logging """ def __init__(self, controller, actions, subordinate_actions, machine=None, ignore_assigned=False, ignore_deployed=False, assigned_only=False, deployed_only=False, show_type='all', show_constraints=False, show_placements=False, title="Services", trace_updates=False): self.controller = controller self.actions = actions self.subordinate_actions = subordinate_actions self.service_widgets = [] self.machine = machine self.ignore_assigned = ignore_assigned self.ignore_deployed = ignore_deployed self.assigned_only = assigned_only self.deployed_only = deployed_only self.show_type = show_type self.show_constraints = show_constraints self.show_placements = show_placements self.title = title self.trace = trace_updates w = self.build_widgets() self.update() super().__init__(w) def selectable(self): # overridden to ensure that we can arrow through the buttons # shouldn't be necessary according to documented behavior of # Pile & Columns, but discovered via trial & error. return True def build_widgets(self): self.service_pile = Pile( [Text(self.title), Divider(' ')] + self.service_widgets) return self.service_pile def find_service_widget(self, cc): return next((sw for sw in self.service_widgets if sw.charm_class.charm_name == cc.charm_name), None) def update(self): def trace(cc, s): if self.trace: log.debug("{}: {} {}".format(self.title, cc, s)) for cc in self.controller.charm_classes(): if self.machine: if not satisfies(self.machine, cc.constraints)[0] \ or not (self.controller.is_assigned_to(cc, self.machine) or self.controller.is_deployed_to(cc, self.machine)): self.remove_service_widget(cc) trace(cc, "removed because machine doesn't match") continue if self.ignore_assigned and self.assigned_only: raise Exception("Can't both ignore and only show assigned.") if self.ignore_assigned: n = self.controller.assignment_machine_count_for_charm(cc) if n == cc.required_num_units() \ and not cc.allow_multi_units \ and self.controller.is_assigned(cc): self.remove_service_widget(cc) trace(cc, "removed because max units are " "assigned") continue elif self.assigned_only: if not self.controller.is_assigned(cc): self.remove_service_widget(cc) trace( cc, "removed because it is not assigned and " "assigned_only is True") continue if self.ignore_deployed and self.deployed_only: raise Exception("Can't both ignore and only show deployed.") if self.ignore_deployed: n = self.controller.deployment_machine_count_for_charm(cc) if n == cc.required_num_units() \ and self.controller.is_deployed(cc): self.remove_service_widget(cc) trace( cc, "removed because the required number of units" " has been deployed") continue elif self.deployed_only: if not self.controller.is_deployed(cc): self.remove_service_widget(cc) continue state, _, _ = self.controller.get_charm_state(cc) if self.show_type == 'required': if state != CharmState.REQUIRED: self.remove_service_widget(cc) continue elif self.show_type == 'non-required': if state == CharmState.REQUIRED: self.remove_service_widget(cc) trace( cc, "removed because show_type is 'non-required' and" "state is REQUIRED.") continue assigned_or_deployed = (self.controller.is_assigned(cc) or self.controller.is_deployed(cc)) if not cc.allow_multi_units and assigned_or_deployed: self.remove_service_widget(cc) trace( cc, "removed because it doesn't allow multiple units" " and is not assigned or deployed.") continue sw = self.find_service_widget(cc) if sw is None: sw = self.add_service_widget(cc) trace(cc, "added widget") sw.update() def add_service_widget(self, charm_class): if charm_class.subordinate: actions = self.subordinate_actions else: actions = self.actions sw = ServiceWidget(charm_class, self.controller, actions, self.show_constraints, show_placements=self.show_placements) self.service_widgets.append(sw) options = self.service_pile.options() self.service_pile.contents.append((sw, options)) self.service_pile.contents.append( (AttrMap(Padding(Divider('\u23bc'), left=2, right=2), 'label'), options)) return sw def remove_service_widget(self, charm_class): sw = self.find_service_widget(charm_class) if sw is None: return self.service_widgets.remove(sw) sw_idx = 0 for w, opts in self.service_pile.contents: if w == sw: break sw_idx += 1 c = self.service_pile.contents[:sw_idx] + \ self.service_pile.contents[sw_idx + 2:] self.service_pile.contents = c