260 lines
7.1 KiB
TypeScript
260 lines
7.1 KiB
TypeScript
import { getNocoDbConfiguration } from '../utils/nocodb';
|
|
import { getMinioClient } from '../utils/minio';
|
|
import { useDirectus } from '#imports';
|
|
|
|
interface HealthCheckResult {
|
|
status: 'healthy' | 'unhealthy' | 'degraded';
|
|
timestamp: string;
|
|
uptime: number;
|
|
services: {
|
|
[key: string]: {
|
|
status: 'up' | 'down' | 'slow';
|
|
responseTime?: number;
|
|
error?: string;
|
|
};
|
|
};
|
|
memory: {
|
|
used: number;
|
|
total: number;
|
|
percentage: number;
|
|
};
|
|
environment: {
|
|
nodeVersion: string;
|
|
configStatus: 'complete' | 'partial' | 'missing';
|
|
missingVars?: string[];
|
|
};
|
|
}
|
|
|
|
export default defineEventHandler(async (event): Promise<HealthCheckResult> => {
|
|
const startTime = Date.now();
|
|
const result: HealthCheckResult = {
|
|
status: 'healthy',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: process.uptime(),
|
|
services: {},
|
|
memory: {
|
|
used: 0,
|
|
total: 0,
|
|
percentage: 0
|
|
},
|
|
environment: {
|
|
nodeVersion: process.version,
|
|
configStatus: 'complete'
|
|
}
|
|
};
|
|
|
|
// Memory check
|
|
const memUsage = process.memoryUsage();
|
|
result.memory = {
|
|
used: Math.round(memUsage.heapUsed / 1024 / 1024), // MB
|
|
total: Math.round(memUsage.heapTotal / 1024 / 1024), // MB
|
|
percentage: Math.round((memUsage.heapUsed / memUsage.heapTotal) * 100)
|
|
};
|
|
|
|
// Check if memory usage is too high
|
|
if (result.memory.percentage > 90) {
|
|
result.status = 'unhealthy';
|
|
} else if (result.memory.percentage > 80) {
|
|
result.status = 'degraded';
|
|
}
|
|
|
|
// Environment variables check
|
|
const requiredEnvVars = [
|
|
'NUXT_NOCODB_URL',
|
|
'NUXT_NOCODB_TOKEN',
|
|
'NUXT_MINIO_ACCESS_KEY',
|
|
'NUXT_MINIO_SECRET_KEY',
|
|
'NUXT_DOCUMENSO_API_KEY',
|
|
'NUXT_DOCUMENSO_BASE_URL'
|
|
];
|
|
|
|
const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
|
|
if (missingVars.length > 0) {
|
|
result.environment.configStatus = missingVars.length > 3 ? 'missing' : 'partial';
|
|
result.environment.missingVars = missingVars;
|
|
result.status = 'unhealthy';
|
|
}
|
|
|
|
// Service health checks
|
|
|
|
// 1. NocoDB Check
|
|
try {
|
|
const nocoStart = Date.now();
|
|
const nocoConfig = getNocoDbConfiguration();
|
|
|
|
if (!nocoConfig.url || !nocoConfig.token) {
|
|
result.services.nocodb = {
|
|
status: 'down',
|
|
error: 'Missing configuration'
|
|
};
|
|
result.status = 'unhealthy';
|
|
} else {
|
|
const response = await fetch(`${nocoConfig.url}/api/v1/health`, {
|
|
headers: {
|
|
'xc-token': nocoConfig.token
|
|
},
|
|
signal: AbortSignal.timeout(5000) // 5 second timeout
|
|
});
|
|
|
|
const responseTime = Date.now() - nocoStart;
|
|
|
|
if (response.ok) {
|
|
result.services.nocodb = {
|
|
status: responseTime > 3000 ? 'slow' : 'up',
|
|
responseTime
|
|
};
|
|
if (responseTime > 3000) result.status = 'degraded';
|
|
} else {
|
|
result.services.nocodb = {
|
|
status: 'down',
|
|
responseTime,
|
|
error: `HTTP ${response.status}`
|
|
};
|
|
result.status = 'unhealthy';
|
|
}
|
|
}
|
|
} catch (error) {
|
|
result.services.nocodb = {
|
|
status: 'down',
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
};
|
|
result.status = 'unhealthy';
|
|
}
|
|
|
|
// 2. Directus Check
|
|
try {
|
|
const directusStart = Date.now();
|
|
const { $directus } = useNuxtApp();
|
|
|
|
// Check if Directus URL is configured
|
|
const directusUrl = useRuntimeConfig().public.directus?.url;
|
|
if (!directusUrl) {
|
|
result.services.directus = {
|
|
status: 'down',
|
|
error: 'Missing configuration'
|
|
};
|
|
result.status = 'unhealthy';
|
|
} else {
|
|
const response = await fetch(`${directusUrl}/server/health`, {
|
|
signal: AbortSignal.timeout(5000)
|
|
});
|
|
|
|
const responseTime = Date.now() - directusStart;
|
|
|
|
if (response.ok) {
|
|
result.services.directus = {
|
|
status: responseTime > 3000 ? 'slow' : 'up',
|
|
responseTime
|
|
};
|
|
if (responseTime > 3000) result.status = 'degraded';
|
|
} else {
|
|
result.services.directus = {
|
|
status: 'down',
|
|
responseTime,
|
|
error: `HTTP ${response.status}`
|
|
};
|
|
result.status = 'unhealthy';
|
|
}
|
|
}
|
|
} catch (error) {
|
|
result.services.directus = {
|
|
status: 'down',
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
};
|
|
result.status = 'unhealthy';
|
|
}
|
|
|
|
// 3. MinIO Check
|
|
try {
|
|
const minioStart = Date.now();
|
|
const minioClient = getMinioClient();
|
|
const bucketName = useRuntimeConfig().minio.bucketName;
|
|
|
|
// Try to check if bucket exists
|
|
const bucketExists = await minioClient.bucketExists(bucketName);
|
|
const responseTime = Date.now() - minioStart;
|
|
|
|
if (bucketExists) {
|
|
result.services.minio = {
|
|
status: responseTime > 3000 ? 'slow' : 'up',
|
|
responseTime
|
|
};
|
|
if (responseTime > 3000) result.status = 'degraded';
|
|
} else {
|
|
result.services.minio = {
|
|
status: 'down',
|
|
responseTime,
|
|
error: 'Bucket not found'
|
|
};
|
|
result.status = 'unhealthy';
|
|
}
|
|
} catch (error) {
|
|
result.services.minio = {
|
|
status: 'down',
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
};
|
|
result.status = 'unhealthy';
|
|
}
|
|
|
|
// 4. Documenso Check
|
|
try {
|
|
const documensoStart = Date.now();
|
|
const documensoUrl = process.env.NUXT_DOCUMENSO_BASE_URL;
|
|
const documensoKey = process.env.NUXT_DOCUMENSO_API_KEY;
|
|
|
|
if (!documensoUrl || !documensoKey) {
|
|
result.services.documenso = {
|
|
status: 'down',
|
|
error: 'Missing configuration'
|
|
};
|
|
result.status = 'unhealthy';
|
|
} else {
|
|
const response = await fetch(`${documensoUrl}/api/health`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${documensoKey}`
|
|
},
|
|
signal: AbortSignal.timeout(5000)
|
|
});
|
|
|
|
const responseTime = Date.now() - documensoStart;
|
|
|
|
if (response.ok || response.status === 401) { // 401 means API is up but key might be wrong
|
|
result.services.documenso = {
|
|
status: responseTime > 3000 ? 'slow' : 'up',
|
|
responseTime
|
|
};
|
|
if (responseTime > 3000) result.status = 'degraded';
|
|
} else {
|
|
result.services.documenso = {
|
|
status: 'down',
|
|
responseTime,
|
|
error: `HTTP ${response.status}`
|
|
};
|
|
result.status = 'unhealthy';
|
|
}
|
|
}
|
|
} catch (error) {
|
|
result.services.documenso = {
|
|
status: 'down',
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
};
|
|
// Documenso being down shouldn't make the whole app unhealthy
|
|
if (result.status === 'healthy') result.status = 'degraded';
|
|
}
|
|
|
|
// Overall response time
|
|
const totalTime = Date.now() - startTime;
|
|
if (totalTime > 10000 && result.status === 'healthy') {
|
|
result.status = 'degraded';
|
|
}
|
|
|
|
// Set appropriate HTTP status code
|
|
if (result.status === 'unhealthy') {
|
|
setResponseStatus(event, 503);
|
|
} else if (result.status === 'degraded') {
|
|
setResponseStatus(event, 200); // Still return 200 for degraded to not trigger Nginx failures
|
|
}
|
|
|
|
return result;
|
|
});
|