Add sales dashboard and live system monitor examples, bump prefab-ui to 0.17 (#3696)

This commit is contained in:
Jeremiah Lowin 2026-03-29 21:00:15 -04:00 committed by GitHub
commit 27abe3c3f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 456 additions and 14 deletions

View file

@ -0,0 +1,233 @@
from prefab_ui.components import (
Card,
CardContent,
Column,
Grid,
Heading,
Metric,
Muted,
Row,
Separator,
Text,
)
from prefab_ui.components.charts import AreaChart, ChartSeries, PieChart
from prefab_ui.components.data_table import DataTable, DataTableColumn
from fastmcp import FastMCP
mcp = FastMCP("Sales Dashboard")
MONTHLY_REVENUE = [
{"month": "Jul", "new_business": 182_000, "expansion": 74_000, "renewal": 210_000},
{"month": "Aug", "new_business": 195_000, "expansion": 81_000, "renewal": 215_000},
{"month": "Sep", "new_business": 224_000, "expansion": 93_000, "renewal": 208_000},
{"month": "Oct", "new_business": 210_000, "expansion": 88_000, "renewal": 222_000},
{"month": "Nov", "new_business": 248_000, "expansion": 102_000, "renewal": 230_000},
{"month": "Dec", "new_business": 271_000, "expansion": 115_000, "renewal": 238_000},
{"month": "Jan", "new_business": 235_000, "expansion": 97_000, "renewal": 241_000},
{"month": "Feb", "new_business": 262_000, "expansion": 108_000, "renewal": 245_000},
{"month": "Mar", "new_business": 289_000, "expansion": 121_000, "renewal": 252_000},
{"month": "Apr", "new_business": 305_000, "expansion": 134_000, "renewal": 258_000},
{"month": "May", "new_business": 318_000, "expansion": 142_000, "renewal": 263_000},
{"month": "Jun", "new_business": 342_000, "expansion": 156_000, "renewal": 270_000},
]
REVENUE_BY_SEGMENT = [
{"segment": "Enterprise", "revenue": 3_840_000},
{"segment": "Mid-Market", "revenue": 2_160_000},
{"segment": "SMB", "revenue": 1_440_000},
{"segment": "Startup", "revenue": 720_000},
]
RECENT_DEALS = [
{
"company": "Meridian Health Systems",
"amount": "$485,000",
"stage": "Closed Won",
"rep": "Sarah Chen",
"close_date": "Jun 12, 2026",
},
{
"company": "Atlas Financial Group",
"amount": "$372,000",
"stage": "Closed Won",
"rep": "Marcus Rivera",
"close_date": "Jun 10, 2026",
},
{
"company": "Pinnacle Manufacturing",
"amount": "$298,000",
"stage": "Negotiation",
"rep": "Aisha Patel",
"close_date": "Jun 28, 2026",
},
{
"company": "Crestview Logistics",
"amount": "$264,000",
"stage": "Proposal Sent",
"rep": "James O'Brien",
"close_date": "Jul 5, 2026",
},
{
"company": "Northstar Retail",
"amount": "$215,000",
"stage": "Closed Won",
"rep": "Sarah Chen",
"close_date": "Jun 8, 2026",
},
{
"company": "Ironclad Security",
"amount": "$189,000",
"stage": "Negotiation",
"rep": "Lena Kowalski",
"close_date": "Jul 1, 2026",
},
{
"company": "Summit Analytics",
"amount": "$176,000",
"stage": "Closed Won",
"rep": "Marcus Rivera",
"close_date": "Jun 5, 2026",
},
{
"company": "Brightpath Education",
"amount": "$142,000",
"stage": "Proposal Sent",
"rep": "Aisha Patel",
"close_date": "Jul 12, 2026",
},
{
"company": "Vantage Media",
"amount": "$128,000",
"stage": "Closed Won",
"rep": "Lena Kowalski",
"close_date": "Jun 3, 2026",
},
{
"company": "Redwood Hospitality",
"amount": "$97,000",
"stage": "Discovery",
"rep": "James O'Brien",
"close_date": "Jul 20, 2026",
},
]
@mcp.tool(app=True)
def sales_dashboard() -> Column:
"""Company sales dashboard with KPIs, revenue trends, segment breakdown, and recent deals."""
total_revenue = sum(
row["new_business"] + row["expansion"] + row["renewal"]
for row in MONTHLY_REVENUE
)
current_quarter = sum(
row["new_business"] + row["expansion"] + row["renewal"]
for row in MONTHLY_REVENUE[-3:]
)
prior_quarter = sum(
row["new_business"] + row["expansion"] + row["renewal"]
for row in MONTHLY_REVENUE[-6:-3]
)
growth_pct = (current_quarter - prior_quarter) / prior_quarter * 100
with Column(gap=6, css_class="p-6") as view:
with Row(gap=2, align="center"):
Heading("Sales Dashboard")
Muted("FY2026 | Last updated Jun 15, 2026")
with Grid(columns=4, gap=4):
with Card():
with CardContent():
Metric(
label="Total Revenue",
value=f"${total_revenue / 1_000_000:.1f}M",
delta="+18.2% YoY",
trend="up",
)
with Card():
with CardContent():
Metric(
label="Quarterly Growth",
value=f"{growth_pct:.1f}%",
delta="+3.8pp vs prior",
trend="up",
)
with Card():
with CardContent():
Metric(
label="Active Customers",
value="1,847",
delta="+124 this quarter",
trend="up",
)
with Card():
with CardContent():
Metric(
label="Avg Deal Size",
value="$236K",
delta="+12% vs H1",
trend="up",
)
with Grid(columns=3, gap=6):
with Card(css_class="col-span-2"):
with CardContent():
Text(
"Monthly Revenue",
css_class="text-sm font-medium text-muted-foreground mb-2",
)
AreaChart(
data=MONTHLY_REVENUE,
series=[
ChartSeries(data_key="new_business", label="New Business"),
ChartSeries(data_key="expansion", label="Expansion"),
ChartSeries(data_key="renewal", label="Renewal"),
],
x_axis="month",
stacked=True,
curve="smooth",
show_legend=True,
height=280,
y_axis_format="compact",
)
with Card():
with CardContent():
Text(
"Revenue by Segment",
css_class="text-sm font-medium text-muted-foreground mb-2",
)
PieChart(
data=REVENUE_BY_SEGMENT,
data_key="revenue",
name_key="segment",
show_legend=True,
inner_radius=50,
height=280,
)
Separator()
Text("Recent Deals", css_class="text-lg font-semibold")
DataTable(
columns=[
DataTableColumn(key="company", header="Company", sortable=True),
DataTableColumn(key="amount", header="Amount", sortable=True),
DataTableColumn(key="stage", header="Stage", sortable=True),
DataTableColumn(key="rep", header="Sales Rep", sortable=True),
DataTableColumn(key="close_date", header="Close Date", sortable=True),
],
rows=RECENT_DEALS,
search=True,
paginated=True,
)
return view
if __name__ == "__main__":
mcp.run()

View file

@ -0,0 +1,195 @@
"""System monitor — live CPU, memory, and disk stats from the host machine.
Auto-refreshes every 3 seconds via SetInterval + CallTool.
Requires psutil: pip install psutil
Usage:
fastmcp dev apps system_monitor_server.py
"""
import platform
import time
from datetime import datetime
import psutil
from prefab_ui.actions import SetInterval, SetState
from prefab_ui.actions.mcp import CallTool
from prefab_ui.app import PrefabApp
from prefab_ui.components import (
Badge,
Card,
CardContent,
CardHeader,
Column,
Grid,
Heading,
Metric,
Muted,
Progress,
Row,
Select,
SelectOption,
Small,
Text,
)
from prefab_ui.components.charts import AreaChart, ChartSeries
from prefab_ui.components.control_flow import ForEach
from prefab_ui.rx import RESULT, STATE, Rx
from fastmcp import FastMCP
from fastmcp.apps.app import FastMCPApp
app = FastMCPApp("Monitor")
_history: list[dict] = []
def _collect_stats() -> dict:
"""Collect a full snapshot of system stats."""
cpu = psutil.cpu_percent(interval=0.1)
mem = psutil.virtual_memory()
disk = psutil.disk_usage("/")
now = datetime.now().strftime("%H:%M:%S")
_history.append({"time": now, "cpu": cpu, "memory": mem.percent})
if len(_history) > 100:
del _history[: len(_history) - 100]
top_procs = []
for p in sorted(
psutil.process_iter(["pid", "name", "cpu_percent", "memory_percent"]),
key=lambda p: p.info.get("cpu_percent") or 0,
reverse=True,
)[:6]:
info = p.info
top_procs.append(
{
"pid": info.get("pid") or 0,
"name": info.get("name") or "unknown",
"cpu": f"{(info.get('cpu_percent') or 0):.1f}%",
"memory": f"{(info.get('memory_percent') or 0):.1f}%",
}
)
return {
"cpu": cpu,
"mem_pct": mem.percent,
"mem_used": mem.used // (1024**3),
"mem_total": mem.total // (1024**3),
"disk_pct": disk.percent,
"disk_used": disk.used // (1024**3),
"disk_total": disk.total // (1024**3),
"uptime": _format_uptime(),
"cores": psutil.cpu_count(),
"platform": f"{platform.system()} {platform.machine()}",
"hostname": platform.node(),
"healthy": cpu < 80 and mem.percent < 90,
"history": list(_history),
"top_procs": top_procs,
}
def _format_uptime() -> str:
elapsed = int(time.time() - psutil.boot_time())
days, remainder = divmod(elapsed, 86400)
hours, remainder = divmod(remainder, 3600)
minutes, _ = divmod(remainder, 60)
if days > 0:
return f"{days}d {hours}h {minutes}m"
return f"{hours}h {minutes}m"
@app.tool()
def refresh() -> dict:
"""Collect fresh system stats."""
return _collect_stats()
@app.ui()
def system_dashboard() -> PrefabApp:
"""Live system dashboard with auto-refresh."""
initial = _collect_stats()
with PrefabApp(state={"stats": initial, "interval": "500"}) as ui:
with Column(
gap=6,
css_class="p-6",
on_mount=SetInterval(
duration=Rx("interval"),
on_tick=CallTool(
"refresh",
on_success=SetState("stats", RESULT),
),
),
):
with Row(gap=3, align="center"):
Heading("System Monitor")
Badge(STATE.stats.hostname, variant="outline")
with Select(name="interval", css_class="w-32"):
SelectOption("0.5s", value="500")
SelectOption("1s", value="1000")
SelectOption("5s", value="5000")
with Grid(columns=4, gap=4):
with Card():
with CardContent():
Metric(label="CPU", value=f"{STATE.stats.cpu}%")
Progress(value=STATE.stats.cpu)
with Card():
with CardContent():
Metric(label="Memory", value=f"{STATE.stats.mem_pct}%")
Progress(value=STATE.stats.mem_pct)
Muted(f"{STATE.stats.mem_used}GB / {STATE.stats.mem_total}GB")
with Card():
with CardContent():
Metric(label="Disk", value=f"{STATE.stats.disk_pct}%")
Progress(value=STATE.stats.disk_pct)
Muted(f"{STATE.stats.disk_used}GB / {STATE.stats.disk_total}GB")
with Card():
with CardContent():
Metric(label="Uptime", value=STATE.stats.uptime)
Muted(f"{STATE.stats.cores} cores")
with Grid(columns=[2, 1], gap=4):
with Card():
with CardHeader():
Text("CPU & Memory", css_class="text-sm font-medium")
with CardContent():
AreaChart(
data=STATE.stats.history,
series=[
ChartSeries(data_key="cpu", label="CPU %"),
ChartSeries(data_key="memory", label="Memory %"),
],
x_axis="time",
curve="smooth",
show_legend=True,
height=220,
animate=False,
)
with Card():
with CardHeader():
Text("Top Processes", css_class="text-sm font-medium")
with CardContent():
with Column(gap=2):
with ForEach("stats.top_procs") as proc:
with Row(justify="between", align="center"):
with Column(gap=0):
Small(proc.name)
Muted(proc.pid)
with Row(gap=2):
Badge(proc.cpu, variant="outline")
Badge(proc.memory, variant="outline")
return ui
mcp = FastMCP("System Monitor", providers=[app])
if __name__ == "__main__":
mcp.run()

View file

@ -53,7 +53,7 @@ classifiers = [
[project.optional-dependencies]
anthropic = ["anthropic>=0.48.0"]
apps = ["prefab-ui>=0.15.0"]
apps = ["prefab-ui>=0.17.0"]
# PyJWT floor: transitive via msal; CVE-2026-32597 affects <= 2.11.0
azure = ["azure-identity>=1.16.0", "PyJWT>=2.12.0"]
code-mode = ["pydantic-monty==0.0.8"]

View file

@ -3,6 +3,7 @@
from __future__ import annotations
import asyncio
import logging
import re
import secrets
import warnings
@ -100,6 +101,19 @@ if TYPE_CHECKING:
logger = get_logger(__name__)
# The MCP SDK warns "Tool X not listed, no validation will be performed"
# for every call to app-only tools (hidden from list_tools by design).
# This fires even when validate_input=False. Suppress it.
class _SuppressUnlistedToolWarning(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
return "not listed, no validation" not in record.getMessage()
logging.getLogger("mcp.server.lowlevel.server").addFilter(
_SuppressUnlistedToolWarning()
)
F = TypeVar("F", bound=Callable[..., Any])
DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]

26
uv.lock generated
View file

@ -878,7 +878,7 @@ requires-dist = [
{ name = "opentelemetry-api", specifier = ">=1.20.0" },
{ name = "packaging", specifier = ">=24.0" },
{ name = "platformdirs", specifier = ">=4.0.0" },
{ name = "prefab-ui", marker = "extra == 'apps'", specifier = ">=0.15.0" },
{ name = "prefab-ui", marker = "extra == 'apps'", specifier = ">=0.17.0" },
{ name = "py-key-value-aio", extras = ["filetree", "keyring", "memory"], specifier = ">=0.4.4,<0.5.0" },
{ name = "pydantic", extras = ["email"], specifier = ">=2.11.7" },
{ name = "pydantic-monty", marker = "extra == 'code-mode'", specifier = "==0.0.8" },
@ -1811,16 +1811,16 @@ wheels = [
[[package]]
name = "prefab-ui"
version = "0.15.0"
version = "0.17.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cyclopts" },
{ name = "pydantic" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ab/1a/f915a7b2b97392e723f3aaada7438e615cbf119bd01b78f8869dd24dbc33/prefab_ui-0.15.0.tar.gz", hash = "sha256:9fa6ef0b997a1322a7103daf92c340a3badb394ba8ea043249b247f27f65976d", size = 3202919, upload-time = "2026-03-29T00:59:23.185Z" }
sdist = { url = "https://files.pythonhosted.org/packages/87/63/e974f66befb851801dbf80e747a787efa59fb7d335ed3fb2e0d0d6f39b0e/prefab_ui-0.17.0.tar.gz", hash = "sha256:c3b2e9706349d013966d317abfa08a82b128f6ecddc8490e8bba73b3a6e3d16c", size = 3993324, upload-time = "2026-03-29T22:35:41.489Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/d8/2a3d73a3efc50e5fb7fcbe4f6516943b9506f930b23362990bed30a921c7/prefab_ui-0.15.0-py3-none-any.whl", hash = "sha256:b8cd787332ab77b808bdc5f21b1305b0bd749c07f4bf1a97dba97302383850da", size = 1044677, upload-time = "2026-03-29T00:59:21.649Z" },
{ url = "https://files.pythonhosted.org/packages/69/36/994783fc52dfaef0123d740cefcbbc2cdd201389b5ae60e3d33110e34042/prefab_ui-0.17.0-py3-none-any.whl", hash = "sha256:9f6a5d8b659acceb2b9da7e3cf1df8a207e5ab87df4f13cd580915443c6816cc", size = 1823254, upload-time = "2026-03-29T22:35:42.953Z" },
]
[[package]]
@ -2235,11 +2235,11 @@ wheels = [
[[package]]
name = "pygments"
version = "2.19.2"
version = "2.20.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
]
[[package]]
@ -2484,11 +2484,11 @@ wheels = [
[[package]]
name = "python-json-logger"
version = "4.0.0"
version = "4.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f7/ff/3cc9165fd44106973cd7ac9facb674a65ed853494592541d339bdc9a30eb/python_json_logger-4.1.0.tar.gz", hash = "sha256:b396b9e3ed782b09ff9d6e4f1683d46c83ad0d35d2e407c09a9ebbf038f88195", size = 17573, upload-time = "2026-03-29T04:39:56.805Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" },
{ url = "https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl", hash = "sha256:132994765cf75bf44554be9aa49b06ef2345d23661a96720262716438141b6b2", size = 15021, upload-time = "2026-03-29T04:39:55.266Z" },
]
[[package]]
@ -2851,15 +2851,15 @@ wheels = [
[[package]]
name = "sse-starlette"
version = "3.3.3"
version = "3.3.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "starlette" },
]
sdist = { url = "https://files.pythonhosted.org/packages/14/2f/9223c24f568bb7a0c03d751e609844dce0968f13b39a3f73fbb3a96cd27a/sse_starlette-3.3.3.tar.gz", hash = "sha256:72a95d7575fd5129bd0ae15275ac6432bb35ac542fdebb82889c24bb9f3f4049", size = 32420, upload-time = "2026-03-17T20:05:55.529Z" }
sdist = { url = "https://files.pythonhosted.org/packages/26/8c/f9290339ef6d79badbc010f067cd769d6601ec11a57d78569c683fb4dd87/sse_starlette-3.3.4.tar.gz", hash = "sha256:aaf92fc067af8a5427192895ac028e947b484ac01edbc3caf00e7e7137c7bef1", size = 32427, upload-time = "2026-03-29T09:00:23.307Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/e2/b8cff57a67dddf9a464d7e943218e031617fb3ddc133aeeb0602ff5f6c85/sse_starlette-3.3.3-py3-none-any.whl", hash = "sha256:c5abb5082a1cc1c6294d89c5290c46b5f67808cfdb612b7ec27e8ba061c22e8d", size = 14329, upload-time = "2026-03-17T20:05:54.35Z" },
{ url = "https://files.pythonhosted.org/packages/f8/7f/3de5402f39890ac5660b86bcf5c03f9d855dad5c4ed764866d7b592b46fd/sse_starlette-3.3.4-py3-none-any.whl", hash = "sha256:84bb06e58939a8b38d8341f1bc9792f06c2b53f48c608dd207582b664fc8f3c1", size = 14330, upload-time = "2026-03-29T09:00:21.846Z" },
]
[[package]]