Improve Integration Hub preview card and API Key navigation

- Redesign multi-service summary card with structured Statistic layout
  and namespace tags instead of wrapping comma-separated text
- Add direct "Generate API Key" button linking to Settings > API Keys tab
- Add URL param support (?tab=) to Settings for deep-linking to specific tabs
- Fix admin warning incorrectly showing on API Keys tab (key mismatch: api-keys vs api-tokens)

Made-with: Cursor
This commit is contained in:
taylanbakircioglu
2026-04-07 10:44:07 +03:00
parent b65077c62a
commit 2c180db85b
2 changed files with 103 additions and 27 deletions
+90 -24
View File
@@ -276,16 +276,23 @@ const IntegrationHub: React.FC = () => {
All API calls require authentication via <Text strong>API Key</Text>. Include the header <Text code>X-API-Key: fk_your_key</Text> in every request.
</Paragraph>
<ol>
<li>Go to <Link to="/settings"><Text strong>Settings</Text></Link> and open the <Text strong>API Keys</Text> tab</li>
<li>Go to <Link to="/settings?tab=api-tokens"><Text strong>Settings &gt; API Keys</Text></Link></li>
<li>Click <Text strong>Generate New API Key</Text> and give it a descriptive name (e.g. &quot;azure-devops-pipeline&quot;)</li>
<li>Copy the generated key (starts with <Text code>fk_</Text>) and store it securely in your CI/CD platform&apos;s secrets/variables</li>
</ol>
<Alert
type="warning"
showIcon
message="API keys provide full API access. Store them as encrypted secrets in your pipeline platform, never in source code."
style={{ marginTop: 8 }}
/>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginTop: 12 }}>
<Link to="/settings?tab=api-tokens">
<Button type="primary" icon={<KeyOutlined />}>
Generate API Key
</Button>
</Link>
<Alert
type="warning"
showIcon
message="API keys provide full API access. Store them as encrypted secrets in your pipeline platform, never in source code."
style={{ flex: 1, margin: 0 }}
/>
</div>
</Card>
);
@@ -626,23 +633,82 @@ const IntegrationHub: React.FC = () => {
)}
{integrationType === 'dependency' && currentStep === 2 && summaryData?.success && (
<div>
<Card size="small" style={{ borderLeft: `3px solid ${token.colorPrimary}`, marginBottom: 16 }}>
<Row gutter={16} align="middle">
<Col flex="auto">
<Space split={<Divider type="vertical" />}>
<Text strong style={{ fontSize: 16 }}>{summaryData.service.name}</Text>
<Text type="secondary">{summaryData.service.namespace}</Text>
{summaryData.service.kind && <Tag>{summaryData.service.kind}</Tag>}
</Space>
</Col>
<Col>
<Space size="large">
<Statistic title={<><ArrowDownOutlined /> Downstream</>} value={summaryData.summary?.total_downstream_unique ?? 0} valueStyle={{ fontSize: 18 }} />
<Statistic title={<><ArrowUpOutlined /> Callers</>} value={summaryData.summary?.total_callers_unique ?? 0} valueStyle={{ fontSize: 18 }} />
</Space>
</Col>
</Row>
</Card>
{summaryData.multi_service && summaryData.matched_services ? (() => {
const namespaces = Array.from(new Set(summaryData.matched_services.map(s => s.namespace))).sort();
const critCount = summaryData.summary?.downstream_critical_count ?? 0;
return (
<Card size="small" style={{ borderLeft: `3px solid ${token.colorPrimary}`, marginBottom: 16 }}>
<Row gutter={24} align="middle">
<Col>
<Statistic
title="Matched Services"
value={summaryData.matched_services.length}
valueStyle={{ fontSize: 28, fontWeight: 700, color: token.colorPrimary }}
/>
</Col>
<Col>
<Statistic
title="Namespaces"
value={namespaces.length}
valueStyle={{ fontSize: 28, fontWeight: 700 }}
/>
</Col>
<Col>
<Statistic
title={<><ArrowDownOutlined /> Downstream</>}
value={summaryData.summary?.total_downstream_unique ?? 0}
valueStyle={{ fontSize: 28, fontWeight: 700 }}
/>
</Col>
<Col>
<Statistic
title={<><ArrowUpOutlined /> Callers</>}
value={summaryData.summary?.total_callers_unique ?? 0}
valueStyle={{ fontSize: 28, fontWeight: 700 }}
/>
</Col>
{critCount > 0 && (
<Col>
<Statistic
title="Critical"
value={critCount}
valueStyle={{ fontSize: 28, fontWeight: 700, color: token.colorError }}
/>
</Col>
)}
</Row>
{namespaces.length > 0 && (
<div style={{
display: 'flex', flexWrap: 'wrap', gap: 4,
marginTop: 12, paddingTop: 12,
borderTop: `1px solid ${token.colorBorderSecondary}`,
}}>
{namespaces.map(ns => (
<Tag key={ns} style={{ margin: 0, fontSize: 12 }}>{ns}</Tag>
))}
</div>
)}
</Card>
);
})() : (
<Card size="small" style={{ borderLeft: `3px solid ${token.colorPrimary}`, marginBottom: 16 }}>
<Row gutter={16} align="middle">
<Col flex="auto">
<Space split={<Divider type="vertical" />}>
<Text strong style={{ fontSize: 16 }}>{summaryData.service.name}</Text>
<Text type="secondary">{summaryData.service.namespace}</Text>
{summaryData.service.kind && <Tag>{summaryData.service.kind}</Tag>}
</Space>
</Col>
<Col>
<Space size="large">
<Statistic title={<><ArrowDownOutlined /> Downstream</>} value={summaryData.summary?.total_downstream_unique ?? 0} valueStyle={{ fontSize: 18 }} />
<Statistic title={<><ArrowUpOutlined /> Callers</>} value={summaryData.summary?.total_callers_unique ?? 0} valueStyle={{ fontSize: 18 }} />
</Space>
</Col>
</Row>
</Card>
)}
{summaryData.multi_service && summaryData.matched_services && summaryData.matched_services.length > 0 && (
<Card size="small" title={`Matched Upstream Services (${summaryData.matched_services.length})`} style={{ marginBottom: 16 }}>
+13 -3
View File
@@ -4,6 +4,7 @@
*/
import React, { useState, useEffect, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useTheme } from '../contexts/ThemeContext';
import {
Card,
@@ -223,8 +224,14 @@ interface IntegrationSettings {
// ================== MAIN COMPONENT ==================
const Settings: React.FC = () => {
const [activeTab, setActiveTab] = useState('general');
const [searchParams, setSearchParams] = useSearchParams();
const [activeTab, setActiveTab] = useState(() => searchParams.get('tab') || 'general');
const [loading, setLoading] = useState(true);
useEffect(() => {
const tab = searchParams.get('tab') || 'general';
setActiveTab(prev => prev !== tab ? tab : prev);
}, [searchParams]);
const [saving, setSaving] = useState<string | null>(null);
const [isAdmin, setIsAdmin] = useState(false);
const [lastUpdated, setLastUpdated] = useState<string | null>(null);
@@ -1337,7 +1344,7 @@ const Settings: React.FC = () => {
</div>
{/* Admin Warning - not shown for API Keys tab (users can manage their own keys) */}
{!isAdmin && activeTab !== 'api-keys' && (
{!isAdmin && activeTab !== 'api-tokens' && (
<Alert
message="Read-Only Mode"
description="Admin privileges required to make changes."
@@ -1350,7 +1357,10 @@ const Settings: React.FC = () => {
{/* Settings Tabs */}
<Tabs
activeKey={activeTab}
onChange={setActiveTab}
onChange={(key) => {
setActiveTab(key);
setSearchParams(key === 'general' ? {} : { tab: key }, { replace: true });
}}
type="card"
size="large"
>