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