import urwid from .base import UIBaseClass, ui_button, FixedEdit, SelectableText import threading class UI_Credentials(UIBaseClass): title = "Host Access" seq_no = 4 hint = ("If your hosts use the same password, use the 'Common " "Password' feature.") password_length = 20 def __init__(self, parent=None): self.text = ( "{}\n\nClick 'Check' to confirm passwordless is setup. For hosts " "that have an AUTHFAIL/NOPASSWD status, enter the root password " "and check again.".format(self.title)) self.check_btn = ui_button(label='Check', align='right', callback=self.check_access) self.enable_password = urwid.CheckBox("Common Password", state=False, on_state_change=self.common_pswd_toggle) self.common_password = urwid.AttrMap( FixedEdit(edit_text="", multiline=False, width=self.password_length), "title") urwid.connect_signal(self.common_password.base_widget, 'change', callback=self.common_pswd_change) # pending access table elements self.pending_table_headings = urwid.Columns([ (12, urwid.Text('Hostname')), (10, urwid.Text('Status')), (self.password_length, urwid.Text('Password')) ], dividechars=1) self.pending_table_title = urwid.Text("Access Pending", align='center') self.pending_table_body = urwid.SimpleListWalker([]) self.pending_table = urwid.ListBox(self.pending_table_body) # ssh ok table elements self.sshok_table_title = urwid.Text("Access OK", align='left') self.sshok_table_headings = urwid.Columns([ (12, urwid.Text('Hostname')) ]) self.sshok_table_body = urwid.SimpleListWalker([]) self.sshok_table = urwid.ListBox(self.sshok_table_body) self.debug = None # Unused # instance uses a mutex to control updates to the screen when the # ssh setup method is called in parallel across each host self.table_mutex = threading.Lock() UIBaseClass.__init__(self, parent) # self.widget_in_focus = 4 def _update_hosts(self, hosts): """ Extract the hostname and password from the UI and update the hosts dict :param hosts (dict): dictionary of hosts :return: None """ rows = self.pending_table_body.contents for row in rows: w = row.original_widget contents = w.contents hostname = contents[0][0].text password = contents[2][0].text hosts[hostname].ssh.password = password def check_access(self, button): """ User clicked 'check' or 'Next' so we update the hosts dict with the current settings from the UI and call each hosts ssh object's setup method in parallel to check DNS and ssh access is in place :param button: UI button pressed :return: None """ btn_label = button.get_label() if btn_label == 'Next': self.next_page() app = self.parent hosts = app.hosts self._update_hosts(hosts) callback = self.refresh threads = [] for hostname in sorted(hosts.keys()): this_host = hosts[hostname] if not this_host.ssh.ok: _t = threading.Thread(target=this_host.ssh.setup, args=(callback,)) _t.start() threads.append(_t) for _t in threads: _t.join() if len(self.pending_table_body) == 0: button.set_label('Next') def next_page(self): # disconnect the signal handler setup by __init__ urwid.disconnect_signal(self.common_password.base_widget, 'change', callback=self.common_pswd_change) app = self.parent app.next_page() def refresh(self): """ refresh is called locally within this page element, and also as a callback from the ssh setup method (hence the mutex). It is responsible for maintaining the 'pending' and 'access ok' tables shown in the UI based on the current state of the hosts dict :return: None """ self.table_mutex.acquire() app = self.parent hosts = app.hosts pending_access_items = [] ssh_ok_items = [] for hostname in sorted(hosts.keys()): this_host = hosts[hostname] if this_host.ssh.status_code != 0: w = urwid.AttrMap( urwid.Columns([ (12, urwid.Text(this_host.hostname)), (10, urwid.Text(this_host.ssh.shortmsg)), (self.password_length, FixedEdit(edit_text=this_host.ssh.password, width=self.password_length)) ], dividechars=1), "active_step", "reversed") pending_access_items.append(w) else: w = urwid.Columns([ urwid.AttrMap( SelectableText(this_host.hostname), "active_step", "reversed_green") ]) ssh_ok_items.append(w) self.pending_table_body[:] = pending_access_items self.sshok_table_body[:] = ssh_ok_items # Update the table headings to show progress self.pending_table_title.set_text( "Access Pending({})".format(len(self.pending_table_body))) self.sshok_table_title.set_text( "Access OK({}/{})".format(len(self.sshok_table_body), len(hosts.keys()))) if hasattr(app, 'loop'): # performing a screen redraw here enables the ssh setup threads to # update the UI instead of waiting for the thread to complete app.loop.draw_screen() self.table_mutex.release() return def common_pswd_change(self, *args): if self.enable_password.state is True: new_password = self.common_password.original_widget.edit_text self._update_passwords(new_password) else: # change registered, but checkbox not active so this is a NOOP pass def _update_passwords(self, new_password): """ Update the password field of all rows within the pending access table :param new_password (str): password to use to update all hosts with :return: None """ rows = self.pending_table_body.contents for row in rows: w = row.original_widget contents = w.contents pswd = contents[2][0] pswd.set_edit_text(new_password) def common_pswd_toggle(self, *args): # set the selected state for each host listed new_password = self.common_password.original_widget.edit_text if self.enable_password.state is False: # State before call if new_password: self._update_passwords(new_password) @property def render_page(self): w = urwid.Pile([ urwid.Padding(urwid.Text(self.text), left=1, right=1), urwid.Divider(), urwid.Padding( urwid.Columns([ ("fixed", 20, self.enable_password), ("fixed", 22, self.common_password) ]), left=1), urwid.Divider(), urwid.Padding( urwid.Columns([ ("weight", 3, urwid.Pile([ self.pending_table_title, urwid.BoxAdapter( urwid.Frame(self.pending_table, header=self.pending_table_headings), 10), ])), ("weight", 1, urwid.Pile([ self.sshok_table_title, urwid.BoxAdapter( urwid.Frame(self.sshok_table, header=self.sshok_table_headings), 10), ])) ], dividechars=1), left=1), self.check_btn ]) w.focus_position = 5 # the Check button widget return urwid.AttrMap( urwid.Filler(w, valign='top', top=1), "active_step")