Beispiel #1
0
    def build_unselected_widgets(self):
        cdict = juju.constraints_to_dict(self.md.get('constraints', ''))

        self.juju_machine_id_button = MenuSelectButton(
            '{:20s}'.format(self.juju_machine_id), self.show_pin_chooser)
        self.juju_machine_id_label = Text("{:20s}".format(
            self.juju_machine_id))
        self.cores_field = IntEdit('', str(cdict.get('cores', '')))
        connect_signal(self.cores_field, 'change', self.handle_cores_changed)
        memval = cdict.get('mem', '')
        if memval != '':
            memval = memval / 1024
        self.mem_field = Edit('', str(memval))
        connect_signal(self.mem_field, 'change', self.handle_mem_changed)
        diskval = cdict.get('root-disk', '')
        if diskval != '':
            diskval = diskval / 1024
        self.disk_field = Edit('', str(diskval))
        connect_signal(self.disk_field, 'change', self.handle_disk_changed)

        if self.show_pin:
            machine_id_w = self.juju_machine_id_button
        else:
            machine_id_w = self.juju_machine_id_label
        cols = [
            machine_id_w, self.cores_field, self.mem_field, self.disk_field
        ]
        cols = [AttrMap(w, 'string_input', 'string_input_focus') for w in cols]
        cols.append(Text("placeholder"))
        self.unselected_columns = Columns(cols, dividechars=2)
        self.update_assignments()
        return self.unselected_columns
    def build_widgets(self):

        self.button = MenuSelectButton("I AM A MACHINE", self.do_select)
        self.action_button_cols = Columns([])
        self.action_buttons = []

        self.pile = Pile([self.button])
        return self.pile
    def build_widgets(self):

        self.button = MenuSelectButton("I AM A SERVICE", self.do_select)

        self.action_button_cols = Columns([], dividechars=1)
        self.action_buttons = []

        self.pile = Pile([self.button])
        return self.pile
class RelationWidget(WidgetWrap):

    def __init__(self, source_service_name, source_relname, interface,
                 reltype, target_service_name, target_relname,
                 placement_controller, select_cb):
        self.source_service_name = source_service_name
        self.source_relname = source_relname
        self.interface = interface
        self.reltype = reltype
        self.target_service_name = target_service_name
        self.target_relname = target_relname
        self.select_cb = select_cb
        self.placement_controller = placement_controller
        w = self.build_widgets()
        super().__init__(w)
        self.update()

    def selectable(self):
        return True

    def build_widgets(self):

        self.button = MenuSelectButton("",
                                       on_press=self.do_select)
        return AttrMap(self.button, 'text',
                       'button_secondary focus')

    def update(self):
        arrow = {RelationType.Provides: "\N{RIGHTWARDS ARROW}",
                 RelationType.Requires:
                 "\N{LEFTWARDS ARROW}"}[self.reltype]

        pc = self.placement_controller
        self.connected = pc.is_related(self.source_service_name,
                                       self.source_relname,
                                       self.target_service_name,
                                       self.target_relname)

        connstr = {True: "\N{CHECK MARK} ",
                   False: "  "}[self.connected]

        title = "{} {} {}  {}:{}".format(connstr,
                                         self.source_relname,
                                         arrow,
                                         self.target_service_name,
                                         self.target_relname)
        self.button.set_label(title)

    def do_select(self, sender):
        self.select_cb(self.source_relname, self.target_service_name,
                       self.target_relname)
    def build_unselected_widgets(self):
        cdict = juju.constraints_to_dict(self.md.get('constraints', ''))

        self.juju_machine_id_button = MenuSelectButton(
            '{:20s}'.format(self.juju_machine_id), self.show_pin_chooser)
        self.juju_machine_id_label = Text(
            "{:20s}".format(self.juju_machine_id))
        self.cores_field = IntEdit('', cdict.get('cores', ''))
        connect_signal(self.cores_field, 'change', self.handle_cores_changed)
        self.mem_field = Edit('', cdict.get('mem', ''))
        connect_signal(self.mem_field, 'change', self.handle_mem_changed)
        self.disk_field = Edit('', cdict.get('root-disk', ''))
        connect_signal(self.disk_field, 'change', self.handle_disk_changed)

        if self.show_pin:
            machine_id_w = self.juju_machine_id_button
        else:
            machine_id_w = self.juju_machine_id_label
        cols = [machine_id_w, self.cores_field,
                self.mem_field, self.disk_field]
        cols = [AttrMap(w, 'string_input',
                        'string_input_focus') for w in cols]
        cols.append(Text("placeholder"))
        self.unselected_columns = Columns(cols, dividechars=2)
        self.update_assignments()
        return self.unselected_columns
    def build_widgets(self):

        self.button = MenuSelectButton("I AM A MACHINE", self.do_select)
        self.action_button_cols = Columns([])
        self.action_buttons = []

        self.pile = Pile([self.button])
        return self.pile
class RelationWidget(WidgetWrap):
    def __init__(self, source_service_name, source_relname, interface, reltype,
                 target_service_name, target_relname, placement_controller,
                 select_cb):
        self.source_service_name = source_service_name
        self.source_relname = source_relname
        self.interface = interface
        self.reltype = reltype
        self.target_service_name = target_service_name
        self.target_relname = target_relname
        self.select_cb = select_cb
        self.placement_controller = placement_controller
        w = self.build_widgets()
        super().__init__(w)
        self.update()

    def selectable(self):
        return True

    def build_widgets(self):

        self.button = MenuSelectButton("", on_press=self.do_select)
        return AttrMap(self.button, 'text', 'button_secondary focus')

    def update(self):
        arrow = {
            RelationType.Provides: "\N{RIGHTWARDS ARROW}",
            RelationType.Requires: "\N{LEFTWARDS ARROW}"
        }[self.reltype]

        pc = self.placement_controller
        self.connected = pc.is_related(self.source_service_name,
                                       self.source_relname,
                                       self.target_service_name,
                                       self.target_relname)

        connstr = {True: "\N{CHECK MARK} ", False: "  "}[self.connected]

        title = "{} {} {}  {}:{}".format(connstr, self.source_relname, arrow,
                                         self.target_service_name,
                                         self.target_relname)
        self.button.set_label(title)

    def do_select(self, sender):
        self.select_cb(self.source_relname, self.target_service_name,
                       self.target_relname)
    def build_widgets(self):

        self.button = MenuSelectButton("I AM A SERVICE", self.do_select)

        self.action_button_cols = Columns([], dividechars=1)
        self.action_buttons = []

        self.pile = Pile([self.button])
        return self.pile
Beispiel #9
0
    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 __init__(self, relname, iface, reltype):
     other_reltype = {
         RelationType.Requires: RelationType.Provides,
         RelationType.Provides: RelationType.Requires
     }[reltype]
     self.source_relname = relname
     self.reltype = reltype
     s = "({}: nothing in bundle {} {})".format(relname,
                                                other_reltype.name.lower(),
                                                iface)
     super().__init__(
         AttrMap(MenuSelectButton(s), 'label', 'button_secondary focus'))
Beispiel #11
0
 def build_widgets(self):
     self.charm_name = self.md['Name']
     summary = self.md['Summary']
     top = []
     pad = 0
     if self.header:
         top = [Text("\n{}\n".format(self.header))]
     if self.recommended:
         pad = 2
     s = "{} ({})\n  {}\n".format(self.charm_name, self.charm_source,
                                  summary)
     b = MenuSelectButton(s, on_press=self.handle_press)
     return Pile(
         top +
         [AttrMap(Padding(b, left=pad), 'text', 'button_secondary focus')])
Beispiel #12
0
    def build_widgets(self):
        name_with_rev = self.bundle_source.split("/")[-1]
        bundle_name = "-".join(name_with_rev.split("-")[:-1])

        if 'Machines' in self.md:
            summary = ("{} services "
                       "on {} machines").format(len(self.md['Services']),
                                                len(self.md['Machines']))
        else:
            summary = "{} services".format(len(self.md['Services']))
        s = "{} ({})\n    {}\n".format(bundle_name, self.bundle_source,
                                       summary)

        top = []
        if self.header:
            top = [Text("\n{}\n".format(self.header))]

        b = MenuSelectButton(s, on_press=self.handle_press)
        return Pile(top + [AttrMap(b, 'text', 'button_secondary focus')])
Beispiel #13
0
class JujuMachineWidget(WidgetWrap):
    """A widget displaying a machine and action buttons.

    juju_machine_id = juju id of the machine

    md = the machine dict

    application - the current application for which machines are being shown

    assign_cb - a function that takes a machine and assignmenttype to
    perform the button action

    unassign_cb - a function that takes a machine and removes
    assignments for the machine

    controller - a controller object that provides show_pin_chooser

    show_assignments - display info about which charms are assigned
    and what assignment type (LXC, KVM, etc) they have.

    show_pin - show button to pin juju machine to a maas machine

    """
    def __init__(self,
                 juju_machine_id,
                 md,
                 application,
                 assign_cb,
                 unassign_cb,
                 controller,
                 show_assignments=True,
                 show_pin=False):
        self.juju_machine_id = juju_machine_id
        self.md = md
        self.application = application
        self.is_selected = False
        self.assign_cb = assign_cb
        self.unassign_cb = unassign_cb
        self.controller = controller
        self.show_assignments = show_assignments
        self.show_pin = show_pin
        self.all_assigned = False

        w = self.build_widgets()
        super().__init__(w)
        self.update()

    def selectable(self):
        return True

    def __repr__(self):
        return "jujumachinewidget #" + str(self.juju_machine_id)

    def build_widgets(self):

        self.action_button_cols = Columns([])
        self.action_buttons = []
        self.build_unselected_widgets()
        self.pile = Pile([self.unselected_columns])
        return self.pile

    def build_unselected_widgets(self):
        cdict = juju.constraints_to_dict(self.md.get('constraints', ''))

        self.juju_machine_id_button = MenuSelectButton(
            '{:20s}'.format(self.juju_machine_id), self.show_pin_chooser)
        self.juju_machine_id_label = Text("{:20s}".format(
            self.juju_machine_id))
        self.cores_field = IntEdit('', str(cdict.get('cores', '')))
        connect_signal(self.cores_field, 'change', self.handle_cores_changed)
        memval = cdict.get('mem', '')
        if memval != '':
            memval = memval / 1024
        self.mem_field = Edit('', str(memval))
        connect_signal(self.mem_field, 'change', self.handle_mem_changed)
        diskval = cdict.get('root-disk', '')
        if diskval != '':
            diskval = diskval / 1024
        self.disk_field = Edit('', str(diskval))
        connect_signal(self.disk_field, 'change', self.handle_disk_changed)

        if self.show_pin:
            machine_id_w = self.juju_machine_id_button
        else:
            machine_id_w = self.juju_machine_id_label
        cols = [
            machine_id_w, self.cores_field, self.mem_field, self.disk_field
        ]
        cols = [AttrMap(w, 'string_input', 'string_input_focus') for w in cols]
        cols.append(Text("placeholder"))
        self.unselected_columns = Columns(cols, dividechars=2)
        self.update_assignments()
        return self.unselected_columns

    def update_assignments(self):
        assignments = []

        mps = self.controller.get_all_assignments(self.juju_machine_id)
        if len(mps) > 0:
            if self.show_assignments:
                ad = defaultdict(list)
                for application, atype in mps:
                    ad[atype].append(application)
                astr = " ".join([
                    "{}{}".format(
                        atype_to_label([atype])[0], ",".join(
                            [application.service_name for application in al]))
                    for atype, al in ad.items()
                ])
                assignments.append(astr)
        else:
            if self.show_assignments:
                assignments.append("-")

        if any([application == self.application for application, _ in mps]):
            action = self.do_remove
            label = "Remove"
        else:
            action = self.do_select
            label = "Select"

        self.select_button = PlainButton(label, action)

        cols = [Text(s) for s in assignments]

        current_assignments = [a for a, _ in mps if a == self.application]
        if self.all_assigned and len(current_assignments) == 0:
            cols.append(Text(""))
        else:
            cols += [
                AttrMap(self.select_button, 'text', 'button_secondary focus')
            ]
        opts = self.unselected_columns.options()
        self.unselected_columns.contents[4:] = [(w, opts) for w in cols]

    def update(self):
        self.update_action_buttons()

        if self.is_selected:
            self.update_selected()
        else:
            self.update_unselected()

    def update_selected(self):
        cn = self.application.service_name
        msg = Text("  Add {} to machine #{}:".format(cn, self.juju_machine_id))
        self.pile.contents = [(msg, self.pile.options()),
                              (self.action_button_cols, self.pile.options()),
                              (Divider(), self.pile.options())]

    def update_unselected(self):
        if self.show_pin:
            pinned_machine = self.controller.get_pin(self.juju_machine_id)

            if pinned_machine:
                pin_label = " {} \N{PENCIL}".format(pinned_machine.hostname)
            else:
                pin_label = " \N{PENCIL}"
            self.juju_machine_id_button.set_label(
                '{:20s}'.format(self.juju_machine_id + " " + pin_label))
        else:
            self.juju_machine_id_label.set_text('{:20s}'.format(
                self.juju_machine_id))

        self.pile.contents = [(self.unselected_columns, self.pile.options()),
                              (Divider(), self.pile.options())]
        self.update_assignments()

    def update_action_buttons(self):

        all_actions = [(AssignmentType.BareMetal, 'Add as Bare Metal',
                        self.select_baremetal),
                       (AssignmentType.LXD, 'Add as LXD', self.select_lxd),
                       (AssignmentType.KVM, 'Add as KVM', self.select_kvm)]

        sc = self.application
        if sc:
            allowed_set = set(sc.allowed_assignment_types)
            allowed_types = set([atype for atype, _, _ in all_actions])
            allowed_types = allowed_types.intersection(allowed_set)
        else:
            allowed_types = set()

        # + 1 for the cancel button:
        if len(self.action_buttons) == len(allowed_types) + 1:
            return

        self.action_buttons = [
            AttrMap(PlainButton(label, on_press=func), 'button_secondary',
                    'button_secondary focus')
            for atype, label, func in all_actions if atype in allowed_types
        ]
        self.action_buttons.append(
            AttrMap(PlainButton("Cancel", on_press=self.do_cancel),
                    'button_secondary', 'button_secondary focus'))

        opts = self.action_button_cols.options()
        self.action_button_cols.contents = [(b, opts)
                                            for b in self.action_buttons]

    def do_select(self, sender):
        self.is_selected = True
        self.update()
        self.pile.focus_position = 1
        self.action_button_cols.focus_position = 0

    def do_remove(self, sender):
        self.unassign_cb(self.juju_machine_id)

    def do_cancel(self, sender):
        self.is_selected = False
        self.update()
        self.pile.focus_position = 0

    def _do_select_assignment(self, atype):
        self.assign_cb(self.juju_machine_id, atype)
        self.pile.focus_position = 0
        self.is_selected = False
        self.update()

    def select_baremetal(self, sender):
        self._do_select_assignment(AssignmentType.BareMetal)

    def select_lxd(self, sender):
        self._do_select_assignment(AssignmentType.LXD)

    def select_kvm(self, sender):
        self._do_select_assignment(AssignmentType.KVM)

    def handle_cores_changed(self, sender, val):
        if val == '':
            self.md = self.controller.clear_constraint(self.juju_machine_id,
                                                       'cores')
        else:
            self.md = self.controller.set_constraint(self.juju_machine_id,
                                                     'cores', val)

    def _format_constraint(self, val):
        "Store constraints as int values of megabytes"
        if val.isnumeric():
            return int(val) * 1024
        else:
            return units.human_to_mb(val)

    def handle_mem_changed(self, sender, val):
        if val == '':
            self.md = self.controller.clear_constraint(self.juju_machine_id,
                                                       'mem')
        else:
            try:
                self.md = self.controller.set_constraint(
                    self.juju_machine_id, 'mem', self._format_constraint(val))
            except ValueError:
                return

    def handle_disk_changed(self, sender, val):
        if val == '':
            self.md = self.controller.clear_constraint(self.juju_machine_id,
                                                       'root-disk')
        else:
            try:
                self.md = self.controller.set_constraint(
                    self.juju_machine_id, 'root-disk',
                    self._format_constraint(val))
            except ValueError:
                return

    def show_pin_chooser(self, sender):
        self.controller.show_pin_chooser(self.juju_machine_id)
class SimpleServiceWidget(WidgetWrap):

    """A widget displaying a service as a button

    service - the service to display

    placement_controller - a PlacementController instance

    display_controller - a PlacerView instance

    callback - a function to be called when either of the buttons is
    pressed. The service will be passed to the function as userdata.

    show_placements - display the machine(s) currently assigned to
    host this service, both planned deployments (aka 'assignments',
    and already-deployed, called 'deployments').

    """

    def __init__(self, service, placement_controller,
                 display_controller, show_placements=False):
        self.service = service
        self.placement_controller = placement_controller
        self.display_controller = display_controller
        self.show_placements = show_placements
        self.state = ServiceWidgetState.UNSELECTED
        w = self.build_widgets()
        super().__init__(w)
        self.update()

    def selectable(self):
        return True

    def build_widgets(self):

        self.button = MenuSelectButton("I AM A SERVICE", self.do_select)

        self.action_button_cols = Columns([], dividechars=1)
        self.action_buttons = []

        self.pile = Pile([self.button])
        return self.pile

    def get_markup(self):
        if self.service.subordinate:
            return [self.service.service_name +
                    " (subordinate)\n  " +
                    self.service.charm_source], []

        nr = self.service.required_num_units()
        pl = "s" if nr > 1 else ""
        title_markup = [self.service.service_name +
                        "\n  {} unit{}: ".format(nr, pl) +
                        self.service.charm_source]
        info_markup = []

        if not self.display_controller.has_maas:
            return title_markup, info_markup

        pd = self.placement_controller.get_assignments(self.service)
        nplaced = sum([len(pd[k]) for k in pd])

        if nr-nplaced > 0:
            pl = ""
            if nr-nplaced > 1:
                pl = "s"
            info_str = ("  {} unit{} will be auto-placed "
                        "by Juju\n".format(nr-nplaced, pl))

            info_markup.append(info_str)

        def string_for_placement_dict(d):
            if self.display_controller.has_maas:
                defstring = "Bare Metal (Default)"
            else:
                defstring = "LXD (Default)"
            s = []
            for atype, ml in sorted(d.items()):
                if atype == AssignmentType.DEFAULT:
                    aname = defstring
                else:
                    aname = atype.name

                hostnames = [m.hostname for m in ml]
                s.append("    {}: {}".format(aname,
                                             ", ".join(hostnames)))
            if len(s) == 0:
                return []
            return "\n".join(s)

        ad = self.placement_controller.get_assignments(self.service)
        info_markup += string_for_placement_dict(ad)
        return title_markup, info_markup

    def update_choosing(self):
        title_markup, _ = self.get_markup()
        msg = Padding(Text(title_markup), left=2, right=2, align='center')
        self.pile.contents = [(msg, self.pile.options()),
                              (self.action_button_cols,
                               self.pile.options()),
                              (Divider(), self.pile.options())]

    def update_default(self):
        title_markup, info_markup = self.get_markup()
        self.button.set_label(title_markup + ["\n"] + info_markup)
        if self.state == ServiceWidgetState.SELECTED:
            b = AttrMap(self.button, 'deploy_highlight_start',
                        'button_secondary focus')
        else:
            b = AttrMap(self.button, 'text', 'button_secondary focus')

        self.pile.contents = [(b, self.pile.options()),
                              (Divider(), self.pile.options())]

    def update(self):
        self.service = next((s for s in
                             self.placement_controller.bundle.services
                             if s.service_name == self.service.service_name),
                            self.service)
        self.update_action_buttons()

        if self.state == ServiceWidgetState.CHOOSING:
            self.update_choosing()
        else:
            self.update_default()

    def keypress(self, size, key):
        if key == 'backspace':
            self.display_controller.remove_service(self.service)
        elif key == '+':
            if not self.service.subordinate:
                self.display_controller.scale_service(self.service, 1)
        elif key == '-':
            if not self.service.subordinate:
                self.display_controller.scale_service(self.service, -1)

        return super().keypress(size, key)

    def update_action_buttons(self):
        all_actions = []
        if self.display_controller.has_maas:
            all_actions = [('Choose Placement',
                            self.handle_placement_button_pressed)]
        all_actions += [('Edit Relations',
                         self.handle_relation_button_pressed),
                        ('Edit Options',
                         self.handle_options_button_pressed)]

        self.action_buttons = [AttrMap(PlainButton(label, on_press=func),
                                       'button_secondary',
                                       'button_secondary focus')
                               for label, func in all_actions]

        self.action_buttons.append(AttrMap(
            PlainButton("Cancel",
                        on_press=self.do_cancel),
            'button_secondary',
            'button_secondary focus'))

        opts = self.action_button_cols.options()
        self.action_button_cols.contents = [(b, opts) for
                                            b in self.action_buttons]

    def do_select(self, sender):
        self.display_controller.clear_selections()
        if self.state == ServiceWidgetState.SELECTED:
            self.state = ServiceWidgetState.UNSELECTED
            self.display_controller.set_selected_service(None)
        else:
            self.display_controller.set_selected_service(self.service)
            self.state = ServiceWidgetState.CHOOSING
            self.pile.focus_position = 1
            self.action_button_cols.focus_position = 0
        self.update()

    def do_cancel(self, sender):
        self.state = ServiceWidgetState.UNSELECTED
        self.update()
        self.display_controller.show_default_view()
        self.pile.focus_position = 0

    def handle_placement_button_pressed(self, sender):
        self.state = ServiceWidgetState.SELECTED
        self.update()
        self.display_controller.edit_placement()
        self.pile.focus_position = 0

    def handle_relation_button_pressed(self, sender):
        self.state = ServiceWidgetState.SELECTED
        self.update()
        self.display_controller.edit_relations()
        self.pile.focus_position = 0

    def handle_options_button_pressed(self, sender):
        self.state = ServiceWidgetState.SELECTED
        self.update()
        self.display_controller.edit_options()
        self.pile.focus_position = 0
    def build_widgets(self):

        self.button = MenuSelectButton("", on_press=self.do_select)
        return AttrMap(self.button, 'text', 'button_secondary focus')
class SimpleServiceWidget(WidgetWrap):

    """A widget displaying a service as a button

    service - the service to display

    placement_controller - a PlacementController instance

    display_controller - a PlacerView instance

    callback - a function to be called when either of the buttons is
    pressed. The service will be passed to the function as userdata.

    show_placements - display the machine(s) currently assigned to
    host this service, both planned deployments (aka 'assignments',
    and already-deployed, called 'deployments').

    """

    def __init__(self, service, placement_controller,
                 display_controller, show_placements=False):
        self.service = service
        self.placement_controller = placement_controller
        self.display_controller = display_controller
        self.show_placements = show_placements
        self.state = ServiceWidgetState.UNSELECTED
        w = self.build_widgets()
        super().__init__(w)
        self.update()

    def selectable(self):
        return True

    def build_widgets(self):

        self.button = MenuSelectButton("I AM A SERVICE", self.do_select)

        self.action_button_cols = Columns([], dividechars=1)
        self.action_buttons = []

        self.pile = Pile([self.button])
        return self.pile

    def get_markup(self):
        if self.service.subordinate:
            return [self.service.service_name +
                    " (subordinate)\n  " +
                    self.service.charm_source], []

        nr = self.service.required_num_units()
        pl = "s" if nr > 1 else ""
        title_markup = [self.service.service_name +
                        "\n  {} unit{}: ".format(nr, pl) +
                        self.service.charm_source]
        info_markup = []

        if not self.display_controller.has_maas:
            return title_markup, info_markup

        pd = self.placement_controller.get_assignments(self.service)
        nplaced = sum([len(pd[k]) for k in pd])

        if nr - nplaced > 0:
            pl = ""
            if nr - nplaced > 1:
                pl = "s"
            info_str = ("  {} unit{} will be auto-placed "
                        "by Juju\n".format(nr - nplaced, pl))

            info_markup.append(info_str)

        def string_for_placement_dict(d):
            if self.display_controller.has_maas:
                defstring = "Bare Metal (Default)"
            else:
                defstring = "LXD (Default)"
            s = []
            for atype, ml in sorted(d.items()):
                if atype == AssignmentType.DEFAULT:
                    aname = defstring
                else:
                    aname = atype.name

                hostnames = [m.hostname for m in ml]
                s.append("    {}: {}".format(aname,
                                             ", ".join(hostnames)))
            if len(s) == 0:
                return []
            return "\n".join(s)

        ad = self.placement_controller.get_assignments(self.service)
        info_markup += string_for_placement_dict(ad)
        return title_markup, info_markup

    def update_choosing(self):
        title_markup, _ = self.get_markup()
        msg = Padding(Text(title_markup), left=2, right=2, align='center')
        self.pile.contents = [(msg, self.pile.options()),
                              (self.action_button_cols,
                               self.pile.options()),
                              (Divider(), self.pile.options())]

    def update_default(self):
        title_markup, info_markup = self.get_markup()
        self.button.set_label(title_markup + ["\n"] + info_markup)
        if self.state == ServiceWidgetState.SELECTED:
            b = AttrMap(self.button, 'deploy_highlight_start',
                        'button_secondary focus')
        else:
            b = AttrMap(self.button, 'text', 'button_secondary focus')

        self.pile.contents = [(b, self.pile.options()),
                              (Divider(), self.pile.options())]

    def update(self):
        self.service = next((s for s in
                             self.placement_controller.bundle.services
                             if s.service_name == self.service.service_name),
                            self.service)
        self.update_action_buttons()

        if self.state == ServiceWidgetState.CHOOSING:
            self.update_choosing()
        else:
            self.update_default()

    def keypress(self, size, key):
        if key == 'backspace':
            self.display_controller.remove_service(self.service)
        elif key == '+':
            if not self.service.subordinate:
                self.display_controller.scale_service(self.service, 1)
        elif key == '-':
            if not self.service.subordinate:
                self.display_controller.scale_service(self.service, -1)

        return super().keypress(size, key)

    def update_action_buttons(self):
        all_actions = []
        if self.display_controller.has_maas:
            all_actions = [('Choose Placement',
                            self.handle_placement_button_pressed)]
        all_actions += [('Edit Relations',
                         self.handle_relation_button_pressed),
                        ('Edit Options',
                         self.handle_options_button_pressed)]

        self.action_buttons = [AttrMap(PlainButton(label, on_press=func),
                                       'button_secondary',
                                       'button_secondary focus')
                               for label, func in all_actions]

        self.action_buttons.append(AttrMap(
            PlainButton("Cancel",
                        on_press=self.do_cancel),
            'button_secondary',
            'button_secondary focus'))

        opts = self.action_button_cols.options()
        self.action_button_cols.contents = [(b, opts) for
                                            b in self.action_buttons]

    def do_select(self, sender):
        self.display_controller.clear_selections()
        if self.state == ServiceWidgetState.SELECTED:
            self.state = ServiceWidgetState.UNSELECTED
            self.display_controller.set_selected_service(None)
        else:
            self.display_controller.set_selected_service(self.service)
            self.state = ServiceWidgetState.CHOOSING
            self.pile.focus_position = 1
            self.action_button_cols.focus_position = 0
        self.update()

    def do_cancel(self, sender):
        self.state = ServiceWidgetState.UNSELECTED
        self.update()
        self.display_controller.show_default_view()
        self.pile.focus_position = 0

    def handle_placement_button_pressed(self, sender):
        self.state = ServiceWidgetState.SELECTED
        self.update()
        self.display_controller.edit_placement()
        self.pile.focus_position = 0

    def handle_relation_button_pressed(self, sender):
        self.state = ServiceWidgetState.SELECTED
        self.update()
        self.display_controller.edit_relations()
        self.pile.focus_position = 0

    def handle_options_button_pressed(self, sender):
        self.state = ServiceWidgetState.SELECTED
        self.update()
        self.display_controller.edit_options()
        self.pile.focus_position = 0
    def build_widgets(self):

        self.button = MenuSelectButton("",
                                       on_press=self.do_select)
        return AttrMap(self.button, 'text',
                       'button_secondary focus')
class SimpleMachineWidget(WidgetWrap):
    """A widget displaying a machine. When selected, shows action buttons
    for placement types.

    machine - the machine to display

    controller - a PlacementController instance

    display_controller - a PlacerView instance

    show_assignments - display info about which charms are assigned
    and what assignment type (LXC, KVM, etc) they have.

    """
    def __init__(self,
                 machine,
                 controller,
                 display_controller,
                 show_assignments=True):
        self.machine = machine
        self.controller = controller
        self.display_controller = display_controller
        self.show_assignments = show_assignments
        self.is_selected = False
        w = self.build_widgets()
        super().__init__(w)
        self.update()

    def selectable(self):
        return True

    def build_widgets(self):

        self.button = MenuSelectButton("I AM A MACHINE", self.do_select)
        self.action_button_cols = Columns([])
        self.action_buttons = []

        self.pile = Pile([self.button])
        return self.pile

    def 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()
        self.update_action_buttons()

        if self.is_selected:
            self.update_selected()
        else:
            self.update_unselected()

    def info_markup(self):
        m = self.machine
        return [
            self.machine.hostname + "\n", 'arch: {}  '.format(m.arch),
            'cores: {}  '.format(m.cpu_cores), 'mem: {}  '.format(m.mem),
            'storage: {}'.format(m.storage)
        ]

    def update_selected(self):
        cn = self.display_controller.selected_service.service_name
        msg = Text("  Add {} to {}:".format(cn, self.machine.hostname))
        self.pile.contents = [(msg, self.pile.options()),
                              (self.action_button_cols, self.pile.options()),
                              (Divider(), self.pile.options())]

    def update_unselected(self):
        markup = self.info_markup()
        self.button.set_label(markup)
        self.pile.contents = [(AttrMap(self.button, 'text',
                                       'button_secondary focus'),
                               self.pile.options()),
                              (Divider(), self.pile.options())]

    def update_action_buttons(self):

        all_actions = [(AssignmentType.BareMetal, 'Add as Bare Metal',
                        self.select_baremetal),
                       (AssignmentType.LXD, 'Add as LXD', self.select_lxd),
                       (AssignmentType.KVM, 'Add as KVM', self.select_kvm)]

        sc = self.display_controller.selected_service
        if sc:
            allowed_set = set(sc.allowed_assignment_types)
            allowed_types = set([atype for atype, _, _ in all_actions])
            allowed_types = allowed_types.intersection(allowed_set)
        else:
            allowed_types = set()

        # + 1 for the cancel button:
        if len(self.action_buttons) == len(allowed_types) + 1:
            return

        self.action_buttons = [
            AttrMap(PlainButton(label, on_press=func), 'button_secondary',
                    'button_secondary focus')
            for atype, label, func in all_actions if atype in allowed_types
        ]
        self.action_buttons.append(
            AttrMap(PlainButton("Cancel", on_press=self.do_cancel),
                    'button_secondary', 'button_secondary focus'))

        opts = self.action_button_cols.options()
        self.action_button_cols.contents = [(b, opts)
                                            for b in self.action_buttons]

    def do_select(self, sender):
        if self.display_controller.selected_service is None:
            return
        self.is_selected = True
        self.update()
        self.pile.focus_position = 1
        self.action_button_cols.focus_position = 0

    def do_cancel(self, sender):
        self.is_selected = False
        self.update()
        self.pile.focus_position = 0

    def _do_select_assignment(self, atype):
        {
            AssignmentType.BareMetal:
            self.display_controller.do_select_baremetal,
            AssignmentType.LXD: self.display_controller.do_select_lxd,
            AssignmentType.KVM: self.display_controller.do_select_kvm
        }[atype](self.machine)
        self.pile.focus_position = 0

    def select_baremetal(self, sender):
        self._do_select_assignment(AssignmentType.BareMetal)

    def select_lxd(self, sender):
        self._do_select_assignment(AssignmentType.LXD)

    def select_kvm(self, sender):
        self._do_select_assignment(AssignmentType.KVM)
class SimpleMachineWidget(WidgetWrap):

    """A widget displaying a machine. When selected, shows action buttons
    for placement types.

    machine - the machine to display

    controller - a PlacementController instance

    display_controller - a PlacerView instance

    show_assignments - display info about which charms are assigned
    and what assignment type (LXC, KVM, etc) they have.

    """

    def __init__(self, machine, controller,
                 display_controller, show_assignments=True):
        self.machine = machine
        self.controller = controller
        self.display_controller = display_controller
        self.show_assignments = show_assignments
        self.is_selected = False
        w = self.build_widgets()
        super().__init__(w)
        self.update()

    def selectable(self):
        return True

    def build_widgets(self):

        self.button = MenuSelectButton("I AM A MACHINE", self.do_select)
        self.action_button_cols = Columns([])
        self.action_buttons = []

        self.pile = Pile([self.button])
        return self.pile

    def 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()
        self.update_action_buttons()

        if self.is_selected:
            self.update_selected()
        else:
            self.update_unselected()

    def info_markup(self):
        m = self.machine
        return [self.machine.hostname + "\n",
                'arch: {}  '.format(m.arch),
                'cores: {}  '.format(m.cpu_cores),
                'mem: {}  '.format(m.mem),
                'storage: {}'.format(m.storage)]

    def update_selected(self):
        cn = self.display_controller.selected_service.service_name
        msg = Text("  Add {} to {}:".format(cn,
                                            self.machine.hostname))
        self.pile.contents = [(msg, self.pile.options()),
                              (self.action_button_cols,
                               self.pile.options()),
                              (Divider(), self.pile.options())]

    def update_unselected(self):
        markup = self.info_markup()
        self.button.set_label(markup)
        self.pile.contents = [(AttrMap(self.button, 'text',
                                       'button_secondary focus'),
                               self.pile.options()),
                              (Divider(), self.pile.options())]

    def update_action_buttons(self):

        all_actions = [(AssignmentType.BareMetal,
                        'Add as Bare Metal',
                        self.select_baremetal),
                       (AssignmentType.LXD,
                        'Add as LXD',
                        self.select_lxd),
                       (AssignmentType.KVM,
                        'Add as KVM',
                        self.select_kvm)]

        sc = self.display_controller.selected_service
        if sc:
            allowed_set = set(sc.allowed_assignment_types)
            allowed_types = set([atype for atype, _, _ in all_actions])
            allowed_types = allowed_types.intersection(allowed_set)
        else:
            allowed_types = set()

        # + 1 for the cancel button:
        if len(self.action_buttons) == len(allowed_types) + 1:
            return

        self.action_buttons = [AttrMap(PlainButton(label,
                                                   on_press=func),
                                       'button_secondary',
                                       'button_secondary focus')
                               for atype, label, func in all_actions
                               if atype in allowed_types]
        self.action_buttons.append(
            AttrMap(PlainButton("Cancel",
                                on_press=self.do_cancel),
                    'button_secondary',
                    'button_secondary focus'))

        opts = self.action_button_cols.options()
        self.action_button_cols.contents = [(b, opts) for b in
                                            self.action_buttons]

    def do_select(self, sender):
        if self.display_controller.selected_service is None:
            return
        self.is_selected = True
        self.update()
        self.pile.focus_position = 1
        self.action_button_cols.focus_position = 0

    def do_cancel(self, sender):
        self.is_selected = False
        self.update()
        self.pile.focus_position = 0

    def _do_select_assignment(self, atype):
        {AssignmentType.BareMetal:
         self.display_controller.do_select_baremetal,
         AssignmentType.LXD:
         self.display_controller.do_select_lxd,
         AssignmentType.KVM:
         self.display_controller.do_select_kvm}[atype](self.machine)
        self.pile.focus_position = 0

    def select_baremetal(self, sender):
        self._do_select_assignment(AssignmentType.BareMetal)

    def select_lxd(self, sender):
        self._do_select_assignment(AssignmentType.LXD)

    def select_kvm(self, sender):
        self._do_select_assignment(AssignmentType.KVM)
class JujuMachineWidget(WidgetWrap):

    """A widget displaying a machine and action buttons.

    juju_machine_id = juju id of the machine

    md = the machine dict

    application - the current application for which machines are being shown

    assign_cb - a function that takes a machine and assignmenttype to
    perform the button action

    unassign_cb - a function that takes a machine and removes
    assignments for the machine

    controller - a controller object that provides show_pin_chooser

    show_assignments - display info about which charms are assigned
    and what assignment type (LXC, KVM, etc) they have.

    show_pin - show button to pin juju machine to a maas machine

    """

    def __init__(self, juju_machine_id, md, application, assign_cb,
                 unassign_cb, controller, show_assignments=True,
                 show_pin=False):
        self.juju_machine_id = juju_machine_id
        self.md = md
        self.application = application
        self.is_selected = False
        self.assign_cb = assign_cb
        self.unassign_cb = unassign_cb
        self.controller = controller
        self.show_assignments = show_assignments
        self.show_pin = show_pin
        self.all_assigned = False

        w = self.build_widgets()
        super().__init__(w)
        self.update()

    def selectable(self):
        return True

    def __repr__(self):
        return "jujumachinewidget #" + str(self.juju_machine_id)

    def build_widgets(self):

        self.action_button_cols = Columns([])
        self.action_buttons = []
        self.build_unselected_widgets()
        self.pile = Pile([self.unselected_columns])
        return self.pile

    def build_unselected_widgets(self):
        cdict = juju.constraints_to_dict(self.md.get('constraints', ''))

        self.juju_machine_id_button = MenuSelectButton(
            '{:20s}'.format(self.juju_machine_id), self.show_pin_chooser)
        self.juju_machine_id_label = Text(
            "{:20s}".format(self.juju_machine_id))
        self.cores_field = IntEdit('', cdict.get('cores', ''))
        connect_signal(self.cores_field, 'change', self.handle_cores_changed)
        self.mem_field = Edit('', cdict.get('mem', ''))
        connect_signal(self.mem_field, 'change', self.handle_mem_changed)
        self.disk_field = Edit('', cdict.get('root-disk', ''))
        connect_signal(self.disk_field, 'change', self.handle_disk_changed)

        if self.show_pin:
            machine_id_w = self.juju_machine_id_button
        else:
            machine_id_w = self.juju_machine_id_label
        cols = [machine_id_w, self.cores_field,
                self.mem_field, self.disk_field]
        cols = [AttrMap(w, 'string_input',
                        'string_input_focus') for w in cols]
        cols.append(Text("placeholder"))
        self.unselected_columns = Columns(cols, dividechars=2)
        self.update_assignments()
        return self.unselected_columns

    def update_assignments(self):
        assignments = []

        mps = self.controller.get_all_assignments(self.juju_machine_id)
        if len(mps) > 0:
            if self.show_assignments:
                ad = defaultdict(list)
                for application, atype in mps:
                    ad[atype].append(application)
                astr = " ".join(["{}{}".format(
                    atype_to_label([atype])[0],
                    ",".join([application.service_name
                              for application in al]))
                    for atype, al in ad.items()])
                assignments.append(astr)
        else:
            if self.show_assignments:
                assignments.append("-")

        if any([application == self.application for application, _ in mps]):
            action = self.do_remove
            label = "Remove"
        else:
            action = self.do_select
            label = "Select"

        self.select_button = PlainButton(label, action)

        cols = [Text(s) for s in assignments]

        current_assignments = [a for a, _ in mps if a == self.application]
        if self.all_assigned and len(current_assignments) == 0:
            cols.append(Text(""))
        else:
            cols += [AttrMap(self.select_button, 'text',
                             'button_secondary focus')]
        opts = self.unselected_columns.options()
        self.unselected_columns.contents[4:] = [(w, opts) for w in cols]

    def update(self):
        self.update_action_buttons()

        if self.is_selected:
            self.update_selected()
        else:
            self.update_unselected()

    def update_selected(self):
        cn = self.application.service_name
        msg = Text("  Add {} to machine #{}:".format(cn,
                                                     self.juju_machine_id))
        self.pile.contents = [(msg, self.pile.options()),
                              (self.action_button_cols,
                               self.pile.options()),
                              (Divider(), self.pile.options())]

    def update_unselected(self):
        if self.show_pin:
            pinned_machine = self.controller.get_pin(self.juju_machine_id)

            if pinned_machine:
                pin_label = " {} \N{PENCIL}".format(pinned_machine.hostname)
            else:
                pin_label = " \N{PENCIL}"
            self.juju_machine_id_button.set_label('{:20s}'.format(
                self.juju_machine_id + " " + pin_label))
        else:
            self.juju_machine_id_label.set_text('{:20s}'.format(
                self.juju_machine_id))

        self.pile.contents = [(self.unselected_columns, self.pile.options()),
                              (Divider(), self.pile.options())]
        self.update_assignments()

    def update_action_buttons(self):

        all_actions = [(AssignmentType.BareMetal,
                        'Add as Bare Metal',
                        self.select_baremetal),
                       (AssignmentType.LXD,
                        'Add as LXD',
                        self.select_lxd),
                       (AssignmentType.KVM,
                        'Add as KVM',
                        self.select_kvm)]

        sc = self.application
        if sc:
            allowed_set = set(sc.allowed_assignment_types)
            allowed_types = set([atype for atype, _, _ in all_actions])
            allowed_types = allowed_types.intersection(allowed_set)
        else:
            allowed_types = set()

        # + 1 for the cancel button:
        if len(self.action_buttons) == len(allowed_types) + 1:
            return

        self.action_buttons = [AttrMap(PlainButton(label,
                                                   on_press=func),
                                       'button_secondary',
                                       'button_secondary focus')
                               for atype, label, func in all_actions
                               if atype in allowed_types]
        self.action_buttons.append(
            AttrMap(PlainButton("Cancel",
                                on_press=self.do_cancel),
                    'button_secondary',
                    'button_secondary focus'))

        opts = self.action_button_cols.options()
        self.action_button_cols.contents = [(b, opts) for b in
                                            self.action_buttons]

    def do_select(self, sender):
        self.is_selected = True
        self.update()
        self.pile.focus_position = 1
        self.action_button_cols.focus_position = 0

    def do_remove(self, sender):
        self.unassign_cb(self.juju_machine_id)

    def do_cancel(self, sender):
        self.is_selected = False
        self.update()
        self.pile.focus_position = 0

    def _do_select_assignment(self, atype):
        self.assign_cb(self.juju_machine_id, atype)
        self.pile.focus_position = 0
        self.is_selected = False
        self.update()

    def select_baremetal(self, sender):
        self._do_select_assignment(AssignmentType.BareMetal)

    def select_lxd(self, sender):
        self._do_select_assignment(AssignmentType.LXD)

    def select_kvm(self, sender):
        self._do_select_assignment(AssignmentType.KVM)

    def handle_cores_changed(self, sender, val):
        if val == '':
            self.md = self.controller.clear_constraint(self.juju_machine_id,
                                                       'cores')
        else:
            self.md = self.controller.set_constraint(self.juju_machine_id,
                                                     'cores', val)

    def _format_constraint(self, val):
        """Ensure that a constraint has a unit. bare numbers are treated as
        gigabytes"""
        try:
            return units.gb_to_human(float(val))
        except ValueError:
            return val

    def handle_mem_changed(self, sender, val):
        if val == '':
            self.md = self.controller.clear_constraint(self.juju_machine_id,
                                                       'mem')
        else:
            self.md = self.controller.set_constraint(
                self.juju_machine_id, 'mem',
                self._format_constraint(val))

    def handle_disk_changed(self, sender, val):
        if val == '':
            self.md = self.controller.clear_constraint(self.juju_machine_id,
                                                       'root-disk')
        else:
            self.md = self.controller.set_constraint(
                self.juju_machine_id, 'root-disk',
                self._format_constraint(val))

    def show_pin_chooser(self, sender):
        self.controller.show_pin_chooser(self.juju_machine_id)