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 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 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
class MachinesList(WidgetWrap): """A list of machines with configurable action buttons for each machine. select_cb - a function that takes a machine and assignmenttype to perform the button action unselect_cb - a function that takes a machine and clears assignments target_info - a string that describes what you're picking a machine for current_pin_cb - a function that takes a machine and returns None if the machine is not currently selected, or returns a context value to show the user about the current selection (like a juju machine ID that represents the current pin) 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_only_ready - bool, only show machines with a ready state. show_filter_box - bool, show text box to filter listed machines """ def __init__(self, select_cb, unselect_cb, target_info, current_pin_cb, constraints=None, show_hardware=False, title_widgets=None, show_only_ready=False, show_filter_box=False): self.select_cb = select_cb self.unselect_cb = unselect_cb self.target_info = target_info self.current_pin_cb = current_pin_cb self.n_selected = 0 self.machine_widgets = [] if constraints is None: self.constraints = {} else: self.constraints = constraints self.show_hardware = show_hardware self.show_only_ready = show_only_ready self.show_filter_box = show_filter_box self.filter_string = "" self.loading = False w = self.build_widgets(title_widgets) self.update() super().__init__(w) def __repr__(self): return "machineslist" 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 '{}'".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 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): if app.maas.client: machines = app.maas.client.get_machines() else: machines = None if machines is None: if not self.loading: self.loading = True p = (Text("\n\nLoading...", align='center'), self.machine_pile.options()) self.machine_pile.contents.append(p) return if self.loading: self.loading = False self.machine_pile.contents = self.machine_pile.contents[:-1] 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 filter_label = m.filter_label() 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 = MachineWidget(machine, self.handle_select, self.handle_unselect, self.target_info, self.current_pin_cb) self.machine_widgets.append(mw) options = self.machine_pile.options() self.machine_pile.contents.append((mw, 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, MachineWidget): 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 handle_select(self, machine): self.select_cb(machine) self.n_selected += 1 self.update() def handle_unselect(self, machine): self.unselect_cb(machine) self.n_selected -= 1 self.update()
class JujuMachinesList(WidgetWrap): """A list of machines in a juju bundle, with configurable action buttons for each machine. application - an application machines - list of machine info dicts assign_cb - a function that takes a machine and assignmenttype to perform the button action unassign_cb - a function that takes a machine and clears assignments controller - a controller object that provides get_placements and get_pin. show_constraints - bool, whether or not to show the constraints for each of the machines title_widgets - A list of widgets 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_filter_box - bool, show text box to filter listed machines """ def __init__(self, application, machines, assign_cb, unassign_cb, add_machine_cb, remove_machine_cb, controller, show_constraints=True, title_widgets=None, show_assignments=True, show_filter_box=False, show_pins=False): self.application = application self.machines = machines self.assign_cb = assign_cb self.unassign_cb = unassign_cb self.add_machine_cb = add_machine_cb self.remove_machine_cb = remove_machine_cb self.controller = controller self.machine_widgets = [] self.show_assignments = show_assignments self.all_assigned = False self.show_filter_box = show_filter_box self.show_pins = show_pins self.filter_string = "" w = self.build_widgets(title_widgets) self.update() super().__init__(w) def __repr__(self): return "machineslist" 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: 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 do_add_machine(self, sender): self.add_machine_cb() self.update() def remove_machine(self, sender): self.remove_machine_cb() self.update() def handle_filter_change(self, edit_button, userdata): self.filter_string = userdata self.update() def find_machine_widget(self, midx): return next( (mw for mw in self.machine_widgets if mw.juju_machine_id == midx), None) def update(self): for midx, md in sorted(self.machines.items()): allvalues = ["{}={}".format(k, v) for k, v in md.items()] filter_label = midx + " " + " ".join(allvalues) if self.filter_string != "" and \ self.filter_string not in filter_label: self.remove_machine_widget(midx) continue mw = self.find_machine_widget(midx) if mw is None: mw = self.add_machine_widget(midx, md) mw.all_assigned = self.all_assigned mw.update() n = len(self.machine_widgets) self.filter_edit_box.set_info(n, n) self.sort_machine_widgets() def add_machine_widget(self, midx, md): mw = JujuMachineWidget(midx, md, self.application, self.assign_cb, self.unassign_cb, self.controller, self.show_assignments, self.show_pins) self.machine_widgets.append(mw) options = self.machine_pile.options() self.machine_pile.contents.insert( len(self.machine_pile.contents) - 1, (mw, options)) return mw def remove_machine_widget(self, midx): mw = self.find_machine_widget(midx) 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): self.machine_widgets.sort(key=attrgetter('juju_machine_id')) def wrappedkeyfunc(t): mw, options = t if isinstance(mw, JujuMachineWidget): return "B{}".format(mw.juju_machine_id) if mw in [self.add_new_cols]: return 'C' return 'A' 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")
class MachinesList(WidgetWrap): """A list of machines with configurable action buttons for each machine. select_cb - a function that takes a machine and assignmenttype to perform the button action unselect_cb - a function that takes a machine and clears assignments target_info - a string that describes what you're picking a machine for current_pin_cb - a function that takes a machine and returns None if the machine is not currently selected, or returns a context value to show the user about the current selection (like a juju machine ID that represents the current pin) 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_only_ready - bool, only show machines with a ready state. show_filter_box - bool, show text box to filter listed machines """ def __init__(self, select_cb, unselect_cb, target_info, current_pin_cb, constraints=None, show_hardware=False, title_widgets=None, show_only_ready=False, show_filter_box=False): self.select_cb = select_cb self.unselect_cb = unselect_cb self.target_info = target_info self.current_pin_cb = current_pin_cb self.n_selected = 0 self.machine_widgets = [] if constraints is None: self.constraints = {} else: self.constraints = constraints self.show_hardware = show_hardware self.show_only_ready = show_only_ready self.show_filter_box = show_filter_box self.filter_string = "" self.loading = False w = self.build_widgets(title_widgets) self.update() super().__init__(w) def __repr__(self): return "machineslist" 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 '{}'".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 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): if app.maas.client: machines = app.maas.client.get_machines() else: machines = None if machines is None: if not self.loading: self.loading = True p = (Text("\n\nLoading...", align='center'), self.machine_pile.options()) self.machine_pile.contents.append(p) return if self.loading: self.loading = False self.machine_pile.contents = self.machine_pile.contents[:-1] 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 filter_label = m.filter_label() 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 = MachineWidget(machine, self.handle_select, self.handle_unselect, self.target_info, self.current_pin_cb) self.machine_widgets.append(mw) options = self.machine_pile.options() self.machine_pile.contents.append((mw, 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, MachineWidget): 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 handle_select(self, machine): self.select_cb(machine) self.n_selected += 1 self.update() def handle_unselect(self, machine): self.unselect_cb(machine) self.n_selected -= 1 self.update()
class JujuMachinesList(WidgetWrap): """A list of machines in a juju bundle, with configurable action buttons for each machine. application - an application machines - list of machine info dicts assign_cb - a function that takes a machine and assignmenttype to perform the button action unassign_cb - a function that takes a machine and clears assignments controller - a controller object that provides get_placements and get_pin. show_constraints - bool, whether or not to show the constraints for each of the machines title_widgets - A list of widgets 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_filter_box - bool, show text box to filter listed machines """ def __init__(self, application, machines, assign_cb, unassign_cb, add_machine_cb, remove_machine_cb, controller, show_constraints=True, title_widgets=None, show_assignments=True, show_filter_box=False, show_pins=False): self.application = application self.machines = machines self.assign_cb = assign_cb self.unassign_cb = unassign_cb self.add_machine_cb = add_machine_cb self.remove_machine_cb = remove_machine_cb self.controller = controller self.machine_widgets = [] self.show_assignments = show_assignments self.all_assigned = False self.show_filter_box = show_filter_box self.show_pins = show_pins self.filter_string = "" w = self.build_widgets(title_widgets) self.update() super().__init__(w) def __repr__(self): return "machineslist" 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: 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 do_add_machine(self, sender): self.add_machine_cb() self.update() def remove_machine(self, sender): self.remove_machine_cb() self.update() def handle_filter_change(self, edit_button, userdata): self.filter_string = userdata self.update() def find_machine_widget(self, midx): return next((mw for mw in self.machine_widgets if mw.juju_machine_id == midx), None) def update(self): for midx, md in sorted(self.machines.items()): allvalues = ["{}={}".format(k, v) for k, v in md.items()] filter_label = midx + " " + " ".join(allvalues) if self.filter_string != "" and \ self.filter_string not in filter_label: self.remove_machine_widget(midx) continue mw = self.find_machine_widget(midx) if mw is None: mw = self.add_machine_widget(midx, md) mw.all_assigned = self.all_assigned mw.update() n = len(self.machine_widgets) self.filter_edit_box.set_info(n, n) self.sort_machine_widgets() def add_machine_widget(self, midx, md): mw = JujuMachineWidget(midx, md, self.application, self.assign_cb, self.unassign_cb, self.controller, self.show_assignments, self.show_pins) self.machine_widgets.append(mw) options = self.machine_pile.options() self.machine_pile.contents.insert( len(self.machine_pile.contents) - 1, (mw, options)) return mw def remove_machine_widget(self, midx): mw = self.find_machine_widget(midx) 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): self.machine_widgets.sort(key=attrgetter('juju_machine_id')) def wrappedkeyfunc(t): mw, options = t if isinstance(mw, JujuMachineWidget): return "B{}".format(mw.juju_machine_id) if mw in [self.add_new_cols]: return 'C' return 'A' 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")