def show_search_bar(self): self.search_bar = SearchBar() self.view.footer = urwid.Pile([self.user_input, self.search_bar], focus_item=1) self.view.focus_position = "footer" self.print_status("Press 'ESC' to abort search...") urwid.connect_signal(self.search_bar, 'change', self.execute_search) self.listbox.disable_keys()
class JenkinsJobBrowser(): OPTION_LABEL_JOB_INFO = "Job Info" OPTION_LABEL_BUILD = "Build" OPTION_LABEL_LAST_BUILD_LOG = "Last Build Log" COLOR_MAPPING = { 'blue': 'SUCCESS', 'green': 'SUCCESS', 'red': 'FAILED', 'yellow': 'UNSTABLE', 'aborted': 'ABORTED', 'disabled': 'DISABLED', 'grey': 'NOTBUILT', 'notbuilt': 'NOTBUILT', 'building': 'BUILDIONG', } palette = [ ('body', urwid.LIGHT_GRAY, urwid.BLACK), ('table_heading', urwid.LIGHT_GRAY + ",bold", urwid.BLACK), ('focus', urwid.LIGHT_GRAY + ",standout", urwid.BLACK), ('selected', urwid.DARK_MAGENTA, urwid.DARK_CYAN), # ('title', urwid.WHITE, urwid.DARK_BLUE), # ('head_foot', urwid.WHITE, urwid.DARK_BLUE, ), ('title', urwid.LIGHT_GRAY + ",standout", urwid.BLACK), ('head_foot', urwid.LIGHT_GRAY + ",standout", urwid.BLACK), ('key', urwid.BLACK, urwid.DARK_GREEN,), ('console', urwid.WHITE, urwid.DARK_BLUE), ('searchbar', urwid.WHITE, urwid.DARK_BLUE), ('search_result', urwid.DARK_GREEN + ",standout", urwid.WHITE), ('searchterm', urwid.DARK_GREEN, urwid.WHITE), ('SUCCESS', urwid.DARK_GREEN, urwid.DEFAULT), ('FAILED', urwid.DARK_RED, urwid.DEFAULT), ('UNSTABLE', urwid.DARK_CYAN, urwid.DEFAULT), ('ABORTED', urwid.DARK_MAGENTA, urwid.DEFAULT), ('DISABLED', urwid.LIGHT_GRAY, urwid.DEFAULT), ('NOTBUILT', urwid.DARK_BLUE, urwid.DEFAULT), ('BUILDIONG', urwid.BROWN, urwid.DEFAULT), ] table_head_text = [("table_heading", "ID"), ("table_heading", " Available Jobs"), ("table_heading", "Last Successful"), ("table_heading", "Last Fail"), ("table_heading", "Last Duration")] footer_text = [ " -", ('title', "Jenkins Job Browser"), "- ", ('key', "(PAGE) UP/DOWN"), ",", ('key', "LEFT/RIGHT"), ",", ('key', "HOME/END"), " | ", ('key', "Enter"), ",", ('key', "+"), ",", ('key', "-"), " | ", ('key', "Q"), ] def __init__(self, jenkins_settings): # set some class vars self.jenkins_settings = jenkins_settings self.jenkins_wrapper = JenkinsWrapper(self.jenkins_settings) self.openedconsole = False self.quit_confirm = False self.pager_term = None self.search_bar = None self.searchmode = False self.current_jobdict = None self.search_result = [] self.ragequit = False self.main_loop = None self.old_search_term = "" self.current_search_term = "" # ctrl+c support def signal_handler(signal, frame): self.ragequit = True raise urwid.ExitMainLoop() signal.signal(signal.SIGINT, signal_handler) # create the layout self.view = urwid.Frame(None, header=None, footer=None) self.__refresh_header_footer() self.__refresh_jenkins(init=True) def main(self): """Run the program.""" self.main_loop = urwid.MainLoop(self.view, self.palette, unhandled_input=self.unhandled_input) # self.reset_view() def initial_update(y, z): self.__refresh_jenkins() self.main_loop.set_alarm_in(0.01, initial_update) self.main_loop.run() return self.ragequit def reset_view(self): self.__refresh_jenkins(init=True) self.__refresh_header_footer() self.__refresh_jenkins() def __refresh_header_footer(self): # headder self.head_connection = urwid.AttrWrap(urwid.Text(self.get_header(self.jenkins_wrapper.settings.host, user=self.jenkins_wrapper.settings.auth[0] if self.jenkins_wrapper.settings.auth else "Anonymous")), 'head_foot') self.head_table = URWIDElements.job_lines_to_column(self.table_head_text) self.header = urwid.Pile([self.head_connection, self.head_table], focus_item=0) self.view.header = self.header # footer self.user_input = urwid.Text("Status: ") self.status_bar = urwid.AttrWrap(urwid.Text(self.footer_text), 'head_foot') self.footer = urwid.Pile([self.status_bar, self.user_input], focus_item=0) self.view.footer = self.footer def __refresh_jenkins(self, init=False): if not init: # Actually load data from jenkins ... self.print_status("Refreshing Jobs - please wait...") self.main_loop.draw_screen() self.current_jobdict = self.jenkins_wrapper.get_detailed_joblist(self.status_function) else: self.current_jobdict = {"Loading...": {"color": "blue"}} self.topnode = JenkinsInstanceNode(self.jobdict2urwiddict(self.current_jobdict)) self.listboxcontent = urwid.TreeWalker(self.topnode) self.listbox = VimBindingTreeListBox(self.listboxcontent, self.print_status) self.listbox.offset_rows = 1 self.view.body = self.listbox def jobdict2urwiddict(self, detailed_jobdict): retval = {"name": [""], "children": []} for i, (job_name, info) in enumerate(detailed_jobdict.iteritems()): color = info['color'] if '_anime' in color: color = color.split('_')[0] color = 'building' last_success = None last_fail = None last_dur = None try: last_success = info['lastSuccessfulBuild']['number'] if info['lastSuccessfulBuild'] else "N/A" last_fail = info['lastFailedBuild']['number'] if info['lastFailedBuild'] else "N/A" last_dur = info['lastSuccessfulBuild']['number'] if info['lastSuccessfulBuild'] else "N/A" for a_build in info['builds']: if last_success and a_build['number'] == last_success: last_success = datetime.datetime.fromtimestamp(a_build['info']['timestamp'] / 1000).strftime('%H:%M:%S %d.%m.%y') if last_fail and a_build['number'] == last_fail: last_fail = datetime.datetime.fromtimestamp(a_build['info']['timestamp'] / 1000).strftime('%H:%M:%S %d.%m.%y') if last_dur and a_build['number'] == last_dur: ms = a_build['info']['duration'] last_dur = "{:02}".format(int((ms / (1000.0 * 60.0 * 60.0)) % 24.0)) + ":" + \ "{:02}".format(int((ms / (1000.0 * 60.0)) % 60.0)) + ":" + \ "{:02}".format(int((ms / 1000.0) % 60.0)) except Exception as e: pass retval['children'].append({"name": [str(i + 1), (self.COLOR_MAPPING[color], job_name), str(last_success), str(last_fail), str(last_dur)], "realname": str(job_name), "job_number": i + 1}) retval['children'][i]['children'] = [] retval['children'][i]['children'].append({"name": self.OPTION_LABEL_JOB_INFO}) retval['children'][i]['children'].append({"name": self.OPTION_LABEL_BUILD}) retval['children'][i]['children'].append({"name": self.OPTION_LABEL_LAST_BUILD_LOG}) return retval def get_header(self, host, user="******"): return [('title', "Jenkins runnung at "), " ", ('key', str(host)), " | ", ('title', 'Login: '******'key', str(user))] def update_search_info(self, amount, extra=[]): status = ["Found {} search results for '".format(amount), ('searchterm', str(self.current_search_term)), "' -- ", ('key', 'ESC'), " exit search mode"] status = status + extra self.print_status(status) def search_function(self, search_term): search_result = [] for a_job, _ in self.current_jobdict.iteritems(): if search_term.lower() in a_job.lower(): search_result.append(a_job) self.update_search_info(len(search_result), extra=[", ", ('key', 'Enter'), " submit search"]) # always select the first hit self.search_result_selection = -1 self.select_next_searchresult() return search_result def execute_search(self, x, y): self.current_search_term = y self.search_result = self.search_function(self.current_search_term) self.search_result_selection = -1 self.old_search_term = self.search_bar.get_search_term() self.highlight_search_results() def show_search_bar(self): self.search_bar = SearchBar() self.view.footer = urwid.Pile([self.user_input, self.search_bar], focus_item=1) self.view.focus_position = "footer" self.print_status("Press 'ESC' to abort search...") urwid.connect_signal(self.search_bar, 'change', self.execute_search) self.listbox.disable_keys() def hide_search_bar(self): if self.search_bar: self.listbox.enable_keys() self.topnode.reset_highlights() self.__refresh_header_footer() self.search_bar = None self.print_status("") self.current_search_term = "" self.searchmode = False self.view.focus_position = "body" def show_pager_term(self, text): self.pager_term = ConsoleOutputPager(self.hide_pager_term, text, self.main_loop) self.view.body = self.pager_term self.print_status("Press 'q' to quit console view...") def hide_pager_term(self): if self.pager_term: self.pager_term.change_focus(False) self.view.body = self.listbox self.pager_term = None self.print_status("") def status_function(self, message): self.print_status(message) self.main_loop.draw_screen() def print_status(self, text): self.user_input.set_text(text) def print_append_status(self, text): self.user_input.set_text(str(self.user_input.get_text()) + text) def append_status(self, text): self.user_input.set_text(self.user_input.get_text()[0] + text) def unhandled_input(self, k): if self.quit_confirm: if k in ('q', 'Q'): raise urwid.ExitMainLoop() self.quit_confirm = False self.print_status("") return if self.searchmode: self.highlight_search_results() if self.view.focus_position == "body": if k is 'enter': self.hide_search_bar() self._keypress_enter() if k is '/': self.view.focus_position = "footer" elif k in ('f3', "n", 'j', 'down'): self.select_next_searchresult() elif k in ('f4', "N", 'k', 'up'): self.select_next_searchresult(revese=True) elif k in ('ctrl l', 'f8'): self.search_bar.set_edit_text("") elif k in ('esc', 'q'): self.hide_search_bar() elif k in ('h', 'l', 'left', 'right'): pass else: if k is 'enter': self.update_search_info(len(self.search_result), extra=[", ", ('key', 'n'), "/", ('key', 'N'), " or ", ('key', 'F3'), "/", ('key', 'F4'), " jump to next/previous search result"]) # Note: this is a hack as results off screen would not be # highlighted. no idea why ... self.view.focus_position = "body" return False if k in ('q', 'Q'): if not self.quit_confirm: self.quit_confirm = True self.print_status("Press 'q' again to quit...") elif k is '/': self.show_search_bar() self.searchmode = True # elif k in ('ctrl d',): # self.print_status("wooop") elif k is 'f5': self.reset_view() self.print_status("Refresh done.") elif k is 'enter': self._keypress_enter() return True def _keypress_enter(self): selected_node = self.listbox.get_focus()[1] # Execute the selected option if isinstance(selected_node, JenkinsOptionNode): job_name = selected_node.get_job_name() chosen_option = selected_node.get_display_text() if chosen_option is self.OPTION_LABEL_JOB_INFO: import pprint self.show_pager_term(pprint.pformat(self.current_jobdict[job_name], indent=4)) #self.show_pager_term(pprint.pformat(self.jenkins_wrapper.get_jobs_details(job_name), indent=4)) elif chosen_option is self.OPTION_LABEL_BUILD: self.print_status("Triggering build of '{}'...".format(job_name)) try: result_code = self.jenkins_wrapper.jenkins_build(job_name) if result_code < 400: self.print_status("Build was successfully triggered, Code: " + str(result_code)) except Exception as error: self.print_status("Error when building {}: {}".format(job_name, str(error))) elif chosen_option is self.OPTION_LABEL_LAST_BUILD_LOG: try: log = self.jenkins_wrapper.get_last_build_log(job_name) self.show_pager_term(log) except Exception as error: self.print_status("Error when querying for log of {}: {}".format(job_name, str(error))) # Toggle folding using enter elif isinstance(selected_node, JenkinsJobNode) or \ isinstance(selected_node, JenkinsInstanceNode): widget = self.listbox.get_focus()[0] widget.expanded = not widget.expanded widget.update_expanded_icon() # Should never happen ... else: self.user_input.set_text("wut?!") def highlight_search_results(self): self.topnode.reset_highlights() # make sure we have the instance root element self.topnode.visually_highlight_jobs(self.search_result) def select_next_searchresult(self, revese=False): # reset the view to draw the new results self.topnode.reset_highlights() if len(self.search_result) > 0: # increase the counter if revese: self.search_result_selection = (self.search_result_selection + -1) if self.search_result_selection > 0 else len(self.search_result) - 1 else: self.search_result_selection = (self.search_result_selection + 1) if self.search_result_selection < len(self.search_result) - 1 else 0 # obtain name of the job to highlight job_name = self.search_result[self.search_result_selection] self.view.body.set_focus(self.topnode.job_name2node(job_name)) self.main_loop.draw_screen() self.highlight_search_results() selected_node = self.topnode.visually_highlight_jobs(job_name, style="focused")