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
15 changes: 14 additions & 1 deletion ui/index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
<!doctype html>
<html lang="en" class="dark">
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Faze</title>
<script>
(function () {
try {
var t = localStorage.getItem("faze-theme");
if (t !== "light" && t !== "dark") {
t = window.matchMedia("(prefers-color-scheme: light)").matches
? "light"
: "dark";
}
if (t === "dark") document.documentElement.classList.add("dark");
} catch (e) {}
})();
</script>
<link href="/src/index.css" rel="stylesheet" />
</head>
<body>
Expand Down
Binary file added ui/public/fonts/GeistMono-Variable.woff2
Binary file not shown.
12 changes: 11 additions & 1 deletion ui/src/components/layout/app-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,22 @@ interface AppShellProps {
export function AppShell({ children }: AppShellProps) {
return (
<div className="flex h-screen w-screen bg-background text-foreground">
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-4 focus:z-50 focus:border focus:border-border focus:bg-card focus:px-3 focus:py-2 focus:font-mono focus:text-sm"
>
Skip to content
</a>
<MainSidebar />

<div className="flex flex-col flex-1 border-l border-border">
<Topbar />

<main className="flex-1 overflow-auto p-4">
<main
id="main-content"
tabIndex={-1}
className="flex-1 overflow-auto p-4 outline-none"
>
{children || <Outlet />}
</main>
</div>
Expand Down
3 changes: 2 additions & 1 deletion ui/src/components/layout/main-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function MainSidebar() {
<span className="font-mono tracking-tight text-sm">Faze</span>
</div>

<nav className="flex flex-col p-2 text-sm flex-1">
<nav aria-label="Main" className="flex flex-col p-2 text-sm flex-1">
<Section title="Overview">
<NavItem to="/" label="Dashboard" />
</Section>
Expand Down Expand Up @@ -57,6 +57,7 @@ function NavItem({ to, label }: { to: string; label: string }) {
className="
px-2 py-1 flex items-center
hover:bg-sidebar-accent hover:text-sidebar-accent-foreground
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-ring focus-visible:ring-inset
transition-colors
"
activeProps={{
Expand Down
6 changes: 3 additions & 3 deletions ui/src/components/layout/theme-toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ export function ThemeToggle() {
return (
<button
onClick={handleToggle}
className="text-xs border border-border px-2 py-1 hover:bg-card transition-colors font-mono"
className="text-xs border border-border px-2 py-1 hover:bg-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-colors font-mono"
title="Toggle theme"
aria-label="Toggle theme"
aria-label={theme === "dark" ? "Switch to light theme" : "Switch to dark theme"}
>
{theme === "dark" ? "☀" : "☾"}
<span aria-hidden="true">{theme === "dark" ? "☀" : "☾"}</span>
</button>
);
}
2 changes: 1 addition & 1 deletion ui/src/components/layout/top-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function Topbar() {
className="
h-12 flex items-center px-4
border-b border-border
bg-background/80 backdrop-blur
bg-background
"
>
<div className="text-sm text-muted-foreground font-mono">
Expand Down
14 changes: 9 additions & 5 deletions ui/src/components/shared/attributes-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function AttributesViewer({
const entries = Object.entries(attributes);

if (entries.length === 0) {
return <div className="text-xs text-foreground/30 py-2">No attributes</div>;
return <div className="text-xs text-muted-foreground py-2">No attributes</div>;
}

const toggleExpand = (key: string) => {
Expand All @@ -79,7 +79,7 @@ export function AttributesViewer({

return (
<div>
<h4 className="text-xs font-mono text-foreground/50 mb-2">{title}</h4>
<h4 className="text-xs font-mono text-muted-foreground mb-2">{title}</h4>
<div className="border border-border">
{entries.map(([key, value], index) => {
const isExpanded = expandedKeys.has(key);
Expand All @@ -99,23 +99,27 @@ export function AttributesViewer({
<span className="text-xs font-mono text-foreground/70 break-all">
{key}
</span>
<span className="text-xs text-foreground/30">
<span className="text-xs text-muted-foreground">
{valueType}
</span>
</div>
<div className="text-xs font-mono text-foreground mt-1">
{expandable && !isExpanded ? (
<button
type="button"
aria-expanded={false}
onClick={() => toggleExpand(key)}
className="text-foreground/50 hover:text-foreground"
className="text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
{collapsedLabel} (click to expand)
</button>
) : expandable && isExpanded ? (
<div>
<button
type="button"
aria-expanded={true}
onClick={() => toggleExpand(key)}
className="text-foreground/50 hover:text-foreground mb-1"
className="text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring mb-1"
>
(click to collapse)
</button>
Expand Down
49 changes: 4 additions & 45 deletions ui/src/components/shared/command-palette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,59 +23,18 @@ export function CommandPalette() {
const navigate = useNavigate();

useEffect(() => {
// cmdk handles arrow-key navigation and Enter internally; we only own the
// Ctrl+K open/close toggle, so this listener registers once and stays stable.
const down = (e: KeyboardEvent) => {
if (e.key === "k" && e.ctrlKey && !e.metaKey) {
e.preventDefault();
setOpen((open) => !open);
}

if (open) {
if (e.key === "h" && !e.metaKey && !e.ctrlKey) {
const input = document.querySelector(
"[cmdk-input]",
) as HTMLInputElement;
if (document.activeElement !== input) {
e.preventDefault();
navigate({ to: "/", search: {} });
setOpen(false);
}
}
if (e.key === "j" && !e.metaKey && !e.ctrlKey) {
const input = document.querySelector(
"[cmdk-input]",
) as HTMLInputElement;
if (document.activeElement !== input) {
e.preventDefault();
const items = document.querySelectorAll("[cmdk-item]");
const current = Array.from(items).findIndex(
(item) => item.getAttribute("data-selected") === "true",
);
if (current < items.length - 1) {
(items[current + 1] as HTMLElement).click();
}
}
}
if (e.key === "k" && !e.metaKey && !e.ctrlKey) {
const input = document.querySelector(
"[cmdk-input]",
) as HTMLInputElement;
if (document.activeElement !== input) {
e.preventDefault();
const items = document.querySelectorAll("[cmdk-item]");
const current = Array.from(items).findIndex(
(item) => item.getAttribute("data-selected") === "true",
);
if (current > 0) {
(items[current - 1] as HTMLElement).click();
}
}
}
setOpen((prev) => !prev);
}
};

document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, [navigate, open]);
}, []);

const handleSelect = (path: string) => {
navigate({ to: path as any, search: {} as any });
Expand Down
10 changes: 7 additions & 3 deletions ui/src/components/shared/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from "react";
import { useToast } from "./toast-provider";
import { Button } from "@/components/ui/button";

interface CopyButtonProps {
text: string;
Expand All @@ -23,11 +24,14 @@ export function CopyButton({ text, label = "Copy" }: CopyButtonProps) {
};

return (
<button
<Button
variant="outline"
size="sm"
onClick={handleCopy}
className="text-xs border border-border px-2 py-1 hover:bg-card transition-colors font-mono"
aria-label={label}
className="font-mono text-xs"
>
{copied ? "✓ Copied" : label}
</button>
</Button>
);
}
22 changes: 13 additions & 9 deletions ui/src/components/shared/error-state.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { Button } from "@/components/ui/button";

interface ErrorStateProps {
title?: string;
message?: string;
onRetry?: () => void;
}

export function ErrorState({ message, onRetry }: ErrorStateProps) {
export function ErrorState({
title = "Something went wrong",
message,
onRetry,
}: ErrorStateProps) {
return (
<div className="flex items-center justify-center h-64">
<div role="alert" className="flex items-center justify-center h-64">
<div className="text-center">
<p className="text-red-500 text-sm mb-2">Error loading data</p>
<p className="text-sev-error text-sm mb-2">{title}</p>
{message && (
<p className="text-foreground/30 text-xs mb-3">{message}</p>
<p className="text-muted-foreground text-xs mb-3">{message}</p>
)}
{onRetry && (
<button
onClick={onRetry}
className="text-xs border border-border px-3 py-1 hover:bg-card transition-colors"
>
<Button variant="outline" size="sm" onClick={onRetry}>
Retry
</button>
</Button>
)}
</div>
</div>
Expand Down
13 changes: 10 additions & 3 deletions ui/src/components/shared/loading-state.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
export function LoadingState() {
return (
<div className="flex items-center justify-center h-64">
<div
role="status"
aria-label="Loading"
className="flex items-center justify-center h-64"
>
<div className="text-center">
<div className="inline-block w-8 h-8 border-2 border-foreground/20 border-t-foreground rounded-full animate-spin mb-2" />
<p className="text-sm text-foreground/50">Loading...</p>
<div
aria-hidden="true"
className="inline-block w-8 h-8 border-2 border-foreground/20 border-t-foreground rounded-full animate-spin mb-2"
/>
<p className="text-sm text-muted-foreground">Loading...</p>
</div>
</div>
);
Expand Down
81 changes: 81 additions & 0 deletions ui/src/components/shared/page-skeletons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Skeleton } from "./skeleton";

function PageHeaderSkeleton() {
return (
<div className="mb-6 space-y-2">
<Skeleton className="h-6 w-32" />
<Skeleton className="h-4 w-44" />
</div>
);
}

/** Header + filter row + table rows. For logs and traces. */
export function ListPageSkeleton({
filters = 3,
rows = 8,
}: {
filters?: number;
rows?: number;
}) {
return (
<div>
<PageHeaderSkeleton />
<div className="flex gap-3 mb-4 flex-wrap">
{Array.from({ length: filters }, (_, i) => (
<Skeleton key={i} className="h-9 w-40" />
))}
</div>
<div className="border border-border">
{Array.from({ length: rows }, (_, i) => (
<div key={i} className="border-b border-border last:border-0 p-3">
<Skeleton className="h-4 w-full" />
</div>
))}
</div>
</div>
);
}

/** Header + optional filter row + card grid. For metrics and services. */
export function GridPageSkeleton({
filters = 1,
cards = 8,
}: {
filters?: number;
cards?: number;
}) {
return (
<div>
<PageHeaderSkeleton />
{filters > 0 && (
<div className="flex gap-3 mb-4">
{Array.from({ length: filters }, (_, i) => (
<Skeleton key={i} className="h-9 w-48" />
))}
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{Array.from({ length: cards }, (_, i) => (
<Skeleton key={i} className="h-32 border border-border" />
))}
</div>
</div>
);
}

/** Back link + title + overview + content block. For trace and service detail. */
export function DetailPageSkeleton() {
return (
<div>
<div className="mb-6">
<Skeleton className="h-4 w-28" />
</div>
<div className="mb-6 space-y-2">
<Skeleton className="h-7 w-64" />
<Skeleton className="h-4 w-48" />
</div>
<Skeleton className="h-40 border border-border mb-6" />
<Skeleton className="h-64 border border-border" />
</div>
);
}
Loading
Loading