[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
Use Cases/Build a Tenant Usage Dashboard

Build a Tenant Usage Dashboard

Build a multi-tenant usage dashboard with per-tenant metrics, resource consumption tracking, quota management, cost allocation, and self-service analytics for SaaS platforms.

#redis#caching#database#pub-sub#queues
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 93/100·All SAFE
>

typescript

v

Not yet scored
View skill
>

redis

v1.0.0

Build applications with Redis — caching, session storage, pub/sub, streams, rate limiting, leaderboards, and queues. Use when tasks involve in-memory data storage, real-time messaging, distributed locking, or performance optimization with caching layers.

93/100 quality
1.81× impact
SAFE
View skill
>

postgresql

v1.0.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

87/100 quality
1.53× impact
SAFE
View skill
>

hono

v1.0.0

You are an expert in Hono, the ultrafast web framework for the edge. You help developers build APIs and web applications that run on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda, and Vercel Edge — with a tiny footprint (~14KB), middleware ecosystem, JSX support, RPC client, and Web Standards API compatibility that makes code truly portable across runtimes.

93/100 quality
3.00× impact
SAFE
View skill
>

zod

v1.0.0

You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.

100/100 quality
1.21× impact
SAFE
View skill
$

The Problem

Olga leads ops at a 25-person multi-tenant SaaS. Each tenant asks "how much am I using?" and the answer requires an engineer running SQL queries. There's no visibility into which tenants are approaching storage limits. Cost allocation per tenant is guesswork — the biggest customer might be subsidized by smaller ones. When a tenant hits a limit, they get a cryptic 500 error instead of a helpful message. They need a tenant usage dashboard: self-service usage metrics, quota tracking with alerts, cost breakdown per tenant, and admin overview of all tenants.

Step 1: Build the Usage Dashboard

typescript
// src/tenants/usage.ts — Multi-tenant usage tracking with quotas and cost allocation
import { pool } from "../db";
import { Redis } from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);

interface TenantUsage {
  tenantId: string;
  metrics: {
    apiCalls: { used: number; limit: number; percentage: number };
    storage: { usedMb: number; limitMb: number; percentage: number };
    users: { active: number; limit: number; percentage: number };
    bandwidth: { usedMb: number; limitMb: number; percentage: number };
  };
  cost: { estimated: number; breakdown: Record<string, number> };
  period: string;
}

interface TenantQuota {
  tenantId: string;
  plan: string;
  limits: Record<string, number>;
  overageAllowed: boolean;
  overageRate: Record<string, number>;
}

// Track usage event
export async function trackUsage(tenantId: string, metric: string, amount: number = 1): Promise<{ allowed: boolean; remaining: number }> {
  const period = getCurrentPeriod();
  const key = `usage:${tenantId}:${metric}:${period}`;

  const current = await redis.incrbyfloat(key, amount);
  await redis.expire(key, 86400 * 35);

  const quota = await getTenantQuota(tenantId);
  const limit = quota.limits[metric] || Infinity;
  const remaining = Math.max(0, limit - current);

  if (current > limit && !quota.overageAllowed) {
    await redis.incrbyfloat(key, -amount);
    return { allowed: false, remaining: 0 };
  }

  // Alert at thresholds
  const percentage = (current / limit) * 100;
  for (const threshold of [80, 90, 95, 100]) {
    if (percentage >= threshold) {
      const alertKey = `usage:alert:${tenantId}:${metric}:${threshold}:${period}`;
      const sent = await redis.set(alertKey, "1", "EX", 86400, "NX");
      if (sent) {
        await redis.rpush("notification:queue", JSON.stringify({
          type: "usage_threshold", tenantId, metric, percentage: threshold, current, limit,
        }));
      }
    }
  }

  return { allowed: true, remaining };
}

// Get tenant usage dashboard
export async function getTenantDashboard(tenantId: string): Promise<TenantUsage> {
  const period = getCurrentPeriod();
  const quota = await getTenantQuota(tenantId);

  const metrics: Record<string, { used: number; limit: number; percentage: number }> = {};
  const costBreakdown: Record<string, number> = {};

  for (const [metric, limit] of Object.entries(quota.limits)) {
    const key = `usage:${tenantId}:${metric}:${period}`;
    const used = parseFloat(await redis.get(key) || "0");
    const percentage = limit > 0 ? Math.round((used / limit) * 100) : 0;
    metrics[metric] = { used, limit, percentage } as any;

    // Cost calculation
    const includedCost = 0;
    const overageCost = used > limit && quota.overageAllowed
      ? (used - limit) * (quota.overageRate[metric] || 0)
      : 0;
    costBreakdown[metric] = Math.round(overageCost * 100) / 100;
  }

  const estimatedCost = Object.values(costBreakdown).reduce((s, c) => s + c, 0);

  return {
    tenantId,
    metrics: metrics as any,
    cost: { estimated: estimatedCost, breakdown: costBreakdown },
    period,
  };
}

// Admin: overview of all tenants
export async function getAllTenantsOverview(): Promise<Array<{
  tenantId: string; plan: string; topMetric: string; topUsagePercentage: number; estimatedCost: number;
}>> {
  const { rows: tenants } = await pool.query("SELECT id, plan FROM tenants WHERE status = 'active'");
  const overviews = [];

  for (const tenant of tenants) {
    const dashboard = await getTenantDashboard(tenant.id);
    const topMetric = Object.entries(dashboard.metrics)
      .sort((a, b) => (b[1] as any).percentage - (a[1] as any).percentage)[0];

    overviews.push({
      tenantId: tenant.id,
      plan: tenant.plan,
      topMetric: topMetric?.[0] || "none",
      topUsagePercentage: (topMetric?.[1] as any)?.percentage || 0,
      estimatedCost: dashboard.cost.estimated,
    });
  }

  return overviews.sort((a, b) => b.topUsagePercentage - a.topUsagePercentage);
}

// Middleware: check quota before processing request
export function quotaMiddleware(metric: string, amount: number = 1) {
  return async (c: any, next: any) => {
    const tenantId = c.get("tenantId") || c.req.header("X-Tenant-ID");
    if (!tenantId) return c.json({ error: "Tenant ID required" }, 400);

    const { allowed, remaining } = await trackUsage(tenantId, metric, amount);
    c.header("X-Usage-Remaining", String(remaining));

    if (!allowed) {
      return c.json({
        error: "Quota exceeded",
        metric,
        message: `You've reached your ${metric} limit. Upgrade your plan for more.`,
      }, 429);
    }

    await next();
  };
}

async function getTenantQuota(tenantId: string): Promise<TenantQuota> {
  const cached = await redis.get(`quota:${tenantId}`);
  if (cached) return JSON.parse(cached);

  const { rows: [tenant] } = await pool.query("SELECT id, plan FROM tenants WHERE id = $1", [tenantId]);
  const planLimits: Record<string, Record<string, number>> = {
    free: { apiCalls: 1000, storage: 100, users: 3, bandwidth: 500 },
    starter: { apiCalls: 50000, storage: 5000, users: 10, bandwidth: 10000 },
    pro: { apiCalls: 500000, storage: 50000, users: 50, bandwidth: 100000 },
    enterprise: { apiCalls: 5000000, storage: 500000, users: 500, bandwidth: 1000000 },
  };

  const quota: TenantQuota = {
    tenantId,
    plan: tenant?.plan || "free",
    limits: planLimits[tenant?.plan || "free"] || planLimits.free,
    overageAllowed: ["pro", "enterprise"].includes(tenant?.plan),
    overageRate: { apiCalls: 0.001, storage: 0.05, bandwidth: 0.02 },
  };

  await redis.setex(`quota:${tenantId}`, 300, JSON.stringify(quota));
  return quota;
}

function getCurrentPeriod(): string {
  return new Date().toISOString().slice(0, 7);
}

Results

  • Self-service usage visibility — tenants see their API calls, storage, users, bandwidth in real-time dashboard; no more engineering support requests for "how much am I using?"
  • Quota enforcement with friendly errors — free tier hits 1000 API calls → clear message "upgrade for more" with remaining count in header; no cryptic 500 errors
  • Cost allocation accurate — enterprise tenant consuming 60% of resources pays proportionally; no more cross-subsidization; pricing aligned with actual usage
  • Threshold alerts — 80% usage → warning email; 95% → urgent Slack; 100% → admin notified; tenants never surprised by hard limits
  • Admin overview — ops team sees all tenants sorted by usage; identifies tenants approaching limits; proactive outreach for upsell; no more reactive firefighting