mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
feat: enhance metrics display in ProviderResourceTable and update localization keys
This commit is contained in:
@@ -48,10 +48,48 @@
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.modelsCell {
|
||||
font-size: 12px;
|
||||
.metricsCell {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.metric {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--muted-bg);
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.metricLabel {
|
||||
color: var(--muted-foreground);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.metricValue {
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.flagTag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--primary-30);
|
||||
background: var(--primary-10);
|
||||
color: var(--primary-color);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.statusBadge {
|
||||
@@ -88,18 +126,15 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-md);
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: background-color $transition-fast, color $transition-fast;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--bg-tertiary);
|
||||
@@ -125,9 +160,3 @@
|
||||
color: var(--destructive-color);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.iconBtn span:not(.icon) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
IconAlertTriangle,
|
||||
@@ -26,7 +27,7 @@ interface ProviderResourceTableProps {
|
||||
onDelete: (resource: ProviderResource) => void;
|
||||
}
|
||||
|
||||
const columnWidths = ['20%', '22%', '9%', '14%', '9%', '26%'];
|
||||
const columnWidths = ['20%', '22%', '8%', '22%', '8%', '20%'];
|
||||
|
||||
export function ProviderResourceTable({
|
||||
resources,
|
||||
@@ -38,31 +39,45 @@ export function ProviderResourceTable({
|
||||
}: ProviderResourceTableProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const renderMetric = (key: string, label: string, value: number) => (
|
||||
<span key={key} className={styles.metric}>
|
||||
<span className={styles.metricLabel}>{label}</span>
|
||||
<span className={styles.metricValue}>{value}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
const renderFlagTag = (key: string, label: string) => (
|
||||
<span key={key} className={styles.flagTag}>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
|
||||
const renderModelsSummary = (r: ProviderResource) => {
|
||||
const items: ReactNode[] = [];
|
||||
if (r.brand === 'openaiCompatibility') {
|
||||
return t('providersPage.table.openaiModelSummary', {
|
||||
models: r.modelCount,
|
||||
keys: r.apiKeyEntryCount,
|
||||
headers: r.headerCount,
|
||||
});
|
||||
items.push(
|
||||
renderMetric('models', t('providersPage.table.metrics.models'), r.modelCount),
|
||||
renderMetric('keys', t('providersPage.table.metrics.keys'), r.apiKeyEntryCount),
|
||||
renderMetric('headers', t('providersPage.table.metrics.headers'), r.headerCount),
|
||||
);
|
||||
} else if (r.brand === 'ampcode') {
|
||||
items.push(
|
||||
renderMetric('mappings', t('providersPage.table.metrics.mappings'), r.modelCount),
|
||||
renderMetric('keys', t('providersPage.table.metrics.keys'), r.apiKeyEntryCount),
|
||||
);
|
||||
} else {
|
||||
items.push(
|
||||
renderMetric('models', t('providersPage.table.metrics.models'), r.modelCount),
|
||||
renderMetric('headers', t('providersPage.table.metrics.headers'), r.headerCount),
|
||||
);
|
||||
if (r.brand === 'codex' && r.flags.websockets) {
|
||||
items.push(renderFlagTag('ws', t('providersPage.table.websocketsTag')));
|
||||
}
|
||||
if (r.brand === 'claude' && r.flags.cloakEnabled) {
|
||||
items.push(renderFlagTag('cloak', t('providersPage.table.cloakTag')));
|
||||
}
|
||||
}
|
||||
if (r.brand === 'ampcode') {
|
||||
return t('providersPage.table.ampcodeSummary', {
|
||||
mappings: r.modelCount,
|
||||
keys: r.apiKeyEntryCount,
|
||||
});
|
||||
}
|
||||
let label = t('providersPage.table.modelSummary', {
|
||||
models: r.modelCount,
|
||||
headers: r.headerCount,
|
||||
});
|
||||
if (r.brand === 'codex' && r.flags.websockets) {
|
||||
label += ` · ${t('providersPage.table.websocketsTag')}`;
|
||||
}
|
||||
if (r.brand === 'claude' && r.flags.cloakEnabled) {
|
||||
label += ` · ${t('providersPage.table.cloakTag')}`;
|
||||
}
|
||||
return label;
|
||||
return <div className={styles.metricsCell}>{items}</div>;
|
||||
};
|
||||
|
||||
const renderStatus = (r: ProviderResource) => {
|
||||
@@ -172,9 +187,7 @@ export function ProviderResourceTable({
|
||||
<span className={styles.baseUrl}>{t('providersPage.status.none')}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className={styles.modelsCell}>{renderModelsSummary(resource)}</span>
|
||||
</TableCell>
|
||||
<TableCell>{renderModelsSummary(resource)}</TableCell>
|
||||
<TableCell>{renderStatus(resource)}</TableCell>
|
||||
<TableCell alignRight>
|
||||
<div className={styles.actions}>
|
||||
@@ -182,18 +195,19 @@ export function ProviderResourceTable({
|
||||
type="button"
|
||||
className={styles.iconBtn}
|
||||
aria-label={t('providersPage.actions.view')}
|
||||
title={t('providersPage.actions.view')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onView(resource);
|
||||
}}
|
||||
>
|
||||
<IconEye size={14} />
|
||||
<span>{t('providersPage.actions.view')}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.iconBtn}
|
||||
aria-label={t('providersPage.actions.edit')}
|
||||
title={t('providersPage.actions.edit')}
|
||||
disabled={disableMutations}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -201,13 +215,13 @@ export function ProviderResourceTable({
|
||||
}}
|
||||
>
|
||||
<IconPencil size={14} />
|
||||
<span>{t('providersPage.actions.edit')}</span>
|
||||
</button>
|
||||
{isAmpcode ? (
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.iconBtn} ${styles.iconBtnDanger}`}
|
||||
aria-label={t('providersPage.actions.clear')}
|
||||
title={t('providersPage.actions.clear')}
|
||||
disabled={disableMutations || resource.flags.isPlaceholder}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -215,13 +229,13 @@ export function ProviderResourceTable({
|
||||
}}
|
||||
>
|
||||
<IconTrash2 size={14} />
|
||||
<span>{t('providersPage.actions.clear')}</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.iconBtn} ${styles.iconBtnDanger}`}
|
||||
aria-label={t('providersPage.actions.delete')}
|
||||
title={t('providersPage.actions.delete')}
|
||||
disabled={disableMutations}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -229,7 +243,6 @@ export function ProviderResourceTable({
|
||||
}}
|
||||
>
|
||||
<IconTrash2 size={14} />
|
||||
<span>{t('providersPage.actions.delete')}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1492,9 +1492,12 @@
|
||||
"models": "Models/Headers",
|
||||
"status": "Status",
|
||||
"actions": "Actions",
|
||||
"modelSummary": "{{models}} models · {{headers}} headers",
|
||||
"openaiModelSummary": "{{models}} models · {{keys}} keys · {{headers}} headers",
|
||||
"ampcodeSummary": "{{mappings}} mappings · {{keys}} key groups",
|
||||
"metrics": {
|
||||
"models": "Models",
|
||||
"keys": "Keys",
|
||||
"headers": "Headers",
|
||||
"mappings": "Mappings"
|
||||
},
|
||||
"websocketsTag": "WebSockets",
|
||||
"cloakTag": "Cloak",
|
||||
"noFallbackKey": "No fallback key",
|
||||
|
||||
@@ -1489,9 +1489,12 @@
|
||||
"models": "Модели/Заголовки",
|
||||
"status": "Статус",
|
||||
"actions": "Действия",
|
||||
"modelSummary": "{{models}} моделей · {{headers}} заголовков",
|
||||
"openaiModelSummary": "{{models}} моделей · {{keys}} ключей · {{headers}} заголовков",
|
||||
"ampcodeSummary": "{{mappings}} сопоставлений · {{keys}} групп ключей",
|
||||
"metrics": {
|
||||
"models": "Модели",
|
||||
"keys": "Ключи",
|
||||
"headers": "Заголовки",
|
||||
"mappings": "Сопоставления"
|
||||
},
|
||||
"websocketsTag": "WebSockets",
|
||||
"cloakTag": "Cloak",
|
||||
"noFallbackKey": "Резервный ключ не задан",
|
||||
|
||||
@@ -1492,9 +1492,12 @@
|
||||
"models": "模型/请求头",
|
||||
"status": "状态",
|
||||
"actions": "操作",
|
||||
"modelSummary": "{{models}} 个模型 · {{headers}} 个请求头",
|
||||
"openaiModelSummary": "{{models}} 个模型 · {{keys}} 个密钥 · {{headers}} 个请求头",
|
||||
"ampcodeSummary": "{{mappings}} 个映射 · {{keys}} 个密钥组",
|
||||
"metrics": {
|
||||
"models": "模型",
|
||||
"keys": "密钥",
|
||||
"headers": "请求头",
|
||||
"mappings": "映射"
|
||||
},
|
||||
"websocketsTag": "WebSockets",
|
||||
"cloakTag": "Cloak",
|
||||
"noFallbackKey": "未设置兜底密钥",
|
||||
|
||||
@@ -1518,9 +1518,12 @@
|
||||
"models": "模型/請求標頭",
|
||||
"status": "狀態",
|
||||
"actions": "操作",
|
||||
"modelSummary": "{{models}} 個模型 · {{headers}} 個請求標頭",
|
||||
"openaiModelSummary": "{{models}} 個模型 · {{keys}} 個金鑰 · {{headers}} 個請求標頭",
|
||||
"ampcodeSummary": "{{mappings}} 個映射 · {{keys}} 個金鑰群組",
|
||||
"metrics": {
|
||||
"models": "模型",
|
||||
"keys": "金鑰",
|
||||
"headers": "請求標頭",
|
||||
"mappings": "映射"
|
||||
},
|
||||
"websocketsTag": "WebSockets",
|
||||
"cloakTag": "Cloak",
|
||||
"noFallbackKey": "未設定備援金鑰",
|
||||
|
||||
Reference in New Issue
Block a user