dashcontrol
DashControl — Databricks Control Center.
21@dataclass 22class ControlCenterConfig: 23 """ 24 Configuration for the Databricks Control Center. 25 26 Parameters 27 ---------- 28 date_range_days 29 Default lookback window for all time-based queries (default: 30). 30 catalogs 31 List of catalogs to scope table/lineage queries to. 32 Empty list means all visible catalogs. 33 panels 34 Which built-in panels to show. Defaults to all. 35 custom_panels 36 User-defined SQL panels appended after the built-in tabs. 37 row_limit 38 Max rows returned per panel query (default: 500). 39 workspace_name 40 Optional display name shown in the dashboard header. 41 """ 42 date_range_days: int = 30 43 catalogs: list[str] = field(default_factory=list) 44 panels: list[str] = field(default_factory=lambda: list(ALL_PANELS)) 45 custom_panels: list[CustomPanel] = field(default_factory=list) 46 row_limit: int = 500 47 workspace_name: str = "" 48 49 def __post_init__(self): 50 unknown = set(self.panels) - set(ALL_PANELS) 51 if unknown: 52 raise ValueError(f"Unknown panels: {unknown}. Valid: {ALL_PANELS}") 53 if self.date_range_days < 1 or self.date_range_days > 365: 54 raise ValueError("date_range_days must be between 1 and 365") 55 if self.row_limit < 1 or self.row_limit > 10_000: 56 raise ValueError("row_limit must be between 1 and 10,000") 57 58 def catalog_filter(self, col: str = "table_catalog") -> str: 59 """SQL WHERE fragment to filter by configured catalogs.""" 60 if not self.catalogs: 61 return "" 62 quoted = ", ".join(f"'{c}'" for c in self.catalogs) 63 return f"AND {col} IN ({quoted})"
Configuration for the Databricks Control Center.
Parameters
date_range_days Default lookback window for all time-based queries (default: 30). catalogs List of catalogs to scope table/lineage queries to. Empty list means all visible catalogs. panels Which built-in panels to show. Defaults to all. custom_panels User-defined SQL panels appended after the built-in tabs. row_limit Max rows returned per panel query (default: 500). workspace_name Optional display name shown in the dashboard header.
58 def catalog_filter(self, col: str = "table_catalog") -> str: 59 """SQL WHERE fragment to filter by configured catalogs.""" 60 if not self.catalogs: 61 return "" 62 quoted = ", ".join(f"'{c}'" for c in self.catalogs) 63 return f"AND {col} IN ({quoted})"
SQL WHERE fragment to filter by configured catalogs.
14def env_setup() -> None: 15 """Open the environment setup panel — where should dashcontrol read/write 16 its configs? Defaults to the notebook's current working directory if 17 never called.""" 18 try: 19 import dashui 20 from IPython.display import display 21 except ImportError: 22 raise RuntimeError("ipywidgets required. Run: %pip install ipywidgets") from None 23 24 display(dashui.card([ 25 dashui.header("Databricks Control Center — Environment Setup", library=_LIBRARY), 26 dashui.env_setup_panel(_LIBRARY).widget, 27 ]))
Open the environment setup panel — where should dashcontrol read/write its configs? Defaults to the notebook's current working directory if never called.
30def launch(config: ControlCenterConfig = None): 31 try: 32 import ipywidgets as w 33 from IPython.display import display 34 except ImportError: 35 raise RuntimeError("ipywidgets required. Run: %pip install ipywidgets") 36 37 import dashui 38 from dashcontrol.runner import run_query_safe 39 from dashcontrol import sql as Q 40 from dashcontrol.formatters import ( 41 html_table, stat_tile, stat_row, error_box, info_box, 42 section_header, sparkline_html, format_number, _TEAL, _RED, _AMBER, _GREEN, 43 ) 44 45 if config is None: 46 saved = dashui.load_config(_LIBRARY) 47 if saved: 48 from dashcontrol.config import CustomPanel 49 saved["custom_panels"] = [CustomPanel(**p) for p in saved.get("custom_panels", [])] 50 config = ControlCenterConfig(**saved) 51 cfg = config or ControlCenterConfig() 52 53 # ── Global controls ─────────────────────────────────────────────────────── 54 days_slider = w.IntSlider( 55 value=cfg.date_range_days, min=1, max=90, step=1, 56 description="Days:", style={"description_width": "40px"}, 57 layout=w.Layout(width="320px"), 58 ) 59 catalog_text = w.Text( 60 value=", ".join(cfg.catalogs), 61 description="Catalogs:", 62 placeholder="main, hive_metastore (blank = all)", 63 style={"description_width": "70px"}, 64 layout=w.Layout(width="360px"), 65 ) 66 workspace_label = w.HTML( 67 value=f"<div style='font-size:11px;color:#6b7280;padding:4px 0'>" 68 f"Workspace: <b>{cfg.workspace_name or 'auto-detected'}</b></div>" 69 ) 70 71 def _days() -> int: 72 return days_slider.value 73 74 def _cat_filter() -> str: 75 cats = [c.strip() for c in catalog_text.value.split(",") if c.strip()] 76 if not cats: 77 return "" 78 quoted = ", ".join(f"'{c}'" for c in cats) 79 return f"AND table_catalog IN ({quoted})" 80 81 # ── Panel builder helper ────────────────────────────────────────────────── 82 def _save_state() -> None: 83 try: 84 from dataclasses import asdict 85 state = asdict(cfg) 86 state["date_range_days"] = days_slider.value 87 state["catalogs"] = [c.strip() for c in catalog_text.value.split(",") if c.strip()] 88 dashui.save_config(_LIBRARY, state) 89 except Exception: 90 pass # persistence is a convenience, never block the actual operation on it 91 92 def _panel(sections_fn) -> tuple: 93 """Create a (load_btn, output) pair and wire the click.""" 94 out = dashui.output_panel() 95 btn = dashui.action_button("Load", style="info") 96 97 def _on_click(b): 98 btn.disabled = True 99 btn.description = "Loading…" 100 _save_state() 101 with out: 102 out.clear_output() 103 try: 104 sections_fn(out) 105 except Exception as e: 106 import ipywidgets as _w 107 from IPython.display import display as _d 108 _d(_w.HTML(error_box(str(e)))) 109 btn.disabled = False 110 btn.description = "Refresh" 111 112 btn.on_click(_on_click) 113 return btn, out 114 115 # ───────────────────────────────────────────────────────────────────────── 116 # TAB: HEALTH 117 # ───────────────────────────────────────────────────────────────────────── 118 def _health(out): 119 from IPython.display import display as _d 120 import ipywidgets as _w 121 122 d = _days() 123 dbu = run_query_safe(Q.health_dbu_today(), 1) 124 users = run_query_safe(Q.health_active_users(d), 1) 125 jobs = run_query_safe(Q.health_failed_jobs(24), 1) 126 tbls = run_query_safe(Q.health_table_count(_cat_filter()), 1) 127 trend = run_query_safe(Q.health_dbu_trend(min(d, 14))) 128 129 dbu_val = format_number(dbu.first_value("dbu_today", 0)) 130 user_val = format_number(users.first_value("active_users", 0)) 131 job_val = jobs.first_value("failed_jobs", 0) 132 tbl_val = format_number(tbls.first_value("total_tables", 0)) 133 job_color = _RED if (job_val or 0) > 0 else _GREEN 134 135 tiles = stat_row([ 136 stat_tile("DBU Today", dbu_val, _TEAL), 137 stat_tile(f"Active Users ({d}d)", user_val, _TEAL), 138 stat_tile("Failed Jobs (24h)", job_val, job_color), 139 stat_tile("Total Tables", tbl_val, _TEAL), 140 ]) 141 142 sparkline = "" 143 if trend.ok and trend.rows: 144 vals = [float(r.get("dbu", 0) or 0) for r in trend.rows] 145 dates = [str(r.get("usage_date", ""))[-5:] for r in trend.rows] 146 label = f"{dates[0]} → {dates[-1]}" if dates else "" 147 sparkline = ( 148 section_header("DBU Trend") + 149 f"<div style='padding:8px 0'>{sparkline_html(vals, label)}</div>" 150 ) 151 152 _d(_w.HTML(tiles + sparkline)) 153 154 health_btn, health_out = _panel(_health) 155 156 # ───────────────────────────────────────────────────────────────────────── 157 # TAB: COST 158 # ───────────────────────────────────────────────────────────────────────── 159 def _cost(out): 160 from IPython.display import display as _d 161 import ipywidgets as _w 162 163 d = _days() 164 burn = run_query_safe(Q.cost_burn_rate(d), 1) 165 skus = run_query_safe(Q.cost_daily_by_sku(d), cfg.row_limit) 166 clust = run_query_safe(Q.cost_top_clusters(d), 10) 167 jobs = run_query_safe(Q.cost_top_jobs(d), 10) 168 users = run_query_safe(Q.cost_top_users_by_dbu(d), 10) 169 170 total = format_number(burn.first_value("total_dbu", 0)) 171 daily = format_number(burn.first_value("avg_daily_dbu", 0)) 172 proj = format_number(burn.first_value("projected_monthly_dbu", 0)) 173 174 tiles = stat_row([ 175 stat_tile(f"Total DBU ({d}d)", total, _TEAL), 176 stat_tile("Avg Daily DBU", daily, _TEAL), 177 stat_tile("Projected Monthly DBU", proj, _AMBER), 178 ]) 179 180 html = tiles 181 if skus.ok: 182 html += section_header("Daily DBU by SKU") 183 html += html_table(skus.rows[:30], highlight_col="dbu") 184 if clust.ok: 185 html += section_header("Top Clusters by DBU") 186 html += html_table(clust.rows) 187 if jobs.ok: 188 html += section_header("Top Jobs by DBU") 189 html += html_table(jobs.rows) 190 if users.ok: 191 html += section_header("Top Users by DBU") 192 html += html_table(users.rows) 193 194 for res in [skus, clust, jobs, users]: 195 if not res.ok: 196 html += error_box(res.error) 197 198 _d(_w.HTML(html)) 199 200 cost_btn, cost_out = _panel(_cost) 201 202 # ───────────────────────────────────────────────────────────────────────── 203 # TAB: USERS 204 # ───────────────────────────────────────────────────────────────────────── 205 def _users(out): 206 from IPython.display import display as _d 207 import ipywidgets as _w 208 209 d = _days() 210 top_q = run_query_safe(Q.users_top_by_queries(d), 20) 211 top_t = run_query_safe(Q.users_top_by_tables_accessed(d), 20) 212 inact = run_query_safe(Q.users_inactive(d, min(d * 3, 90)), 20) 213 perms = run_query_safe(Q.users_permission_changes(d), 30) 214 215 html = "" 216 if top_q.ok: 217 html += section_header("Top Users by Activity", f"last {d} days") 218 html += html_table(top_q.rows) 219 if top_t.ok: 220 html += section_header("Top Users by Tables Accessed") 221 html += html_table(top_t.rows) 222 if inact.ok and inact.rows: 223 html += section_header("Inactive Users", f"not seen in {d}d") 224 html += html_table(inact.rows, highlight_col="days_inactive") 225 if perms.ok and perms.rows: 226 html += section_header("Recent Permission Changes") 227 html += html_table(perms.rows, highlight_col="action_name") 228 229 for res in [top_q, top_t, inact, perms]: 230 if not res.ok: 231 html += error_box(res.error) 232 233 _d(_w.HTML(html)) 234 235 users_btn, users_out = _panel(_users) 236 237 # ───────────────────────────────────────────────────────────────────────── 238 # TAB: CATALOG 239 # ───────────────────────────────────────────────────────────────────────── 240 def _catalog(out): 241 from IPython.display import display as _d 242 import ipywidgets as _w 243 244 cf = _cat_filter() 245 d = _days() 246 inv = run_query_safe(Q.catalog_tables_by_schema(cf), 100) 247 stale = run_query_safe(Q.catalog_stale_tables(90, cf), 50) 248 hot = run_query_safe(Q.catalog_most_accessed(d, 20)) 249 cols = run_query_safe(Q.catalog_column_count(cf), 30) 250 251 html = "" 252 if inv.ok: 253 total = sum(r.get("table_count", 0) or 0 for r in inv.rows) 254 html += stat_row([stat_tile("Total Tables", format_number(total), _TEAL)]) 255 html += section_header("Tables by Schema") 256 html += html_table(inv.rows) 257 if stale.ok and stale.rows: 258 html += section_header("Stale Tables (90+ days unmodified)") 259 html += html_table(stale.rows, highlight_col="days_stale") 260 if hot.ok: 261 html += section_header(f"Most Accessed Tables (last {d}d)") 262 html += html_table(hot.rows) 263 if cols.ok: 264 html += section_header("Widest Tables (by column count)") 265 html += html_table(cols.rows) 266 267 for res in [inv, stale, hot, cols]: 268 if not res.ok: 269 html += error_box(res.error) 270 271 _d(_w.HTML(html)) 272 273 catalog_btn, catalog_out = _panel(_catalog) 274 275 # ───────────────────────────────────────────────────────────────────────── 276 # TAB: JOBS 277 # ───────────────────────────────────────────────────────────────────────── 278 def _jobs(out): 279 from IPython.display import display as _d 280 import ipywidgets as _w 281 282 d = _days() 283 rates = run_query_safe(Q.jobs_success_rate(d)) 284 fails = run_query_safe(Q.jobs_top_failures(d), 20) 285 slow = run_query_safe(Q.jobs_longest_runs(d), 20) 286 volume = run_query_safe(Q.jobs_daily_run_volume(d)) 287 288 html = "" 289 if rates.ok and rates.rows: 290 total = sum(r.get("run_count", 0) or 0 for r in rates.rows) 291 success = next((r["run_count"] for r in rates.rows if r.get("result_state") == "SUCCEEDED"), 0) 292 rate_pct = round(success / total * 100, 1) if total else 0 293 color = _GREEN if rate_pct >= 95 else (_AMBER if rate_pct >= 80 else _RED) 294 html += stat_row([ 295 stat_tile(f"Success Rate ({d}d)", f"{rate_pct}%", color), 296 stat_tile("Total Runs", format_number(total), _TEAL), 297 ]) 298 html += section_header("Run State Breakdown") 299 html += html_table(rates.rows, highlight_col="result_state") 300 if fails.ok and fails.rows: 301 html += section_header("Top Failing Jobs") 302 html += html_table(fails.rows, highlight_col="failure_count") 303 if slow.ok: 304 html += section_header("Longest Running Jobs") 305 html += html_table(slow.rows, highlight_col="duration_min") 306 if volume.ok and volume.rows: 307 html += section_header("Daily Run Volume") 308 html += html_table(volume.rows) 309 310 for res in [rates, fails, slow, volume]: 311 if not res.ok: 312 html += error_box(res.error) 313 314 _d(_w.HTML(html)) 315 316 jobs_btn, jobs_out = _panel(_jobs) 317 318 # ───────────────────────────────────────────────────────────────────────── 319 # TAB: QUERIES 320 # ───────────────────────────────────────────────────────────────────────── 321 def _queries(out): 322 from IPython.display import display as _d 323 import ipywidgets as _w 324 325 d = min(_days(), 7) # query.history can be large; cap at 7d default 326 slow = run_query_safe(Q.queries_slowest(d), 20) 327 exp = run_query_safe(Q.queries_most_expensive(d), 20) 328 top = run_query_safe(Q.queries_top_users(d), 20) 329 errs = run_query_safe(Q.queries_error_summary(d), 20) 330 331 html = "" 332 if slow.ok: 333 html += section_header(f"Slowest Queries (last {d}d)") 334 html += html_table(slow.rows, highlight_col="duration_sec") 335 if exp.ok: 336 html += section_header("Most Expensive Queries (by task time)") 337 html += html_table(exp.rows, highlight_col="task_sec") 338 if top.ok: 339 html += section_header("Top Query Authors") 340 html += html_table(top.rows) 341 if errs.ok and errs.rows: 342 html += section_header("Most Common Query Errors") 343 html += html_table(errs.rows, highlight_col="occurrences") 344 345 for res in [slow, exp, top, errs]: 346 if not res.ok: 347 html += error_box(res.error) 348 349 _d(_w.HTML(html)) 350 351 queries_btn, queries_out = _panel(_queries) 352 353 # ───────────────────────────────────────────────────────────────────────── 354 # TAB: GOVERNANCE 355 # ───────────────────────────────────────────────────────────────────────── 356 def _governance(out): 357 from IPython.display import display as _d 358 import ipywidgets as _w 359 360 cf = _cat_filter() 361 d = _days() 362 noown = run_query_safe(Q.governance_tables_without_owners(cf), 50) 363 pii = run_query_safe(Q.governance_pii_columns(cf), 100) 364 anomal = run_query_safe(Q.governance_access_anomalies(d), 30) 365 schema = run_query_safe(Q.governance_schema_changes(d), 30) 366 367 html = "" 368 noown_n = len(noown.rows) if noown.ok else "?" 369 pii_n = len(pii.rows) if pii.ok else "?" 370 anomal_n= len(anomal.rows)if anomal.ok else "?" 371 372 noown_color = _RED if noown_n and noown_n != "?" and noown_n > 0 else _GREEN 373 anomal_color = _RED if anomal_n and anomal_n != "?" and anomal_n > 0 else _GREEN 374 375 html += stat_row([ 376 stat_tile("Tables Without Owners", noown_n, noown_color), 377 stat_tile("PII Columns Detected", pii_n, _AMBER), 378 stat_tile(f"Access Denials ({d}d)", anomal_n, anomal_color), 379 ]) 380 381 if noown.ok and noown.rows: 382 html += section_header("Tables Without Owners") 383 html += html_table(noown.rows) 384 elif noown.ok: 385 html += section_header("Tables Without Owners") 386 html += info_box("All tables have owners. Good.") 387 388 if pii.ok and pii.rows: 389 html += section_header("Potential PII Columns (pattern-matched)") 390 html += html_table(pii.rows) 391 392 if anomal.ok and anomal.rows: 393 html += section_header(f"Access Denials / Anomalies (last {d}d)") 394 html += html_table(anomal.rows, highlight_col="action_name") 395 396 if schema.ok and schema.rows: 397 html += section_header(f"Schema Changes (last {d}d)") 398 html += html_table(schema.rows, highlight_col="action_name") 399 400 for res in [noown, pii, anomal, schema]: 401 if not res.ok: 402 html += error_box(res.error) 403 404 _d(_w.HTML(html)) 405 406 gov_btn, gov_out = _panel(_governance) 407 408 # ───────────────────────────────────────────────────────────────────────── 409 # TAB: CUSTOM 410 # ───────────────────────────────────────────────────────────────────────── 411 custom_title = w.Text(description="Title:", placeholder="My panel title") 412 custom_sql = w.Textarea( 413 description="SQL:", 414 placeholder="SELECT * FROM system.access.audit LIMIT 20", 415 layout=w.Layout(width="100%", height="100px"), 416 ) 417 custom_btn = dashui.action_button("Run Custom Query", style="warning") 418 custom_out = dashui.output_panel() 419 420 # Pre-wire any custom panels from config 421 _config_custom_widgets = [] 422 for cp in cfg.custom_panels: 423 cp_out = dashui.output_panel() 424 cp_btn = dashui.action_button(f"Load: {cp.title}", style="info") 425 _sql_closure = cp.sql 426 427 def _make_handler(s, o): 428 def _h(b): 429 from IPython.display import display as _d 430 import ipywidgets as _w 431 r = run_query_safe(s, cfg.row_limit) 432 with o: 433 o.clear_output() 434 _d(_w.HTML( 435 html_table(r.rows) if r.ok else error_box(r.error) 436 )) 437 return _h 438 439 cp_btn.on_click(_make_handler(_sql_closure, cp_out)) 440 _config_custom_widgets.extend([cp_btn, cp_out]) 441 442 def on_custom(b): 443 sql = custom_sql.value.strip() 444 if not sql: 445 return 446 with custom_out: 447 custom_out.clear_output() 448 from IPython.display import display as _d 449 import ipywidgets as _w 450 r = run_query_safe(sql, cfg.row_limit) 451 _d(_w.HTML( 452 html_table(r.rows) if r.ok else error_box(r.error) 453 )) 454 455 custom_btn.on_click(on_custom) 456 457 # ── Assemble tabs ───────────────────────────────────────────────────────── 458 panel_map = { 459 "health": ("Health", w.VBox([health_btn, health_out])), 460 "cost": ("Cost", w.VBox([cost_btn, cost_out])), 461 "users": ("Users", w.VBox([users_btn, users_out])), 462 "catalog": ("Catalog", w.VBox([catalog_btn, catalog_out])), 463 "jobs": ("Jobs", w.VBox([jobs_btn, jobs_out])), 464 "queries": ("Queries", w.VBox([queries_btn, queries_out])), 465 "governance": ("Governance", w.VBox([gov_btn, gov_out])), 466 } 467 468 tab = w.Tab() 469 children, titles = [], [] 470 for panel_id in cfg.panels: 471 if panel_id in panel_map: 472 title, content = panel_map[panel_id] 473 children.append(content) 474 titles.append(title) 475 476 # Custom tab always last 477 custom_content = w.VBox( 478 _config_custom_widgets + [custom_title, custom_sql, custom_btn, custom_out] 479 ) 480 children.append(custom_content) 481 titles.append("Custom") 482 483 tab.children = children 484 for i, t in enumerate(titles): 485 tab.set_title(i, t) 486 487 env_accordion = w.Accordion(children=[dashui.env_setup_panel(_LIBRARY).widget]) 488 env_accordion.set_title(0, "Environment setup") 489 env_accordion.selected_index = None 490 491 ui = dashui.card([ 492 dashui.header( 493 f"Databricks Control Center{' — ' + cfg.workspace_name if cfg.workspace_name else ''}", 494 library="dashcontrol", 495 ), 496 env_accordion, 497 dashui.html( 498 "<div style='font-size:11px;color:#6b7280;margin-bottom:4px'>" 499 "Click a tab then <b>Load</b> to query system tables. " 500 "Results are lazy-loaded and cached until you Refresh.</div>" 501 ), 502 w.HBox([days_slider, catalog_text]), 503 workspace_label, 504 tab, 505 ]) 506 display(ui)