Beispiel #1
0
class MachineWidget(WidgetWrap):
    """A widget displaying a machine and one action button.

    machine - the machine to display

    select_cb - a function that takes a machine to select

    unselect_cb - a function that takes a machine to un-select

    target_info - a string describing what we're pinning to

    current_pin_cb - a function that takes a machine and returns a
    string if it's already pinned or None if it is not

    """
    def __init__(self, machine, select_cb, unselect_cb, target_info,
                 current_pin_cb):
        self.machine = machine
        self.select_cb = select_cb
        self.unselect_cb = unselect_cb
        self.target_info = target_info
        self.current_pin_cb = current_pin_cb

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

    def selectable(self):
        return True

    def build_widgets(self):
        m = self.machine
        l = [
            '{:20s}'.format(m.hostname), '{:3d}'.format(m.cpu_cores),
            '{:6s}'.format(m.mem), '{:10s}'.format(m.storage)
        ]

        self.select_button_label = "Pin {} to {}".format(
            m.hostname, self.target_info)
        self.unselect_button_label = "Un-pin {} from ".format(
            m.hostname) + "{}"

        self.select_button = PlainButton(self.select_button_label,
                                         self.handle_button)
        cols = [Text(m) for m in l]
        cols += [AttrMap(self.select_button, 'text', 'button_secondary focus')]

        self.columns = Columns(cols)
        return self.columns

    def update_machine(self):
        """Refresh with potentially updated machine info from controller.
        Assumes that machine exists - machines going away is handled
        in machineslist.update().
        """
        machines = app.maas.client.get_machines()
        if machines is None:
            return
        self.machine = next(
            (m for m in machines if m.instance_id == self.machine.instance_id),
            None)

    def __repr__(self):
        return "widget for " + str(self.machine)

    def update(self):
        self.update_machine()
        current_pin = self.current_pin_cb(self.machine)
        if current_pin:
            l = self.unselect_button_label.format(current_pin)
            self.select_button.set_label(l)
        else:
            self.select_button.set_label(self.select_button_label)

    def handle_button(self, sender):
        if self.current_pin_cb(self.machine):
            self.unselect_cb(self.machine)
        else:
            self.select_cb(self.machine)

        self.update()
class ApplicationConfigureView(WidgetWrap):

    def __init__(self, application, metadata_controller, controller):
        self.controller = controller
        self.application = application
        self.options_copy = self.application.options.copy()
        self.metadata_controller = metadata_controller
        self.widgets = self.build_widgets()
        self.description_w = Text("")
        self.showing_all = False
        self.buttons_selected = False
        self.frame = Frame(body=self.build_widgets(),
                           footer=self.build_footer())
        super().__init__(self.frame)

        self.metadata_controller.get_readme(
            self.application.csid.as_seriesname(),
            partial(self._handle_readme_load))

    def _handle_readme_load(self, readme_f):
        EventLoop.loop.event_loop._loop.call_soon_threadsafe(
            partial(self._update_readme_on_main_thread,
                    readme_f.result()))

    def _update_readme_on_main_thread(self, readme):
        rt = self._trim_readme(readme)
        self.description_w.set_text(rt)

    def _trim_readme(self, readme):
        rls = readme.splitlines()
        rls = [l for l in rls if not l.startswith("#")]
        nrls = []
        for i in range(len(rls)):
            if i + 1 == len(rls):
                break
            if len(rls[i]) > 0:
                if rls[i][0] in ['-', '#', '=']:
                    continue
            if len(rls[i + 1]) > 0:
                if rls[i + 1][0] in ['-', '=']:
                    continue
            nrls.append(rls[i])

        if len(nrls) == 0:
            return

        if nrls[0] == '':
            nrls = nrls[1:]
        # split after two paragraphs:
        if '' in nrls:
            firstparidx = nrls.index('')
        else:
            firstparidx = 1
        try:
            splitidx = nrls.index('', firstparidx + 1)
        except:
            splitidx = firstparidx
        nrls = nrls[:splitidx]
        return "\n".join(nrls)

    def selectable(self):
        return True

    def keypress(self, size, key):
        # handle keypress first, then get new focus widget
        rv = super().keypress(size, key)
        if key in ['tab', 'shift tab']:
            self._swap_focus()
        return rv

    def _swap_focus(self):
        if not self.buttons_selected:
            self.buttons_selected = True
            self.frame.focus_position = 'footer'
            self.buttons.focus_position = 3
        else:
            self.buttons_selected = False
            self.frame.focus_position = 'body'

    def build_widgets(self):
        ws = [Text("Configure {}".format(
            self.application.service_name))]
        num_unit_ow = OptionWidget("Units", "int",
                                   "How many units to deploy.",
                                   self.application.orig_num_units,
                                   current_value=self.application.num_units,
                                   value_changed_callback=self.handle_scale)
        ws.append(num_unit_ow)
        ws += self.get_whitelisted_option_widgets()
        self.toggle_show_all_button_index = len(ws) + 1
        self.toggle_show_all_button = PlainButton(
            "Show Advanced Configuration",
            self.do_toggle_show_all_config)
        ws += [HR(),
               Columns([('weight', 1, Text(" ")),
                        (36, Color.button_secondary(
                            self.toggle_show_all_button))])]
        self.pile = Pile(ws)
        return Padding.center_90(Filler(self.pile, valign="top"))

    def build_footer(self):
        cancel = menu_btn(on_press=self.do_cancel,
                          label="\n  BACK\n")
        confirm = menu_btn(on_press=self.do_commit,
                           label="\n APPLY CHANGES\n")
        self.buttons = Columns([
            ('fixed', 2, Text("")),
            ('fixed', 13, Color.menu_button(
                cancel,
                focus_map='button_primary focus')),
            Text(""),
            ('fixed', 20, Color.menu_button(
                confirm,
                focus_map='button_primary focus')),
            ('fixed', 2, Text(""))
        ])

        footer = Pile([
            HR(top=0),
            Padding.center_90(self.description_w),
            Padding.line_break(""),
            Color.frame_footer(Pile([
                Padding.line_break(""),
                self.buttons]))
        ])

        return footer

    def get_whitelisted_option_widgets(self):
        service_id = self.application.csid.as_str_without_rev()
        options = self.metadata_controller.get_options(service_id)

        svc_opts_whitelist = utils.get_options_whitelist(
            self.application.service_name)
        hidden = [n for n in options.keys() if n not in svc_opts_whitelist]
        log.info("Hiding options not in the whitelist: {}".format(hidden))

        return self._get_option_widgets(svc_opts_whitelist, options)

    def get_non_whitelisted_option_widgets(self):
        service_id = self.application.csid.as_str_without_rev()
        options = self.metadata_controller.get_options(service_id)

        svc_opts_whitelist = utils.get_options_whitelist(
            self.application.service_name)
        hidden = [n for n in options.keys() if n not in svc_opts_whitelist]
        return self._get_option_widgets(hidden, options)

    def _get_option_widgets(self, opnames, options):
        ws = []
        for opname in opnames:
            opdict = options[opname]
            cv = self.application.options.get(opname, None)
            ow = OptionWidget(opname,
                              opdict['Type'],
                              opdict['Description'],
                              opdict['Default'],
                              current_value=cv,
                              value_changed_callback=self.handle_edit)
            ws.append(ow)
        return ws

    def do_toggle_show_all_config(self, sender):
        if not self.showing_all:
            new_ows = self.get_non_whitelisted_option_widgets()
            header = Text("Advanced Configuration Options")
            opts = self.pile.options()
            self.pile.contents.append((header, opts))
            for ow in new_ows:
                self.pile.contents.append((ow, opts))
            self.toggle_show_all_button.set_label(
                "Hide Advanced Configuration")
            self.showing_all = True
        else:
            i = self.toggle_show_all_button_index
            self.pile.contents = self.pile.contents[:i + 1]
            self.toggle_show_all_button.set_label(
                "Show Advanced Configuration")
            self.showing_all = False

    def handle_edit(self, opname, value):
        self.options_copy[opname] = value

    def handle_scale(self, opname, scale):
        self.application.num_units = scale

    def do_cancel(self, sender):
        self.controller.handle_sub_view_done()

    def do_commit(self, sender):
        self.application.options = self.options_copy
        self.controller.handle_sub_view_done()
class ApplicationConfigureView(WidgetWrap):
    def __init__(self, application, metadata_controller, controller):
        self.controller = controller
        self.application = application
        self.options_copy = self.application.options.copy()
        self.num_units_copy = self.application.num_units
        self.metadata_controller = metadata_controller
        self.widgets = self.build_widgets()
        self.description_w = Text("")
        self.showing_all = False
        self.buttons_selected = False
        self.frame = Frame(body=self.build_widgets(),
                           footer=self.build_footer())
        super().__init__(self.frame)

        self.metadata_controller.get_readme(
            self.application.csid.as_seriesname(),
            partial(self._handle_readme_load))

    def _handle_readme_load(self, readme_f):
        EventLoop.loop.event_loop._loop.call_soon_threadsafe(
            partial(self._update_readme_on_main_thread, readme_f.result()))

    def _update_readme_on_main_thread(self, readme):
        rt = self._trim_readme(readme)
        self.description_w.set_text(rt)

    def _trim_readme(self, readme):
        rls = readme.splitlines()
        rls = [l for l in rls if not l.startswith("#")]
        nrls = []
        for i in range(len(rls)):
            if i + 1 == len(rls):
                break
            if len(rls[i]) > 0:
                if rls[i][0] in ['-', '#', '=']:
                    continue
            if len(rls[i + 1]) > 0:
                if rls[i + 1][0] in ['-', '=']:
                    continue
            nrls.append(rls[i])

        if len(nrls) == 0:
            return

        if nrls[0] == '':
            nrls = nrls[1:]
        # split after two paragraphs:
        if '' in nrls:
            firstparidx = nrls.index('')
        else:
            firstparidx = 1
        try:
            splitidx = nrls.index('', firstparidx + 1)
        except:
            splitidx = firstparidx
        nrls = nrls[:splitidx]
        return "\n".join(nrls)

    def selectable(self):
        return True

    def keypress(self, size, key):
        # handle keypress first, then get new focus widget
        rv = super().keypress(size, key)
        if key in ['tab', 'shift tab']:
            self._swap_focus()
        return rv

    def _swap_focus(self):
        if not self.buttons_selected:
            self.buttons_selected = True
            self.frame.focus_position = 'footer'
            self.buttons.focus_position = 3
        else:
            self.buttons_selected = False
            self.frame.focus_position = 'body'

    def build_widgets(self):
        ws = [Text("Configure {}".format(self.application.service_name))]
        num_unit_ow = OptionWidget("Units",
                                   "int",
                                   "How many units to deploy.",
                                   self.application.orig_num_units,
                                   current_value=self.num_units_copy,
                                   value_changed_callback=self.handle_scale)
        ws.append(num_unit_ow)
        ws += self.get_whitelisted_option_widgets()
        self.toggle_show_all_button_index = len(ws) + 1
        self.toggle_show_all_button = PlainButton(
            "Show Advanced Configuration", self.do_toggle_show_all_config)
        ws += [
            HR(),
            Columns([('weight', 1, Text(" ")),
                     (36, Color.button_secondary(self.toggle_show_all_button))
                     ])
        ]
        self.pile = Pile(ws)
        return Padding.center_90(Filler(self.pile, valign="top"))

    def build_footer(self):
        cancel = menu_btn(on_press=self.do_cancel, label="\n  BACK\n")
        confirm = menu_btn(on_press=self.do_commit, label="\n APPLY CHANGES\n")
        self.buttons = Columns([
            ('fixed', 2, Text("")),
            ('fixed', 13,
             Color.menu_button(cancel, focus_map='button_primary focus')),
            Text(""),
            ('fixed', 20,
             Color.menu_button(confirm, focus_map='button_primary focus')),
            ('fixed', 2, Text(""))
        ])

        footer = Pile([
            HR(top=0),
            Padding.center_90(self.description_w),
            Padding.line_break(""),
            Color.frame_footer(Pile([Padding.line_break(""), self.buttons]))
        ])

        return footer

    def get_whitelisted_option_widgets(self):
        service_id = self.application.csid.as_str_without_rev()
        options = self.metadata_controller.get_options(service_id)

        svc_opts_whitelist = utils.get_options_whitelist(
            self.application.service_name)
        hidden = [n for n in options.keys() if n not in svc_opts_whitelist]
        log.info("Hiding options not in the whitelist: {}".format(hidden))

        return self._get_option_widgets(svc_opts_whitelist, options)

    def get_non_whitelisted_option_widgets(self):
        service_id = self.application.csid.as_str_without_rev()
        options = self.metadata_controller.get_options(service_id)

        svc_opts_whitelist = utils.get_options_whitelist(
            self.application.service_name)
        hidden = [n for n in options.keys() if n not in svc_opts_whitelist]
        return self._get_option_widgets(hidden, options)

    def _get_option_widgets(self, opnames, options):
        ws = []
        for opname in opnames:
            try:
                opdict = options[opname]
            except KeyError:
                app.log.debug(
                    "Unknown charm option ({}), skipping".format(opname))
                continue
            cv = self.application.options.get(opname, None)
            ow = OptionWidget(opname,
                              opdict['Type'],
                              opdict['Description'],
                              opdict['Default'],
                              current_value=cv,
                              value_changed_callback=self.handle_edit)
            ws.append(ow)
        return ws

    def do_toggle_show_all_config(self, sender):
        if not self.showing_all:
            new_ows = self.get_non_whitelisted_option_widgets()
            header = Text("Advanced Configuration Options")
            opts = self.pile.options()
            self.pile.contents.append((header, opts))
            for ow in new_ows:
                self.pile.contents.append((ow, opts))
            self.toggle_show_all_button.set_label(
                "Hide Advanced Configuration")
            self.showing_all = True
        else:
            i = self.toggle_show_all_button_index
            self.pile.contents = self.pile.contents[:i + 1]
            self.toggle_show_all_button.set_label(
                "Show Advanced Configuration")
            self.showing_all = False

    def handle_edit(self, opname, value):
        self.options_copy[opname] = value

    def handle_scale(self, opname, scale):
        self.num_units_copy = scale

    def do_cancel(self, sender):
        self.controller.handle_sub_view_done()

    def do_commit(self, sender):
        self.application.options = self.options_copy
        self.application.num_units = self.num_units_copy
        self.controller.handle_sub_view_done()
class MachineWidget(WidgetWrap):

    """A widget displaying a machine and one action button.

    machine - the machine to display

    select_cb - a function that takes a machine to select

    unselect_cb - a function that takes a machine to un-select

    target_info - a string describing what we're pinning to

    current_pin_cb - a function that takes a machine and returns a
    string if it's already pinned or None if it is not

    """

    def __init__(self, machine, select_cb, unselect_cb,
                 target_info, current_pin_cb):
        self.machine = machine
        self.select_cb = select_cb
        self.unselect_cb = unselect_cb
        self.target_info = target_info
        self.current_pin_cb = current_pin_cb

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

    def selectable(self):
        return True

    def build_widgets(self):
        m = self.machine
        l = ['{:20s}'.format(m.hostname),
             '{:3d}'.format(m.cpu_cores),
             '{:6s}'.format(m.mem),
             '{:10s}'.format(m.storage)]

        self.select_button_label = "Pin {} to {}".format(m.hostname,
                                                         self.target_info)
        self.unselect_button_label = "Un-pin {} from ".format(
            m.hostname) + "{}"

        self.select_button = PlainButton(self.select_button_label,
                                         self.handle_button)
        cols = [Text(m) for m in l]
        cols += [AttrMap(self.select_button, 'text',
                         'button_secondary focus')]

        self.columns = Columns(cols)
        return self.columns

    def update_machine(self):
        """Refresh with potentially updated machine info from controller.
        Assumes that machine exists - machines going away is handled
        in machineslist.update().
        """
        machines = app.maas.client.get_machines()
        if machines is None:
            return
        self.machine = next((m for m in machines
                             if m.instance_id == self.machine.instance_id),
                            None)

    def __repr__(self):
        return "widget for " + str(self.machine)

    def update(self):
        self.update_machine()
        current_pin = self.current_pin_cb(self.machine)
        if current_pin:
            l = self.unselect_button_label.format(current_pin)
            self.select_button.set_label(l)
        else:
            self.select_button.set_label(self.select_button_label)

    def handle_button(self, sender):
        if self.current_pin_cb(self.machine):
            self.unselect_cb(self.machine)
        else:
            self.select_cb(self.machine)

        self.update()
Beispiel #5
0
class ApplicationListView(WidgetWrap):
    def __init__(self, applications, metadata_controller, controller):
        self.controller = controller
        self.applications = applications
        assert(len(applications) > 0)
        self.metadata_controller = metadata_controller
        self.n_remaining = len(self.applications)
        self.widgets = self.build_widgets()
        super().__init__(self.widgets)
        self.pile.focus_position = 2
        self.selected_app_w = None
        self.handle_focus_changed()
        self.update_skip_rest_button()

    def selectable(self):
        return True

    def keypress(self, size, key):
        # handle keypress first, then get new focus widget
        rv = super().keypress(size, key)
        self.handle_focus_changed()
        return rv

    def handle_focus_changed(self):
        "Check if focused widget changed, then update readme."
        fw = self.pile.focus
        if not isinstance(fw, ApplicationWidget):
            return
        if fw != self.selected_app_w:
            self.selected_app_w = fw
            if fw is None:
                self.description_w.set_text("No selected application")
            else:
                self.metadata_controller.get_readme(
                    fw.application.csid.as_seriesname(),
                    partial(self._handle_readme_load, fw))

    def _handle_readme_load(self, fw, readme_f):
        if self.selected_app_w == fw:
            EventLoop.loop.event_loop._loop.call_soon_threadsafe(
                partial(self._update_readme_on_main_thread,
                        readme_f.result()))

    def _update_readme_on_main_thread(self, readme):
        rt = self._trim_readme(readme)
        self.description_w.set_text(rt)

    def _trim_readme(self, readme):
        rls = readme.splitlines()
        rls = [l for l in rls if not l.startswith("#")]
        nrls = []
        for i in range(len(rls)):
            if i+1 == len(rls):
                break
            if len(rls[i]) > 0:
                if rls[i][0] in ['-', '#', '=']:
                    continue
            if len(rls[i+1]) > 0:
                if rls[i+1][0] in ['-', '=']:
                    continue
            nrls.append(rls[i])

        if len(nrls) == 0:
            return

        if nrls[0] == '':
            nrls = nrls[1:]
        # split after two paragraphs:
        if '' in nrls:
            firstparidx = nrls.index('')
        else:
            firstparidx = 1
        try:
            splitidx = nrls.index('', firstparidx + 1)
        except:
            splitidx = firstparidx
        nrls = nrls[:splitidx]
        return "\n".join(nrls)

    def build_widgets(self):
        ws = [Text("{} Applications in {}:".format(len(self.applications),
                                                   app.config['spell']))]
        max_app_name_len = max([len(a.service_name) for a in
                                self.applications])

        for a in self.applications:
            ws.append(Text(""))
            ws.append(ApplicationWidget(a, max_app_name_len,
                                        self.controller,
                                        self.do_deploy))

        self.description_w = Text("App description")
        ws += [HR(), self.description_w]

        self.skip_rest_button = PlainButton(
            "Deploy all",
            self.controller.do_deploy_remaining
        )
        cws = [('weight', 1, Text(" ")),
               (20, Color.button_secondary(
                   self.skip_rest_button,
                   focus_map='button_secondary focus'))]
        self.button_columns = Columns(cws, dividechars=1)

        ws += [HR(), self.button_columns]
        self.pile = Pile(ws)
        return Padding.center_90(Filler(self.pile, valign="top"))

    def do_deploy(self, application, sender):
        self.n_remaining -= 1
        self.update_skip_rest_button()
        self.selected_app_w.remove_buttons()

        self.controller.do_deploy(application,
                                  msg_cb=self.selected_app_w.set_progress)
        if self.n_remaining > 0:
            # find next available app widget to highlight. Start after
            # the current one and wrap around to top
            next_w = None
            reordered = self.pile.contents[self.pile.focus_position:] + \
                self.pile.contents[:self.pile.focus_position]
            for w, opts in reordered:
                if isinstance(w, ApplicationWidget) and w.selectable():
                    next_w = w
                    break
            ni = [w for w, _ in self.pile.contents].index(next_w)
            self.pile.focus_position = ni
        else:
            self.controller.finish()

    def update_skip_rest_button(self):
        t = "Deploy all {} Remaining Applications".format(
            self.n_remaining)
        self.skip_rest_button.set_label(t)