Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions app/(dashboard)/alerts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ function ChannelConfigForm({ channel, onSave, onTest, onDelete, onClose }: {
<span className="text-lg">{CHANNEL_ICON[channel.type]}</span>
<span className="text-sm font-medium text-[#F5F5F5]">{channel.name}</span>
</div>
<button onClick={onClose} className="text-[#333] hover:text-[#666] text-xs">✕</button>
<button onClick={onClose} className="text-[#808080] hover:text-[#949494] text-xs">✕</button>
</div>

<div>
<label className="text-[10px] text-[#444] uppercase tracking-wider">
<label className="text-[10px] text-[#808080] uppercase tracking-wider">
{channel.type === "pagerduty" ? "Routing key" : "Webhook URL"}
</label>
<input
Expand All @@ -111,7 +111,7 @@ function ChannelConfigForm({ channel, onSave, onTest, onDelete, onClose }: {
)}

{channel.lastDeliveryAt && (
<p className="text-[10px] text-[#333]">
<p className="text-[10px] text-[#808080]">
Last delivery: {timeAgo(channel.lastDeliveryAt)} ·{" "}
<span className={channel.lastDeliveryOk ? "text-green" : "text-red"}>
{channel.lastDeliveryOk ? "OK" : "Failed"}
Expand All @@ -129,7 +129,7 @@ function ChannelConfigForm({ channel, onSave, onTest, onDelete, onClose }: {
</Button>
<button
onClick={() => onDelete(channel.id)}
className="ml-auto text-[10px] text-[#555] hover:text-red transition-colors"
className="ml-auto text-[10px] text-[#949494] hover:text-red transition-colors"
>
Delete
</button>
Expand Down Expand Up @@ -158,7 +158,7 @@ function AddChannelForm({ onCreate, onClose }: {
<div className="rounded-lg border border-dashed border-[#2A2A2A] bg-[#0D0D0D] p-4 space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-[#888] uppercase tracking-wider">New channel</span>
<button onClick={onClose} className="text-[#333] hover:text-[#666] text-xs">✕</button>
<button onClick={onClose} className="text-[#808080] hover:text-[#949494] text-xs">✕</button>
</div>
<div className="flex flex-wrap items-center gap-2">
<select
Expand Down Expand Up @@ -294,7 +294,7 @@ export default function AlertsPage() {
{ label: "Triggered Today", value: triggeredToday, color: "#F59E0B" },
].map((stat) => (
<div key={stat.label} className="rounded-lg border border-[#1E1E1E] bg-[#111] p-4">
<p className="text-[10px] text-[#444] uppercase tracking-wider mb-1">{stat.label}</p>
<p className="text-[10px] text-[#808080] uppercase tracking-wider mb-1">{stat.label}</p>
<p className="text-2xl font-semibold tabular-nums" style={{ color: stat.color }}>
{stat.value}
</p>
Expand All @@ -309,7 +309,7 @@ export default function AlertsPage() {
key={tab}
onClick={() => setActiveTab(tab)}
className={`px-3 py-1.5 text-xs rounded capitalize font-medium transition-colors ${
activeTab === tab ? "bg-blue text-white" : "text-[#555] hover:text-[#888]"
activeTab === tab ? "bg-blue text-white" : "text-[#949494] hover:text-[#888]"
}`}
>
{tab}
Expand All @@ -324,11 +324,11 @@ export default function AlertsPage() {

{/* Enhanced rule builder */}
<div className="rounded-lg border border-dashed border-[#2A2A2A] bg-[#0D0D0D] p-4 space-y-3">
<p className="text-xs font-medium text-[#444] uppercase tracking-wider">Rule Builder</p>
<p className="text-xs font-medium text-[#808080] uppercase tracking-wider">Rule Builder</p>

{/* Main condition row */}
<div className="flex items-center gap-2 flex-wrap">
<span className="text-xs text-[#555]">Alert when</span>
<span className="text-xs text-[#949494]">Alert when</span>
<select
value={metric}
onChange={(e) => setMetric(e.target.value)}
Expand All @@ -354,19 +354,19 @@ export default function AlertsPage() {
onChange={(e) => setThreshold(e.target.value)}
className="bg-[#111] border border-[#2A2A2A] rounded px-2 py-1 text-xs text-[#F5F5F5] outline-none w-16 focus:border-blue transition-colors"
/>
<span className="text-xs text-[#555]">{metric === "Error Rate" ? "%" : "ms"} for</span>
<span className="text-xs text-[#949494]">{metric === "Error Rate" ? "%" : "ms"} for</span>
<input
type="number"
value={window}
onChange={(e) => setWindow(e.target.value)}
className="bg-[#111] border border-[#2A2A2A] rounded px-2 py-1 text-xs text-[#F5F5F5] outline-none w-14 focus:border-blue transition-colors"
/>
<span className="text-xs text-[#555]">min</span>
<span className="text-xs text-[#949494]">min</span>
</div>

{/* Severity + channel row */}
<div className="flex items-center gap-3 flex-wrap">
<span className="text-xs text-[#555]">Severity</span>
<span className="text-xs text-[#949494]">Severity</span>
<div className="flex items-center gap-1">
{SEVERITIES.map((s) => (
<button
Expand All @@ -376,7 +376,7 @@ export default function AlertsPage() {
"px-2.5 py-1 rounded text-[10px] font-semibold border transition-colors",
severity === s.value
? "border-current"
: "border-[#1E1E1E] text-[#555] hover:text-[#888]"
: "border-[#1E1E1E] text-[#949494] hover:text-[#888]"
)}
style={severity === s.value ? { color: s.color, borderColor: s.color, backgroundColor: `${s.color}18` } : {}}
>
Expand All @@ -385,7 +385,7 @@ export default function AlertsPage() {
))}
</div>

<span className="text-xs text-[#555]">Send to</span>
<span className="text-xs text-[#949494]">Send to</span>
<select
value={channelName}
onChange={(e) => setChannelName(e.target.value)}
Expand All @@ -406,7 +406,7 @@ export default function AlertsPage() {
</div>

{/* Preview */}
<p className="text-[10px] text-[#333] font-mono bg-[#0A0A0A] rounded px-3 py-2 border border-[#161616]">
<p className="text-[10px] text-[#808080] font-mono bg-[#0A0A0A] rounded px-3 py-2 border border-[#161616]">
{preview}
</p>
</div>
Expand All @@ -431,7 +431,7 @@ export default function AlertsPage() {
{connected ? "connected" : "not configured"}
</Badge>
{ch.lastDeliveryAt && (
<span className="text-[10px] text-[#333]">
<span className="text-[10px] text-[#808080]">
{timeAgo(ch.lastDeliveryAt)} · {ch.lastDeliveryOk ? "OK" : "failed"}
</span>
)}
Expand Down Expand Up @@ -459,7 +459,7 @@ export default function AlertsPage() {
);
})}
{channels.length === 0 && !addingChannel && (
<p className="col-span-2 text-xs text-[#444] text-center py-6">
<p className="col-span-2 text-xs text-[#808080] text-center py-6">
No channels yet. Add one to receive alert notifications.
</p>
)}
Expand All @@ -469,7 +469,7 @@ export default function AlertsPage() {
) : (
<button
onClick={() => setAddingChannel(true)}
className="rounded-lg border border-dashed border-[#2A2A2A] bg-transparent p-4 flex items-center justify-center gap-2 text-[#444] hover:text-[#888] hover:border-[#333] transition-colors w-full"
className="rounded-lg border border-dashed border-[#2A2A2A] bg-transparent p-4 flex items-center justify-center gap-2 text-[#808080] hover:text-[#888] hover:border-[#333] transition-colors w-full"
>
<Plus className="h-4 w-4" />
<span className="text-sm">Add Channel</span>
Expand All @@ -482,14 +482,14 @@ export default function AlertsPage() {
{activeTab === "history" && (
<div className="rounded-lg border border-[#1E1E1E] bg-[#111] divide-y divide-[#161616]">
{historyEntries.length === 0 && (
<p className="text-xs text-[#444] px-4 py-6 text-center">No alert history yet.</p>
<p className="text-xs text-[#808080] px-4 py-6 text-center">No alert history yet.</p>
)}
{historyEntries.map((h) => (
<div key={h.id} className="flex items-center gap-4 px-4 py-3 hover:bg-[#151515] transition-colors cursor-pointer">
<div className={`h-2 w-2 rounded-full flex-shrink-0 ${h.resolved ? "bg-green" : "bg-red animate-pulse"}`} />
<div className="flex-1">
<p className="text-xs font-medium text-[#F5F5F5]">{h.ruleName}</p>
<p className="text-[10px] text-[#444] mt-0.5">
<p className="text-[10px] text-[#808080] mt-0.5">
{timeAgo(h.firedAt)} · {h.duration} · via {h.channel}
</p>
</div>
Expand Down
22 changes: 11 additions & 11 deletions app/(dashboard)/endpoints/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export default function EndpointsPage() {
<Badge variant="red" dot>{summary.critical} Critical</Badge>
</div>
<div className="ml-auto flex items-center gap-2">
<span className="text-xs text-[#444]">{filtered.length} endpoints</span>
<span className="text-xs text-[#808080]">{filtered.length} endpoints</span>
<Button
variant={compareMode ? "primary" : "ghost"}
size="sm"
Expand All @@ -126,13 +126,13 @@ export default function EndpointsPage() {

{/* Compare mode hint */}
{compareMode && !showingComparison && (
<div className="flex items-center gap-2 text-xs text-[#555] bg-blue-dim border border-blue/20 rounded px-3 py-2">
<div className="flex items-center gap-2 text-xs text-[#949494] bg-blue-dim border border-blue/20 rounded px-3 py-2">
<GitCompare className="h-3 w-3 text-blue" />
{compareSelection.length === 0
? "Select 2 endpoints to compare"
: `${compareSelection.length}/2 selected — pick one more`}
{compareSelection.length > 0 && (
<button onClick={() => setCompareSelection([])} className="ml-auto text-[#444] hover:text-[#888]">
<button onClick={() => setCompareSelection([])} className="ml-auto text-[#808080] hover:text-[#888]">
<X className="h-3 w-3" />
</button>
)}
Expand All @@ -151,13 +151,13 @@ export default function EndpointsPage() {
<div className="flex items-center gap-3 flex-wrap">
{/* Route search */}
<div className="flex items-center gap-1.5 px-2.5 py-1.5 rounded border border-[#1E1E1E] bg-[#111] flex-1 max-w-xs hover:border-[#2A2A2A] transition-colors">
<Search className="h-3 w-3 text-[#444] flex-shrink-0" />
<Search className="h-3 w-3 text-[#808080] flex-shrink-0" />
<input
type="text"
placeholder="Filter by route..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="bg-transparent text-xs text-[#F5F5F5] placeholder-[#333] outline-none flex-1"
className="bg-transparent text-xs text-[#F5F5F5] placeholder-[#808080] outline-none flex-1"
/>
</div>

Expand All @@ -168,7 +168,7 @@ export default function EndpointsPage() {
key={m}
onClick={() => setMethodFilter(m)}
className={`px-2 py-1 text-[10px] font-bold rounded transition-colors ${
methodFilter === m ? "bg-blue text-white" : "text-[#555] hover:text-[#888]"
methodFilter === m ? "bg-blue text-white" : "text-[#949494] hover:text-[#888]"
}`}
>
{m}
Expand All @@ -189,7 +189,7 @@ export default function EndpointsPage() {
: label === "4xx"
? "bg-yellow/20 text-yellow"
: "bg-red/20 text-red"
: "text-[#555] hover:text-[#888]"
: "text-[#949494] hover:text-[#888]"
}`}
>
{label}
Expand All @@ -199,17 +199,17 @@ export default function EndpointsPage() {

{/* Latency threshold */}
<div className="flex items-center gap-1.5 px-2 py-1.5 rounded border border-[#1E1E1E] bg-[#111]">
<span className="text-[10px] text-[#444]">p99 &lt;</span>
<span className="text-[10px] text-[#808080]">p99 &lt;</span>
<input
type="number"
placeholder="ms"
value={maxLatency}
onChange={(e) => setMaxLatency(e.target.value)}
className="bg-transparent text-xs text-[#F5F5F5] placeholder-[#333] outline-none w-14 text-right tabular-nums"
className="bg-transparent text-xs text-[#F5F5F5] placeholder-[#808080] outline-none w-14 text-right tabular-nums"
/>
<span className="text-[10px] text-[#333]">ms</span>
<span className="text-[10px] text-[#808080]">ms</span>
{maxLatency && (
<button onClick={() => setMaxLatency("")} className="text-[#333] hover:text-[#666]">
<button onClick={() => setMaxLatency("")} className="text-[#808080] hover:text-[#949494]">
<X className="h-2.5 w-2.5" />
</button>
)}
Expand Down
40 changes: 31 additions & 9 deletions app/(dashboard)/overview/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { TimeRangePicker } from "@/components/layout/time-range-picker";
import { StatCardComponent } from "@/components/dashboard/stat-card";
import { RequestsChart } from "@/components/charts/requests-chart";
import { ErrorRateChart } from "@/components/charts/error-rate-chart";
import { EmptyChartState } from "@/components/charts/empty-chart-state";
import { LiveLogTable } from "@/components/dashboard/live-log-table";
import { IncidentPanel } from "@/components/dashboard/incident-panel";
import { Card, CardHeader, CardTitle } from "@/components/ui/card";
import { useTimeRange } from "@/hooks/use-time-range";
import { useMetrics, useIncidentSocket } from "@/hooks/use-websocket";
import { useMetrics, useIncidentSocket, useLiveChartData } from "@/hooks/use-websocket";
import { useChartData, useIncidents } from "@/hooks/use-data";
import { useProjects } from "@/components/providers/project-provider";

Expand All @@ -18,22 +19,31 @@ const SPARK_COLORS = ["#378ADD", "#EF4444", "#F59E0B", "#22C55E"];
export default function OverviewPage() {
const { range, setRange, hours } = useTimeRange("24h");
const { activeProject } = useProjects();
const isLive = range === "live";

// REST: chart timeseries + incidents (real data; mock while loading / API down)
const { data: chartData } = useChartData(hours);
// REST: chart timeseries + incidents — real data only; empty while
// loading, on error, or genuinely no traffic in range (never fabricated).
// In "live" mode the REST range is irrelevant (not displayed) — pass a
// harmless valid value rather than skipping the hook (Rules of Hooks).
const { data: restChartData } = useChartData(isLive ? 1 : hours);
const { data: incidents } = useIncidents();

// Live stat cards + incidents via Socket.io — project-scoped realtime token
// fetched from the BFF; falls back to demo data while connecting / keyless.
// fetched from the BFF.
const projectId = activeProject?.id ?? null;
const cards = useMetrics(projectId);
const liveIncidents = useIncidentSocket(projectId);
// Rolling ~2 min buffer from the same real-time metric stream as the stat
// cards — only actually connects while "Live" is selected below.
const liveChartData = useLiveChartData(isLive ? projectId : null);

const chartData = isLive ? liveChartData : restChartData;

return (
<div className="flex flex-col min-h-screen">
<Topbar
breadcrumb={[{ label: "Projects" }, { label: activeProject?.name ?? "—" }, { label: "Overview" }]}
actions={<TimeRangePicker value={range} onChange={setRange} />}
actions={<TimeRangePicker value={range} onChange={setRange} showLive />}
/>

<div className="flex-1 flex gap-0">
Expand All @@ -51,9 +61,15 @@ export default function OverviewPage() {
<Card>
<CardHeader>
<CardTitle>Requests Over Time</CardTitle>
<span className="text-[10px] text-[#444] font-mono">req/min</span>
<span className="text-[10px] text-[#808080] font-mono">req/min</span>
</CardHeader>
<RequestsChart data={chartData} />
{chartData.length > 0 ? (
<RequestsChart data={chartData} />
) : (
<EmptyChartState
message={isLive ? "Waiting for live traffic…" : "No traffic in this time range yet."}
/>
)}
</Card>
<Card>
<CardHeader>
Expand All @@ -66,12 +82,18 @@ export default function OverviewPage() {
].map((l) => (
<div key={l.label} className="flex items-center gap-1">
<div className="h-1.5 w-1.5 rounded-full" style={{ backgroundColor: l.color }} />
<span className="text-[10px] text-[#444]">{l.label}</span>
<span className="text-[10px] text-[#808080]">{l.label}</span>
</div>
))}
</div>
</CardHeader>
<ErrorRateChart data={chartData} />
{chartData.length > 0 ? (
<ErrorRateChart data={chartData} />
) : (
<EmptyChartState
message={isLive ? "Waiting for live traffic…" : "No response data in this time range yet."}
/>
)}
</Card>
</div>

Expand Down
Loading
Loading