fix(ui): UI/UX pass — connectivity probe, mobile overflow, real metrics, deep links (#85)

Found during a browser-driven audit of the whole app:

- connectivity banner showed permanently: pingServer() resolved
  undefined, which React Query treats as an error; returns boolean now
- Vite dev proxy now forwards /health so the probe actually measures
  the backend, and the API target is env-overridable (VITE_API_PROXY)
- auth-session removed from the persisted query cache — stale sessions
  no longer rehydrate into a false signed-in flash
- workspace Observability tab no longer shows fabricated random metrics
  ("87K Active User", fake performance/speed-test cards); the dashboard
  now aggregates real per-service metrics (CPU, memory, network, instances)
- service detail tabs write ?section= so tab state is shareable/deep-linked
- Security page auto-selects the first project instead of rendering blank
- People page user-creation form no longer overflows its card
  (minmax(0,1fr) grid tracks; same fix on env-var rows)
- page padding is responsive (px-4 sm:px-8) so mobile keeps content width;
  Projects header wraps instead of clipping the "+ new project" button
- template deploy modal shows a loading state while the session resolves
  instead of flashing the sign-in prompt
- audit log table strips the CIDR suffix from displayed IPs
- docs list excerpts no longer repeat the document title
- activity filter placeholder "Search search…" → "Search events…"
- workspace view tabs get more room: ⌘K button now waits for lg:

Generated with [Devin](https://devin.ai)

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomáš DvořákandDevin authored and GitHub committed 2026-10-10 10:56:33 +02:00
1 parent 0dc5857b5f
commit f88b4f1bf3
18 files changed
+270 -349

No files matched your search

@@ -143,7 +143,7 @@ function StatusBadge({ status }: { status: string }) {
export function ServiceDetailPage() {
const { projectId = '', serviceId = '' } = useParams<{ projectId: string; serviceId: string }>();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const [searchParams, setSearchParams] = useSearchParams();
const queryClient = useQueryClient();
const isDemoMode = useDemoMode();
const sessionQuery = useAuthSession({ enabled: !isDemoMode });
@@ -791,7 +791,7 @@ export function ServiceDetailPage() {
)}
{/* Metrics Overview */}
<div className="w-full px-8 py-6">
<div className="w-full px-4 py-6 sm:px-8">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<EnhancedMetricCard
title="CPU Usage"
@@ -904,7 +904,18 @@ export function ServiceDetailPage() {
return (
<button
key={item.key}
onClick={() => setActiveSection(item.key)}
onClick={() => {
setActiveSection(item.key);
setSearchParams(
(prev) => {
const next = new URLSearchParams(prev);
if (item.key === 'metrics') next.delete('section');
else next.set('section', item.key);
return next;
},
{ replace: true },
);
}}
className={`tab ${active ? 'active' : ''}`}
>
<Icon size={14} />
@@ -1189,7 +1200,7 @@ export function ServiceDetailPage() {
{' '}
<span className="text-[var(--accent-primary)]">{entry.stream}</span>
{' '}
{entry.message}
{entry.message.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z?\s+/, '')}
</p>
))}
</div>
@@ -1362,7 +1373,7 @@ export function ServiceDetailPage() {
</div>
) : (
<div className="space-y-2">
<div className="grid grid-cols-[1fr_1fr_auto_auto] gap-2 px-1">
<div className="grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto_auto] gap-2 px-1">
<span className="text-xs font-medium uppercase tracking-wider text-[var(--text-muted)]">Key</span>
<span className="text-xs font-medium uppercase tracking-wider text-[var(--text-muted)]">Value</span>
<span className="text-xs font-medium uppercase tracking-wider text-[var(--text-muted)]">Secret</span>
@@ -1370,7 +1381,7 @@ export function ServiceDetailPage() {
</div>
{varRows.map((row, i) => (
<div key={i}>
<div className="grid grid-cols-[1fr_1fr_auto_auto] gap-2 items-center">
<div className="grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto_auto] gap-2 items-center">
<input
value={row.key}
onChange={(e) => updateVarRow(i, { key: e.target.value })}