Admin customer detail: "Open Grafana drilldown" button
Wires the existing customer-drilldown dashboard JSON to the customer
detail page. Button opens ${Grafana.BaseUrl}/d/customer-drilldown
in a new tab with var-customer=<customer-id> pre-filled, kiosk mode,
light theme.
- Fetches /api/grafana/config (cached 5min, reuses the existing
TanStack query key so GrafanaInfoCard's cache is shared).
- Button disabled with tooltip explaining when Grafana baseUrl isn't
configured for the Admin stack (points to Settings → Grafana).
- Customer id is URI-encoded before interpolation (defence in depth —
it's a UUID, but encodeURIComponent costs nothing).
- Dashboard UID hardcoded as 'customer-drilldown' to match the
provisioned JSON. Renaming requires changing both together.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
aaa522058e
commit
59c3f949d0
@ -1,15 +1,20 @@
|
||||
import { Card, Descriptions, Tabs, Table, Tag, Typography, Button, Space, Spin, Result } from 'antd';
|
||||
import { Card, Descriptions, Tabs, Table, Tag, Typography, Button, Space, Spin, Result, Tooltip } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||
import { ArrowLeftOutlined, LineChartOutlined } from '@ant-design/icons';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import {
|
||||
fetchFleetCustomerDetail, type FleetSite, type FleetDevice,
|
||||
type FleetRecentMeasurement, type FleetIngestEvent,
|
||||
} from '../api/fleet';
|
||||
import { fetchGrafanaConfig } from '../api/grafana';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
// UID of the customer-drilldown dashboard provisioned in grafana/dashboards-admin/.
|
||||
// Coordinated change: rename here and in the JSON together.
|
||||
const CUSTOMER_DRILLDOWN_UID = 'customer-drilldown';
|
||||
|
||||
export function AdminCustomerDetailPage() {
|
||||
const { id = '' } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
@ -18,6 +23,17 @@ export function AdminCustomerDetailPage() {
|
||||
queryFn: () => fetchFleetCustomerDetail(id),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
const { data: grafana } = useQuery({
|
||||
queryKey: ['grafana-config'],
|
||||
queryFn: fetchGrafanaConfig,
|
||||
staleTime: 5 * 60_000,
|
||||
});
|
||||
|
||||
const drilldownUrl = (() => {
|
||||
if (!data || !grafana?.baseUrl) return null;
|
||||
const base = grafana.baseUrl.replace(/\/$/, '');
|
||||
return `${base}/d/${encodeURIComponent(CUSTOMER_DRILLDOWN_UID)}?orgId=1&kiosk=tv&theme=light&var-customer=${encodeURIComponent(data.id)}`;
|
||||
})();
|
||||
|
||||
if (isLoading) return <div style={{ textAlign: 'center', padding: 64 }}><Spin size="large" /></div>;
|
||||
if (error || !data) {
|
||||
@ -56,10 +72,28 @@ export function AdminCustomerDetailPage() {
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
<Space>
|
||||
<Button icon={<ArrowLeftOutlined />} onClick={() => navigate('/admin/customers')}>Customers</Button>
|
||||
<Text strong style={{ fontSize: 20 }}>{data.code} · {data.name}</Text>
|
||||
{data.isActive ? <Tag color="green">Active</Tag> : <Tag color="red">Disabled</Tag>}
|
||||
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
<Space>
|
||||
<Button icon={<ArrowLeftOutlined />} onClick={() => navigate('/admin/customers')}>Customers</Button>
|
||||
<Text strong style={{ fontSize: 20 }}>{data.code} · {data.name}</Text>
|
||||
{data.isActive ? <Tag color="green">Active</Tag> : <Tag color="red">Disabled</Tag>}
|
||||
</Space>
|
||||
<Tooltip
|
||||
title={
|
||||
drilldownUrl
|
||||
? `Opens the Grafana customer-drilldown dashboard with var-customer=${data.id}`
|
||||
: 'Grafana base URL is not configured for this Admin stack — see Settings → Grafana.'
|
||||
}
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<LineChartOutlined />}
|
||||
disabled={!drilldownUrl}
|
||||
onClick={() => drilldownUrl && window.open(drilldownUrl, '_blank', 'noopener')}
|
||||
>
|
||||
Open Grafana drilldown
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
|
||||
<Card size="small">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user