Per guide ch.7 page split pattern (skeleton orchestrates, private components live in features/<domain>/): - InstancePage 1716->328, UsersPage 1309->240, TicketsPage 1076->195, TicketDetailPage 911->287, QueriesPage 883->479, LdapConfigPage 860->393, WorkflowPage 852->194 - new feature modules: datasource/ rbac/ ticket/ query/ system/ldap/ governance/workflow/ (components + hooks + types + utils) - stable keys replace index keys in dynamic lists; polling intervals and toast call sites preserved 1:1 (verified by agents) - filter/pagination state moved to URL query via new useQueryState hook (Instance/Users/Tickets/AuditLogs pages): refresh/share/back-button restore state; composite updates applied atomically - permission pure functions extracted to lib/permissions.ts (used by useAuth + useLogin); page-size audit: 0 pages over 700-line budget
121 lines
4.6 KiB
TypeScript
121 lines
4.6 KiB
TypeScript
/**
|
|
* 数据源域纯工具函数:状态/环境映射、列表筛选与健康分计算。
|
|
* 用途:供 InstancePage 及 features/datasource/components 下各组件共享,无副作用、不依赖 React。
|
|
* 已知限制:健康分为客户端按指标阈值估算的结果,并非后端权威评分。
|
|
*/
|
|
import type { InstanceItem } from './types';
|
|
|
|
/** 各数据库类型默认端口 */
|
|
export const DB_DEFAULT_PORTS: Record<string, number> = {
|
|
mysql: 3306, postgres: 5432, redis: 6379, mongo: 27017,
|
|
};
|
|
|
|
/** 环境展示名 */
|
|
export const envLabel: Record<string, string> = {
|
|
production: '生产', staging: '预发', development: '开发', test: '测试', testing: '测试', uat: 'UAT',
|
|
};
|
|
|
|
/** 环境徽标配色 */
|
|
export const envColor: Record<string, string> = {
|
|
production: 'bg-red-500/10 text-red-400 border-red-500/20',
|
|
staging: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',
|
|
development: 'bg-green-500/10 text-green-400 border-green-500/20',
|
|
test: 'bg-blue-500/10 text-blue-400 border-blue-500/20',
|
|
testing: 'bg-blue-500/10 text-blue-400 border-blue-500/20',
|
|
uat: 'bg-cyan-500/10 text-cyan-400 border-cyan-500/20',
|
|
};
|
|
|
|
/** 状态展示信息(在线/离线/异常文案) */
|
|
const statusLabel: Record<string, { text: string; online: boolean }> = {
|
|
connected: { text: '在线', online: true },
|
|
online: { text: '在线', online: true },
|
|
active: { text: '在线', online: true },
|
|
disconnected: { text: '离线', online: false },
|
|
offline: { text: '离线', online: false },
|
|
error: { text: '异常', online: false },
|
|
};
|
|
|
|
/** 取状态展示信息,未知状态回退「未知」 */
|
|
export function getStatusInfo(status: string) {
|
|
return statusLabel[status] || { text: '未知', online: false };
|
|
}
|
|
|
|
/** 是否离线状态 */
|
|
export function isOfflineStatus(status: string): boolean {
|
|
return status === 'offline' || status === 'disconnected';
|
|
}
|
|
|
|
/** 状态筛选匹配(online/offline 归并为一类) */
|
|
export function matchesStatusFilter(status: string, filterStatus: string): boolean {
|
|
if (!filterStatus) return true;
|
|
const info = getStatusInfo(status);
|
|
if (filterStatus === 'online') return info.online;
|
|
if (filterStatus === 'offline') return isOfflineStatus(status);
|
|
return status === filterStatus;
|
|
}
|
|
|
|
/** 环境筛选匹配(test/testing 归并为一类) */
|
|
export function matchesEnvFilter(env: string, filterEnv: string): boolean {
|
|
if (!filterEnv) return true;
|
|
if (filterEnv === 'test') return env === 'test' || env === 'testing';
|
|
return env === filterEnv;
|
|
}
|
|
|
|
/** 计算实例健康分(0-100) */
|
|
export function calcHealthScore(inst: InstanceItem): number {
|
|
if (inst.status === 'offline' || inst.status === 'disconnected' || inst.status === 'error') return 0;
|
|
let score = 100;
|
|
const cpu = inst.cpu ?? 0;
|
|
const memory = inst.memory ?? 0;
|
|
const latency = inst.latency ?? 0;
|
|
const disk = inst.disk ?? 0;
|
|
|
|
if (cpu > 80) score -= 25;
|
|
else if (cpu > 60) score -= 10;
|
|
if (memory > 85) score -= 20;
|
|
else if (memory > 70) score -= 10;
|
|
if (latency > 10) score -= 20;
|
|
else if (latency > 5) score -= 10;
|
|
if (disk > 80) score -= 15;
|
|
return Math.max(0, score);
|
|
}
|
|
|
|
/** 健康分文字配色 */
|
|
export function healthColor(score: number): string {
|
|
if (score >= 90) return 'text-green-400';
|
|
if (score >= 70) return 'text-yellow-400';
|
|
if (score >= 50) return 'text-orange-400';
|
|
return 'text-red-400';
|
|
}
|
|
|
|
/** 健康分背景配色 */
|
|
export function healthBg(score: number): string {
|
|
if (score >= 90) return 'bg-green-500/15';
|
|
if (score >= 70) return 'bg-yellow-500/15';
|
|
if (score >= 50) return 'bg-orange-500/15';
|
|
return 'bg-red-500/15';
|
|
}
|
|
|
|
/** 健康分档位文案 */
|
|
export function healthLabel(score: number): string {
|
|
if (score >= 90) return '健康';
|
|
if (score >= 70) return '良好';
|
|
if (score >= 50) return '一般';
|
|
return '危险';
|
|
}
|
|
|
|
/** 列表筛选:按类型/状态/环境/关键字过滤,与页面原过滤逻辑一致 */
|
|
export function filterInstances(
|
|
instances: InstanceItem[],
|
|
criteria: { activeType: string; filterStatus: string; filterEnv: string; searchQuery: string },
|
|
): InstanceItem[] {
|
|
const { activeType, filterStatus, filterEnv, searchQuery } = criteria;
|
|
return instances.filter(i => {
|
|
if (activeType !== 'all' && (activeType === 'postgres' ? !['postgres', 'postgresql', 'pg'].includes(i.db_type) : i.db_type !== activeType)) return false;
|
|
if (!matchesStatusFilter(i.status, filterStatus)) return false;
|
|
if (!matchesEnvFilter(i.env, filterEnv)) return false;
|
|
if (searchQuery && !i.name.toLowerCase().includes(searchQuery.toLowerCase()) && !i.host.includes(searchQuery)) return false;
|
|
return true;
|
|
});
|
|
}
|