def build_unselected_widgets(self): cdict = juju.constraints_to_dict(self.md.get('constraints', '')) label = str(self.juju_machine_id) self.juju_machine_id_button = SecondaryButton(label, self.show_pin_chooser) self.juju_machine_id_label = Text(label) 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 = self._format_constraint(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 = self._format_constraint(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] for field in (self.cores_field, self.mem_field, self.disk_field): cols.append(AttrMap(field, 'string_input', 'string_input_focus')) cols.append(Text("placeholder")) self.unselected_columns = Columns(cols, dividechars=2) self.update_assignments() return self.unselected_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.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 = [ SecondaryButton(label, func) for atype, label, func in all_actions if atype in allowed_types ] self.action_buttons.append(SecondaryButton("Cancel", self.do_cancel)) opts = self.action_button_cols.options() self.action_button_cols.contents = [(b, opts) for b in self.action_buttons]
async def _build_widget(self): ws = [] if not self.application.is_subordinate: ws.append(OptionWidget("Units", "int", "How many units to deploy.", self.application.num_units, current_value=self.num_units_copy, value_changed_callback=self.handle_scale)) constraints_ow = OptionWidget( "Constraints", "string", "Set constraints on the application, ie. cores=4 mem=4G.", self.application.constraints, current_value=self.constraints_copy, value_changed_callback=self.handle_constraints) ws.append(constraints_ow) ws.append(self.constraints_error_label) ws += await self.get_whitelisted_option_widgets() self.toggle_show_all_button_index = len(ws) + 1 self.toggle_show_all_button = SecondaryButton( "Show Advanced Configuration", lambda sender: app.loop.create_task( self.do_toggle_show_all_config())) if await self.get_non_whitelisted_option_widgets(): ws += [HR(), Columns([('weight', 1, Text(" ")), (36, self.toggle_show_all_button)])] for widget in ws: self.widget.contents.append((widget, self.widget.options()))
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 = SecondaryButton("Add New Machine", self.do_add_machine) self.add_new_cols = Columns([Text(s) for s in [' ', ' ', ' ', ' ', ' ']] + [self.add_new_button], dividechars=2) self.machine_pile = Pile(header_widgets + self.machine_widgets + [self.add_new_cols]) return self.machine_pile
def build_widgets(self): 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 = SecondaryButton(self.select_button_label, self.handle_button) cols = [Text(m) for m in l] cols += [self.select_button] self.columns = Columns(cols) return self.columns
def build_widget(self): ws = [] 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 = SecondaryButton( "Show Advanced Configuration", self.do_toggle_show_all_config) ws += [ HR(), Columns([('weight', 1, Text(" ")), (36, self.toggle_show_all_button)]) ] return ws
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), ('weight', 1, Text(" ")), # placeholder for instance type ('weight', 1, Text(" ")), # placeholder for configure button ('weight', 1, Text(" ")), # placeholder for architecture button (20, SubmitButton("Deploy", self._cb('deploy'))), ] if not self.hide_config: cws[3] = (20, SecondaryButton("Configure", self._cb('config'))) if self.application.num_units > 0: cws[4] = (20, SecondaryButton("Architect", self._cb('arch'))) self.columns = Columns(cws, dividechars=1) return self.columns
def build_widgets(self, maxlen): num_str = "{}".format(self.application.num_units) col_pad = 6 self.unit_w = Text('', align='right') self.update_units() cws = [ (maxlen + col_pad, Text(self.application.name)), (10 + len(num_str), self.unit_w), ('weight', 1, Text(" ")), # placeholder for instance type (20, SecondaryButton("Configure", self._cb('config'))), ] self.columns = Columns(cws, dividechars=1) return self.columns
def build_widgets(self): desc_text = Text(["\n", strip_solo_dots(self.description)]) self.reset_button = SecondaryButton("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([self.reset_button], 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 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 = SecondaryButton(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.append(self.select_button) opts = self.unselected_columns.options() self.unselected_columns.contents[4:] = [(w, opts) for w in cols]
class MachineWidget(ContainerWidgetWrap): """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 = SecondaryButton(self.select_button_label, self.handle_button) cols = [Text(m) for m in l] cols += [self.select_button] 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 "<MachineWidget for {}>".format(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 JujuMachineWidget(ContainerWidgetWrap): """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', '')) label = str(self.juju_machine_id) self.juju_machine_id_button = SecondaryButton(label, self.show_pin_chooser) self.juju_machine_id_label = Text(label) 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 = self._format_constraint(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 = self._format_constraint(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] for field in (self.cores_field, self.mem_field, self.disk_field): cols.append(AttrMap(field, 'string_input', 'string_input_focus')) 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 = SecondaryButton(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.append(self.select_button) 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: label = "{} \N{RIGHTWARDS ARROW} {}".format( self.juju_machine_id, pinned_machine.hostname) else: label = str(self.juju_machine_id) label = "\N{PENCIL} {}".format(label) self.juju_machine_id_button.set_label(label) else: self.juju_machine_id_label.set_text(str(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 = [ SecondaryButton(label, func) for atype, label, func in all_actions if atype in allowed_types ] self.action_buttons.append(SecondaryButton("Cancel", self.do_cancel)) 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 ApplicationConfigureView(BaseView): metrics_title = 'Configure Application' def __init__(self, application, close_cb): self.title = "Configure {}".format(application.name) self.application = application self.prev_screen = close_cb self.options_copy = self.application.options.copy() self.num_units_copy = self.application.num_units self.constraints_copy = self.application.constraints self.constraints_error_label = Text(('body', '')) self.showing_all = False super().__init__() def set_constraints_error(self, msg=None): if not msg: msg = ( "Invalid constraints given, your choices are '{}'".format( ", ".join(consts.ALLOWED_CONSTRAINTS))) self.constraints_error_label.set_text(('error_major', msg)) def clear_constraints_error(self): self.constraints_error_label.set_text(('body', '')) def build_widget(self): app.loop.create_task(self._build_widget()) return [] async def _build_widget(self): ws = [] if not self.application.is_subordinate: ws.append(OptionWidget("Units", "int", "How many units to deploy.", self.application.num_units, current_value=self.num_units_copy, value_changed_callback=self.handle_scale)) constraints_ow = OptionWidget( "Constraints", "string", "Set constraints on the application, ie. cores=4 mem=4G.", self.application.constraints, current_value=self.constraints_copy, value_changed_callback=self.handle_constraints) ws.append(constraints_ow) ws.append(self.constraints_error_label) ws += await self.get_whitelisted_option_widgets() self.toggle_show_all_button_index = len(ws) + 1 self.toggle_show_all_button = SecondaryButton( "Show Advanced Configuration", lambda sender: app.loop.create_task( self.do_toggle_show_all_config())) if await self.get_non_whitelisted_option_widgets(): ws += [HR(), Columns([('weight', 1, Text(" ")), (36, self.toggle_show_all_button)])] for widget in ws: self.widget.contents.append((widget, self.widget.options())) def build_buttons(self): return [self.button('APPLY CHANGES', self.submit)] async def get_whitelisted_option_widgets(self): options = await app.juju.charmstore.config(self.application.charm) svc_opts_whitelist = utils.get_options_whitelist( self.application.name) hidden = [n for n in options['Options'].keys() if n not in svc_opts_whitelist] app.log.info("Hiding options not in the whitelist: {}".format(hidden)) return self._get_option_widgets(svc_opts_whitelist, options['Options']) async def get_non_whitelisted_option_widgets(self): options = await app.juju.charmstore.config(self.application.charm) svc_opts_whitelist = utils.get_options_whitelist( self.application.name) hidden = [n for n in options['Options'].keys() if n not in svc_opts_whitelist] return self._get_option_widgets(hidden, options['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 async def do_toggle_show_all_config(self): if not self.showing_all: new_ows = await self.get_non_whitelisted_option_widgets() header = Text("Advanced Configuration Options") opts = self.widget.options() self.widget.contents.append((header, opts)) for ow in new_ows: self.widget.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.widget.contents = self.widget.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 handle_constraints(self, opname, constraint): self.constraints_copy = constraint def submit(self): if self.constraints_copy: try: parsed = set(parse_constraints(self.constraints_copy)) except ValueError: return self.set_constraints_error() has_valid_constraints = parsed.issubset( {normalize_key(field) for field in consts.ALLOWED_CONSTRAINTS}) if not has_valid_constraints: return self.set_constraints_error() self.application.options = self.options_copy self.application.num_units = self.num_units_copy self.application.constraints = self.constraints_copy # Apply fragment updates to bundle app.current_bundle.apply({"applications": { self.application.name: self.application.to_dict() }}) self.prev_screen()
class ApplicationConfigureView(BaseView): def __init__(self, application, close_cb): self.title = "Configure {}".format(application.service_name) self.application = application self.prev_screen = close_cb self.options_copy = self.application.options.copy() self.num_units_copy = self.application.num_units self.showing_all = False super().__init__() def build_widget(self): ws = [] 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 = SecondaryButton( "Show Advanced Configuration", self.do_toggle_show_all_config) ws += [ HR(), Columns([('weight', 1, Text(" ")), (36, self.toggle_show_all_button)]) ] return ws def build_buttons(self): return [self.button('APPLY CHANGES', self.submit)] def get_whitelisted_option_widgets(self): service_id = self.application.csid.as_str_without_rev() options = app.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] app.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 = app.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.widget.options() self.widget.contents.append((header, opts)) for ow in new_ows: self.widget.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.widget.contents = self.widget.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 submit(self): self.application.options = self.options_copy self.application.num_units = self.num_units_copy self.prev_screen()