deepboard 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. deepboard/__init__.py +1 -0
  2. deepboard/__version__.py +4 -0
  3. deepboard/gui/THEME.yml +28 -0
  4. deepboard/gui/__init__.py +0 -0
  5. deepboard/gui/assets/artefacts.css +108 -0
  6. deepboard/gui/assets/base.css +208 -0
  7. deepboard/gui/assets/base.js +77 -0
  8. deepboard/gui/assets/charts.css +188 -0
  9. deepboard/gui/assets/compare.css +90 -0
  10. deepboard/gui/assets/datagrid.css +120 -0
  11. deepboard/gui/assets/fileview.css +13 -0
  12. deepboard/gui/assets/right_panel.css +227 -0
  13. deepboard/gui/assets/theme.css +85 -0
  14. deepboard/gui/components/__init__.py +8 -0
  15. deepboard/gui/components/artefact_group.py +12 -0
  16. deepboard/gui/components/chart_type.py +22 -0
  17. deepboard/gui/components/legend.py +34 -0
  18. deepboard/gui/components/log_selector.py +22 -0
  19. deepboard/gui/components/modal.py +20 -0
  20. deepboard/gui/components/smoother.py +21 -0
  21. deepboard/gui/components/split_selector.py +21 -0
  22. deepboard/gui/components/stat_line.py +8 -0
  23. deepboard/gui/entry.py +21 -0
  24. deepboard/gui/main.py +93 -0
  25. deepboard/gui/pages/__init__.py +1 -0
  26. deepboard/gui/pages/compare_page/__init__.py +6 -0
  27. deepboard/gui/pages/compare_page/compare_page.py +22 -0
  28. deepboard/gui/pages/compare_page/components/__init__.py +4 -0
  29. deepboard/gui/pages/compare_page/components/card_list.py +19 -0
  30. deepboard/gui/pages/compare_page/components/chart.py +54 -0
  31. deepboard/gui/pages/compare_page/components/compare_setup.py +30 -0
  32. deepboard/gui/pages/compare_page/components/split_card.py +51 -0
  33. deepboard/gui/pages/compare_page/components/utils.py +20 -0
  34. deepboard/gui/pages/compare_page/routes.py +58 -0
  35. deepboard/gui/pages/main_page/__init__.py +4 -0
  36. deepboard/gui/pages/main_page/datagrid/__init__.py +5 -0
  37. deepboard/gui/pages/main_page/datagrid/compare_button.py +21 -0
  38. deepboard/gui/pages/main_page/datagrid/datagrid.py +67 -0
  39. deepboard/gui/pages/main_page/datagrid/handlers.py +54 -0
  40. deepboard/gui/pages/main_page/datagrid/header.py +43 -0
  41. deepboard/gui/pages/main_page/datagrid/routes.py +112 -0
  42. deepboard/gui/pages/main_page/datagrid/row.py +20 -0
  43. deepboard/gui/pages/main_page/datagrid/sortable_column_js.py +45 -0
  44. deepboard/gui/pages/main_page/datagrid/utils.py +9 -0
  45. deepboard/gui/pages/main_page/handlers.py +16 -0
  46. deepboard/gui/pages/main_page/main_page.py +21 -0
  47. deepboard/gui/pages/main_page/right_panel/__init__.py +12 -0
  48. deepboard/gui/pages/main_page/right_panel/config.py +57 -0
  49. deepboard/gui/pages/main_page/right_panel/fragments.py +133 -0
  50. deepboard/gui/pages/main_page/right_panel/hparams.py +25 -0
  51. deepboard/gui/pages/main_page/right_panel/images.py +358 -0
  52. deepboard/gui/pages/main_page/right_panel/run_info.py +86 -0
  53. deepboard/gui/pages/main_page/right_panel/scalars.py +251 -0
  54. deepboard/gui/pages/main_page/right_panel/template.py +151 -0
  55. deepboard/gui/pages/main_page/routes.py +25 -0
  56. deepboard/gui/pages/not_found.py +3 -0
  57. deepboard/gui/requirements.txt +5 -0
  58. deepboard/gui/utils.py +267 -0
  59. deepboard/resultTable/__init__.py +2 -0
  60. deepboard/resultTable/cursor.py +20 -0
  61. deepboard/resultTable/logwritter.py +667 -0
  62. deepboard/resultTable/resultTable.py +529 -0
  63. deepboard/resultTable/scalar.py +29 -0
  64. deepboard/resultTable/table_schema.py +135 -0
  65. deepboard/resultTable/utils.py +50 -0
  66. deepboard-0.2.0.dist-info/METADATA +164 -0
  67. deepboard-0.2.0.dist-info/RECORD +69 -0
  68. deepboard-0.2.0.dist-info/WHEEL +4 -0
  69. deepboard-0.2.0.dist-info/entry_points.txt +2 -0
deepboard/gui/main.py ADDED
@@ -0,0 +1,93 @@
1
+ import os
2
+ import sys
3
+ sys.path.append(os.getcwd())
4
+ from fasthtml.common import *
5
+ from deepboard.gui.pages.main_page.datagrid import SortableColumnsJs, right_click_handler_row, right_click_handler
6
+ from deepboard.gui.pages.main_page import MainPage, build_main_page_endpoints
7
+ from deepboard.gui.utils import prepare_db, Config, initiate_files, get_table_path_from_cli, verify_runids
8
+ from deepboard.gui.pages.compare_page import build_compare_routes, ComparePage
9
+ from deepboard.gui.pages import _not_found
10
+ from deepboard.gui.components import Modal
11
+ from deepboard.resultTable import ResultTable
12
+ from fh_plotly import plotly_headers
13
+
14
+ DEBUG = False
15
+ # Create config files to customize the UI
16
+ initiate_files()
17
+
18
+ # Load config and DB
19
+ CONFIG = Config.FromFile(os.path.expanduser('~/.config/deepboard/THEME.yml'))
20
+ DATABASE = get_table_path_from_cli()
21
+ if not os.path.exists(DATABASE):
22
+ raise RuntimeError(f"ResultTable {DATABASE} does not exist")
23
+ prepare_db()
24
+
25
+ # Load the result Table
26
+ rTable = ResultTable(DATABASE)
27
+
28
+ cls = FastHTMLWithLiveReload if DEBUG else FastHTML
29
+ app = cls(
30
+ exception_handlers={404: _not_found},
31
+ hdrs=(
32
+ Link(rel='stylesheet', href='assets/base.css', type='text/css'),
33
+ Link(rel='stylesheet', href='assets/datagrid.css', type='text/css'),
34
+ Link(rel='stylesheet', href='assets/right_panel.css', type='text/css'),
35
+ Link(rel='stylesheet', href='assets/charts.css', type='text/css'),
36
+ Link(rel='stylesheet', href='assets/fileview.css', type='text/css'),
37
+ Link(rel='stylesheet', href='assets/compare.css', type='text/css'),
38
+ Link(rel='stylesheet', href='assets/artefacts.css', type='text/css'),
39
+ Link(href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css", rel="stylesheet"),
40
+ plotly_headers,
41
+ Script(src="assets/base.js"),
42
+ SortableColumnsJs(),
43
+ ),
44
+ static_path='assets'
45
+ )
46
+
47
+ rt = app.route
48
+ @rt("/assets/{fname:path}.{ext:static}")
49
+ async def get(fname:str, ext:str):
50
+ if fname == "theme" and ext == "css" and not DEBUG:
51
+ if os.path.exists(os.path.expanduser('~/.config/deepboard/theme.css')):
52
+ return FileResponse(os.path.expanduser('~/.config/deepboard/THEME.css'))
53
+ root = os.path.dirname(os.path.abspath(__file__))
54
+ return FileResponse(f'{root}/assets/{fname}.{ext}')
55
+
56
+
57
+ @rt("/")
58
+ def get(session):
59
+ if "show_hidden" not in session:
60
+ session["show_hidden"] = False
61
+
62
+ # Check if row_selected exists.
63
+ verify_runids(session, rTable)
64
+
65
+ return (Title("Table"),
66
+ Div(id="custom-menu"),
67
+ Modal(P("Hellp world"), active=False),
68
+ MainPage(session),
69
+ )
70
+
71
+
72
+ @rt("/compare")
73
+ def get(session, run_ids: str):
74
+ return ComparePage(session, run_ids)
75
+
76
+
77
+ # Dropdown menu when right-clicked
78
+ @rt("/get-context-menu")
79
+ def get(session, elementIds: str, top: int, left: int):
80
+ elementIds = elementIds.split(",")
81
+ if any(elementId.startswith("grid-header") for elementId in elementIds):
82
+ return right_click_handler(elementIds, top, left)
83
+ elif any(elementId.startswith("grid-row") for elementId in elementIds):
84
+ return right_click_handler_row(session, elementIds, top, left)
85
+ else:
86
+ return Div(
87
+ id='custom-menu',
88
+ style=f'visibility: visible; top: {top}px; left: {left}px;',
89
+ )
90
+
91
+ build_main_page_endpoints(rt)
92
+ build_compare_routes(rt)
93
+ serve(reload=DEBUG)
@@ -0,0 +1 @@
1
+ from .not_found import _not_found
@@ -0,0 +1,6 @@
1
+ from .components.card_list import ChartCardList
2
+ from .components.compare_setup import CompareSetup
3
+ from .components.split_card import SplitCard
4
+ from .components.chart import Chart, LoadingChart
5
+ from .compare_page import ComparePage
6
+ from .routes import build_compare_routes
@@ -0,0 +1,22 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+ from .components import CompareSetup, ChartCardList
4
+
5
+
6
+ def ComparePage(session, run_ids: str):
7
+ run_ids = run_ids.split(",")
8
+ session["compare"] = {"selected-rows": run_ids}
9
+ return (Title("Compare"),
10
+ Div(id="custom-menu"),
11
+ Div(
12
+ Div(
13
+ CompareSetup(session),
14
+ cls="compare-setup-container"
15
+ ),
16
+ Div(
17
+ ChartCardList(session),
18
+ cls="cards-list-container"
19
+ ),
20
+ cls="compare-container"
21
+ )
22
+ )
@@ -0,0 +1,4 @@
1
+ from .compare_setup import CompareSetup
2
+ from .split_card import SplitCard
3
+ from .card_list import ChartCardList
4
+ from .chart import Chart, LoadingChart
@@ -0,0 +1,19 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+ from .split_card import SplitCard
4
+
5
+ def ChartCardList(session, swap: bool = False):
6
+ runIDs = sorted([int(rid) for rid in session["compare"]["selected-rows"]])
7
+ from __main__ import rTable
8
+ sockets = [rTable.load_run(runID) for runID in runIDs]
9
+ keys = {key for socket in sockets for key in socket.formatted_scalars}
10
+ splits = {split for split, metric in keys}
11
+ splits = sorted(splits)
12
+ metrics = {split: [metric for sp, metric in keys if sp == split] for split in splits}
13
+
14
+ return Ul(
15
+ *[SplitCard(session, split, metrics[split]) for split in splits],
16
+ cls="comparison-list",
17
+ id="chart-card-list",
18
+ hx_swap_oob="true" if swap else None
19
+ )
@@ -0,0 +1,54 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+ from fh_plotly import plotly2fasthtml
4
+ from .utils import make_lines
5
+ from deepboard.gui.utils import make_fig
6
+
7
+ def Chart(session, split: str, metric: str, type: Literal["step", "duration"], running: bool, logscale: bool):
8
+ from __main__ import rTable
9
+ runIDs = [int(txt) for txt in session["compare"]["selected-rows"]]
10
+ runIDs.sort()
11
+ sockets = [rTable.load_run(runID) for runID in runIDs]
12
+ hidden_lines = session["compare"]["hidden_lines"] if "hidden_lines" in session["compare"] else []
13
+ smoothness = session["compare"]["smoother_value"] - 1 if "smoother_value" in session["compare"] else 0
14
+ lines = make_lines(sockets, split, metric, runIDs, type)
15
+
16
+ # # Sort lines by label
17
+ lines.sort(key=lambda x: x[0])
18
+ # Hide lines if needed
19
+ lines = [line for line in lines if line[0] not in hidden_lines]
20
+ fig = make_fig(lines, type=type, smoothness=smoothness, log_scale=logscale)
21
+
22
+ if running:
23
+ update_params = dict(
24
+ hx_get=f"/compare/chart?split={split}&metric={metric}&type={type}&running={running}&logscale={logscale}",
25
+ hx_target=f"#chart-container-{split}-{metric}",
26
+ hx_trigger="every 10s",
27
+ hx_swap="outerHTML",
28
+ )
29
+ else:
30
+ update_params = {}
31
+ return Div(
32
+ plotly2fasthtml(fig, js_options=dict(responsive=True)),
33
+ cls="chart-container",
34
+ id=f"chart-container-{split}-{metric}",
35
+ **update_params
36
+ ),
37
+
38
+ def LoadingChart(session, split: str, metric: str, type: Literal["step", "duration"], running: bool = False, logscale: bool = False):
39
+ return Div(
40
+ Div(
41
+ H1(metric, cls="chart-title"),
42
+ cls="chart-header",
43
+ id=f"chart-header-{split}-{metric}"
44
+ ),
45
+ Div(
46
+ cls="chart-container",
47
+ id=f"chart-container-{split}-{metric}",
48
+ hx_get=f"/compare/chart?split={split}&metric={metric}&type={type}&running={running}&logscale={logscale}",
49
+ hx_target=f"#chart-container-{split}-{metric}",
50
+ hx_trigger="load",
51
+ ),
52
+ cls = "chart",
53
+ id = f"chart-{split}-{metric}",
54
+ )
@@ -0,0 +1,30 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+ from deepboard.gui.components import Legend, ChartType, Smoother, LogSelector
4
+
5
+ def CompareSetup(session, swap: bool = False):
6
+ from __main__ import CONFIG
7
+ from __main__ import rTable
8
+ if "hidden_lines" in session["compare"]:
9
+ hidden_lines = session["compare"]["hidden_lines"]
10
+ else:
11
+ hidden_lines = []
12
+ raw_labels = [int(txt) for txt in session["compare"]["selected-rows"]]
13
+ raw_labels = sorted(raw_labels)
14
+ sockets = [rTable.load_run(runID) for runID in raw_labels]
15
+ repetitions = [socket.get_repetitions() for socket in sockets]
16
+ if any(len(rep) > 1 for rep in repetitions):
17
+ labels = [(f"{label}.{rep}", CONFIG.COLORS[i % len(CONFIG.COLORS)], f"{label}.{rep}" in hidden_lines) for i, label in enumerate(raw_labels) for rep in sockets[i].get_repetitions()]
18
+ else:
19
+ labels = [(f"{label}", CONFIG.COLORS[i % len(CONFIG.COLORS)], f"{label}" in hidden_lines) for
20
+ i, label in enumerate(raw_labels)]
21
+ return Div(
22
+ H1("Setup", cls="chart-scalar-title"),
23
+ Legend(session, labels, path="/compare", selected_rows_key="compare"),
24
+ ChartType(session, path="/compare", selected_rows_key="compare", session_path="compare"),
25
+ LogSelector(session, path="/compare", selected_rows_key="compare", session_path="compare"),
26
+ Smoother(session, path="/compare", selected_rows_key="compare", session_path="compare"),
27
+ cls="setup-card",
28
+ id="setup-card",
29
+ hx_swap_oob="true" if swap else None,
30
+ )
@@ -0,0 +1,51 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+ from .chart import LoadingChart
4
+
5
+ def SplitCard(session, split: str, metrics: List[str]):
6
+ from __main__ import rTable
7
+ runIDs = sorted([int(rid) for rid in session["compare"]["selected-rows"]])
8
+ sockets = [rTable.load_run(runID) for runID in runIDs]
9
+ running = any([socket.status == "running" for socket in sockets])
10
+ metrics = sorted(metrics)
11
+ chart_type = session["compare"]["chart_type"] if "chart_type" in session["compare"] else "step"
12
+ logscale = session["compare"]["chart_scale"] == "log" if "chart_scale" in session["compare"] else False
13
+
14
+ opened = session["compare"]["cards-state"][split] if "cards-state" in session["compare"] and split in session["compare"]["cards-state"] else True
15
+ if opened:
16
+ return Li(
17
+ Div(
18
+ H1(split, cls="split-card-title"),
19
+ Button(
20
+ I(cls="fas fa-chevron-down"),
21
+ hx_get=f"/compare/toggle_accordion?split={split}&metrics={','.join(metrics)}&open=false",
22
+ hx_target=f"#split-card-{split}",
23
+ hx_swap="outerHTML",
24
+ cls="accordion-toggle"
25
+ ),
26
+ cls="split-card-header"
27
+ ),
28
+ Div(
29
+ *[LoadingChart(session, split, metric, type=chart_type, running=running, logscale=logscale) for metric in metrics],
30
+ cls="multi-charts-container"
31
+ ),
32
+ cls="split-card",
33
+ id=f"split-card-{split}",
34
+ )
35
+ else:
36
+ return Li(
37
+ Div(
38
+ H1(split, cls=".split-card-title"),
39
+ Button(
40
+ I(cls="fas fa-chevron-down"),
41
+ hx_get=f"/compare/toggle_accordion?split={split}&metrics={','.join(metrics)}&open=true",
42
+ hx_target=f"#split-card-{split}",
43
+ hx_swap="outerHTML",
44
+ cls="accordion-toggle rotated"
45
+ ),
46
+
47
+ cls="split-card-header"
48
+ ),
49
+ cls="split-card closed",
50
+ id=f"split-card-{split}",
51
+ )
@@ -0,0 +1,20 @@
1
+ from deepboard.gui.utils import get_lines
2
+ from typing import *
3
+
4
+ def make_lines(sockets, split: str, metric: str, runIDs: List[int], type: Literal["step", "duration"]):
5
+ from __main__ import CONFIG
6
+ lines = []
7
+ all_reps = [socket.get_repetitions() for socket in sockets]
8
+ multi_rep = any(len(rep) > 1 for rep in all_reps)
9
+ for i, runID in enumerate(runIDs):
10
+ reps = get_lines(sockets[i], split, metric, key=type)
11
+
12
+ for rep_idx, rep in enumerate(reps):
13
+ lines.append((
14
+ f'{runID}.{rep_idx}' if multi_rep else f'{runID}',
15
+ rep["index"],
16
+ rep["value"],
17
+ CONFIG.COLORS[i % len(CONFIG.COLORS)],
18
+ rep["epoch"],
19
+ ))
20
+ return lines
@@ -0,0 +1,58 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+ from deepboard.gui.components import Legend, ChartType, Smoother, LogSelector
4
+ from .components import SplitCard, ChartCardList, CompareSetup, Chart
5
+ from .compare_page import ComparePage
6
+
7
+ def build_compare_routes(rt):
8
+ rt("/compare")(ComparePage)
9
+ rt("/compare/toggle_accordion")(toggle_accordion)
10
+ rt("/compare/change_chart")(change_chart_type)
11
+ rt("/compare/change_scale")(change_chart_scale)
12
+ rt("/compare/hide_line")(hide_line)
13
+ rt("/compare/show_line")(show_line)
14
+ rt("/compare/change_smoother")(change_smoother)
15
+ rt("/compare/chart")(load_chart)
16
+
17
+ # Routes
18
+ def toggle_accordion(session, split: str, metrics: str, open: bool):
19
+ if "cards-state" not in session["compare"]:
20
+ session["compare"]["cards-state"] = {}
21
+ session["compare"]["cards-state"][split] = open
22
+ return SplitCard(session, split, metrics=metrics.split(","))
23
+
24
+ def change_chart_type(session, runIDs: str, step: bool):
25
+ new_type = "time" if step else "step"
26
+ session["compare"]["chart_type"] = new_type
27
+ return (ChartType(session, path="/compare", session_path="compare", selected_rows_key="compare"), # We want to toggle it
28
+ ChartCardList(session, swap=True)
29
+ )
30
+
31
+ def change_chart_scale(session, runIDs: str, log: bool):
32
+ new_scale = "default" if log else "log"
33
+ session["compare"]["chart_scale"] = new_scale
34
+ return (LogSelector(session, path="/compare", session_path="compare", selected_rows_key="compare"), # We want to toggle it
35
+ ChartCardList(session, swap=True)
36
+ )
37
+
38
+ def hide_line(session, runIDs: str, label: str):
39
+ if 'hidden_lines' not in session["compare"]:
40
+ session["compare"]['hidden_lines'] = []
41
+ session["compare"]['hidden_lines'].append(label)
42
+ return CompareSetup(session, swap=True), ChartCardList(session, swap=True)
43
+
44
+
45
+ def show_line(session, runIDs: str, label: str):
46
+ if 'hidden_lines' not in session["compare"]:
47
+ session["compare"]['hidden_lines'] = []
48
+ if label in session["compare"]['hidden_lines']:
49
+ session["compare"]['hidden_lines'].remove(label)
50
+
51
+ return CompareSetup(session, swap=True), ChartCardList(session, swap=True)
52
+
53
+ def change_smoother(session, runIDs: str, smoother: int):
54
+ session["compare"]["smoother_value"] = smoother
55
+ return CompareSetup(session, swap=True), ChartCardList(session, swap=True)
56
+
57
+ def load_chart(session, split: str, metric: str, type: str, running: bool, logscale: bool = False):
58
+ return Chart(session, split, metric, type, running, logscale=logscale)
@@ -0,0 +1,4 @@
1
+ from .right_panel import RightPanel
2
+ from .datagrid import DataGrid, CompareButton
3
+ from .routes import build_main_page_endpoints
4
+ from .main_page import MainPage
@@ -0,0 +1,5 @@
1
+ from .compare_button import CompareButton
2
+ from .datagrid import DataGrid
3
+ from .routes import build_datagrid_endpoints
4
+ from .handlers import right_click_handler_row, right_click_handler
5
+ from .sortable_column_js import SortableColumnsJs
@@ -0,0 +1,21 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+
4
+ def CompareButton(session, swap: bool = False):
5
+ show = "datagrid" in session and "selected-rows" in session["datagrid"] and len(session["datagrid"]["selected-rows"]) > 1
6
+ run_ids = session["datagrid"].get("selected-rows") or []
7
+ run_ids_formatted = ','.join([str(i) for i in run_ids])
8
+ return Div(
9
+ Button(
10
+ "Compare",
11
+ cls="compare-button",
12
+ style="display: block;" if show else "display: none;",
13
+ # onclick=f"window.open('{url}', '_blank')",
14
+ hx_get=f"/compare_action?run_ids={run_ids_formatted}",
15
+ hx_target="#container",
16
+ data_new_tab="true"
17
+ ),
18
+ cls="compare-button-container",
19
+ id="compare-button-container",
20
+ hx_swap_oob="true" if swap else "false",
21
+ )
@@ -0,0 +1,67 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+ from .row import Row
4
+ from .header import Header, HeaderRename
5
+
6
+ def DataGrid(session, rename_col: str = None, wrapincontainer: bool = False, fullscreen: bool = False):
7
+ """
8
+ Note that fullscreen only work if the container is requested because it applies on the container
9
+ """
10
+ from __main__ import rTable, CONFIG
11
+
12
+ if "datagrid" not in session:
13
+ session["datagrid"] = dict()
14
+ show_hidden = session.get("show_hidden", False)
15
+ rows_selected = session["datagrid"].get("selected-rows") or []
16
+ sort_by: Optional[str] = session["datagrid"].get("sort_by", None)
17
+ sort_order: Optional[str] = session["datagrid"].get("sort_order", None)
18
+ columns, col_ids, data = rTable.get_results(show_hidden=show_hidden)
19
+ # If the columns to sort by is hidden, we reset it
20
+ if sort_by is not None and sort_by not in col_ids:
21
+ session["datagrid"]["sort_by"] = sort_by = None
22
+ session["datagrid"]["sort_order"] = sort_order = None
23
+
24
+ if sort_by is not None and sort_order is not None:
25
+ data = sorted(
26
+ data,
27
+ key=lambda x: (
28
+ x[col_ids.index(sort_by)] is None, # True = 1, False = 0 — Nones last
29
+ x[col_ids.index(sort_by)]
30
+ ),
31
+ reverse=(sort_order == "desc")
32
+ )
33
+
34
+ run_ids = [row[col_ids.index("run_id")] for row in data]
35
+ rows_hidden = rTable.get_hidden_runs() if show_hidden else []
36
+ table = Table(
37
+ # We put the headers in a form so that we can sort them using htmx
38
+ Thead(
39
+ Tr(
40
+ *[
41
+ HeaderRename(col_name, col_id) if col_id == rename_col else Header(
42
+ col_name,
43
+ col_id,
44
+ sort_order if col_id == sort_by else None)
45
+ for col_name, col_id in zip(columns, col_ids)],
46
+ id="column-header-row"
47
+ )
48
+ ),
49
+ Tbody(
50
+ *[Row(row,
51
+ run_id,
52
+ max_decimals=CONFIG.MAX_DEC,
53
+ selected=run_id in rows_selected,
54
+ hidden=run_id in rows_hidden,
55
+ fullscreen=fullscreen) for row, run_id in zip(data, run_ids)],
56
+ ),
57
+ cls="data-grid"
58
+ ),
59
+
60
+ if wrapincontainer:
61
+ return Div(
62
+ table,
63
+ cls="scroll-container" if not fullscreen else "scroll-container fullscreen",
64
+ id="experiment-table",
65
+ ),
66
+ else:
67
+ return table
@@ -0,0 +1,54 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+
4
+ def right_click_handler(elementIds: List[str], top: int, left: int):
5
+ from __main__ import rTable
6
+ elementId = [elem for elem in elementIds if elem.startswith("grid-header-")][0]
7
+ clicked_col = elementId.replace("grid-header-", "")
8
+ hidden_columns = [(key, alias) for key, (order, alias) in rTable.result_columns.items() if order is None]
9
+ return Div(
10
+ Ul(
11
+ Li('Hide', hx_get=f"/hide?col={clicked_col}", hx_target='#experiment-table', hx_swap="innerHTML", cls="menu-item"),
12
+ Li('Rename', hx_get=f'/rename_col_datagrid?col={clicked_col}', hx_target='#experiment-table', hx_swap="innerHTML", cls="menu-item"),
13
+ Li(
14
+ Div(A('Add', href="#", cls="has-submenu"), Span("►"),
15
+ style="display: flex; flex-direction: row; justify-content: space-between;"),
16
+ Ul(
17
+ *[Li(alias, hx_get=f"/show?col={col_name}&after={clicked_col}", hx_target='#experiment-table', hx_swap="innerHTML", cls="menu-item")
18
+ for col_name, alias in hidden_columns],
19
+ cls="submenu"
20
+ ),
21
+ cls="menu-item has-submenu-wrapper"
22
+ ),
23
+ cls='dropdown-menu'
24
+ ),
25
+ id='custom-menu',
26
+ style=f'visibility: visible; top: {top}px; left: {left}px;',
27
+ )
28
+ def right_click_handler_row(session, elementIds: List[str], top: int, left: int):
29
+ from __main__ import rTable
30
+ elementId = [elem for elem in elementIds if elem.startswith("grid-row-")][0]
31
+ clicked_row = int(elementId.replace("grid-row-", ""))
32
+ hidden_runs = rTable.get_hidden_runs()
33
+ if clicked_row in hidden_runs:
34
+ hideshow_button = Li('Show', hx_get=f"/show_run?run_id={clicked_row}", hx_target='#experiment-table',
35
+ hx_swap="innerHTML", cls="menu-item")
36
+ else:
37
+ hideshow_button = Li('Hide', hx_get=f"/hide_run?run_id={clicked_row}", hx_target='#experiment-table',
38
+ hx_swap="innerHTML",cls="menu-item")
39
+ print(session)
40
+ if session.get("show_hidden", False):
41
+ toggle_visibility_button = Li('Hide Hidden', hx_get=f"/hide_hidden", hx_target='#experiment-table',
42
+ hx_swap="innerHTML", cls="menu-item")
43
+ else:
44
+ toggle_visibility_button = Li('Show Hidden', hx_get=f"/show_hidden", hx_target='#experiment-table',
45
+ hx_swap="innerHTML", cls="menu-item")
46
+ return Div(
47
+ Ul(
48
+ hideshow_button,
49
+ toggle_visibility_button,
50
+ cls='dropdown-menu'
51
+ ),
52
+ id='custom-menu',
53
+ style=f'visibility: visible; top: {top}px; left: {left}px;',
54
+ )
@@ -0,0 +1,43 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+
4
+ def Header(name: str, col_id: str, sort_dir: str = None):
5
+ if sort_dir == "asc":
6
+ return Th(
7
+ Div(name, Span("↑", cls='sort-icon'), hx_get=f'/sort?by={col_id}&order=desc', target_id='experiment-table',
8
+ hx_swap='innerHTML',
9
+ cls='sortable',
10
+ id=f"grid-header-{col_id}"
11
+ ),
12
+ data_col=col_id
13
+ ),
14
+ elif sort_dir == "desc":
15
+ return Th(
16
+ Div(name, Span("↓", cls='sort-icon'), hx_get=f'/sort?by={col_id}&order=', target_id='experiment-table',
17
+ hx_swap='innerHTML',
18
+ cls='sortable',
19
+ id=f"grid-header-{col_id}"),
20
+ data_col=col_id
21
+ ),
22
+ else:
23
+ return Th(
24
+ Div(name, Span('⇅', cls='sort-icon'), hx_get=f'/sort?by={col_id}&order=asc', target_id='experiment-table',
25
+ hx_swap='innerHTML',
26
+ cls='sortable',
27
+ id=f"grid-header-{col_id}"),
28
+ data_col=col_id
29
+ ),
30
+
31
+ def HeaderRename(name: str, col_id: str):
32
+ return Th(
33
+ Input(
34
+ type="text",
35
+ value=name,
36
+ name="new_name",
37
+ hx_post=f'/rename_col?col_id={col_id}',
38
+ hx_target='#experiment-table',
39
+ hx_swap='innerHTML',
40
+ id=f"grid-header-{col_id}",
41
+ cls="rename-input"
42
+ )
43
+ ),