Files
e0280cb650 docs: restructure observability navigation and overview (#49505)
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Stack

Draft stack extracted from `docs/monitoring`. Merge bottom-up. The
troubleshooting *catalog* rewrite (`content/troubleshooting` and the
Diagnosing UI) stays out of scope.

1. #49503 move inspect and advisors
2. #49501 split Studio logs from ClickHouse queries
3. #49500 treat reports as signal dashboards
4. #49502 add Observe the data hub
5. #49506 add agent setup components
6. #49504 add hire-an-agent templates
7. **#49505** restructure observability nav, overview, Detecting, and
flatten Observe the data ← **this PR**

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Docs update. Top layer in the observability stack.

## What is the current behavior?

The section is still titled Monitoring and Debugging, with a Debugging /
Monitoring split that does not match the new pages. The debugging guide
is still the master layer-isolation + symptom table. Observe the data is
split into “what data” vs “where to observe it,” which duplicates the
source pages.

## What is the new behavior?

- Section title is Observability
- Overview groups Observe the data, Detect and resolve, Hire an agent,
and Export
- **Observe the data is flattened by source.** Logs, Metrics API,
Database, Advisors, and Reports each list where to read that source.
There is no separate MCP/API/CLI/Studio nav group.
- **Observe vs Detecting:** Observe is the catalog (what exists, how to
access it). Detecting is how to *use* those sources to pick up a Health
/ Security / Performance / Usage signal. Named errors skip to
Diagnosing.
- Studio Logs sits under Logs. Reports sits beside the other sources.
- Troubleshooting stays in the global menu and also appears as
Diagnosing under Detect and resolve

## Additional context

This is the last PR in the stack. Together the seven PRs reconstruct the
`docs/monitoring` observability IA and guide content, without shipping
the troubleshooting catalog overhaul.
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-a3cb5ece-925b-4046-b58a-5d69e9a9d794?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-a3cb5ece-925b-4046-b58a-5d69e9a9d794&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Saxon Fletcher <SaxonF@users.noreply.github.com>
Co-authored-by: Nik Richers <nik@validmind.ai>
2026-09-04 13:38:39 +10:00

174 lines
5.5 KiB
TypeScript

import { ChevronRight, ChevronUp } from 'lucide-react'
import { useMemo, useState } from 'react'
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger } from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { type ExposedEntity } from './DataApiEnableSwitch.utils'
interface UnsafeEntitiesConfirmModalProps {
visible: boolean
loading: boolean
unsafeEntities: Array<ExposedEntity>
onCancel: () => void
onConfirm: () => void
}
const ENTITY_TYPE_META: Record<
ExposedEntity['type'],
{ heading: string; recommendation: string; docsUrl: string }
> = {
table: {
heading: 'Tables without Row Level Security',
recommendation: 'Enable RLS on these tables to control access per-row.',
docsUrl:
'https://supabase.com/docs/guides/observability/advisors?lint=0013_rls_disabled_in_public',
},
'foreign table': {
heading: 'Foreign tables',
recommendation:
'Foreign tables do not support RLS. Revoke access from the anon and authenticated roles.',
docsUrl:
'https://supabase.com/docs/guides/observability/advisors?lint=0017_foreign_table_in_api',
},
'materialized view': {
heading: 'Materialized views',
recommendation:
'Materialized views do not support RLS. Revoke access from the anon and authenticated roles.',
docsUrl:
'https://supabase.com/docs/guides/observability/advisors?lint=0016_materialized_view_in_api',
},
view: {
heading: 'Views without SECURITY INVOKER',
recommendation:
'These views run with the permissions of the view creator, not the querying user. Set SECURITY INVOKER to enforce caller permissions.',
docsUrl:
'https://supabase.com/docs/guides/observability/advisors?lint=0010_security_definer_view',
},
}
const ENTITY_TYPE_ORDER: Array<ExposedEntity['type']> = [
'table',
'foreign table',
'materialized view',
'view',
]
const COLLAPSE_THRESHOLD = 3
export const UnsafeEntitiesConfirmModal = ({
visible,
loading,
unsafeEntities,
onCancel,
onConfirm,
}: UnsafeEntitiesConfirmModalProps) => {
const groupedEntities = useMemo(() => {
const groups = new Map<ExposedEntity['type'], Array<ExposedEntity>>()
for (const entity of unsafeEntities) {
const group = groups.get(entity.type)
if (group) {
group.push(entity)
} else {
groups.set(entity.type, [entity])
}
}
return ENTITY_TYPE_ORDER.filter((type) => groups.has(type)).map((type) => ({
type,
...ENTITY_TYPE_META[type],
entities: groups.get(type) ?? [],
}))
}, [unsafeEntities])
return (
<ConfirmationModal
variant="warning"
visible={visible}
loading={loading}
title="Insecure objects detected"
confirmLabel="Enable Data API"
confirmLabelLoading="Enabling"
onCancel={onCancel}
onConfirm={onConfirm}
className="max-h-[50vh] overflow-y-auto"
>
<div className="text-sm text-foreground-light space-y-4">
<p>
The following objects will be publicly accessible through the Data API and are insecure.
</p>
{groupedEntities.map(({ type, heading, recommendation, docsUrl, entities }) => (
<div key={type} className="space-y-1">
<h4 className="text-foreground font-medium">{heading}</h4>
<EntityList entities={entities} />
<p className="text-foreground-lighter text-xs">
{recommendation}{' '}
<a
href={docsUrl}
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
Learn more
</a>
</p>
</div>
))}
</div>
</ConfirmationModal>
)
}
const EntityListItem = ({ entity }: { entity: ExposedEntity }) => (
<li>
<code className="text-xs">
{entity.schema}.{entity.name}
</code>
</li>
)
const EntityList = ({ entities }: { entities: Array<ExposedEntity> }) => {
const [open, setOpen] = useState(false)
const shouldCollapse = entities.length > COLLAPSE_THRESHOLD
const visibleEntities = entities.slice(0, COLLAPSE_THRESHOLD)
const hiddenEntities = entities.slice(COLLAPSE_THRESHOLD)
if (!shouldCollapse) {
return (
<ul className="list-disc pl-5 space-y-0.5">
{entities.map((entity) => (
<EntityListItem key={`${entity.schema}.${entity.name}`} entity={entity} />
))}
</ul>
)
}
return (
<Collapsible open={open} onOpenChange={setOpen}>
<ul className="list-disc pl-5 space-y-0.5">
{visibleEntities.map((entity) => (
<EntityListItem key={`${entity.schema}.${entity.name}`} entity={entity} />
))}
</ul>
<CollapsibleContent className="transition-all data-closed:animate-collapsible-up data-open:animate-collapsible-down">
<ul className="list-disc pl-5 space-y-0.5">
{hiddenEntities.map((entity) => (
<EntityListItem key={`${entity.schema}.${entity.name}`} entity={entity} />
))}
</ul>
</CollapsibleContent>
<CollapsibleTrigger asChild>
<Button
variant="text"
size="tiny"
className="px-0 h-auto text-xs text-foreground-lighter hover:text-foreground"
>
<div className="flex items-center gap-1">
{open ? <ChevronUp size={12} /> : <ChevronRight size={12} />}
<span>{open ? 'Show less' : `Show ${hiddenEntities.length} more`}</span>
</div>
</Button>
</CollapsibleTrigger>
</Collapsible>
)
}