import gradio as gr import json import os import pandas as pd from filter_utils import filter_lines from timestamp_utils import parse_timestamp def add_file(files, state): if files: for file in files: filename = os.path.basename(file.name) if filename not in state: with open(file.name) as f: lines = f.readlines() parsed_lines = [] for line in lines: parsed_lines.append({ "timestamp": parse_timestamp(line), "content": line, "source": filename }) state[filename] = {"lines": parsed_lines, "filters": []} file_selector_update = gr.update(choices=list(state.keys()), value=list(state.keys())[0] if state else None) selected_file = list(state.keys())[0] if state else None table_output_update = generate_merged_view(state) filter_list_update = update_filter_list(selected_file, state) return state, file_selector_update, table_output_update, filter_list_update def select_file(selected_file, state): if selected_file and selected_file in state: filter_list_update = update_filter_list(selected_file, state) return filter_list_update return gr.update(choices=[]) def add_filter(state, selected_file, filter_type, filter_value, case_sensitive): if selected_file and filter_value: state[selected_file]["filters"].append({"type": filter_type, "value": filter_value, "case_sensitive": case_sensitive}) return state, "" def remove_filter(state, selected_file, selected_filter): if selected_file and selected_filter: parts = selected_filter.split(" - ") if len(parts) == 3: filter_type, filter_value, case_str = parts case_sensitive = case_str == "Case Sensitive: True" filters = state[selected_file]["filters"] for i, f in enumerate(filters): if f["type"] == filter_type and f["value"] == filter_value and f["case_sensitive"] == case_sensitive: filters.pop(i) break return state, None def move_filter_up(state, selected_file, selected_filter): if selected_file and selected_filter: parts = selected_filter.split(" - ") if len(parts) == 3: filter_type, filter_value, case_str = parts case_sensitive = case_str == "Case Sensitive: True" filters = state[selected_file]["filters"] for i, f in enumerate(filters): if f["type"] == filter_type and f["value"] == filter_value and f["case_sensitive"] == case_sensitive: if i > 0: filters[i], filters[i-1] = filters[i-1], filters[i] break return state, selected_filter def move_filter_down(state, selected_file, selected_filter): if selected_file and selected_filter: parts = selected_filter.split(" - ") if len(parts) == 3: filter_type, filter_value, case_str = parts case_sensitive = case_str == "Case Sensitive: True" filters = state[selected_file]["filters"] for i, f in enumerate(filters): if f["type"] == filter_type and f["value"] == filter_value and f["case_sensitive"] == case_sensitive: if i < len(filters) - 1: filters[i], filters[i+1] = filters[i+1], filters[i] break return state, selected_filter def generate_merged_view(state): all_lines = [] for filename, data in state.items(): lines = data["lines"] filters = data["filters"] processed_lines = lines for f in filters: filter_type = f["type"] value = f["value"] case = f["case_sensitive"] content_lines = [line["content"] for line in processed_lines] if filter_type == "Include Text": filtered_content = filter_lines(content_lines, include_text=value, case_sensitive=case) elif filter_type == "Exclude Text": filtered_content = filter_lines(content_lines, exclude_text=value, case_sensitive=case) elif filter_type == "Include Regex": filtered_content = filter_lines(content_lines, include_regex=value, case_sensitive=case) elif filter_type == "Exclude Regex": filtered_content = filter_lines(content_lines, exclude_regex=value, case_sensitive=case) processed_lines = [line for line in processed_lines if line["content"] in filtered_content] all_lines.extend(processed_lines) if not all_lines: return pd.DataFrame(columns=["File", "Timestamp", "Log Entry"]) # Sort lines by timestamp all_lines.sort(key=lambda x: x["timestamp"] if x["timestamp"] is not None else pd.Timestamp.min) df = pd.DataFrame(all_lines) df["Timestamp"] = df["timestamp"].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S') if pd.notnull(x) else "") df = df.rename(columns={"source": "File", "content": "Log Entry"}) return df[["File", "Timestamp", "Log Entry"]] def update_filter_list(selected_file, state): if selected_file and selected_file in state: filters = state[selected_file]["filters"] filter_strings = [f'{f["type"]} - {f["value"]} - Case Sensitive: {f["case_sensitive"]}' for f in filters] return gr.update(choices=filter_strings) return gr.update(choices=[]) def save_filters(selected_file, state): if selected_file and selected_file in state: filters = state[selected_file]["filters"] with open(f"{selected_file}_filters.json", "w") as f: json.dump(filters, f) return f"{selected_file}_filters.json" return None def load_filters(selected_file, state, filter_file): if selected_file and filter_file is not None: with open(filter_file.name) as f: state[selected_file]["filters"] = json.load(f) return state def save_filtered_log(log_data): if not log_data.empty: log_content = "" for index, row in log_data.iterrows(): log_content += f"[{row['File']}] [{row['Timestamp']}] {row['Log Entry']}" with open("filtered_log.txt", "w") as f: f.write(log_content) return "filtered_log.txt" return None def show_regex_help(): gr.Info(""" **Regular Expression Quick Guide** - `.` : Matches any single character. - `*` : Matches the preceding character zero or more times. - `+` : Matches the preceding character one or more times. - `^` : Matches the start of a line. : Matches the end of a line. - `|` : Acts as an OR operator (e.g., `error|warn`). - `[...]`: Matches any single character within the brackets (e.g., `[aeiou]`). - `(...)`: Groups expressions. **Example:** `^ERROR.*database` will find lines starting with "ERROR" that also contain "database". """) with gr.Blocks(theme=gr.themes.Soft(), css="#log_content .gr-dataframe { font-family: monospace; } .gradio-toast { max-width: 500px !important; }") as demo: files_state = gr.State({}) gr.Markdown("## Log File Viewer") with gr.Row(): file_input = gr.File(label="Upload Log File(s)", file_count="multiple") file_selector = gr.Dropdown(label="Select Log File") gr.Markdown("### Filters") with gr.Row(elem_id="filter_row"): filter_type = gr.Dropdown([ "Include Text", "Exclude Text", "Include Regex", "Exclude Regex" ], label="Filter Type") filter_value = gr.Textbox(label="Filter Value", scale=4) with gr.Column(scale=0, min_width=50): help_button = gr.Button("?", scale=0) case_sensitive_checkbox = gr.Checkbox(label="Case Sensitive", value=True) with gr.Column(scale=0): add_filter_button = gr.Button("Add Filter") save_filters_button = gr.Button("Save Filters") load_filters_file = gr.UploadButton("Load Filters (.json)", file_types=[".json"]) with gr.Row(): applied_filters_list = gr.Radio(label="Applied Filters", interactive=True) remove_filter_button = gr.Button("Remove Selected Filter") move_up_button = gr.Button("Move Up") move_down_button = gr.Button("Move Down") with gr.Row(): save_filtered_log_button = gr.Button("Save Filtered Log") log_table = gr.DataFrame(headers=["File", "Timestamp", "Log Entry"], interactive=False, elem_id="log_content") # Event Handlers help_button.click(show_regex_help, inputs=None, outputs=None) file_input.upload( add_file, inputs=[file_input, files_state], outputs=[files_state, file_selector, log_table, applied_filters_list] ) file_selector.change( select_file, inputs=[file_selector, files_state], outputs=[applied_filters_list] ).then( generate_merged_view, inputs=files_state, outputs=log_table ) add_filter_button.click( add_filter, inputs=[files_state, file_selector, filter_type, filter_value, case_sensitive_checkbox], outputs=[files_state, filter_value] ).then( update_filter_list, inputs=[file_selector, files_state], outputs=applied_filters_list ).then( generate_merged_view, inputs=files_state, outputs=log_table ) remove_filter_button.click( remove_filter, inputs=[files_state, file_selector, applied_filters_list], outputs=[files_state, applied_filters_list] ).then( update_filter_list, inputs=[file_selector, files_state], outputs=applied_filters_list ).then( generate_merged_view, inputs=files_state, outputs=log_table ) move_up_button.click( move_filter_up, inputs=[files_state, file_selector, applied_filters_list], outputs=[files_state, applied_filters_list] ).then( update_filter_list, inputs=[file_selector, files_state], outputs=applied_filters_list ).then( generate_merged_view, inputs=files_state, outputs=log_table ) move_down_button.click( move_filter_down, inputs=[files_state, file_selector, applied_filters_list], outputs=[files_state, applied_filters_list] ).then( update_filter_list, inputs=[file_selector, files_state], outputs=applied_filters_list ).then( generate_merged_view, inputs=files_state, outputs=log_table ) save_filters_button.click( save_filters, inputs=[file_selector, files_state], outputs=gr.File(label="Download Filter File") ) load_filters_file.upload( load_filters, inputs=[file_selector, files_state, load_filters_file], outputs=files_state ).then( update_filter_list, inputs=[file_selector, files_state], outputs=applied_filters_list ).then( generate_merged_view, inputs=files_state, outputs=log_table ) save_filtered_log_button.click( save_filtered_log, inputs=log_table, outputs=gr.File(label="Download Filtered Log") ) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)