def build_widget(self): self.step_pile = pile = Pile([ Columns([ ('fixed', 3, self.icon), self.description, ], dividechars=1), Padding.line_break(""), Padding.push_4(self.output), ]) if utils.is_linux() and self.model.needs_sudo: pile.contents.append((Padding.line_break(""), pile.options())) label = 'This step requires sudo.' if not self.app.sudo_pass: label += ' Enter sudo password, if needed:' self.sudo_input = PasswordEditor() columns = [ ('weight', 0.5, Padding.left(Text(('body', label)), left=5)), ] if self.sudo_input: columns.append( ('weight', 1, Color.string_input(self.sudo_input, focus_map='string_input focus'))) pile.contents.append((Columns(columns, dividechars=3), pile.options()))
class ApplicationWidget(WidgetWrap): def __init__(self, application, maxlen, controller, deploy_cb, hide_config=False): self.application = application self.controller = controller self.deploy_cb = deploy_cb self.hide_config = hide_config self._selectable = True super().__init__(self.build_widgets(maxlen)) self.columns.focus_position = len(self.columns.contents) - 1 def __repr__(self): return "<ApplicationWidget for {}>".format( self.application.service_name) def selectable(self): return self._selectable def update(self): self.unit_w.set_text("Units: {:4d}".format(self.application.num_units)) 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 remove_buttons(self): self._selectable = False self.columns.contents = self.columns.contents[:-2] self.columns.contents.append((Text(""), self.columns.options())) def set_progress(self, progress_str): self.columns.contents[-1] = (Text(progress_str, align='right'), self.columns.options())
def build_widget(self): rows = [ Text("Select primary/external network, " "and datastore for this deployment:"), HR(), Columns([ ('weight', 0.5, Text('primary network', align="right")), Color.string_input( self.vsphere_config['primary-network'], focus_map='string_input focus') ], dividechars=1), HR(), Columns([ ('weight', 0.5, Text('external network (optional)', align="right")), Color.string_input( self.vsphere_config['external-network'], focus_map='string_input focus') ], dividechars=1), HR(), Columns([ ('weight', 0.5, Text('datastore', align="right")), Color.string_input( self.vsphere_config['datastore'], focus_map='string_input focus') ], dividechars=1) ] self.pile = Pile(rows) return self.pile
def _build_model_inputs(self): items = [ Columns( [ ("weight", 0.2, Text("Ceph MON", align="right")), ("weight", 0.3, Color.string_input(self.ceph_mon, focus_map="string_input focus")) ], dividechars=4 ), Columns( [ ("weight", 0.2, Text("Username", align="right")), ("weight", 0.3, Color.string_input(self.username, focus_map="string_input focus")) ], dividechars=4 ), Columns( [ ("weight", 0.2, Text("Key", align="right")), ("weight", 0.3, Color.string_input(self.ceph_key, focus_map="string_input focus")) ], dividechars=4 ) ] return Pile(items)
def __init__(self, keyMap=None, bindArgs=None, bindKwargs=None): self._keyMap = keyMap or {} self._bindArgs = bindArgs or () self._bindKwargs = bindKwargs or {} self._state = None self._w = Columns([], 1) self.refresh() super().__init__(self._w, 'style1')
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
def __init__(self, text): self._editable = False self._content = Text(text) self._root = Columns(()) self._content_options = self._root.options('pack') self._left = Text("("), self._root.options('given', 1) self._right = Text(")"), self._root.options('given', 1) self._update() super().__init__(self._root)
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, data, parent, color=None, **kwargs): self.table = parent self.color = color self.dict = data Columns.__init__(self, [], **kwargs) for header in self.table.columns: self.contents.append(self.build_cell(header))
def _display_schedule(self, s, f, date, schedule): rows = [] for fitness in self.fitness: for cols in self._single_response(s, f, date, schedule, fitness, schedule.frame_type == 'd'): rows.append(Columns(cols)) for fatigue in self.fatigue: for cols in self._single_response(s, f, date, schedule, fatigue, schedule.frame_type == 'd'): rows.append(Columns(cols)) if rows: yield Pile([Text('SHRIMP'), Indent(Pile(rows))])
def build_widget(self): return [ Columns([('fixed', 16, Text('network bridge', align="right")), Color.string_input(self.lxd_config['network'], focus_map='string_input focus')], dividechars=1), HR(), Columns([('fixed', 16, Text('storage pool', align="right")), Color.string_input(self.lxd_config['storage-pool'], focus_map='string_input focus')], dividechars=1), ]
def __init__(self, height, directory=".", file="", attr=(None, None), show_hidden=False): """ height -- height of the directory list and the file list directory, file -- default selection attr -- (inner selectable widgets, selected widgets) show_hidden -- If True, hidden files are shown by default. """ self.directory = abspath(directory) self.file = "" self.attr = attr self.height = height self.show_hidden = show_hidden # Create dummy widgets for directory and file display: self.dir_widget = AttrWrap( BoxAdapter(ListBox([self._blank]), self.height), self.attr[0]) self.file_widget = AttrWrap( BoxAdapter(ListBox([self._blank]), self.height), self.attr[0]) columns = Columns([self.dir_widget, self.file_widget], 1) # Selection widget: self.select_widget = AttrWrap(Edit("", ""), self.attr[0], self.attr[1]) # Buttons and checkbox: button_widgets = [ AttrWrap(Button(button, self._action), attr[0], attr[1]) for button in ["OK", "Cancel"] ] button_grid = GridFlow(button_widgets, 12, 2, 1, 'center') button_cols = Columns([ CheckBox(self.SHOW_HIDDEN_TEXT, self.show_hidden, False, self._toggle_hidden), button_grid ]) self.outer_widget = Pile([ columns, self._blank, Text(self.SELECTION_TEXT), self.select_widget, self._blank, button_cols ]) self.update_widgets() WidgetWrap.__init__(self, self.outer_widget)
class ApplicationWidget(WidgetWrap): def __init__(self, application, maxlen, controller, deploy_cb): self.application = application self.controller = controller self.deploy_cb = deploy_cb self._selectable = True super().__init__(self.build_widgets(maxlen)) self.columns.focus_position = len(self.columns.contents) - 1 def __repr__(self): return "<ApplicationWidget for {}>".format( self.application.service_name) def selectable(self): return self._selectable def build_widgets(self, maxlen): num_str = "{}".format(self.application.num_units) col_pad = 6 cws = [ (maxlen + col_pad, Text(self.application.service_name)), (7 + len(num_str), Text('Units: {}'.format(num_str), align='right')), # placeholder for instance type ('weight', 1, Text(" ")), (20, Color.button_secondary( PlainButton("Configure", partial(self.controller.do_configure, self.application)), focus_map='button_secondary focus')), (20, Color.button_primary( PlainButton("Deploy", partial(self.deploy_cb, self.application)), focus_map='button_primary focus')) ] self.columns = Columns(cws, dividechars=1) return self.columns def remove_buttons(self): self._selectable = False self.columns.contents = self.columns.contents[:-2] self.columns.contents.append((Text(""), self.columns.options())) def set_progress(self, progress_str): self.columns.contents[-1] = (Text(progress_str, align='right'), self.columns.options())
def _container(self): total_items = [ Columns([("weight", 0.2, Text("Format", align="right")), ("weight", 0.3, Color.string_input(self._format_edit(), focus_map="string_input focus"))], dividechars=4), Columns([("weight", 0.2, Text("Mount", align="right")), ("weight", 0.3, Color.string_input(self.mountpoint, focus_map="string_input focs"))], dividechars=4) ] return Pile(total_items)
class HotkeyBar( AttrWrap, ): def __init__(self, keyMap=None, bindArgs=None, bindKwargs=None): self._keyMap = keyMap or {} self._bindArgs = bindArgs or () self._bindKwargs = bindKwargs or {} self._state = None self._w = Columns([], 1) self.refresh() super().__init__(self._w, 'style1') def refresh(self): tMap = self._keyMap res = [] if self._state is not None: n, tMap = self._state w = AttrWrap(Text(f'{n}:'), 'style1bold') res.append((w, self._w.options('pack'))) res.append( (HotkeyItem('esc', '', on_mouseLeft=self._fire), self._w.options('pack'))) for key, o in tMap.items(): res.append( (HotkeyItem(key, o[0], on_mouseLeft=self._fire), self._w.options('pack'))) self._w.contents = res def _fire(self, key=None): if not key: return tMap = self._keyMap if self._state is None else self._state[1] if key == 'esc' and self._state is not None: self._state = None self.refresh() elif key in tMap: n, v = tMap[key] print('HOTKEY', key, n, v) if callable(v): _args = (n, ) + self._bindArgs _kwargs = self._bindKwargs v(*_args, **_kwargs) self._state = None else: self._state = (n, v) self.refresh() else: return False return True def keypress(self, size, key): if not self._fire(key): return key
def _journal_date(self, s, f, ajournal, date): zones = build_zones(s, ajournal, HRZ_WIDTH) active_date = self.__active_date(s, ajournal, date) climbs = self.__climbs(s, ajournal, date) details = Pile(([] if climbs else [Divider()]) + active_date + climbs) yield Pile([ Text(ajournal.name), Indent(Columns([details, (HRZ_WIDTH + 2, zones)])), Divider(), Indent(Columns([Pile(self.__template(s, ajournal, MIN_KM_TIME_ANY, 'Min Time', r'(\d+km)', date) + self.__template(s, ajournal, MED_KM_TIME_ANY, 'Med Time', r'(\d+km)', date)), Pile(self.__template(s, ajournal, MAX_MED_HR_M_ANY, 'Max Med Heart Rate', r'(\d+m)', date) + self.__template(s, ajournal, MAX_MEAN_PE_M_ANY, 'Max Mean Power Estimate', r'(\d+m)', date))])) ])
def build_results(self): rows = [] rows.append( Columns([('weight', 0.1, Text('Application')), ('weight', 0.4, Text('Result'))], dividechars=5)) rows.append(HR()) rows.append(Padding.line_break("")) for k, v in self.results.items(): rows.append( Columns([('weight', 0.1, Text(k)), ('weight', 0.4, Text(v))], dividechars=5)) self.app.log.debug(rows) return rows
def __init__(self, original_widget, title="", tlcorner=u'┌', tline=u'─', lline=u'│', trcorner=u'┐', blcorner=u'└', rline=u'│', bline=u'─', brcorner=u'┘'): """ Use 'title' to set an initial title text with will be centered on top of the box. You can also override the widgets used for the lines/corners: tline: top line bline: bottom line lline: left line rline: right line tlcorner: top left corner trcorner: top right corner blcorner: bottom left corner brcorner: bottom right corner """ tline, bline = Divider(tline), Divider(bline) lline, rline = SolidFill(lline), SolidFill(rline) tlcorner, trcorner = Text(tlcorner), Text(trcorner) blcorner, brcorner = Text(blcorner), Text(brcorner) title_widget = ('fixed', len(title), AttrMap(Text(title), 'header')) top = Columns([ ('fixed', 1, tlcorner), title_widget, tline, ('fixed', 1, trcorner) ]) middle = Columns([('fixed', 1, lline), original_widget, ('fixed', 1, rline)], box_columns=[0, 2], focus_column=1) bottom = Columns([('fixed', 1, blcorner), bline, ('fixed', 1, brcorner)]) pile = Pile([('flow', top), middle, ('flow', bottom)], focus_item=1) # super? WidgetDecoration.__init__(self, original_widget) WidgetWrap.__init__(self, pile)
def _layout(self): self._wallpaper_count = Text(str(len(self._wpctrl.wallpapers))) self._info = Text("", wrap='clip') self._head = Columns([('pack', self._wallpaper_count), (10, Text("Wallpapers")), self._info], dividechars=1) header = Pile([self._head, AttrMap(Divider("─"), 'divider')]) self._screens = [ScreenWidget(screen, self._scrctrl) for screen in self._scrctrl.screens] body = ListBoxWithTabSupport(self._screens) self._root = Frame(header=header, body=body)
def _w_init(self): self._w_prep() self.refresh() self._w = Columns([ (1, self._w_indicator), Padding(Columns([ (11, Padding(Pile([self._w_timestamp, self._w_statusbar]), right=1)), (20, Padding(self._w_members, right=2)), Pile([self._w_subject, self._w_lastmsg]), ], 0), left=1), ], 0)
def top(period=1, get_state=get_demo_state): """Display process information. Arguments: - period (float) : update period - get_state (callable) : function to generate state information """ engine_table = Columns([]) process_table = Columns([]) def make_text(title, attr="", align="left"): """Create a Text object with given content, style and alignment.""" return Text((attr, " %s" % title), align=align) def make_separator(): """Create a separator.""" return make_text("") def update(): engines, processes = get_state() engine_table.contents = get_engine_table_content(engines) process_table.contents = get_process_table_content([header] + processes) items = [ make_separator(), make_text("Process Viewer", attr="section", align="center"), make_text("Engine Usage:"), LineBox(engine_table, **{arg: " " for arg in linebox_args}), make_text("Process Table:"), make_separator(), LineBox(process_table), make_separator(), make_text("Press (q) to quit.") ] def on_timer(loop, user_data): update() loop.set_alarm_in(period, on_timer) def on_key(key): if key in ('q', 'Q'): raise ExitMainLoop() loop = MainLoop(get_padded(items), palette, unhandled_input=on_key) on_timer(loop, None) # Start timer loop.run()
def pack_widgets(branch, max_cols=N_COLUMNS): new_branch = [branch[0]] columns, width = [], 0 for widget in branch[1:]: size = widget_size(widget) if size + width <= max_cols: columns.append((COLUMN_WIDTH * size, widget)) width += size else: if columns: new_branch.append(Columns(columns)) columns, width = [(COLUMN_WIDTH * size, widget)], size if columns: new_branch.append(Columns(columns)) return default_after(new_branch)
def __init__(self, num_rows=20, w=(14, 14, 18, 16, 16, 16, 20)): """ @method __init__ Initializes the widget """ self.m_process_list = ProcessList(w) self.prev_sort_item = None self.w_status = HeaderButton('Status', 'status', self.handle_click) self.w_pid = HeaderButton('PID', 'pid', self.handle_click) self.w_name = HeaderButton('Name', 'name', self.handle_click) self.w_cpu = HeaderButton('CPU %', 'cpu_perc', self.handle_click) self.w_mem = HeaderButton('MEM %', 'mem_perc', self.handle_click) self.w_up = HeaderButton('Uptime', 'uptime', self.handle_click) self.w_pname = HeaderButton('Process', 'pname', self.handle_click) self.w_cpu.activate() self.prev_sort_item = self.w_cpu self.header_buttons = h = [ self.w_status, self.w_pid, self.w_name, self.w_cpu, self.w_mem, self.w_up, self.w_pname ] m_header = AttrMap( Columns([('fixed', w[i], h[i]) for i in range(0, len(h))]), 'invert') m_lb = ListBox( SimpleListWalker( [m_header, BoxAdapter(self.m_process_list, num_rows)])) super(ProcessTable, self).__init__(m_lb, None) self.update()
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.action_button_cols = Columns([]) self.action_buttons = [] self.build_unselected_widgets() self.pile = Pile([self.unselected_columns]) return self.pile
def _build_node_waiting(self): """ creates a loading screen if nodes do not exist yet """ text = [ Text("\n\n\n"), Text(self.message, align="center"), Text("\n\n\n") ] load_box = [ AttrWrap(Text("\u2582", align="center"), "pending_icon_on"), AttrWrap(Text("\u2581", align="center"), "pending_icon_on"), AttrWrap(Text("\u2583", align="center"), "pending_icon_on"), AttrWrap(Text("\u2584", align="center"), "pending_icon_on"), AttrWrap(Text("\u2585", align="center"), "pending_icon_on"), AttrWrap(Text("\u2586", align="center"), "pending_icon_on"), AttrWrap(Text("\u2587", align="center"), "pending_icon_on"), AttrWrap(Text("\u2588", align="center"), "pending_icon_on") ] # Add loading boxes random.shuffle(load_box) loading_boxes = [] loading_boxes.append(('weight', 1, Text(''))) for i in load_box: loading_boxes.append( ('pack', load_box[random.randrange(len(load_box))])) loading_boxes.append(('weight', 1, Text(''))) loading_boxes = Columns(loading_boxes) return ScrollableListBox(text + [loading_boxes])
def _make(self): down = QuickChangeBar(self._date, '<', -1, bar=self._bar) connect_signal(down.widget, 'change', self.date_change) up = QuickChangeBar(self._date, '>', 1, bar=self._bar) connect_signal(up.widget, 'change', self.date_change) year = YearBar(self._date, bar=self._bar) connect_signal(year.widget, 'change', self.date_change) month = MonthBar(self._date, as_text=False, bar=self._bar) connect_signal(month.widget, 'change', self.date_change) day_of_month = DayOfMonthBar(self._date, bar=self._bar) connect_signal(day_of_month.widget, 'change', self.date_change) day_of_week = DayOfWeekBar(self._date, bar=self._bar) connect_signal(day_of_week.widget, 'change', self.date_change) return Columns([ (1, FocusAttr(down)), (1, Text(" ")), (4, Padding(FocusAttr(year), align='center', width='pack')), (1, Text("-")), (2, Padding(FocusAttr(month), align='center', width='pack')), (1, Text("-")), (2, Padding(FocusAttr(day_of_month), align='center', width='pack')), (1, Text(" ")), (3, Padding(FocusAttr(day_of_week), align='center', width='pack')), (1, Text(" ")), (1, FocusAttr(up)), ])
def build_footer(self): cancel = menu_btn(on_press=self.do_cancel, label="\n BACK\n") self.apply_button = menu_btn(on_press=self.do_commit, label="\n APPLY\n") self.buttons = Columns([ ('fixed', 2, Text("")), ('fixed', 13, Color.menu_button( cancel, focus_map='button_primary focus')), Text(""), ('fixed', 20, Color.menu_button( self.apply_button, 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 _build_node_waiting(self): """ creates a loading screen if nodes do not exist yet """ text = [ Padding.line_break(""), Text(self.message, align="center"), Padding.line_break("") ] load_box = [ Color.pending_icon_on(Text("\u2581", align="center")), Color.pending_icon_on(Text("\u2582", align="center")), Color.pending_icon_on(Text("\u2583", align="center")), Color.pending_icon_on(Text("\u2584", align="center")), Color.pending_icon_on(Text("\u2585", align="center")), Color.pending_icon_on(Text("\u2586", align="center")), Color.pending_icon_on(Text("\u2587", align="center")), Color.pending_icon_on(Text("\u2588", align="center")) ] # Add loading boxes random.shuffle(load_box) loading_boxes = [] loading_boxes.append(('weight', 1, Text(''))) for i in load_box: loading_boxes.append( ('pack', load_box[random.randrange(len(load_box))])) loading_boxes.append(('weight', 1, Text(''))) loading_boxes = Columns(loading_boxes) return Filler(Pile(text + [loading_boxes]), valign="middle")
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 _construct_arrow_tip(self, pos): cols = [] overall_width = self._icon_offset if self._icon_offset > 0: # how often do we repeat the hbar_char until width icon_offset is reached hbar_char_count = len(self._arrow_hbar_char) / self._icon_offset barw = Text(self._arrow_hbar_char * hbar_char_count) bar = AttrMap(barw, self._arrow_hbar_att or self._arrow_att) cols.insert(1, (self._icon_offset, bar)) # add icon only for non-leafs if self._walker.first_child_position(pos) is not None: iwidth, icon = self._construct_collapse_icon(pos) if icon is not None: cols.insert(0, (iwidth, icon)) overall_width += iwidth # get arrow tip awidth, tip = ArrowTreeListWalker._construct_arrow_tip(self, pos) if tip is not None: cols.append((awidth, tip)) overall_width += awidth return overall_width, Columns(cols)
def _display_date(self, s, f, date): tomorrow = local_date_to_time(date + dt.timedelta(days=1)) today = local_date_to_time(date) pile = [] # todo - rewrite to use above for agroup in s.query(ActivityGroup).order_by( ActivityGroup.sort).all(): segment_pile = [] for sjournal in s.query(SegmentJournal). \ join(Segment). \ filter(SegmentJournal.start >= today, SegmentJournal.start < tomorrow, Segment.activity_group == agroup). \ order_by(SegmentJournal.start).all(): columns = self.__fields(s, date, sjournal) if columns: segment_pile.append(Columns(columns)) if segment_pile: pile.append( Pile([ Text(sjournal.segment.name), Indent(Pile(segment_pile)) ])) if pile: yield Pile([Text('Segments'), Indent(Pile(pile))])
def build_widget(self, **kwargs): def remove_p(charm_class): n = self.pc.assignment_machine_count_for_charm(charm_class) return n > 0 def not_conflicted_p(cc): state, _, _ = self.pc.get_charm_state(cc) return state != CharmState.CONFLICTED actions = [(remove_p, 'Remove', self.do_remove), (not_conflicted_p, 'Add', self.do_add)] self.unrequired_undeployed_sl = ServicesList(self.pc, actions, actions, ignore_deployed=True, title="Un-Deployed") self.deployed_sl = ServicesList(self.pc, actions, actions, deployed_only=True, show_placements=True, title="Deployed Services") self.assigned_sl = ServicesList(self.pc, actions, actions, assigned_only=True, show_placements=True, title="Services to be Deployed") self.buttons = [] self.button_grid = GridFlow(self.buttons, 22, 1, 1, 'center') self.pile1 = Pile([self.button_grid, self.assigned_sl, self.unrequired_undeployed_sl]) self.pile2 = Pile([self.deployed_sl]) return LineBox(Columns([self.pile1, self.pile2]), title="Add Services")
def _build_widget(self, **kwargs): total_items = [ Padding.center_60(Text(self.title, align="center")), Padding.center_60( Divider("\N{BOX DRAWINGS LIGHT HORIZONTAL}", 1, 1)) ] if self.input_items: for item in self.input_items: key = item[0] caption = item[1] try: mask = item[2] except: mask = None self.input_selection[key] = StringEditor(caption="", mask=mask) col = Columns([ ("weight", 0.4, Text(caption, align="right")), Color.string_input(self.input_selection[key], focus_map="string_input focus") ]) total_items.append(Padding.center_60(col)) total_items.append( Padding.center_60( Divider("\N{BOX DRAWINGS LIGHT HORIZONTAL}", 1, 1))) total_items.append(Padding.center_20(self._build_buttons())) return Filler(Pile(total_items), valign='middle')
class TimelinesBuffer(WidgetWrap): """A widget that displays one or more `Timeline` objects.""" def __init__(self, timelines=None, **kwargs): if timelines: timeline_widgets = [TimelineWidget(timeline, **kwargs) for timeline in timelines] else: timeline_widgets = [] WidgetWrap.__init__(self, Columns(timeline_widgets)) def scroll_up(self): active_widget = self._w.get_focus() active_widget.focus_previous() def scroll_down(self): active_widget = self._w.get_focus() active_widget.focus_next() def scroll_top(self): active_widget = self._w.get_focus() active_widget.focus_first() def scroll_bottom(self): active_widget = self._w.get_focus() active_widget.focus_last() def clear(self): """Clears the buffer.""" # FIXME pass def render_timelines(self, timelines): """Renders the given statuses.""" timeline_widgets = [TimelineWidget(timeline) for timeline in timelines] self._w = Columns(timeline_widgets) def set_focus(self, index): active_widget = self._w.get_focus() active_widget.set_focus(index) def focus_timeline(self, index): self._w.set_focus_column(index)
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 render_timelines(self, timelines): """Renders the given statuses.""" timeline_widgets = [TimelineWidget(timeline) for timeline in timelines] self._w = Columns(timeline_widgets)
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 PlacementView(WidgetWrap): """ Handles display of machines and services. displays nothing if self.controller is not set. set it to a PlacementController. :param do_deploy_cb: deploy callback from controller """ def __init__(self, display_controller, placement_controller, config, do_deploy_cb, initial_state=UIState.CHARMSTORE_VIEW, has_maas=False): self.display_controller = display_controller self.placement_controller = placement_controller self.config = config self.do_deploy_cb = do_deploy_cb self.state = initial_state self.has_maas = has_maas self.prev_state = None self.showing_overlay = False self.showing_graph_split = False self.show_scc_graph = False self.bundle = placement_controller.bundle self.metadata_controller = MetadataController(self.bundle, config) w = self.build_widgets() super().__init__(w) self.reset_selections(top=True) # calls self.update def scroll_down(self): pass def scroll_up(self): pass def focus_footer(self): self.frame.focus_position = 'footer' self.footer_grid.focus_position = 1 def handle_tab(self, backward): tabloop = ['headercol1', 'col1', 'headercol2', 'col2', 'footer'] if not self.has_maas: tabloop.remove('headercol1') def goto_header_col1(): self.frame.focus_position = 'header' self.header_columns.focus_position = 0 def goto_header_col2(): self.frame.focus_position = 'header' self.header_columns.focus_position = 1 def goto_col1(): self.frame.focus_position = 'body' self.columns.focus_position = 0 def goto_col2(): self.frame.focus_position = 'body' if self.state == UIState.PLACEMENT_EDITOR: self.focus_machines_column() elif self.state == UIState.RELATION_EDITOR: self.focus_relations_column() elif self.state == UIState.OPTIONS_EDITOR: self.focus_options_column() else: self.focus_charmstore_column() actions = {'headercol1': goto_header_col1, 'headercol2': goto_header_col2, 'col1': goto_col1, 'col2': goto_col2, 'footer': self.focus_footer} if self.frame.focus_position == 'header': cur = ['headercol1', 'headercol2'][self.header_columns.focus_position] elif self.frame.focus_position == 'footer': cur = 'footer' else: cur = ['col1', 'col2'][self.columns.focus_position] cur_idx = tabloop.index(cur) if backward: next_idx = cur_idx - 1 else: next_idx = (cur_idx + 1) % len(tabloop) actions[tabloop[next_idx]]() def keypress(self, size, key): if key in ['tab', 'shift tab']: self.handle_tab('shift' in key) return key unhandled_key = self._w.keypress(size, key) if unhandled_key is None: return None elif unhandled_key in ['g', 'G']: if unhandled_key == 'G': self.show_scc_graph = True else: self.show_scc_graph = False self.showing_graph_split = not self.showing_graph_split if self.showing_graph_split: opts = self.placement_edit_body_pile.options() self.placement_edit_body_pile.contents.insert( 0, (self.bundle_graph_widget, opts)) else: self.placement_edit_body_pile.contents.pop(0) self.update() else: return unhandled_key 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 get_charmstore_header(self, charmstore_column): series = self.placement_controller.bundle.series self.charm_search_widget = CharmStoreSearchWidget(self.do_add_charm, charmstore_column, self.config, series) self.charm_search_header_pile = Pile([Divider(), Text(("body", "Add Charms"), align='center'), Divider(), self.charm_search_widget]) return self.charm_search_header_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 update_machines_header(self): maasinfo = self.placement_controller.maasinfo maasname = "'{}' <{}>".format(maasinfo['server_name'], maasinfo['server_hostname']) self.maastitle.set_text("Connected to MAAS {}".format(maasname)) 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 get_relations_header(self): return Pile(self._simple_header_widgets("Relation Editor")) def get_options_header(self, options_column): simple_widgets = self._simple_header_widgets("Options Editor") fb = FilterBox(options_column.handle_filter_change, info_text="Filter by option name") padded_fb = Padding(AttrMap(fb, 'filter', 'filter_focus'), left=2, right=2) return Pile(simple_widgets + [padded_fb]) 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 update(self): if self.prev_state != self.state: h_opts = self.header_columns.options() c_opts = self.columns.options() if self.state == UIState.PLACEMENT_EDITOR: self.update_machines_header() self.header_columns.contents[-1] = (self.machines_header, h_opts) self.columns.contents[-1] = (self.machines_column, c_opts) elif self.state == UIState.RELATION_EDITOR: self.header_columns.contents[-1] = (self.relations_header, h_opts) self.columns.contents[-1] = (self.relations_column, h_opts) elif self.state == UIState.CHARMSTORE_VIEW: self.header_columns.contents[-1] = (self.charmstore_header, h_opts) self.columns.contents[-1] = (self.charmstore_column, h_opts) elif self.state == UIState.OPTIONS_EDITOR: self.header_columns.contents[-1] = (self.options_header, h_opts) self.columns.contents[-1] = (self.options_column, h_opts) self.prev_state = self.state self.services_column.update() if self.state == UIState.PLACEMENT_EDITOR: self.machines_column.update() elif self.state == UIState.RELATION_EDITOR: self.relations_column.update() elif self.state == UIState.OPTIONS_EDITOR: self.options_column.update() else: self.charmstore_column.update() unplaced = self.placement_controller.unassigned_undeployed_services() all = self.placement_controller.services() n_subs_in_unplaced = len([c for c in unplaced if c.subordinate]) n_subs_in_all = len([c for c in all if c.subordinate]) n_total = len(all) - n_subs_in_all remaining = len(unplaced) - n_subs_in_unplaced if remaining > 0: dmsg = "\nAuto-assigning {}/{} services".format(remaining, n_total) else: dmsg = "" self.deploy_button_label.set_text(dmsg) if self.showing_graph_split: bundle = self.placement_controller.bundle if self.show_scc_graph: gtext = scc_graph_for_bundle(bundle, self.metadata_controller) else: gtext = graph_for_bundle(bundle, self.metadata_controller) if gtext == "": gtext = "No graph to display yet." self.bundle_graph_text.set_text(gtext) def browse_maas(self, sender): bc = self.config.juju_env['bootstrap-config'] try: p = Popen(["sensible-browser", bc['maas-server']], stdout=PIPE, stderr=PIPE) outs, errs = p.communicate(timeout=5) except TimeoutExpired: # went five seconds without an error, so we assume it's # OK. Don't kill it, just let it go: return e = errs.decode('utf-8') msg = "Error opening '{}' in a browser:\n{}".format(bc['name'], e) w = InfoDialogWidget(msg, self.remove_overlay) self.show_overlay(w) def do_clear_all(self, sender): self.placement_controller.clear_all_assignments() def do_add_charm(self, charm_name, charm_dict): """Add new service and focus its widget. """ assert(self.state == UIState.CHARMSTORE_VIEW) def done_cb(f): csid = CharmStoreID(charm_dict['Id']) id_no_rev = csid.as_str_without_rev() info = self.metadata_controller.get_charm_info(id_no_rev, lambda _: None) is_subordinate = info["Meta"]["charm-metadata"].get( "Subordinate", False) service_name = self.placement_controller.add_new_service( charm_name, charm_dict, is_subordinate=is_subordinate) self.frame.focus_position = 'body' self.columns.focus_position = 0 self.update() self.services_column.select_service(service_name) # TODO MMCC: need a 'loading' indicator to start here self.metadata_controller.load([charm_dict['Id']], done_cb) def do_add_bundle(self, bundle_dict): assert(self.state == UIState.CHARMSTORE_VIEW) _, new_services, _ = self.placement_controller.merge_bundle( bundle_dict) self.frame.focus_position = 'body' self.columns.focus_position = 0 charms = list(set([s.charm_source for s in new_services])) self.metadata_controller.load(charms) self.update() ss = sorted(new_services, key=attrgetter('service_name')) first_service = ss[0].service_name self.services_column.select_service(first_service) def do_clear_machine(self, sender, machine): self.placement_controller.clear_assignments(machine) def clear_selections(self): self.services_column.clear_selections() self.machines_column.clear_selections() def reset_selections(self, top=False): self.clear_selections() self.state = UIState.CHARMSTORE_VIEW self.update() self.columns.focus_position = 0 if top: self.services_column.focus_top() else: self.services_column.focus_next() def focus_machines_column(self): self.columns.focus_position = 1 self.machines_column.focus_prev_or_top() def focus_relations_column(self): self.columns.focus_position = 1 self.relations_column.focus_prev_or_top() def focus_options_column(self): self.columns.focus_position = 1 self.options_column.focus_prev_or_top() def focus_charmstore_column(self): self.columns.focus_position = 1 self.charmstore_column.focus_prev_or_top() def edit_placement(self): self.state = UIState.PLACEMENT_EDITOR self.update() self.focus_machines_column() def show_default_view(self, *args): self.state = UIState.CHARMSTORE_VIEW self.update() def edit_relations(self, service): self.state = UIState.RELATION_EDITOR self.relations_column.set_service(service) self.update() self.focus_relations_column() def edit_options(self, service): self.state = UIState.OPTIONS_EDITOR self.options_column.set_service(service) self.update() self.focus_options_column() def do_deploy(self, sender): self.do_deploy_cb() def show_overlay(self, overlay_widget): if not self.showing_overlay: self.orig_w = self._w self._w = Overlay(top_w=overlay_widget, bottom_w=self._w, align='center', width=('relative', 60), min_width=80, valign='middle', height='pack') self.showing_overlay = True def remove_overlay(self, overlay_widget): # urwid note: we could also get orig_w as # self._w.contents[0][0], but this is clearer: self._w = self.orig_w self.showing_overlay = False
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
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)