
Data Driven Panel
- 2 installs
- 7.3k repo stars
- Updated July 27, 2026
- hkuds/openspace
Build dashboard panel components that combine UI rendering with resilient data fetching using a base class with circuit breaker, caching, and retry states.
About
Provides a TypeScript pattern for dashboard panels that combine UI construction with resilient data fetching via a circuit breaker and caching. A developer uses it to build self-contained dashboard panels that manage their own data lifecycle.
- Base DataPanel class with integrated data service
- Built-in circuit breaker, caching, loading/error/retry states
Data Driven Panel by the numbers
- 2 all-time installs (skills.sh)
- Ranked #1,858 of 2,244 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hkuds/openspace --skill data-driven-panelAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 7.3k |
| Last updated | July 27, 2026 |
| Repository | hkuds/openspace ↗ |
What it does
Build dashboard panel components that combine UI rendering with resilient data fetching using a base class with circuit breaker, caching, and retry states.
Files
Data-Driven Panel Pattern
Create dashboard panels that seamlessly integrate UI construction with resilient data fetching. Each panel is a self-contained component that manages its own data lifecycle.
Architecture Overview
Panel (base class)
├── element: HTMLElement (outer container)
│ ├── header (title, status indicators)
│ └── content (data display area)
└── DataService (internal)
├── Circuit Breaker (failure handling)
└── Caching layerImplementation
1. Base Panel with Integrated Data Service
Create src/components/DataPanel.ts:
import { createCircuitBreaker } from '../utils/circuit-breaker';
export interface PanelOptions<T> {
id: string;
title: string;
showCount?: boolean;
className?: string;
dataService: () => Promise<T>;
defaultValue: T;
serviceOptions?: {
maxFailures?: number;
cooldownMs?: number;
cacheTtlMs?: number;
};
}
export abstract class DataPanel<T> {
protected element: HTMLElement;
protected content: HTMLElement;
private breaker: CircuitBreaker<T>;
private refreshTimer: ReturnType<typeof setInterval> | null = null;
constructor(protected options: PanelOptions<T>) {
this.breaker = createCircuitBreaker<T>({
name: options.id,
maxFailures: options.serviceOptions?.maxFailures ?? 2,
cooldownMs: options.serviceOptions?.cooldownMs ?? 300_000,
cacheTtlMs: options.serviceOptions?.cacheTtlMs ?? 60_000,
});
this.element = document.createElement('div');
this.element.className = `panel ${options.className || ''}`;
this.element.dataset.panel = options.id;
// Initialize UI
this.initUI();
this.fetchData();
}
private initUI(): void {
// Header setup
const header = document.createElement('div');
header.className = 'panel-header';
const title = document.createElement('span');
title.className = 'panel-title';
title.textContent = this.options.title;
header.appendChild(title);
// Content area
this.content = document.createElement('div');
this.content.className = 'panel-content';
this.element.appendChild(header);
this.element.appendChild(this.content);
this.showLoading();
}
protected async fetchData(): Promise<void> {
try {
const data = await this.breaker.execute(
this.options.dataService,
this.options.defaultValue
);
this.render(data);
} catch (err) {
this.showError('Failed to load data', () => this.fetchData());
}
}
protected abstract render(data: T): void;
public showLoading(message = 'Loading...'): void {
this.content.innerHTML = `<div class="loading">${message}</div>`;
}
public showError(message: string, retryHandler: () => void): void {
this.content.innerHTML = `
<div class="error">
<p>${message}</p>
<button class="retry-btn">Retry</button>
</div>`;
this.content.querySelector('.retry-btn')?.addEventListener('click', retryHandler);
}
public setAutoRefresh(intervalMs: number): void {
this.refreshTimer = setInterval(() => this.fetchData(), intervalMs);
}
public destroy(): void {
if (this.refreshTimer) clearInterval(this.refreshTimer);
this.element.remove();
}
}2. Concrete Panel Implementation Example
import { DataPanel } from './DataPanel';
interface StockData {
quotes: StockQuote[];
updatedAt: string;
}
export class StockPanel extends DataPanel<StockData> {
constructor() {
super({
id: 'stocks',
title: 'Stock Market',
dataService: fetchStockData,
defaultValue: { quotes: [], updatedAt: new Date().toISOString() },
serviceOptions: {
cacheTtlMs: 30_000 // Refresh cache every 30 seconds
}
});
this.setAutoRefresh(60_000); // Auto-refresh every minute
}
protected render(data: StockData): void {
const html = data.quotes.map(quote => `
<div class="stock-item">
<span class="symbol">${quote.symbol}</span>
<span class="price">$${quote.price?.toFixed(2) || '—'}</span>
</div>
`).join('');
this.content.innerHTML = `
<div class="stock-list">
${html}
<div class="updated">Updated: ${new Date(data.updatedAt).toLocaleTimeString()}</div>
</div>`;
}
}3. Data Service Implementation
// src/services/stockService.ts
interface StockQuote {
symbol: string;
price: number | null;
}
export async function fetchStockData(): Promise<StockData> {
const response = await fetch('/api/stocks');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const quotes: StockQuote[] = await response.json();
return {
quotes,
updatedAt: new Date().toISOString()
};
}Key Benefits
1. Unified Pattern: Combines UI and data logic in one coherent pattern 2. Resilient Data Fetching: Built-in circuit breaker and caching 3. Simplified Implementation: Concrete panels only need to implement rendering 4. Consistent Behavior: All panels share the same loading/error states 5. Flexible Configuration: Service options customizable per panel
Best Practices
1. Service Composition: Create small, focused services for each data type 2. Default Values: Always provide meaningful fallback data 3. Error Boundaries: Use the built-in error handling for consistency 4. Memory Management: Clean up timers and event listeners in destroy() 5. Type Safety: Maintain strong typing throughout the data flow
panel-component-merged__v0_1464da16