"""The Runs page for the Trackio UI.""" import re import gradio as gr import pandas as pd try: import trackio.utils as utils from trackio.sqlite_storage import SQLiteStorage from trackio.ui import fns except ImportError: import utils from sqlite_storage import SQLiteStorage from ui import fns def get_runs_data(project): """Get the runs data as a pandas DataFrame.""" configs = SQLiteStorage.get_all_run_configs(project) if not configs: return pd.DataFrame() df = pd.DataFrame.from_dict(configs, orient="index") df = df.fillna("") df.index.name = "Name" df.reset_index(inplace=True) column_mapping = {"_Username": "Username", "_Created": "Created"} df.rename(columns=column_mapping, inplace=True) if "Created" in df.columns: df["Created"] = df["Created"].apply(utils.format_timestamp) if "Username" in df.columns: df["Username"] = df["Username"].apply( lambda x: f"{x}" if x and x != "None" else x ) if "Name" in df.columns: df["Name"] = df["Name"].apply( lambda x: f"{x}" if x and x != "None" else x ) df.insert(0, " ", False) columns = list(df.columns) if "Username" in columns and "Created" in columns: columns.remove("Username") columns.remove("Created") columns.insert(2, "Username") columns.insert(3, "Created") df = df[columns] return df def get_runs_table(project): df = get_runs_data(project) if df.empty: return gr.DataFrame(pd.DataFrame(), visible=False) datatype = ["bool"] + ["markdown"] * (len(df.columns) - 1) return gr.DataFrame( df, visible=True, pinned_columns=2, datatype=datatype, wrap=True, column_widths=["40px", "150px"], interactive=True, static_columns=list(range(1, len(df.columns))), row_count=(len(df), "fixed"), col_count=(len(df.columns), "fixed"), ) def check_write_access_runs(request: gr.Request, write_token: str) -> bool: """Check if the user has write access based on token validation.""" cookies = request.headers.get("cookie", "") if cookies: for cookie in cookies.split(";"): parts = cookie.strip().split("=") if len(parts) == 2 and parts[0] == "trackio_write_token": return parts[1] == write_token if hasattr(request, "query_params") and request.query_params: token = request.query_params.get("write_token") return token == write_token return False def update_delete_button(runs_data, request: gr.Request): """Update the delete button value and interactivity based on the runs data and user write access.""" if not check_write_access_runs(request, run_page.write_token): return gr.Button("⚠️ Need write access to delete runs", interactive=False) num_selected = 0 if runs_data is not None and len(runs_data) > 0: first_column_values = runs_data.iloc[:, 0].tolist() num_selected = sum(1 for x in first_column_values if x) if num_selected: return gr.Button(f"Delete {num_selected} selected run(s)", interactive=True) else: return gr.Button("Select runs to delete", interactive=False) def delete_selected_runs(runs_data, project, request: gr.Request): """Delete the selected runs and refresh the table.""" if not check_write_access_runs(request, run_page.write_token): return runs_data first_column_values = runs_data.iloc[:, 0].tolist() for i, selected in enumerate(first_column_values): if selected: run_name_raw = runs_data.iloc[i, 1] match = re.search(r">([^<]+)<", run_name_raw) run_name = match.group(1) if match else run_name_raw SQLiteStorage.delete_run(project, run_name) updated_data = get_runs_data(project) return updated_data with gr.Blocks() as run_page: with gr.Sidebar() as sidebar: logo = gr.Markdown( f""" """ ) project_dd = gr.Dropdown(label="Project", allow_custom_value=True) navbar = gr.Navbar(value=[("Metrics", ""), ("Runs", "/runs")], main_page_name=False) timer = gr.Timer(value=1) with gr.Row(): with gr.Column(): pass with gr.Column(): with gr.Row(): delete_run_btn = gr.Button( "⚠️ Need write access to delete runs", interactive=False, variant="stop", size="sm", ) confirm_btn = gr.Button( "Confirm delete", variant="stop", size="sm", visible=False ) cancel_btn = gr.Button("Cancel", size="sm", visible=False) runs_table = gr.DataFrame() gr.on( [run_page.load], fn=fns.get_projects, outputs=project_dd, show_progress="hidden", queue=False, api_name=False, ) gr.on( [timer.tick], fn=lambda: gr.Dropdown(info=fns.get_project_info()), outputs=[project_dd], show_progress="hidden", api_name=False, ) gr.on( [project_dd.change], fn=get_runs_table, inputs=[project_dd], outputs=[runs_table], show_progress="hidden", api_name=False, queue=False, ).then( fns.update_navbar_value, inputs=[project_dd], outputs=[navbar], show_progress="hidden", api_name=False, queue=False, ) gr.on( [run_page.load, runs_table.change], fn=update_delete_button, inputs=[runs_table], outputs=[delete_run_btn], show_progress="hidden", api_name=False, queue=False, ) gr.on( [delete_run_btn.click], fn=lambda: [ gr.Button(visible=False), gr.Button(visible=True), gr.Button(visible=True), ], inputs=None, outputs=[delete_run_btn, confirm_btn, cancel_btn], show_progress="hidden", api_name=False, queue=False, ) gr.on( [confirm_btn.click, cancel_btn.click], fn=lambda: [ gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False), ], inputs=None, outputs=[delete_run_btn, confirm_btn, cancel_btn], show_progress="hidden", api_name=False, queue=False, ) gr.on( [confirm_btn.click], fn=delete_selected_runs, inputs=[runs_table, project_dd], outputs=[runs_table], show_progress="hidden", api_name=False, queue=False, )