npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@slopit/dashboard-ui

v0.1.0

Published

React dashboard UI for slopit behavioral analytics

Readme

@slopit/dashboard-ui

React components for the slopit behavioral analytics dashboard. Displays session data, analysis verdicts, and flags from the Python backend. Can be used as React components in a React application or as a standalone Web Component in any HTML page.

Installation

pnpm add @slopit/dashboard-ui

For React applications, ensure you have React 18 or 19 installed:

pnpm add react react-dom

Quick Start

React Application

import { Dashboard } from "@slopit/dashboard-ui";
import "@slopit/dashboard-ui/style.css";

function App() {
  return (
    <Dashboard
      config={{
        apiUrl: "http://localhost:8000/api/v1",
        enableWebSocket: true,
        theme: "system",
      }}
    />
  );
}

Web Component (Standalone HTML)

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="https://unpkg.com/@slopit/dashboard-ui/dist/slopit-dashboard.css" />
    <script type="module" src="https://unpkg.com/@slopit/dashboard-ui/dist/slopit-dashboard.js"></script>
  </head>
  <body>
    <slopit-dashboard
      api-url="http://localhost:8000/api/v1"
      theme="dark"
      enable-websocket="true">
    </slopit-dashboard>
  </body>
</html>

Features

  • Session list with filtering and pagination
  • Detailed session view with trial timeline
  • Verdict badges (clean, suspicious, flagged)
  • Flag list with severity indicators
  • Analytics charts (flag distribution, confidence histogram)
  • Real-time updates via WebSocket
  • Light and dark theme support
  • Web Component wrapper for non-React applications

React Components

Dashboard

The main dashboard component that combines all subcomponents. Provides navigation between session list and analytics views.

import { Dashboard } from "@slopit/dashboard-ui";

<Dashboard
  config={{
    apiUrl: "http://localhost:8000/api/v1",
    studyId: "my-study",
    theme: "dark",
    pollingInterval: 30000,
    enableWebSocket: true,
  }}
/>;

DashboardConfig

| Property | Type | Default | Description | |----------|------|---------|-------------| | apiUrl | string | required | Base URL for the dashboard API | | studyId | string | undefined | Optional study ID to display | | theme | "light" \| "dark" \| "system" | "system" | Color theme | | pollingInterval | number | undefined | Polling interval in milliseconds | | enableWebSocket | boolean | true | Enable real-time WebSocket updates |

SessionList

Table displaying sessions with verdict badges, pagination, and filtering.

import { SessionList, createApiClient } from "@slopit/dashboard-ui";

const client = createApiClient("http://localhost:8000/api/v1");

<SessionList
  apiClient={client}
  onSessionSelect={(sessionId) => console.log("Selected:", sessionId)}
  filters={{ verdict: "suspicious" }}
  pageSize={20}
/>;

SessionListProps

| Property | Type | Default | Description | |----------|------|---------|-------------| | apiClient | DashboardApiClient | required | API client instance | | onSessionSelect | (sessionId: string) => void | undefined | Callback when a session row is clicked | | filters | SessionFilters | undefined | Filter options for the list | | pageSize | number | 20 | Number of sessions per page |

SessionDetail

Detailed view of a single session with tabs for trials, flags, and raw data.

import { SessionDetail, createApiClient } from "@slopit/dashboard-ui";

const client = createApiClient("http://localhost:8000/api/v1");

<SessionDetail
  apiClient={client}
  sessionId="abc123"
  onBack={() => navigate("/sessions")}
/>;

SessionDetailProps

| Property | Type | Default | Description | |----------|------|---------|-------------| | apiClient | DashboardApiClient | required | API client instance | | sessionId | string | required | ID of the session to display | | onBack | () => void | undefined | Callback for back button |

TrialTimeline

Timeline view of trials with expandable behavioral data sections.

import { TrialTimeline } from "@slopit/dashboard-ui";

<TrialTimeline
  trials={session.trials}
  onTrialSelect={(index) => console.log("Selected trial:", index)}
/>;

TrialTimelineProps

| Property | Type | Default | Description | |----------|------|---------|-------------| | trials | SlopitTrial[] | required | Array of trial objects | | onTrialSelect | (trialIndex: number) => void | undefined | Callback when a trial is expanded |

VerdictBadge

Badge displaying the verdict status with optional confidence score.

import { VerdictBadge } from "@slopit/dashboard-ui";

<VerdictBadge verdict="suspicious" confidence={0.75} showConfidence />;

VerdictBadgeProps

| Property | Type | Default | Description | |----------|------|---------|-------------| | verdict | "clean" \| "suspicious" \| "flagged" | required | Verdict type to display | | confidence | number | undefined | Confidence score (0 to 1) | | showConfidence | boolean | false | Show confidence percentage | | className | string | "" | Additional CSS class |

FlagList

List of analysis flags with severity indicators.

import { FlagList } from "@slopit/dashboard-ui";

<FlagList flags={verdict.flags} groupByAnalyzer />;

FlagListProps

| Property | Type | Default | Description | |----------|------|---------|-------------| | flags | VerdictFlag[] | required | Array of flag objects | | groupByAnalyzer | boolean | false | Group flags by their analyzer |

FlagDistribution

Horizontal bar chart showing the distribution of flag types.

import { FlagDistribution } from "@slopit/dashboard-ui";

<FlagDistribution
  distribution={{
    paste_detected: 15,
    excessive_blur: 8,
    timing_anomaly: 3,
  }}
  title="Flag Distribution"
/>;

FlagDistributionProps

| Property | Type | Default | Description | |----------|------|---------|-------------| | distribution | Record<string, number> | required | Map of flag type to count | | title | string | undefined | Optional chart title |

ConfidenceHistogram

Histogram showing the distribution of confidence scores.

import { ConfidenceHistogram } from "@slopit/dashboard-ui";

<ConfidenceHistogram
  scores={[0.2, 0.4, 0.6, 0.8, 0.95]}
  bins={10}
  title="Confidence Distribution"
/>;

ConfidenceHistogramProps

| Property | Type | Default | Description | |----------|------|---------|-------------| | scores | number[] | required | Array of confidence scores (0 to 1) | | bins | number | 10 | Number of histogram bins | | title | string | undefined | Optional chart title |

ConnectionStatus

Visual indicator for WebSocket connection status.

import { ConnectionStatus, useWebSocket } from "@slopit/dashboard-ui";

const { connectionState } = useWebSocket({ url: "ws://localhost:8000/ws" });

<ConnectionStatus state={connectionState} showLabel />;

ConnectionStatusProps

| Property | Type | Default | Description | |----------|------|---------|-------------| | state | "connecting" \| "connected" \| "disconnected" \| "error" | required | Connection state | | showLabel | boolean | true | Show text label next to indicator |

AnalysisSummary

Summary cards showing overall analysis statistics.

import { AnalysisSummary } from "@slopit/dashboard-ui";

<AnalysisSummary
  summary={{
    totalSessions: 150,
    analyzedSessions: 142,
    verdictDistribution: { clean: 100, suspicious: 30, flagged: 12 },
    flagDistribution: { paste_detected: 25, timing_anomaly: 18 },
    averageConfidence: 0.78,
  }}
/>;

AnalysisSummaryProps

| Property | Type | Default | Description | |----------|------|---------|-------------| | summary | AnalysisSummary | required | Analysis summary data object |

Web Component

The <slopit-dashboard> custom element wraps the React Dashboard component for use in non-React applications. It uses Shadow DOM for style isolation.

Attributes

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | api-url | string | "http://localhost:8000/api/v1" | Base URL for the dashboard API | | study-id | string | undefined | Optional study ID to display | | theme | "light" \| "dark" \| "system" | "system" | Color theme | | polling-interval | number | undefined | Polling interval in milliseconds | | enable-websocket | "true" \| "false" | "true" | Enable real-time WebSocket updates |

Example

<slopit-dashboard
  api-url="http://localhost:8000/api/v1"
  study-id="experiment-1"
  theme="dark"
  polling-interval="30000"
  enable-websocket="true">
</slopit-dashboard>

Dynamic Attribute Updates

Attributes can be updated dynamically and the component will re-render:

const dashboard = document.querySelector("slopit-dashboard");
dashboard.setAttribute("theme", "light");
dashboard.setAttribute("study-id", "new-study");

API Client

The DashboardApiClient class provides methods for interacting with the slopit backend REST API.

Creating a Client

import { createApiClient, DashboardApiClient } from "@slopit/dashboard-ui";

// using factory function
const client = createApiClient("http://localhost:8000/api/v1");

// using constructor
const client = new DashboardApiClient({
  baseUrl: "http://localhost:8000/api/v1",
  headers: { Authorization: "Bearer token" },
});

Session Methods

// list sessions with pagination and filters
const response = await client.listSessions(1, 20, {
  verdict: "suspicious",
  minConfidence: 0.5,
  startDate: "2024-01-01",
});
// returns: PaginatedResponse<SessionSummary>

// get a single session
const session = await client.getSession("session-id");
// returns: SlopitSession

// get trials for a session
const trials = await client.getSessionTrials("session-id");
// returns: SlopitTrial[]

// get verdict for a session
const verdict = await client.getSessionVerdict("session-id");
// returns: SessionVerdict

Analysis Methods

// get summary statistics
const summary = await client.getAnalysisSummary();
// returns: AnalysisSummary

// get flag distribution
const flags = await client.getFlagDistribution();
// returns: Record<string, number>

// get verdict distribution
const verdicts = await client.getVerdictDistribution();
// returns: Record<string, number>

// trigger batch analysis
const task = await client.triggerBatchAnalysis(["session-1", "session-2"]);
// returns: { taskId: string }

Export Methods

// export sessions to CSV or JSON
const blob = await client.exportSessions({
  format: "csv",
  includeTrials: true,
  includeMetrics: true,
  filters: { verdict: "flagged" },
});

// download the blob
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "sessions.csv";
a.click();

JATOS Integration

// connect to JATOS
await client.connectJatos("https://jatos.example.com", "api-token");

// list available studies
const studies = await client.listJatosStudies();
// returns: Array<{ id: string; title: string }>

// sync study results
const task = await client.syncJatos("study-id");
// returns: { taskId: string }

// check connection status
const status = await client.getJatosStatus();
// returns: { connected: boolean; url?: string }

Prolific Integration

// connect to Prolific
await client.connectProlific("api-token");

// list studies
const studies = await client.listProlificStudies();
// returns: Array<{ id: string; name: string; status: string }>

// get participant recommendations
const recommendations = await client.getProlificRecommendations("study-id");
// returns: Array<{ participantId, sessionId, verdict, recommendation }>

WebSocket Hook

The useWebSocket hook manages a WebSocket connection to the backend for real-time updates.

Basic Usage

import { useWebSocket } from "@slopit/dashboard-ui";

function MyComponent() {
  const { connectionState, lastMessage, connect, disconnect } = useWebSocket({
    url: "ws://localhost:8000/ws",
    onSessionNew: (data) => {
      console.log("New session:", data.sessionId);
    },
    onVerdictComputed: (data) => {
      console.log("Verdict computed:", data.verdict, data.confidence);
    },
    onSyncProgress: (data) => {
      console.log(`Sync progress: ${data.progress}/${data.total}`);
    },
  });

  return (
    <div>
      <p>Status: {connectionState}</p>
      <button onClick={disconnect}>Disconnect</button>
    </div>
  );
}

UseWebSocketOptions

| Property | Type | Default | Description | |----------|------|---------|-------------| | url | string | required | WebSocket server URL | | reconnect | boolean | true | Automatically reconnect on disconnect | | reconnectInterval | number | 3000 | Milliseconds between reconnect attempts | | maxReconnectAttempts | number | 5 | Maximum reconnection attempts | | onSessionNew | (data: SessionNewEventData) => void | undefined | Callback for new session events | | onVerdictComputed | (data: VerdictComputedEventData) => void | undefined | Callback for verdict events | | onSyncProgress | (data: SyncProgressEventData) => void | undefined | Callback for sync progress events | | onError | (error: Event) => void | undefined | Callback for connection errors |

UseWebSocketReturn

| Property | Type | Description | |----------|------|-------------| | connectionState | "connecting" \| "connected" \| "disconnected" \| "error" | Current connection state | | lastMessage | WebSocketMessage \| null | Most recent message received | | connect | () => void | Manually initiate connection | | disconnect | () => void | Close connection and stop reconnection |

Types

VerdictType

type VerdictType = "clean" | "suspicious" | "flagged";

SessionVerdict

interface SessionVerdict {
  sessionId: string;
  verdict: VerdictType;
  confidence: number;
  flags: VerdictFlag[];
  analyzedAt: string;
}

VerdictFlag

interface VerdictFlag {
  type: string;
  severity: "low" | "medium" | "high";
  message: string;
  analyzer: string;
  trialIndex?: number;
}

SessionSummary

interface SessionSummary {
  sessionId: string;
  participantId?: string;
  startTime: string;
  endTime?: string;
  trialCount: number;
  verdict?: VerdictType;
  confidence?: number;
  flagCount: number;
}

SessionFilters

interface SessionFilters {
  verdict?: VerdictType;
  minConfidence?: number;
  maxConfidence?: number;
  startDate?: string;
  endDate?: string;
  participantId?: string;
}

AnalysisSummary

interface AnalysisSummary {
  totalSessions: number;
  analyzedSessions: number;
  verdictDistribution: Record<VerdictType, number>;
  flagDistribution: Record<string, number>;
  averageConfidence: number;
}

PaginatedResponse

interface PaginatedResponse<T> {
  items: T[];
  total: number;
  page: number;
  pageSize: number;
  hasMore: boolean;
}

Theming

The dashboard supports light and dark themes via CSS custom properties.

Theme Selection

Set the theme via the theme config option:

// react
<Dashboard config={{ apiUrl: "...", theme: "dark" }} />

// web component
<slopit-dashboard api-url="..." theme="dark"></slopit-dashboard>

Available values:

  • "light": Light theme
  • "dark": Dark theme
  • "system": Follow system preference (default)

CSS Custom Properties

Override theme variables for custom styling:

:root {
  /* background colors */
  --slopit-bg: #ffffff;
  --slopit-bg-secondary: #f8f9fa;
  --slopit-bg-tertiary: #e9ecef;

  /* text colors */
  --slopit-text: #212529;
  --slopit-text-secondary: #6c757d;

  /* border */
  --slopit-border: #dee2e6;

  /* verdict colors */
  --slopit-clean: #28a745;
  --slopit-clean-bg: #d4edda;
  --slopit-suspicious: #ffc107;
  --slopit-suspicious-bg: #fff3cd;
  --slopit-flagged: #dc3545;
  --slopit-flagged-bg: #f8d7da;

  /* interactive */
  --slopit-primary: #0d6efd;
  --slopit-primary-hover: #0b5ed7;

  /* spacing */
  --slopit-space-xs: 0.25rem;
  --slopit-space-sm: 0.5rem;
  --slopit-space-md: 1rem;
  --slopit-space-lg: 1.5rem;

  /* border radius */
  --slopit-radius-sm: 0.25rem;
  --slopit-radius-md: 0.375rem;
  --slopit-radius-lg: 0.5rem;
}

Integration with Python Backend

The dashboard is designed to work with the slopit Python FastAPI backend.

Backend Setup

from slopit.dashboard import create_dashboard_app

app = create_dashboard_app(
    database_url="sqlite:///./slopit.db",
    cors_origins=["http://localhost:3000"],
)

# run with: uvicorn main:app --reload

Serving the Web Component

The Python package includes the built Web Component assets. Serve them from your FastAPI app:

from fastapi.staticfiles import StaticFiles
from slopit.dashboard import get_static_path

app.mount("/static", StaticFiles(directory=get_static_path()), name="static")

Then include in your HTML:

<link rel="stylesheet" href="/static/slopit-dashboard.css" />
<script type="module" src="/static/slopit-dashboard.js"></script>

<slopit-dashboard api-url="/api/v1"></slopit-dashboard>

CORS Configuration

If running the frontend on a different port, configure CORS:

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

WebSocket Endpoint

The backend exposes a WebSocket endpoint at /ws for real-time updates:

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    # send events for new sessions, computed verdicts, sync progress

License

MIT