def build_widget(self, **kwargs): def remove_p(charm_class): n = self.pc.assignment_machine_count_for_charm(charm_class) return n > 0 def not_conflicted_p(cc): state, _, _ = self.pc.get_charm_state(cc) return state != CharmState.CONFLICTED actions = [(remove_p, 'Remove', self.do_remove), (not_conflicted_p, 'Add', self.do_add)] self.unrequired_undeployed_sl = ServicesList(self.pc, actions, actions, ignore_deployed=True, title="Un-Deployed") self.deployed_sl = ServicesList(self.pc, actions, actions, deployed_only=True, show_placements=True, title="Deployed Services") self.assigned_sl = ServicesList(self.pc, actions, actions, assigned_only=True, show_placements=True, title="Services to be Deployed") self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'center') self.pile1 = Pile([self.button_grid, self.assigned_sl, self.unrequired_undeployed_sl]) self.pile2 = Pile([self.deployed_sl]) return LineBox(Columns([self.pile1, self.pile2]), title="Add Services")
def build_widgets(self): self.deploy_view = DeployView(self.display_controller, self.placement_controller, self.placement_view) def not_conflicted_p(cc): state, _, _ = self.placement_controller.get_charm_state(cc) return state != CharmState.CONFLICTED actions = [(not_conflicted_p, "Choose Machine", self.placement_view.do_show_machine_chooser)] subordinate_actions = [(not_conflicted_p, "Add", self.do_place_subordinate)] self.required_services_list = ServicesList(self.placement_controller, actions, subordinate_actions, ignore_assigned=True, ignore_deployed=True, show_type='required', show_constraints=True, title="Required Services") self.additional_services_list = ServicesList(self.placement_controller, actions, subordinate_actions, show_type='non-required', show_constraints=True, title="Additional " "Services") autoplace_func = self.placement_view.do_autoplace self.autoplace_button = AttrMap( Button("Auto-place Remaining Services", on_press=autoplace_func), 'button_secondary', 'button_secondary focus') clear_all_func = self.placement_view.do_clear_all self.clear_all_button = AttrMap( Button("Clear all Placements", on_press=clear_all_func), 'button_secondary', 'button_secondary focus') self.required_services_pile = Pile( [self.required_services_list, Divider()]) self.additional_services_pile = Pile( [self.additional_services_list, Divider()]) self.top_buttons = [] self.top_button_grid = GridFlow(self.top_buttons, 36, 1, 0, 'center') pl = [ Text(('subheading', "Services"), align='center'), Divider(), self.top_button_grid, Divider(), self.deploy_view, Divider(), self.required_services_pile, Divider(), self.additional_services_pile ] self.main_pile = Pile(pl) return self.main_pile
def build_widgets(self): dn = self.charm_class.display_name self.title_markup = ["\N{GEAR} {}".format(dn), ""] self.charm_info_widget = Text(self.title_markup) self.placements_widget = Text("") if self.charm_class.subordinate: c_str = [('label', " (subordinate charm)")] elif len(self.charm_class.constraints) == 0: c_str = [('label', " no constraints set")] else: cpairs = [ format_constraint(k, v) for k, v in self.charm_class.constraints.items() ] c_str = [('label', " constraints: "), ', '.join(cpairs)] self.constraints_widget = Text(c_str) self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [self.charm_info_widget] if self.show_placements: pl.append(self.placements_widget) if self.show_constraints: pl.append(self.constraints_widget) pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2)
def build_widgets(self): title_text = Text([("body", self.name)], align="center") desc_text = Text(["\n", strip_solo_dots(self.description)]) self.reset_button = PlainButton("Reset to Default", self.do_reset) if self.optype == OptionType.BOOLEAN: self.control = CheckBox(self.name, state=bool(self.current_value)) elif self.optype == OptionType.INT: self.control = IntEdit(caption="{}: ".format(self.name), default=self.current_value) elif self.optype == OptionType.STRING: edit_text = self.current_value or "" self.control = StringEditor( caption="{}: ".format(self.name), edit_text=edit_text) else: raise Exception("Unknown option type") if self.optype == OptionType.STRING: connect_signal(self.control._edit, 'change', self.handle_value_changed) else: connect_signal(self.control, 'change', self.handle_value_changed) button_grid = GridFlow([self.reset_button], 36, 1, 0, 'right') return Pile([Divider(), title_text, desc_text, self.control, button_grid])
def build_widgets(self): self.deploy_view = DeployView(self.display_controller, self.placement_controller, self.placement_view) def not_conflicted_p(cc): state, _, _ = self.placement_controller.get_charm_state(cc) return state != CharmState.CONFLICTED actions = [(not_conflicted_p, "Choose Machine", self.placement_view.do_show_machine_chooser)] subordinate_actions = [(not_conflicted_p, "Add", self.do_place_subordinate)] self.required_services_list = ServicesList(self.placement_controller, actions, subordinate_actions, ignore_assigned=True, ignore_deployed=True, show_type='required', show_constraints=True, title="Required Services") self.additional_services_list = ServicesList(self.placement_controller, actions, subordinate_actions, show_type='non-required', show_constraints=True, title="Additional " "Services") autoplace_func = self.placement_view.do_autoplace self.autoplace_button = AttrMap(Button("Auto-place Remaining Services", on_press=autoplace_func), 'button_secondary', 'button_secondary focus') clear_all_func = self.placement_view.do_clear_all self.clear_all_button = AttrMap(Button("Clear all Placements", on_press=clear_all_func), 'button_secondary', 'button_secondary focus') self.required_services_pile = Pile([self.required_services_list, Divider()]) self.additional_services_pile = Pile([self.additional_services_list, Divider()]) self.top_buttons = [] self.top_button_grid = GridFlow(self.top_buttons, 36, 1, 0, 'center') pl = [Text(('subheading', "Services"), align='center'), Divider(), self.top_button_grid, Divider(), self.deploy_view, Divider(), self.required_services_pile, Divider(), self.additional_services_pile] self.main_pile = Pile(pl) return self.main_pile
def build_widgets(self): dn = self.charm_class.display_name self.title_markup = ["\N{GEAR} {}".format(dn), ""] if self.charm_class.summary != "": self.title_markup.append("\n {}\n".format(self.charm_class.summary)) self.charm_info_widget = Text(self.title_markup) self.placements_widget = Text("") if self.charm_class.subordinate: c_str = [('label', " (subordinate charm)")] elif len(self.charm_class.constraints) == 0: c_str = [('label', " no constraints set")] else: cpairs = [format_constraint(k, v) for k, v in self.charm_class.constraints.items()] c_str = [('label', " constraints: "), ', '.join(cpairs)] self.constraints_widget = Text(c_str) self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [self.charm_info_widget] if self.show_placements: pl.append(self.placements_widget) if self.show_constraints: pl.append(self.constraints_widget) pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2)
def build_widgets(self): instructions = Text("Remove services from {}".format( self.machine.hostname)) self.machine_widget = MachineWidget(self.machine, self.controller, show_hardware=True) def show_remove_p(cc): md = self.controller.get_assignments(cc) for atype, ms in md.items(): hostnames = [m.hostname for m in ms] if self.machine.hostname in hostnames: return True return False actions = [(show_remove_p, 'Remove', self.do_remove)] self.services_list = ServicesList(self.controller, actions, actions, machine=self.machine) close_button = AttrMap(Button('X', on_press=self.close_pressed), 'button_secondary', 'button_secondary focus') p = Pile([ GridFlow([close_button], 5, 1, 0, 'right'), instructions, Divider(), self.machine_widget, Divider(), self.services_list ]) return LineBox(p, title="Remove Services")
def __init__(self): self.screen = Screen() self.screen.set_input_timeouts(max_wait=0) self.steps = GridFlow([], 20, 2, 1, 'left') self.progress = SimpleFocusListWalker([]) self.log = SimpleFocusListWalker([]) self.widget = AttrMap( LineBox(Pile([ ('fixed', 6, AttrMap(Filler(self.steps), 'default')), ('fixed', 1, Filler(Divider('\u2500'))), ('fixed', 3, ListBox(self.progress)), AttrMap(LineBox(ListBox(self.log), title='Message log'), 'default') ]), title='Indico 1.2 -> 2.0 migration'), 'global_frame') self.screen.register_palette( [('green', 'light green', ''), ('white', 'white', ''), ('red', 'dark red', ''), ('yellow', 'yellow', ''), ('progress_empty', 'black', 'light gray'), ('progress_progress', 'light cyan', 'light gray'), ('progress_done', 'black', 'light cyan'), ('box', 'white', 'dark gray'), ('step_done', 'light green', ''), ('step_working', 'dark gray', ''), ('global_frame', 'light cyan', ''), ('fill', 'light cyan', 'dark cyan'), ('done', 'white', 'dark green'), ('eta', 'yellow', 'dark gray')] + generate_urwid_palette(PALETTE))
def build_widgets(self): self.machine_info_widget = Text("") self.assignments_widget = Text("") self.hardware_widget = Text("") self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [Divider(' '), self.machine_info_widget] if self.show_hardware: pl.append(self.hardware_widget) if self.show_assignments: pl += [Divider(' '), self.assignments_widget] pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2)
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 _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 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 _make(self, calendar): # if we do this as single gridflow then focus doesn't move down into dates # so instead put names in pile names = GridFlow(list(map(lambda d: Text(('plain', d)), DAYS2)), 2, 1, 0, 'left') prev = dt.date( self._date.year if self._date.month > 1 else self._date.year - 1, self._date.month - 1 if self._date.month > 1 else 12, 1) next = dt.date( self._date.year if self._date.month < 12 else self._date.year + 1, self._date.month + 1 if self._date.month < 12 else 1, 1) prev_days = monthrange(prev.year, prev.month)[1] curr_days = monthrange(self._date.year, self._date.month)[1] first_day = dt.date(self._date.year, self._date.month, 1).weekday() # mon 0 total_days = first_day + curr_days extra_days = 7 - total_days % 7 if extra_days == 7: extra_days = 0 else: total_days += extra_days dates = [ FocusAttr(Day(dt.date(prev.year, prev.month, i)), 'unimportant') for i in range(prev_days - first_day + 1, prev_days + 1) ] dates.extend([ FocusAttr(Day(dt.date(self._date.year, self._date.month, i)), plain='selected' if i == self._date.day else 'plain', focus='selected-focus' if i == self._date.day else 'plain-focus') for i in range(1, curr_days + 1) ]) dates.extend([ FocusAttr(Day(dt.date(next.year, next.month, i)), 'unimportant') for i in range(1, extra_days + 1) ]) for day in dates: connect_signal(day._original_widget, 'click', calendar.date_change) dates = GridFlow(dates, 2, 1, 0, 'left') return Pile([names, dates])
def __init__(self, height, directory=".", file="", attr=(None, None), show_hidden=False): """ height -- height of the directory list and the file list directory, file -- default selection attr -- (inner selectable widgets, selected widgets) show_hidden -- If True, hidden files are shown by default. """ self.directory = abspath(directory) self.file = "" self.attr = attr self.height = height self.show_hidden = show_hidden # Create dummy widgets for directory and file display: self.dir_widget = AttrWrap( BoxAdapter(ListBox([self._blank]), self.height), self.attr[0]) self.file_widget = AttrWrap( BoxAdapter(ListBox([self._blank]), self.height), self.attr[0]) columns = Columns([self.dir_widget, self.file_widget], 1) # Selection widget: self.select_widget = AttrWrap(Edit("", ""), self.attr[0], self.attr[1]) # Buttons and checkbox: button_widgets = [ AttrWrap(Button(button, self._action), attr[0], attr[1]) for button in ["OK", "Cancel"] ] button_grid = GridFlow(button_widgets, 12, 2, 1, 'center') button_cols = Columns([ CheckBox(self.SHOW_HIDDEN_TEXT, self.show_hidden, False, self._toggle_hidden), button_grid ]) self.outer_widget = Pile([ columns, self._blank, Text(self.SELECTION_TEXT), self.select_widget, self._blank, button_cols ]) self.update_widgets() WidgetWrap.__init__(self, self.outer_widget)
def build_widgets(self): self.deploy_view = DeployView(self.display_controller, self.placement_controller, self.placement_view) actions = [("Choose Machine", self.placement_view.do_show_machine_chooser)] self.required_services_list = ServicesList(self.placement_controller, actions, unplaced_only=True, show_type='required', show_constraints=True, title="Required Services") self.additional_services_list = ServicesList(self.placement_controller, actions, show_type='non-required', show_constraints=True, title="Additional " "Services") autoplace_func = self.placement_view.do_autoplace self.autoplace_button = AttrMap(Button("Auto-place Remaining Services", on_press=autoplace_func), 'button_secondary', 'button_secondary focus') clear_all_func = self.placement_view.do_clear_all self.clear_all_button = AttrMap(Button("Clear all Placements", on_press=clear_all_func), 'button_secondary', 'button_secondary focus') self.required_services_pile = Pile([self.required_services_list, Divider()]) self.additional_services_pile = Pile([self.additional_services_list, Divider()]) self.top_buttons = [] self.top_button_grid = GridFlow(self.top_buttons, 36, 1, 0, 'center') pl = [Text(('subheading', "Services"), align='center'), Divider(), self.top_button_grid, Divider(), self.deploy_view, Divider(), self.required_services_pile, Divider(), self.additional_services_pile] self.main_pile = Pile(pl) return self.main_pile
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 build_widgets(self): desc_text = Text(["\n", strip_solo_dots(self.description)]) self.reset_button = PlainButton("Reset to Default", self.do_reset) if self.optype == OptionType.BOOLEAN: self.control = CheckBox('', state=bool(self.current_value)) self.wrapped_control = self.control elif self.optype == OptionType.INT: self.control = IntEdit(default=self.current_value) self.wrapped_control = Color.string_input( self.control, focus_map='string_input focus') elif self.optype == OptionType.STRING: edit_text = self.current_value or "" self.control = StringEditor(edit_text=edit_text) self.wrapped_control = Color.string_input( self.control, focus_map='string_input focus') elif self.optype == OptionType.FLOAT: edit_text = str(self.current_value) self.control = StringEditor(edit_text=edit_text) self.wrapped_control = Color.string_input( self.control, focus_map='string_input focus') else: raise Exception("Unknown option type") self.control_columns = Columns( [('pack', Text("{}:".format(self.name), align='right')), (80, self.wrapped_control)], dividechars=1) if self.optype in [OptionType.STRING, OptionType.FLOAT]: connect_signal(self.control._edit, 'change', self.handle_value_changed) else: connect_signal(self.control, 'change', self.handle_value_changed) button_grid = GridFlow([ Color.button_secondary(self.reset_button, focus_map='button_secondary focus') ], 36, 1, 0, 'right') return Pile([ Padding.line_break(""), Padding.left(self.control_columns, left=1), Padding.left(desc_text, left=2), button_grid ])
def build_widgets(self): if self.charm_class.allow_multi_units: machine_string = "machines" plural_string = "s" else: machine_string = "a machine" plural_string = "" instructions = Text("Select {} to host {}".format( machine_string, self.charm_class.display_name)) self.service_widget = ServiceWidget(self.charm_class, self.controller, show_constraints=True, show_placements=True) all_actions = [(AssignmentType.BareMetal, 'Add as Bare Metal', self.do_select_baremetal), (AssignmentType.LXC, 'Add as LXC', self.do_select_lxc), (AssignmentType.KVM, 'Add as KVM', self.do_select_kvm)] actions = [(label, func) for atype, label, func in all_actions if atype in self.charm_class.allowed_assignment_types] constraints = self.charm_class.constraints # NOTE: show_assignments=False is a WORKAROUND for #194 self.machines_list = MachinesList(self.controller, actions, constraints=constraints, show_hardware=True, show_assignments=False) self.machines_list.update() close_button = AttrMap(Button('X', on_press=self.close_pressed), 'button_secondary', 'button_secondary focus') p = Pile([ GridFlow([close_button], 5, 1, 0, 'right'), instructions, Divider(), self.service_widget, Divider(), self.machines_list ]) return LineBox(p, title="Select Machine{}".format(plural_string))
class AddServicesDialog(WidgetWrap): """ Dialog to add services. Does not specify placement. :param cb: callback routine to process submit/cancel actions """ def __init__(self, install_controller, deploy_cb, cancel_cb): self.install_controller = install_controller self.orig_pc = install_controller.placement_controller self.pc = self.orig_pc.get_temp_copy() self.charms = [] self.deploy_cb = deploy_cb self.cancel_cb = cancel_cb self.boxes = [] w = self.build_widget() self.update() super().__init__(w) def build_widget(self, **kwargs): def remove_p(charm_class): n = self.pc.assignment_machine_count_for_charm(charm_class) return n > 0 def not_conflicted_p(cc): state, _, _ = self.pc.get_charm_state(cc) return state != CharmState.CONFLICTED actions = [(remove_p, 'Remove', self.do_remove), (not_conflicted_p, 'Add', self.do_add)] self.unrequired_undeployed_sl = ServicesList(self.pc, actions, actions, ignore_deployed=True, title="Un-Deployed") self.deployed_sl = ServicesList(self.pc, actions, actions, deployed_only=True, show_placements=True, title="Deployed Services") self.assigned_sl = ServicesList(self.pc, actions, actions, assigned_only=True, show_placements=True, title="Services to be Deployed") self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'center') self.pile1 = Pile([self.button_grid, self.assigned_sl, self.unrequired_undeployed_sl]) self.pile2 = Pile([self.deployed_sl]) return LineBox(Columns([self.pile1, self.pile2]), title="Add Services") def update(self): self.unrequired_undeployed_sl.update() self.deployed_sl.update() self.assigned_sl.update() self.update_buttons() def update_buttons(self): buttons = [(AttrMap(Button("Cancel", self.handle_cancel), 'button_primary', 'button_primary focus'), self.button_grid.options())] n_assigned = len(self.pc.assigned_services) if n_assigned > 0 and self.pc.can_deploy(): b = AttrMap(Button("Deploy", self.handle_deploy), 'button_primary', 'button_primary focus') else: b = AttrMap(SelectableIcon("(Deploy)"), 'disabled_button', 'disabled_button_focus') buttons.append((b, self.button_grid.options())) self.button_grid.contents = buttons def do_add(self, sender, charm_class): """Add the selected charm using default juju location. Equivalent to a simple 'juju deploy foo' Attempt to also add any charms that are required as a result of the newly added charm. """ def num_to_auto_add(charm_class): return (charm_class.required_num_units() - self.pc.assignment_machine_count_for_charm(charm_class)) for i in range(num_to_auto_add(charm_class)): self.pc.assign(self.pc.def_placeholder, charm_class, AssignmentType.DEFAULT) def get_unassigned_requireds(): return [cc for cc in self.pc.unassigned_undeployed_services() if self.pc.get_charm_state(cc)[0] == CharmState.REQUIRED] while len(get_unassigned_requireds()) > 0: for u_r_cc in get_unassigned_requireds(): for i in range(num_to_auto_add(u_r_cc)): self.pc.assign(self.pc.def_placeholder, u_r_cc, AssignmentType.DEFAULT) self.update() def do_remove(self, sender, charm_class): "Undo an assignment" self.pc.remove_one_assignment(self.pc.def_placeholder, charm_class) self.update() def handle_deploy(self, button): """Commits changes to the main placement controller, and calls the deploy callback to do the rest. """ self.orig_pc.update_from_controller(self.pc) self.deploy_cb() def handle_cancel(self, button): self.cancel_cb()
class ServicesColumn(WidgetWrap): """Displays dynamic list of unplaced services and associated controls """ 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): self.deploy_view = DeployView(self.display_controller, self.placement_controller, self.placement_view) def not_conflicted_p(cc): state, _, _ = self.placement_controller.get_charm_state(cc) return state != CharmState.CONFLICTED actions = [(not_conflicted_p, "Choose Machine", self.placement_view.do_show_machine_chooser)] subordinate_actions = [(not_conflicted_p, "Add", self.do_place_subordinate)] self.required_services_list = ServicesList(self.placement_controller, actions, subordinate_actions, ignore_assigned=True, ignore_deployed=True, show_type='required', show_constraints=True, title="Required Services") self.additional_services_list = ServicesList(self.placement_controller, actions, subordinate_actions, ignore_assigned=True, show_type='non-required', show_constraints=True, title="Additional " "Services") autoplace_func = self.placement_view.do_autoplace self.autoplace_button = AttrMap( Button("Auto-place Remaining Services", on_press=autoplace_func), 'button_secondary', 'button_secondary focus') clear_all_func = self.placement_view.do_clear_all self.clear_all_button = AttrMap( Button("Clear all Placements", on_press=clear_all_func), 'button_secondary', 'button_secondary focus') self.required_services_pile = Pile( [self.required_services_list, Divider()]) self.additional_services_pile = Pile( [self.additional_services_list, Divider()]) self.top_buttons = [] self.top_button_grid = GridFlow(self.top_buttons, 36, 1, 0, 'center') pl = [ Text(('subheading', "Services"), align='center'), Divider(), self.top_button_grid, Divider(), self.deploy_view, Divider(), self.required_services_pile, Divider(), self.additional_services_pile ] self.main_pile = Pile(pl) return self.main_pile def update(self): self.deploy_view.update() self.required_services_list.update() self.additional_services_list.update() top_buttons = [] unplaced = self.placement_controller.unassigned_undeployed_services() if len(unplaced) == 0: icon = SelectableIcon(" (Auto-place Remaining Services) ") top_buttons.append((AttrMap(icon, 'disabled_button', 'disabled_button_focus'), self.top_button_grid.options())) else: top_buttons.append( (self.autoplace_button, self.top_button_grid.options())) top_buttons.append( (self.clear_all_button, self.top_button_grid.options())) self.top_button_grid.contents = top_buttons def do_reset_to_defaults(self, sender): self.placement_controller.set_all_assignments( self.placement_controller.gen_defaults()) def do_place_subordinate(self, sender, charm_class): sub_placeholder = self.placement_controller.sub_placeholder self.placement_controller.assign(sub_placeholder, charm_class, AssignmentType.BareMetal)
class ServiceWidget(WidgetWrap): """A widget displaying a service and associated actions. charm_class - the class describing the service to display controller - a PlacementController instance actions - a list of ('label', function) pairs that will be used to create buttons for each machine. The machine will be passed to the function as userdata. optionally, actions can be a 3-tuple (pred, 'label', function), where pred determines whether to add the button. Pred will be passed the charm class. show_constraints - display the charm's constraints show_assignments - display the machine(s) currently assigned to host this service """ def __init__(self, charm_class, controller, actions=None, show_constraints=False, show_assignments=False): self.charm_class = charm_class self.controller = controller if actions is None: self.actions = [] else: self.actions = actions self.show_constraints = show_constraints self.show_assignments = show_assignments w = self.build_widgets() self.update() super().__init__(w) def selectable(self): return True def build_widgets(self): dn = self.charm_class.display_name self.title_markup = ["\N{GEAR} {}".format(dn), ""] self.charm_info_widget = Text(self.title_markup) self.assignments_widget = Text("") if len(self.charm_class.constraints) == 0: c_str = [('label', " no constraints set")] else: cpairs = [format_constraint(k, v) for k, v in self.charm_class.constraints.items()] c_str = [('label', " constraints: "), ', '.join(cpairs)] self.constraints_widget = Text(c_str) self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [self.charm_info_widget] if self.show_assignments: pl.append(self.assignments_widget) if self.show_constraints: pl.append(self.constraints_widget) pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2) def update(self): md = self.controller.machines_for_charm(self.charm_class) mstr = [""] if self.controller.service_is_required(self.charm_class): np = self.controller.machine_count_for_charm(self.charm_class) nr = self.charm_class.required_num_units() self.title_markup[1] = ('info', " ({} of {} placed)".format(np, nr)) self.charm_info_widget.set_text(self.title_markup) for atype, ml in md.items(): n = len(ml) mstr.append(('label', " {} ({}): ".format(atype.name, n))) if len(ml) == 0: mstr.append("\N{DOTTED CIRCLE}") else: mstr.append(", ".join(["\N{TAPE DRIVE} {}".format(m.hostname) for m in ml])) mstr.append("\n") self.assignments_widget.set_text(mstr) self.update_buttons() def update_buttons(self): buttons = [] for at in self.actions: if len(at) == 2: predicate = lambda x: True label, func = at else: predicate, label, func = at if not predicate(self.charm_class): b = AttrMap(SelectableIcon(" (" + label + ")"), 'disabled_button', 'disabled_button_focus') else: b = AttrMap(Button(label, on_press=func, user_data=self.charm_class), 'button_secondary', 'button_secondary focus') buttons.append((b, self.button_grid.options())) self.button_grid.contents = buttons
class ServiceWidget(WidgetWrap): """A widget displaying a service and associated actions. charm_class - the class describing the service to display controller - a PlacementController instance actions - a list of ('label', function) pairs that will be used to create buttons for each machine. The machine will be passed to the function as userdata. optionally, actions can be a 3-tuple (pred, 'label', function), where pred determines whether to add the button. Pred will be passed the charm class. show_constraints - display the charm's constraints 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, charm_class, controller, actions=None, show_constraints=False, show_placements=False): self.charm_class = charm_class self.controller = controller if actions is None: self.actions = [] else: self.actions = actions self.show_constraints = show_constraints self.show_placements = show_placements w = self.build_widgets() self.update() super().__init__(w) def selectable(self): return True def build_widgets(self): dn = self.charm_class.display_name self.title_markup = ["\N{GEAR} {}".format(dn), ""] self.charm_info_widget = Text(self.title_markup) self.placements_widget = Text("") if self.charm_class.subordinate: c_str = [('label', " (subordinate charm)")] elif len(self.charm_class.constraints) == 0: c_str = [('label', " no constraints set")] else: cpairs = [ format_constraint(k, v) for k, v in self.charm_class.constraints.items() ] c_str = [('label', " constraints: "), ', '.join(cpairs)] self.constraints_widget = Text(c_str) self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [self.charm_info_widget] if self.show_placements: pl.append(self.placements_widget) if self.show_constraints: pl.append(self.constraints_widget) pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2) def update(self): mstr = [""] state, cons, deps = self.controller.get_charm_state(self.charm_class) if state == CharmState.REQUIRED: p = self.controller.get_assignments(self.charm_class) d = self.controller.get_deployments(self.charm_class) nr = self.charm_class.required_num_units() info_str = " ({} of {} placed".format(len(p), nr) if len(d) > 0: info_str += ", {} deployed)".format(len(d)) else: info_str += ")" # Add hint to explain why a dep showed up in required if len(p) == 0 and len(deps) > 0: dep_str = ", ".join([c.display_name for c in deps]) info_str += " - required by {}".format(dep_str) self.title_markup[1] = ('info', info_str) self.charm_info_widget.set_text(self.title_markup) elif state == CharmState.CONFLICTED: con_str = ", ".join([c.display_name for c in cons]) self.title_markup[1] = ('error_icon', ' - Conflicts with {}'.format(con_str)) self.charm_info_widget.set_text(self.title_markup) elif state == CharmState.OPTIONAL: self.title_markup[1] = "" self.charm_info_widget.set_text(self.title_markup) def string_for_placement_dict(d): s = [] for atype, ml in d.items(): n = len(ml) s.append(('label', " {} ({}): ".format(atype.name, n))) if len(ml) == 0: s.append("\N{DOTTED CIRCLE}") else: s.append(", ".join( ["\N{TAPE DRIVE} {}".format(m.hostname) for m in ml])) if len(s) == 0: return [('label', "None")] return s mstr += [" ", ('label', "Assignments: ")] ad = self.controller.get_assignments(self.charm_class) dd = self.controller.get_deployments(self.charm_class) mstr += string_for_placement_dict(ad) mstr += ["\n ", ('label', "Deployments: ")] mstr += string_for_placement_dict(dd) self.placements_widget.set_text(mstr) self.update_buttons() def update_buttons(self): buttons = [] for at in self.actions: if len(at) == 2: def predicate(x): return True label, func = at else: predicate, label, func = at if not predicate(self.charm_class): b = AttrMap(SelectableIcon(" (" + label + ")"), 'disabled_button', 'disabled_button_focus') else: b = AttrMap( Button(label, on_press=func, user_data=self.charm_class), 'button_secondary', 'button_secondary focus') buttons.append((b, self.button_grid.options())) self.button_grid.contents = buttons
class ServicesColumn(WidgetWrap): """Displays dynamic list of unplaced services and associated controls """ 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): self.deploy_view = DeployView(self.display_controller, self.placement_controller, self.placement_view) actions = [("Choose Machine", self.placement_view.do_show_machine_chooser)] self.required_services_list = ServicesList(self.placement_controller, actions, unplaced_only=True, show_type='required', show_constraints=True, title="Required Services") self.additional_services_list = ServicesList(self.placement_controller, actions, show_type='non-required', show_constraints=True, title="Additional " "Services") autoplace_func = self.placement_view.do_autoplace self.autoplace_button = AttrMap(Button("Auto-place Remaining Services", on_press=autoplace_func), 'button_secondary', 'button_secondary focus') clear_all_func = self.placement_view.do_clear_all self.clear_all_button = AttrMap(Button("Clear all Placements", on_press=clear_all_func), 'button_secondary', 'button_secondary focus') self.required_services_pile = Pile([self.required_services_list, Divider()]) self.additional_services_pile = Pile([self.additional_services_list, Divider()]) self.top_buttons = [] self.top_button_grid = GridFlow(self.top_buttons, 36, 1, 0, 'center') pl = [Text(('subheading', "Services"), align='center'), Divider(), self.top_button_grid, Divider(), self.deploy_view, Divider(), self.required_services_pile, Divider(), self.additional_services_pile] self.main_pile = Pile(pl) return self.main_pile def update(self): self.deploy_view.update() self.required_services_list.update() self.additional_services_list.update() top_buttons = [] if len(self.placement_controller.unplaced_services) == 0: icon = SelectableIcon(" (Auto-place Remaining Services) ") top_buttons.append((AttrMap(icon, 'disabled_button', 'disabled_button_focus'), self.top_button_grid.options())) else: top_buttons.append((self.autoplace_button, self.top_button_grid.options())) top_buttons.append((self.clear_all_button, self.top_button_grid.options())) self.top_button_grid.contents = top_buttons def do_reset_to_defaults(self, sender): self.placement_controller.set_all_assignments( self.placement_controller.gen_defaults())
class MachineWidget(WidgetWrap): """A widget displaying a service and associated actions. machine - the machine to display controller - a PlacementController instance actions - a list of ('label', function) pairs that wil be used to create buttons for each machine. The machine will be passed to the function as userdata. optionally, actions can be a 3-tuple (pred, 'label', function), where pred determines whether to add the button. Pred will be passed the charm class. show_hardware - display hardware details about this machine show_assignments - display info about which charms are assigned and what assignment type (LXC, KVM, etc) they have. """ def __init__(self, machine, controller, actions=None, show_hardware=False, show_assignments=True): self.machine = machine self.controller = controller if actions is None: self.actions = [] else: self.actions = actions self.show_hardware = show_hardware self.show_assignments = show_assignments w = self.build_widgets() self.update() super().__init__(w) def selectable(self): return True def hardware_info_markup(self): m = self.machine return [('label', 'arch'), ' {} '.format(m.arch), ('label', 'cores'), ' {} '.format(m.cpu_cores), ('label', 'mem'), ' {} '.format(m.mem), ('label', 'storage'), ' {}'.format(m.storage)] def build_widgets(self): self.machine_info_widget = Text("") self.assignments_widget = Text("") self.hardware_widget = Text("") self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [Divider(' '), self.machine_info_widget] if self.show_hardware: pl.append(self.hardware_widget) if self.show_assignments: pl += [Divider(' '), self.assignments_widget] pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2) def update_machine(self): """Refresh with potentially updated machine info from controller. Assumes that machine exists - machines going away is handled in machineslist.update(). """ self.machine = next((m for m in self.controller.machines() if m.instance_id == self.machine.instance_id), None) def update(self): self.update_machine() if self.machine == self.controller.sub_placeholder: self.machine_info_widget.set_text("\N{BULLET} Subordinate Charms") self.hardware_widget.set_text("") elif self.machine == self.controller.def_placeholder: self.machine_info_widget.set_text("\N{BULLET} Juju Default " "Placement") self.hardware_widget.set_text("") else: info_markup = [ "\N{TAPE DRIVE} {}".format(self.machine.hostname), ('label', " ({})".format(self.machine.status)) ] self.machine_info_widget.set_text(info_markup) self.hardware_widget.set_text([" "] + self.hardware_info_markup()) ad = self.controller.assignments_for_machine(self.machine) astr = [('label', " Services: ")] for atype, al in ad.items(): n = len(al) if n == 1: pl_s = "" else: pl_s = "s" if atype == AssignmentType.BareMetal: astr.append(('label', "\n {} service{}" " on Bare Metal: ".format(n, pl_s))) else: astr.append(('label', "\n {} " "{}{}: ".format(n, atype.name, pl_s))) if n == 0: astr.append("\N{EMPTY SET}") else: astr.append(", ".join( ["\N{GEAR} {}".format(c.display_name) for c in al])) if self.machine == self.controller.sub_placeholder: assignments_text = '' for _, al in ad.items(): charm_txts = ["\N{GEAR} {}".format(c.display_name) for c in al] assignments_text += ", ".join(charm_txts) else: assignments_text = astr self.assignments_widget.set_text(assignments_text) self.update_buttons() def update_buttons(self): buttons = [] for at in self.actions: if len(at) == 2: def predicate(x): return True label, func = at else: predicate, label, func = at if not predicate(self.machine): b = AttrMap(SelectableIcon(" (" + label + ")"), 'disabled_button', 'disabled_button_focus') else: b = AttrMap( Button(label, on_press=func, user_data=self.machine), 'button_secondary', 'button_secondary focus') buttons.append((b, self.button_grid.options())) self.button_grid.contents = buttons
class MachineWidget(WidgetWrap): """A widget displaying a service and associated actions. machine - the machine to display controller - a PlacementController instance actions - a list of ('label', function) pairs that wil be used to create buttons for each machine. The machine will be passed to the function as userdata. optionally, actions can be a 3-tuple (pred, 'label', function), where pred determines whether to add the button. Pred will be passed the charm class. show_hardware - display hardware details about this machine show_assignments - display info about which charms are assigned and what assignment type (LXC, KVM, etc) they have. """ def __init__(self, machine, controller, actions=None, show_hardware=False, show_assignments=True): self.machine = machine self.controller = controller if actions is None: self.actions = [] else: self.actions = actions self.show_hardware = show_hardware self.show_assignments = show_assignments w = self.build_widgets() self.update() super().__init__(w) def selectable(self): return True def hardware_info_markup(self): m = self.machine return [('label', 'arch'), ' {} '.format(m.arch), ('label', 'cores'), ' {} '.format(m.cpu_cores), ('label', 'mem'), ' {} '.format(m.mem), ('label', 'storage'), ' {}'.format(m.storage)] def build_widgets(self): self.machine_info_widget = Text("") self.assignments_widget = Text("") self.hardware_widget = Text("") self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [Divider(' '), self.machine_info_widget] if self.show_hardware: pl.append(self.hardware_widget) if self.show_assignments: pl += [Divider(' '), self.assignments_widget] pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2) def update_machine(self): """Refresh with potentially updated machine info from controller. Assumes that machine exists - machines going away is handled in machineslist.update(). """ self.machine = next((m for m in self.controller.machines() if m.instance_id == self.machine.instance_id), None) def update(self): self.update_machine() if self.machine == self.controller.sub_placeholder: self.machine_info_widget.set_text("\N{BULLET} Subordinate Charms") self.hardware_widget.set_text("") elif self.machine == self.controller.def_placeholder: self.machine_info_widget.set_text("\N{BULLET} Juju Default " "Placement") self.hardware_widget.set_text("") else: info_markup = ["\N{TAPE DRIVE} {}".format(self.machine.hostname), ('label', " ({})".format(self.machine.status))] self.machine_info_widget.set_text(info_markup) self.hardware_widget.set_text([" "] + self.hardware_info_markup()) ad = self.controller.assignments_for_machine(self.machine) astr = [('label', " Services: ")] for atype, al in ad.items(): n = len(al) if n == 1: pl_s = "" else: pl_s = "s" if atype == AssignmentType.BareMetal: astr.append(('label', "\n {} service{}" " on Bare Metal: ".format(n, pl_s))) else: astr.append(('label', "\n {} " "{}{}: ".format(n, atype.name, pl_s))) if n == 0: astr.append("\N{EMPTY SET}") else: astr.append(", ".join(["\N{GEAR} {}".format(c.display_name) for c in al])) if self.machine == self.controller.sub_placeholder: assignments_text = '' for _, al in ad.items(): charm_txts = ["\N{GEAR} {}".format(c.display_name) for c in al] assignments_text += ", ".join(charm_txts) else: assignments_text = astr self.assignments_widget.set_text(assignments_text) self.update_buttons() def update_buttons(self): buttons = [] for at in self.actions: if len(at) == 2: def predicate(x): return True label, func = at else: predicate, label, func = at if not predicate(self.machine): b = AttrMap(SelectableIcon(" (" + label + ")"), 'disabled_button', 'disabled_button_focus') else: b = AttrMap(Button(label, on_press=func, user_data=self.machine), 'button_secondary', 'button_secondary focus') buttons.append((b, self.button_grid.options())) self.button_grid.contents = buttons
class AddServicesDialog(WidgetWrap): """ Dialog to add services. Does not specify placement. :param cb: callback routine to process submit/cancel actions """ def __init__(self, install_controller, deploy_cb, cancel_cb): self.install_controller = install_controller self.orig_pc = install_controller.placement_controller self.pc = self.orig_pc.get_temp_copy() self.charms = [] self.deploy_cb = deploy_cb self.cancel_cb = cancel_cb self.boxes = [] w = self.build_widget() self.update() super().__init__(w) def build_widget(self, **kwargs): def remove_p(charm_class): n = self.pc.assignment_machine_count_for_charm(charm_class) return n > 0 def not_conflicted_p(cc): state, _, _ = self.pc.get_charm_state(cc) return state != CharmState.CONFLICTED actions = [(remove_p, 'Remove', self.do_remove), (not_conflicted_p, 'Add', self.do_add)] self.unrequired_undeployed_sl = ServicesList(self.pc, actions, actions, ignore_deployed=True, title="Un-Deployed") self.deployed_sl = ServicesList(self.pc, actions, actions, deployed_only=True, show_placements=True, title="Deployed Services") self.assigned_sl = ServicesList(self.pc, actions, actions, assigned_only=True, show_placements=True, title="Services to be Deployed") self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'center') self.pile1 = Pile([self.button_grid, self.assigned_sl, self.unrequired_undeployed_sl]) self.pile2 = Pile([self.deployed_sl]) return LineBox(Columns([self.pile1, self.pile2]), title="Add Services") def update(self): self.unrequired_undeployed_sl.update() self.deployed_sl.update() self.assigned_sl.update() self.update_buttons() def update_buttons(self): buttons = [(AttrMap(Button("Cancel", self.handle_cancel), 'button_primary', 'button_primary focus'), self.button_grid.options())] n_assigned = len(self.pc.assigned_services) if n_assigned > 0 and self.pc.can_deploy(): b = AttrMap(Button("Deploy", self.handle_deploy), 'button_primary', 'button_primary focus') else: b = AttrMap(SelectableIcon("(Deploy)"), 'disabled_button', 'disabled_button_focus') buttons.append((b, self.button_grid.options())) self.button_grid.contents = buttons def do_add(self, sender, charm_class): """Add the selected charm using default juju location. Equivalent to a simple 'juju deploy foo' Attempt to also add any charms that are required as a result of the newly added charm. """ def num_to_auto_add(charm_class): return (charm_class.required_num_units() - self.pc.assignment_machine_count_for_charm(charm_class)) for i in range(max(1, num_to_auto_add(charm_class))): self.pc.assign(self.pc.def_placeholder, charm_class, AssignmentType.DEFAULT) def get_unassigned_requireds(): return [cc for cc in self.pc.unassigned_undeployed_services() if self.pc.get_charm_state(cc)[0] == CharmState.REQUIRED] while len(get_unassigned_requireds()) > 0: for u_r_cc in get_unassigned_requireds(): for i in range(num_to_auto_add(u_r_cc)): self.pc.assign(self.pc.def_placeholder, u_r_cc, AssignmentType.DEFAULT) self.update() def do_remove(self, sender, charm_class): "Undo an assignment" self.pc.remove_one_assignment(self.pc.def_placeholder, charm_class) self.update() def handle_deploy(self, button): """Commits changes to the main placement controller, and calls the deploy callback to do the rest. """ self.orig_pc.update_from_controller(self.pc) self.deploy_cb() def handle_cancel(self, button): self.cancel_cb()
class ServicesColumn(WidgetWrap): """Displays dynamic list of unplaced services and associated controls """ 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): self.deploy_view = DeployView(self.display_controller, self.placement_controller, self.placement_view) def not_conflicted_p(cc): state, _, _ = self.placement_controller.get_charm_state(cc) return state != CharmState.CONFLICTED actions = [(not_conflicted_p, "Choose Machine", self.placement_view.do_show_machine_chooser)] subordinate_actions = [(not_conflicted_p, "Add", self.do_place_subordinate)] self.required_services_list = ServicesList(self.placement_controller, actions, subordinate_actions, ignore_assigned=True, ignore_deployed=True, show_type='required', show_constraints=True, title="Required Services") self.additional_services_list = ServicesList(self.placement_controller, actions, subordinate_actions, ignore_assigned=True, show_type='non-required', show_constraints=True, title="Additional " "Services") autoplace_func = self.placement_view.do_autoplace self.autoplace_button = AttrMap(Button("Auto-place Remaining Services", on_press=autoplace_func), 'button_secondary', 'button_secondary focus') clear_all_func = self.placement_view.do_clear_all self.clear_all_button = AttrMap(Button("Clear All Placements", on_press=clear_all_func), 'button_secondary', 'button_secondary focus') self.required_services_pile = Pile([self.required_services_list, Divider()]) self.additional_services_pile = Pile([self.additional_services_list, Divider()]) self.top_buttons = [] self.top_button_grid = GridFlow(self.top_buttons, 36, 1, 0, 'center') pl = [ Text(("body", "Services"), align='center'), Divider(), self.top_button_grid, Divider(), self.deploy_view, Divider(), self.required_services_pile, Divider(), self.additional_services_pile ] self.main_pile = Pile(pl) return self.main_pile def update(self): self.deploy_view.update() self.required_services_list.update() self.additional_services_list.update() top_buttons = [] unplaced = self.placement_controller.unassigned_undeployed_services() if len(unplaced) == 0: icon = SelectableIcon(" (Auto-place Remaining Services) ") top_buttons.append((AttrMap(icon, 'disabled_button', 'disabled_button_focus'), self.top_button_grid.options())) else: top_buttons.append((self.autoplace_button, self.top_button_grid.options())) top_buttons.append((self.clear_all_button, self.top_button_grid.options())) self.top_button_grid.contents = top_buttons def do_reset_to_defaults(self, sender): self.placement_controller.set_all_assignments( self.placement_controller.gen_defaults()) def do_place_subordinate(self, sender, charm_class): sub_placeholder = self.placement_controller.sub_placeholder self.placement_controller.assign(sub_placeholder, charm_class, AssignmentType.BareMetal)
class ServiceWidget(WidgetWrap): """A widget displaying a service and associated actions. charm_class - the class describing the service to display controller - a PlacementController instance actions - a list of ('label', function) pairs that will be used to create buttons for each machine. The machine will be passed to the function as userdata. optionally, actions can be a 3-tuple (pred, 'label', function), where pred determines whether to add the button. Pred will be passed the charm class. show_constraints - display the charm's constraints 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, charm_class, controller, actions=None, show_constraints=False, show_placements=False): self.charm_class = charm_class self.controller = controller if actions is None: self.actions = [] else: self.actions = actions self.show_constraints = show_constraints self.show_placements = show_placements w = self.build_widgets() self.update() super().__init__(w) def selectable(self): return True def build_widgets(self): dn = self.charm_class.display_name self.title_markup = ["\N{GEAR} {}".format(dn), ""] if self.charm_class.summary != "": self.title_markup.append("\n {}\n".format(self.charm_class.summary)) self.charm_info_widget = Text(self.title_markup) self.placements_widget = Text("") if self.charm_class.subordinate: c_str = [('label', " (subordinate charm)")] elif len(self.charm_class.constraints) == 0: c_str = [('label', " no constraints set")] else: cpairs = [format_constraint(k, v) for k, v in self.charm_class.constraints.items()] c_str = [('label', " constraints: "), ', '.join(cpairs)] self.constraints_widget = Text(c_str) self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'right') pl = [self.charm_info_widget] if self.show_placements: pl.append(self.placements_widget) if self.show_constraints: pl.append(self.constraints_widget) pl.append(self.button_grid) p = Pile(pl) return Padding(p, left=2, right=2) def update(self): mstr = [""] state, cons, deps = self.controller.get_charm_state(self.charm_class) if state == CharmState.REQUIRED: p = self.controller.get_assignments(self.charm_class) d = self.controller.get_deployments(self.charm_class) nr = self.charm_class.required_num_units() info_str = " ({} of {} placed".format(len(p), nr) if len(d) > 0: info_str += ", {} deployed)".format(len(d)) else: info_str += ")" # Add hint to explain why a dep showed up in required if len(p) == 0 and len(deps) > 0: dep_str = ", ".join([c.display_name for c in deps]) info_str += " - required by {}".format(dep_str) self.title_markup[1] = ('info', info_str) self.charm_info_widget.set_text(self.title_markup) elif state == CharmState.CONFLICTED: con_str = ", ".join([c.display_name for c in cons]) self.title_markup[1] = ('error_icon', ' - Conflicts with {}'.format(con_str)) self.charm_info_widget.set_text(self.title_markup) elif state == CharmState.OPTIONAL: self.title_markup[1] = "" self.charm_info_widget.set_text(self.title_markup) def string_for_placement_dict(d): s = [] for atype, ml in d.items(): n = len(ml) s.append(('label', " {} ({}): ".format(atype.name, n))) if len(ml) == 0: s.append("\N{DOTTED CIRCLE}") else: s.append(", ".join(["\N{TAPE DRIVE} {}".format(m.hostname) for m in ml])) if len(s) == 0: return [('label', "None")] return s mstr += [" ", ('label', "Assignments: ")] ad = self.controller.get_assignments(self.charm_class) dd = self.controller.get_deployments(self.charm_class) mstr += string_for_placement_dict(ad) mstr += ["\n ", ('label', "Deployments: ")] mstr += string_for_placement_dict(dd) self.placements_widget.set_text(mstr) self.update_buttons() def update_buttons(self): buttons = [] for at in self.actions: if len(at) == 2: def predicate(x): return True label, func = at else: predicate, label, func = at if not predicate(self.charm_class): b = AttrMap(SelectableIcon(" (" + label + ")"), 'disabled_button', 'disabled_button_focus') else: b = AttrMap(Button(label, on_press=func, user_data=self.charm_class), 'button_secondary', 'button_secondary focus') buttons.append((b, self.button_grid.options())) self.button_grid.contents = buttons