mirror of
https://github.com/taylanbakircioglu/flowfish.git
synced 2026-09-11 21:38:54 +00:00
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:
@@ -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 > API Keys</Text></Link></li>
|
||||
<li>Click <Text strong>Generate New API Key</Text> and give it a descriptive name (e.g. "azure-devops-pipeline")</li>
|
||||
<li>Copy the generated key (starts with <Text code>fk_</Text>) and store it securely in your CI/CD platform'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 }}>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user