def build_widgets(self, maxlen): num_str = "{}".format(self.application.num_units) col_pad = 6 self.unit_w = Text('Units: {:4d}'.format(self.application.num_units), align='right') cws = [ (maxlen + col_pad, Text(self.application.service_name)), (10 + len(num_str), self.unit_w), # placeholder for instance type ('weight', 1, Text(" ")), # placeholder for configure button ('weight', 1, Text(" ")), (20, Color.button_primary(PlainButton( "Deploy", partial(self.deploy_cb, self.application)), focus_map='button_primary focus')) ] if not self.hide_config: cws[3] = (20, Color.button_secondary( PlainButton( "Configure", partial(self.controller.do_configure, self.application)), focus_map='button_secondary focus')) self.columns = Columns(cws, dividechars=1) return self.columns
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 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 build_widgets(self): ws = [Text("Configure {}".format(self.application.service_name))] ws += self.get_option_widgets() ws += [ HR(), PlainButton("Cancel", self.do_cancel), PlainButton("Accept Changes", self.do_commit) ] self.pile = Pile(ws) return Padding.center_90(Filler(self.pile, valign="top"))
def build_widgets(self): self.description_w = Text("Description Loading…") self.readme_w = Text("README Loading…") self.scale_edit = IntegerEditor(default=self.service.num_units) connect_signal(self.scale_edit._edit, 'change', self.handle_scale_changed) self.skip_rest_button = PlainButton( "Deploy all {} Remaining Applications with Bundle Defaults".format( self.n_remaining), self.do_skip_rest) col = Columns([(6, Text('Units:', align='right')), (15, Color.string_input(self.scale_edit, focus_map='string_input focus'))], dividechars=1) if self.n_remaining == 0: buttons = [ Padding.right_50( Color.button_primary(PlainButton("Deploy and Continue", self.do_deploy), focus_map='button_primary focus')) ] else: buttons = [ Padding.right_50( Color.button_primary(PlainButton( "Deploy and Configure Next Application", self.do_deploy), focus_map='button_primary focus')), Padding.right_50( Color.button_secondary(self.skip_rest_button, focus_map='button_secondary focus')) ] ws = [ Text("{} of {}: {}".format(self.idx + 1, self.n_total, self.service.service_name.upper())), Padding.center(HR()), Padding.center(self.description_w, left=2), Padding.line_break(""), Padding.center(self.readme_w, left=2), Padding.center(HR()) ] if not self.service.subordinate: ws.append(Padding.left(col, left=1)) ws.append(Padding.line_break("")) ws += buttons self.pile = Pile(ws) return Padding.center_90(Filler(self.pile, valign="top"))
def build_widgets(self): title_text = Text([("body", self.name)], align="center") desc_text = Text(["\n", strip_solo_dots(self.description)]) self.reset_button = PlainButton("Reset to Default", self.do_reset) if self.optype == OptionType.BOOLEAN: self.control = CheckBox(self.name, state=bool(self.current_value)) elif self.optype == OptionType.INT: self.control = IntEdit(caption="{}: ".format(self.name), default=self.current_value) elif self.optype == OptionType.STRING: edit_text = self.current_value or "" self.control = StringEditor( caption="{}: ".format(self.name), edit_text=edit_text) else: raise Exception("Unknown option type") if self.optype == OptionType.STRING: connect_signal(self.control._edit, 'change', self.handle_value_changed) else: connect_signal(self.control, 'change', self.handle_value_changed) button_grid = GridFlow([self.reset_button], 36, 1, 0, 'right') return Pile([Divider(), title_text, desc_text, self.control, button_grid])
def build_widgets(self): readme_files = glob(os.path.join(self.spell_dir, 'README.*')) if len(readme_files) == 0: self.readme_w = Text("No README found for bundle.") else: readme_file = readme_files[0] if len(readme_files) != 1: utils.warning("Unexpected: {} files matching README.*" "- using {}".format(len(readme_files), readme_file)) with open(readme_file) as rf: rlines = [Text(l) for l in rf.readlines()] self.readme_w = BoxAdapter(ListBox(rlines), self.initial_height) ws = [ Text("About {}:".format(self.spell_name)), Padding.right_50( Color.button_primary(PlainButton("Continue", self.do_continue), focus_map='button_primary focus')), Padding.center(HR()), Padding.center(self.readme_w, left=2), Padding.center(HR()), Padding.center( Text("Use arrow keys to scroll text " "and TAB to select the button.")) ] self.pile = Pile(ws) return Padding.center_90(Filler(self.pile, valign="top"))
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 get_machines_header(self, machines_column): b = PlainButton("Open in Browser", on_press=self.browse_maas) self.open_maas_button = AttrMap(b, 'button_secondary', 'button_secondary focus') self.maastitle = Text("Connected to MAAS") maastitle_widgets = Padding(Columns( [self.maastitle, (22, self.open_maas_button)]), align='center', width='pack', left=2, right=2) f = machines_column.machines_list.handle_filter_change self.filter_edit_box = FilterBox(f) pl = [ Divider(), Text(('body', "Ready Machines {}".format( MetaScroll().get_text()[0])), align='center'), Divider(), maastitle_widgets, Divider(), self.filter_edit_box ] self.machines_header_pile = Pile(pl) return self.machines_header_pile
def 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_option_widgets() ws += [ HR(), PlainButton("Cancel", self.do_cancel), PlainButton("Accept Changes", self.do_commit) ] self.pile = Pile(ws) return Padding.center_90(Filler(self.pile, valign="top"))
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 _simple_header_widgets(self, title): b = PlainButton("Back to Charm Store", on_press=self.show_default_view) self.back_to_mainview_button = AttrMap(b, 'button_secondary', 'button_secondary focus') button_grid = GridFlow([self.back_to_mainview_button], 36, 1, 0, 'center') return [ Divider(), Text(('body', title), align='center'), Divider(), button_grid ]
def 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 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 get_services_header(self): b = PlainButton("Clear All Placements", on_press=self.do_clear_all) self.clear_all_button = AttrMap(b, 'button_secondary', 'button_secondary focus') self.services_buttons = [self.clear_all_button] self.services_button_grid = GridFlow(self.services_buttons, 36, 1, 0, 'center') ws = [Divider(), Text(("body", "Services"), align='center'), Divider()] if self.has_maas: ws.append(self.services_button_grid) return Pile(ws)
def build_widgets(self): desc_text = Text(["\n", strip_solo_dots(self.description)]) self.reset_button = PlainButton("Reset to Default", self.do_reset) if self.optype == OptionType.BOOLEAN: self.control = CheckBox('', state=bool(self.current_value)) self.wrapped_control = self.control elif self.optype == OptionType.INT: self.control = IntEdit(default=self.current_value) self.wrapped_control = Color.string_input( self.control, focus_map='string_input focus') elif self.optype == OptionType.STRING: edit_text = self.current_value or "" self.control = StringEditor(edit_text=edit_text) self.wrapped_control = Color.string_input( self.control, focus_map='string_input focus') elif self.optype == OptionType.FLOAT: edit_text = str(self.current_value) self.control = StringEditor(edit_text=edit_text) self.wrapped_control = Color.string_input( self.control, focus_map='string_input focus') else: raise Exception("Unknown option type") self.control_columns = Columns( [('pack', Text("{}:".format(self.name), align='right')), (80, self.wrapped_control)], dividechars=1) if self.optype in [OptionType.STRING, OptionType.FLOAT]: connect_signal(self.control._edit, 'change', self.handle_value_changed) else: connect_signal(self.control, 'change', self.handle_value_changed) button_grid = GridFlow([ Color.button_secondary(self.reset_button, focus_map='button_secondary focus') ], 36, 1, 0, 'right') return Pile([ Padding.line_break(""), Padding.left(self.control_columns, left=1), Padding.left(desc_text, left=2), button_grid ])
def build_widgets(self): 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_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_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]
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)
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 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.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()