def build_widgets(self): self.services_column = ServicesColumn(self.display_controller, self.placement_controller, self) self.machines_column = MachinesColumn(self.display_controller, self.placement_controller, self) self.relations_column = RelationsColumn(self.display_controller, self.placement_controller, self, self.metadata_controller) self.charmstore_column = CharmstoreColumn(self.display_controller, self.placement_controller, self, self.metadata_controller) self.options_column = OptionsColumn(self.display_controller, self.placement_controller, self, self.metadata_controller) self.machines_header = self.get_machines_header(self.machines_column) self.relations_header = self.get_relations_header() self.services_header = self.get_services_header() self.charmstore_header = self.get_charmstore_header( self.charmstore_column) self.options_header = self.get_options_header(self.options_column) cs = [self.services_header, self.charmstore_header] self.header_columns = Columns(cs, dividechars=2) self.columns = Columns([self.services_column, self.machines_column], dividechars=2) self.deploy_button = MenuSelectButton("\nCommit\n", on_press=self.do_deploy) self.deploy_button_label = Text("Some charms use default") self.placement_edit_body_pile = Pile([self.columns]) self.placement_edit_body = Filler(Padding( self.placement_edit_body_pile, align='center', width=('relative', 95)), valign='top') self.bundle_graph_text = Text("No graph to display yet.") self.bundle_graph_widget = Padding(self.bundle_graph_text, 'center', 'pack') b = AttrMap(self.deploy_button, 'frame_header', 'button_primary focus') self.footer_grid = GridFlow( [self.deploy_button_label, Padding(b, width=28, align='center')], 28, 1, 1, 'right') f = AttrMap(self.footer_grid, 'frame_footer', 'frame_footer') self.frame = Frame(header=Pile([self.header_columns, HR()]), body=self.placement_edit_body, footer=f) return self.frame
class PlacementView(WidgetWrap): """ Handles display of machines and services. displays nothing if self.controller is not set. set it to a PlacementController. :param do_deploy_cb: deploy callback from controller """ def __init__(self, display_controller, placement_controller, config, do_deploy_cb, initial_state=UIState.CHARMSTORE_VIEW, has_maas=False): self.display_controller = display_controller self.placement_controller = placement_controller self.config = config self.do_deploy_cb = do_deploy_cb self.state = initial_state self.has_maas = has_maas self.prev_state = None self.showing_overlay = False self.showing_graph_split = False self.show_scc_graph = False self.bundle = placement_controller.bundle self.metadata_controller = MetadataController(self.bundle, config) w = self.build_widgets() super().__init__(w) self.reset_selections(top=True) # calls self.update def scroll_down(self): pass def scroll_up(self): pass def focus_footer(self): self.frame.focus_position = 'footer' self.footer_grid.focus_position = 1 def handle_tab(self, backward): tabloop = ['headercol1', 'col1', 'headercol2', 'col2', 'footer'] if not self.has_maas: tabloop.remove('headercol1') def goto_header_col1(): self.frame.focus_position = 'header' self.header_columns.focus_position = 0 def goto_header_col2(): self.frame.focus_position = 'header' self.header_columns.focus_position = 1 def goto_col1(): self.frame.focus_position = 'body' self.columns.focus_position = 0 def goto_col2(): self.frame.focus_position = 'body' if self.state == UIState.PLACEMENT_EDITOR: self.focus_machines_column() elif self.state == UIState.RELATION_EDITOR: self.focus_relations_column() elif self.state == UIState.OPTIONS_EDITOR: self.focus_options_column() else: self.focus_charmstore_column() actions = {'headercol1': goto_header_col1, 'headercol2': goto_header_col2, 'col1': goto_col1, 'col2': goto_col2, 'footer': self.focus_footer} if self.frame.focus_position == 'header': cur = ['headercol1', 'headercol2'][self.header_columns.focus_position] elif self.frame.focus_position == 'footer': cur = 'footer' else: cur = ['col1', 'col2'][self.columns.focus_position] cur_idx = tabloop.index(cur) if backward: next_idx = cur_idx - 1 else: next_idx = (cur_idx + 1) % len(tabloop) actions[tabloop[next_idx]]() def keypress(self, size, key): if key in ['tab', 'shift tab']: self.handle_tab('shift' in key) return key unhandled_key = self._w.keypress(size, key) if unhandled_key is None: return None elif unhandled_key in ['g', 'G']: if unhandled_key == 'G': self.show_scc_graph = True else: self.show_scc_graph = False self.showing_graph_split = not self.showing_graph_split if self.showing_graph_split: opts = self.placement_edit_body_pile.options() self.placement_edit_body_pile.contents.insert( 0, (self.bundle_graph_widget, opts)) else: self.placement_edit_body_pile.contents.pop(0) self.update() else: return unhandled_key def get_services_header(self): b = PlainButton("Clear All Placements", on_press=self.do_clear_all) self.clear_all_button = AttrMap(b, 'button_secondary', 'button_secondary focus') self.services_buttons = [self.clear_all_button] self.services_button_grid = GridFlow(self.services_buttons, 36, 1, 0, 'center') ws = [Divider(), Text(("body", "Services"), align='center'), Divider()] if self.has_maas: ws.append(self.services_button_grid) return Pile(ws) def get_charmstore_header(self, charmstore_column): series = self.placement_controller.bundle.series self.charm_search_widget = CharmStoreSearchWidget(self.do_add_charm, charmstore_column, self.config, series) self.charm_search_header_pile = Pile([Divider(), Text(("body", "Add Charms"), align='center'), Divider(), self.charm_search_widget]) return self.charm_search_header_pile def get_machines_header(self, machines_column): b = PlainButton("Open in Browser", on_press=self.browse_maas) self.open_maas_button = AttrMap(b, 'button_secondary', 'button_secondary focus') self.maastitle = Text("Connected to MAAS") maastitle_widgets = Padding(Columns([self.maastitle, (22, self.open_maas_button)]), align='center', width='pack', left=2, right=2) f = machines_column.machines_list.handle_filter_change self.filter_edit_box = FilterBox(f) pl = [Divider(), Text(('body', "Ready Machines {}".format(MetaScroll().get_text()[0])), align='center'), Divider(), maastitle_widgets, Divider(), self.filter_edit_box] self.machines_header_pile = Pile(pl) return self.machines_header_pile def update_machines_header(self): maasinfo = self.placement_controller.maasinfo maasname = "'{}' <{}>".format(maasinfo['server_name'], maasinfo['server_hostname']) self.maastitle.set_text("Connected to MAAS {}".format(maasname)) def _simple_header_widgets(self, title): b = PlainButton("Back to Charm Store", on_press=self.show_default_view) self.back_to_mainview_button = AttrMap(b, 'button_secondary', 'button_secondary focus') button_grid = GridFlow([self.back_to_mainview_button], 36, 1, 0, 'center') return [Divider(), Text(('body', title), align='center'), Divider(), button_grid] def get_relations_header(self): return Pile(self._simple_header_widgets("Relation Editor")) def get_options_header(self, options_column): simple_widgets = self._simple_header_widgets("Options Editor") fb = FilterBox(options_column.handle_filter_change, info_text="Filter by option name") padded_fb = Padding(AttrMap(fb, 'filter', 'filter_focus'), left=2, right=2) return Pile(simple_widgets + [padded_fb]) def build_widgets(self): self.services_column = ServicesColumn(self.display_controller, self.placement_controller, self) self.machines_column = MachinesColumn(self.display_controller, self.placement_controller, self) self.relations_column = RelationsColumn(self.display_controller, self.placement_controller, self, self.metadata_controller) self.charmstore_column = CharmstoreColumn(self.display_controller, self.placement_controller, self, self.metadata_controller) self.options_column = OptionsColumn(self.display_controller, self.placement_controller, self, self.metadata_controller) self.machines_header = self.get_machines_header(self.machines_column) self.relations_header = self.get_relations_header() self.services_header = self.get_services_header() self.charmstore_header = self.get_charmstore_header( self.charmstore_column) self.options_header = self.get_options_header(self.options_column) cs = [self.services_header, self.charmstore_header] self.header_columns = Columns(cs, dividechars=2) self.columns = Columns([self.services_column, self.machines_column], dividechars=2) self.deploy_button = MenuSelectButton("\nCommit\n", on_press=self.do_deploy) self.deploy_button_label = Text("Some charms use default") self.placement_edit_body_pile = Pile([self.columns]) self.placement_edit_body = Filler(Padding( self.placement_edit_body_pile, align='center', width=('relative', 95)), valign='top') self.bundle_graph_text = Text("No graph to display yet.") self.bundle_graph_widget = Padding(self.bundle_graph_text, 'center', 'pack') b = AttrMap(self.deploy_button, 'frame_header', 'button_primary focus') self.footer_grid = GridFlow([self.deploy_button_label, Padding(b, width=28, align='center')], 28, 1, 1, 'right') f = AttrMap(self.footer_grid, 'frame_footer', 'frame_footer') self.frame = Frame(header=Pile([self.header_columns, HR()]), body=self.placement_edit_body, footer=f) return self.frame def update(self): if self.prev_state != self.state: h_opts = self.header_columns.options() c_opts = self.columns.options() if self.state == UIState.PLACEMENT_EDITOR: self.update_machines_header() self.header_columns.contents[-1] = (self.machines_header, h_opts) self.columns.contents[-1] = (self.machines_column, c_opts) elif self.state == UIState.RELATION_EDITOR: self.header_columns.contents[-1] = (self.relations_header, h_opts) self.columns.contents[-1] = (self.relations_column, h_opts) elif self.state == UIState.CHARMSTORE_VIEW: self.header_columns.contents[-1] = (self.charmstore_header, h_opts) self.columns.contents[-1] = (self.charmstore_column, h_opts) elif self.state == UIState.OPTIONS_EDITOR: self.header_columns.contents[-1] = (self.options_header, h_opts) self.columns.contents[-1] = (self.options_column, h_opts) self.prev_state = self.state self.services_column.update() if self.state == UIState.PLACEMENT_EDITOR: self.machines_column.update() elif self.state == UIState.RELATION_EDITOR: self.relations_column.update() elif self.state == UIState.OPTIONS_EDITOR: self.options_column.update() else: self.charmstore_column.update() unplaced = self.placement_controller.unassigned_undeployed_services() all = self.placement_controller.services() n_subs_in_unplaced = len([c for c in unplaced if c.subordinate]) n_subs_in_all = len([c for c in all if c.subordinate]) n_total = len(all) - n_subs_in_all remaining = len(unplaced) - n_subs_in_unplaced if remaining > 0: dmsg = "\nAuto-assigning {}/{} services".format(remaining, n_total) else: dmsg = "" self.deploy_button_label.set_text(dmsg) if self.showing_graph_split: bundle = self.placement_controller.bundle if self.show_scc_graph: gtext = scc_graph_for_bundle(bundle, self.metadata_controller) else: gtext = graph_for_bundle(bundle, self.metadata_controller) if gtext == "": gtext = "No graph to display yet." self.bundle_graph_text.set_text(gtext) def browse_maas(self, sender): bc = self.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.remove_overlay) self.show_overlay(w) def do_clear_all(self, sender): self.placement_controller.clear_all_assignments() def do_add_charm(self, charm_name, charm_dict): """Add new service and focus its widget. """ assert(self.state == UIState.CHARMSTORE_VIEW) def done_cb(f): csid = CharmStoreID(charm_dict['Id']) id_no_rev = csid.as_str_without_rev() info = self.metadata_controller.get_charm_info(id_no_rev, lambda _: None) is_subordinate = info["Meta"]["charm-metadata"].get( "Subordinate", False) service_name = self.placement_controller.add_new_service( charm_name, charm_dict, is_subordinate=is_subordinate) self.frame.focus_position = 'body' self.columns.focus_position = 0 self.update() self.services_column.select_service(service_name) # TODO MMCC: need a 'loading' indicator to start here self.metadata_controller.load([charm_dict['Id']], done_cb) def do_add_bundle(self, bundle_dict): assert(self.state == UIState.CHARMSTORE_VIEW) _, new_services, _ = self.placement_controller.merge_bundle( bundle_dict) self.frame.focus_position = 'body' self.columns.focus_position = 0 charms = list(set([s.charm_source for s in new_services])) self.metadata_controller.load(charms) self.update() ss = sorted(new_services, key=attrgetter('service_name')) first_service = ss[0].service_name self.services_column.select_service(first_service) def do_clear_machine(self, sender, machine): self.placement_controller.clear_assignments(machine) def clear_selections(self): self.services_column.clear_selections() self.machines_column.clear_selections() def reset_selections(self, top=False): self.clear_selections() self.state = UIState.CHARMSTORE_VIEW self.update() self.columns.focus_position = 0 if top: self.services_column.focus_top() else: self.services_column.focus_next() def focus_machines_column(self): self.columns.focus_position = 1 self.machines_column.focus_prev_or_top() def focus_relations_column(self): self.columns.focus_position = 1 self.relations_column.focus_prev_or_top() def focus_options_column(self): self.columns.focus_position = 1 self.options_column.focus_prev_or_top() def focus_charmstore_column(self): self.columns.focus_position = 1 self.charmstore_column.focus_prev_or_top() def edit_placement(self): self.state = UIState.PLACEMENT_EDITOR self.update() self.focus_machines_column() def show_default_view(self, *args): self.state = UIState.CHARMSTORE_VIEW self.update() def edit_relations(self, service): self.state = UIState.RELATION_EDITOR self.relations_column.set_service(service) self.update() self.focus_relations_column() def edit_options(self, service): self.state = UIState.OPTIONS_EDITOR self.options_column.set_service(service) self.update() self.focus_options_column() def do_deploy(self, sender): self.do_deploy_cb() def show_overlay(self, overlay_widget): if not self.showing_overlay: self.orig_w = self._w self._w = Overlay(top_w=overlay_widget, bottom_w=self._w, align='center', width=('relative', 60), min_width=80, valign='middle', height='pack') self.showing_overlay = True def remove_overlay(self, overlay_widget): # urwid note: we could also get orig_w as # self._w.contents[0][0], but this is clearer: self._w = self.orig_w self.showing_overlay = False
def build_widgets(self): self.services_column = ServicesColumn(self.display_controller, self.placement_controller, self) self.machines_column = MachinesColumn(self.display_controller, self.placement_controller, self) self.relations_column = RelationsColumn(self.display_controller, self.placement_controller, self, self.metadata_controller) self.charmstore_column = CharmstoreColumn(self.display_controller, self.placement_controller, self, self.metadata_controller) self.options_column = OptionsColumn(self.display_controller, self.placement_controller, self, self.metadata_controller) self.machines_header = self.get_machines_header(self.machines_column) self.relations_header = self.get_relations_header() self.services_header = self.get_services_header() self.charmstore_header = self.get_charmstore_header( self.charmstore_column) self.options_header = self.get_options_header(self.options_column) cs = [self.services_header, self.charmstore_header] self.header_columns = Columns(cs, dividechars=2) self.columns = Columns([self.services_column, self.machines_column], dividechars=2) self.deploy_button = MenuSelectButton("\nCommit\n", on_press=self.do_deploy) self.deploy_button_label = Text("Some charms use default") self.placement_edit_body_pile = Pile([self.columns]) self.placement_edit_body = Filler(Padding( self.placement_edit_body_pile, align='center', width=('relative', 95)), valign='top') self.bundle_graph_text = Text("No graph to display yet.") self.bundle_graph_widget = Padding(self.bundle_graph_text, 'center', 'pack') b = AttrMap(self.deploy_button, 'frame_header', 'button_primary focus') self.footer_grid = GridFlow([self.deploy_button_label, Padding(b, width=28, align='center')], 28, 1, 1, 'right') f = AttrMap(self.footer_grid, 'frame_footer', 'frame_footer') self.frame = Frame(header=Pile([self.header_columns, HR()]), body=self.placement_edit_body, footer=f) return self.frame
class PlacementView(WidgetWrap): """ Handles display of machines and services. displays nothing if self.controller is not set. set it to a PlacementController. :param do_deploy_cb: deploy callback from controller """ def __init__(self, display_controller, placement_controller, config, do_deploy_cb, initial_state=UIState.CHARMSTORE_VIEW, has_maas=False): self.display_controller = display_controller self.placement_controller = placement_controller self.config = config self.do_deploy_cb = do_deploy_cb self.state = initial_state self.has_maas = has_maas self.prev_state = None self.showing_overlay = False self.showing_graph_split = False self.show_scc_graph = False self.bundle = placement_controller.bundle self.metadata_controller = MetadataController(self.bundle, config) w = self.build_widgets() super().__init__(w) self.reset_selections(top=True) # calls self.update def scroll_down(self): pass def scroll_up(self): pass def focus_footer(self): self.frame.focus_position = 'footer' self.footer_grid.focus_position = 1 def handle_tab(self, backward): tabloop = ['headercol1', 'col1', 'headercol2', 'col2', 'footer'] if not self.has_maas: tabloop.remove('headercol1') def goto_header_col1(): self.frame.focus_position = 'header' self.header_columns.focus_position = 0 def goto_header_col2(): self.frame.focus_position = 'header' self.header_columns.focus_position = 1 def goto_col1(): self.frame.focus_position = 'body' self.columns.focus_position = 0 def goto_col2(): self.frame.focus_position = 'body' if self.state == UIState.PLACEMENT_EDITOR: self.focus_machines_column() elif self.state == UIState.RELATION_EDITOR: self.focus_relations_column() elif self.state == UIState.OPTIONS_EDITOR: self.focus_options_column() else: self.focus_charmstore_column() actions = { 'headercol1': goto_header_col1, 'headercol2': goto_header_col2, 'col1': goto_col1, 'col2': goto_col2, 'footer': self.focus_footer } if self.frame.focus_position == 'header': cur = ['headercol1', 'headercol2'][self.header_columns.focus_position] elif self.frame.focus_position == 'footer': cur = 'footer' else: cur = ['col1', 'col2'][self.columns.focus_position] cur_idx = tabloop.index(cur) if backward: next_idx = cur_idx - 1 else: next_idx = (cur_idx + 1) % len(tabloop) actions[tabloop[next_idx]]() def keypress(self, size, key): if key in ['tab', 'shift tab']: self.handle_tab('shift' in key) return key unhandled_key = self._w.keypress(size, key) if unhandled_key is None: return None elif unhandled_key in ['g', 'G']: if unhandled_key == 'G': self.show_scc_graph = True else: self.show_scc_graph = False self.showing_graph_split = not self.showing_graph_split if self.showing_graph_split: opts = self.placement_edit_body_pile.options() self.placement_edit_body_pile.contents.insert( 0, (self.bundle_graph_widget, opts)) else: self.placement_edit_body_pile.contents.pop(0) self.update() else: return unhandled_key def get_services_header(self): b = PlainButton("Clear All Placements", on_press=self.do_clear_all) self.clear_all_button = AttrMap(b, 'button_secondary', 'button_secondary focus') self.services_buttons = [self.clear_all_button] self.services_button_grid = GridFlow(self.services_buttons, 36, 1, 0, 'center') ws = [Divider(), Text(("body", "Services"), align='center'), Divider()] if self.has_maas: ws.append(self.services_button_grid) return Pile(ws) def get_charmstore_header(self, charmstore_column): series = self.placement_controller.bundle.series self.charm_search_widget = CharmStoreSearchWidget( self.do_add_charm, charmstore_column, self.config, series) self.charm_search_header_pile = Pile([ Divider(), Text(("body", "Add Charms"), align='center'), Divider(), self.charm_search_widget ]) return self.charm_search_header_pile def get_machines_header(self, machines_column): b = PlainButton("Open in Browser", on_press=self.browse_maas) self.open_maas_button = AttrMap(b, 'button_secondary', 'button_secondary focus') self.maastitle = Text("Connected to MAAS") maastitle_widgets = Padding(Columns( [self.maastitle, (22, self.open_maas_button)]), align='center', width='pack', left=2, right=2) f = machines_column.machines_list.handle_filter_change self.filter_edit_box = FilterBox(f) pl = [ Divider(), Text(('body', "Ready Machines {}".format( MetaScroll().get_text()[0])), align='center'), Divider(), maastitle_widgets, Divider(), self.filter_edit_box ] self.machines_header_pile = Pile(pl) return self.machines_header_pile def update_machines_header(self): maasinfo = self.placement_controller.maasinfo maasname = "'{}' <{}>".format(maasinfo['server_name'], maasinfo['server_hostname']) self.maastitle.set_text("Connected to MAAS {}".format(maasname)) def _simple_header_widgets(self, title): b = PlainButton("Back to Charm Store", on_press=self.show_default_view) self.back_to_mainview_button = AttrMap(b, 'button_secondary', 'button_secondary focus') button_grid = GridFlow([self.back_to_mainview_button], 36, 1, 0, 'center') return [ Divider(), Text(('body', title), align='center'), Divider(), button_grid ] def get_relations_header(self): return Pile(self._simple_header_widgets("Relation Editor")) def get_options_header(self, options_column): simple_widgets = self._simple_header_widgets("Options Editor") fb = FilterBox(options_column.handle_filter_change, info_text="Filter by option name") padded_fb = Padding(AttrMap(fb, 'filter', 'filter_focus'), left=2, right=2) return Pile(simple_widgets + [padded_fb]) def build_widgets(self): self.services_column = ServicesColumn(self.display_controller, self.placement_controller, self) self.machines_column = MachinesColumn(self.display_controller, self.placement_controller, self) self.relations_column = RelationsColumn(self.display_controller, self.placement_controller, self, self.metadata_controller) self.charmstore_column = CharmstoreColumn(self.display_controller, self.placement_controller, self, self.metadata_controller) self.options_column = OptionsColumn(self.display_controller, self.placement_controller, self, self.metadata_controller) self.machines_header = self.get_machines_header(self.machines_column) self.relations_header = self.get_relations_header() self.services_header = self.get_services_header() self.charmstore_header = self.get_charmstore_header( self.charmstore_column) self.options_header = self.get_options_header(self.options_column) cs = [self.services_header, self.charmstore_header] self.header_columns = Columns(cs, dividechars=2) self.columns = Columns([self.services_column, self.machines_column], dividechars=2) self.deploy_button = MenuSelectButton("\nCommit\n", on_press=self.do_deploy) self.deploy_button_label = Text("Some charms use default") self.placement_edit_body_pile = Pile([self.columns]) self.placement_edit_body = Filler(Padding( self.placement_edit_body_pile, align='center', width=('relative', 95)), valign='top') self.bundle_graph_text = Text("No graph to display yet.") self.bundle_graph_widget = Padding(self.bundle_graph_text, 'center', 'pack') b = AttrMap(self.deploy_button, 'frame_header', 'button_primary focus') self.footer_grid = GridFlow( [self.deploy_button_label, Padding(b, width=28, align='center')], 28, 1, 1, 'right') f = AttrMap(self.footer_grid, 'frame_footer', 'frame_footer') self.frame = Frame(header=Pile([self.header_columns, HR()]), body=self.placement_edit_body, footer=f) return self.frame def update(self): if self.prev_state != self.state: h_opts = self.header_columns.options() c_opts = self.columns.options() if self.state == UIState.PLACEMENT_EDITOR: self.update_machines_header() self.header_columns.contents[-1] = (self.machines_header, h_opts) self.columns.contents[-1] = (self.machines_column, c_opts) elif self.state == UIState.RELATION_EDITOR: self.header_columns.contents[-1] = (self.relations_header, h_opts) self.columns.contents[-1] = (self.relations_column, h_opts) elif self.state == UIState.CHARMSTORE_VIEW: self.header_columns.contents[-1] = (self.charmstore_header, h_opts) self.columns.contents[-1] = (self.charmstore_column, h_opts) elif self.state == UIState.OPTIONS_EDITOR: self.header_columns.contents[-1] = (self.options_header, h_opts) self.columns.contents[-1] = (self.options_column, h_opts) self.prev_state = self.state self.services_column.update() if self.state == UIState.PLACEMENT_EDITOR: self.machines_column.update() elif self.state == UIState.RELATION_EDITOR: self.relations_column.update() elif self.state == UIState.OPTIONS_EDITOR: self.options_column.update() else: self.charmstore_column.update() unplaced = self.placement_controller.unassigned_undeployed_services() all = self.placement_controller.services() n_subs_in_unplaced = len([c for c in unplaced if c.subordinate]) n_subs_in_all = len([c for c in all if c.subordinate]) n_total = len(all) - n_subs_in_all remaining = len(unplaced) - n_subs_in_unplaced if remaining > 0: dmsg = "\nAuto-assigning {}/{} services".format(remaining, n_total) else: dmsg = "" self.deploy_button_label.set_text(dmsg) if self.showing_graph_split: bundle = self.placement_controller.bundle if self.show_scc_graph: gtext = scc_graph_for_bundle(bundle, self.metadata_controller) else: gtext = graph_for_bundle(bundle, self.metadata_controller) if gtext == "": gtext = "No graph to display yet." self.bundle_graph_text.set_text(gtext) def browse_maas(self, sender): bc = self.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.remove_overlay) self.show_overlay(w) def do_clear_all(self, sender): self.placement_controller.clear_all_assignments() def do_add_charm(self, charm_name, charm_dict): """Add new service and focus its widget. """ assert (self.state == UIState.CHARMSTORE_VIEW) def done_cb(f): csid = CharmStoreID(charm_dict['Id']) id_no_rev = csid.as_str_without_rev() info = self.metadata_controller.get_charm_info(id_no_rev) is_subordinate = info["Meta"]["charm-metadata"].get( "Subordinate", False) service_name = self.placement_controller.add_new_service( charm_name, charm_dict, is_subordinate=is_subordinate) self.frame.focus_position = 'body' self.columns.focus_position = 0 self.update() self.services_column.select_service(service_name) # TODO MMCC: need a 'loading' indicator to start here self.metadata_controller.load([charm_dict['Id']], done_cb) def do_add_bundle(self, bundle_dict): assert (self.state == UIState.CHARMSTORE_VIEW) _, new_services, _ = self.placement_controller.merge_bundle( bundle_dict) self.frame.focus_position = 'body' self.columns.focus_position = 0 charms = list(set([s.charm_source for s in new_services])) self.metadata_controller.load(charms) self.update() ss = sorted(new_services, key=attrgetter('service_name')) first_service = ss[0].service_name self.services_column.select_service(first_service) def do_clear_machine(self, sender, machine): self.placement_controller.clear_assignments(machine) def clear_selections(self): self.services_column.clear_selections() self.machines_column.clear_selections() def reset_selections(self, top=False): self.clear_selections() self.state = UIState.CHARMSTORE_VIEW self.update() self.columns.focus_position = 0 if top: self.services_column.focus_top() else: self.services_column.focus_next() def focus_machines_column(self): self.columns.focus_position = 1 self.machines_column.focus_prev_or_top() def focus_relations_column(self): self.columns.focus_position = 1 self.relations_column.focus_prev_or_top() def focus_options_column(self): self.columns.focus_position = 1 self.options_column.focus_prev_or_top() def focus_charmstore_column(self): self.columns.focus_position = 1 self.charmstore_column.focus_prev_or_top() def edit_placement(self): self.state = UIState.PLACEMENT_EDITOR self.update() self.focus_machines_column() def show_default_view(self, *args): self.state = UIState.CHARMSTORE_VIEW self.update() def edit_relations(self, service): self.state = UIState.RELATION_EDITOR self.relations_column.set_service(service) self.update() self.focus_relations_column() def edit_options(self, service): self.state = UIState.OPTIONS_EDITOR self.options_column.set_service(service) self.update() self.focus_options_column() def do_deploy(self, sender): self.do_deploy_cb() def show_overlay(self, overlay_widget): if not self.showing_overlay: self.orig_w = self._w self._w = Overlay(top_w=overlay_widget, bottom_w=self._w, align='center', width=('relative', 60), min_width=80, valign='middle', height='pack') self.showing_overlay = True def remove_overlay(self, overlay_widget): # urwid note: we could also get orig_w as # self._w.contents[0][0], but this is clearer: self._w = self.orig_w self.showing_overlay = False