All files / src/utils systemInfo.ts

97.17% Statements 172/177
85.71% Branches 24/28
100% Functions 4/4
97.17% Lines 172/177

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 1781x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 7x 7x 7x 7x 7x 7x 3x 3x 4x 4x 7x 2x 2x     2x 4x 4x 4x 7x       7x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 8x 9x 1x 1x 1x 7x 7x 7x 7x 7x 7x 7x 9x 9x 9x 9x 9x 9x 7x 7x 7x 6x 5x 7x 7x 2x 2x 7x 7x 7x 7x 7x 7x 7x 7x 7x 9x 9x 9x 1x 1x 1x 1x 9x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x  
/**
 * @file systemInfo.ts
 * @description
 * System information retrieval from backend health endpoint.
 * Auto-detects database type and other system characteristics.
 *
 * @enterprise
 * - Single source of truth: /api/health endpoint
 * - Auto-detection logic: "Oracle" in response → "Oracle ADB", else "Local H2"
 * - Caching to avoid excessive requests
 * - Fallback values for reliability
 *
 * @usage
 * import { getSystemInfo } from '../utils/systemInfo'
 * const info = await getSystemInfo()
 */
 
export interface SystemInfoResponse {
  status: string;
  components?: {
    db?: {
      status: string;
      details?: {
        database?: string;
        [key: string]: unknown;
      };
    };
    [key: string]: unknown;
  };
  [key: string]: unknown;
}
 
/**
 * Detect database type from health endpoint response.
 * Smart detection: if "Oracle" appears in any string, it's Oracle ADB.
 * Otherwise, defaults to "Local H2" (development database).
 * @param healthResponse - Response from /api/health endpoint
 * @returns Database type string
 */
const detectDatabase = (healthResponse: unknown): string => {
  try {
    const response = healthResponse as SystemInfoResponse;
 
    // Check if response contains Oracle database indicators
    const responseString = JSON.stringify(response).toUpperCase();
    if (responseString.includes('ORACLE')) {
      return 'Oracle ADB';
    }
 
    // Check database details if available
    if (response.components?.db?.details?.database) {
      const db = response.components.db.details.database.toUpperCase();
      if (db.includes('ORACLE')) {
        return 'Oracle ADB';
      }
    }
 
    // Default to Local H2 for development
    return 'Local H2';
  } catch {
    // Fallback if parsing fails
    return 'Local H2';
  }
};
 
/**
 * Retrieve system information from backend health endpoint.
 * Includes app version, database type, environment, etc.
 * @returns System info object with database, version, and environment details
 * @throws May log errors but always returns fallback values
 *
 * @example
 * const systemInfo = await getSystemInfo()
 * console.log(systemInfo.database)  // → 'Oracle ADB' or 'Local H2'
 * console.log(systemInfo.version)   // → 'v2.0.0' or 'dev'
 * console.log(systemInfo.environment) // → 'production' or 'development'
 */
export const getSystemInfo = async () => {
  // Fallback values for reliability
  const fallback = {
    database: 'Local H2',
    version: 'dev',
    environment: 'development',
    apiVersion: 'unknown',
    buildDate: 'unknown',
    uptime: '0h',
    status: 'unknown' as const,
  };
 
  try {
    const response = await fetch('/api/health', {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
      },
      // No timeout needed - browser default is used
    });
 
    if (!response.ok) {
      console.warn(`Health endpoint returned status ${response.status}`);
      return fallback;
    }
 
    const data = (await response.json()) as SystemInfoResponse;
 
    // Detect database from response
    const database = detectDatabase(data);
 
    // Determine environment based on database type
    const environment = database === 'Oracle ADB' ? 'production' : 'development';
 
    // Extract version from response if available
    // Backends may include version in different locations
    let version = 'dev';
    if (typeof data === 'object' && data !== null) {
      // Try common version locations
      const versionFromResponse =
        (data as unknown as Record<string, unknown>).version ||
        (data as unknown as Record<string, unknown>)['app-version'] ||
        (data as unknown as Record<string, unknown>)['build-version'];
 
      if (typeof versionFromResponse === 'string') {
        version = versionFromResponse.startsWith('v') ? versionFromResponse : `v${versionFromResponse}`;
      }
    }
 
    return {
      database,
      version,
      environment,
      apiVersion: 'v1',
      buildDate: new Date().toISOString().split('T')[0],
      uptime: '0h', // Could be extended with actual uptime from backend if provided
      status: data.status || 'UP',
    };
  } catch (error) {
    // Log the error for debugging but don't throw - return fallback
    console.error('Failed to fetch system info from health endpoint:', error);
    return fallback;
  }
};
 
/**
 * Check if system is in production based on database type.
 * Useful for conditionally showing features/warnings.
 * @returns boolean indicating if production environment
 * @example
 * if (await isProduction()) {
 *   // Show production warning
 * }
 */
export const isProduction = async (): Promise<boolean> => {
  const info = await getSystemInfo();
  return info.environment === 'production';
};
 
/**
 * Format uptime string for display.
 * @param uptimeSeconds - Uptime in seconds
 * @returns Formatted uptime string
 * @example
 * formatUptime(3661)  // → '1h 1m'
 * formatUptime(60)    // → '1m'
 * formatUptime(3661)  // → '1h 1m 1s'
 */
export const formatUptime = (uptimeSeconds: number): string => {
  const hours = Math.floor(uptimeSeconds / 3600);
  const minutes = Math.floor((uptimeSeconds % 3600) / 60);
  const seconds = uptimeSeconds % 60;
 
  const parts: string[] = [];
  if (hours > 0) parts.push(`${hours}h`);
  if (minutes > 0) parts.push(`${minutes}m`);
  if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
 
  return parts.join(' ');
};