[agent-pipeline] pipe-20260318044621-56k5 round-5
This commit is contained in:
parent
ab477abf8b
commit
577e9c12d1
|
|
@ -768,4 +768,238 @@ export class BatchManagementController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배치 대시보드 통계 조회
|
||||||
|
* GET /api/batch-management/stats
|
||||||
|
* totalBatches, activeBatches, todayExecutions, todayFailures, prevDayExecutions, prevDayFailures
|
||||||
|
* 멀티테넌시: company_code 필터링 필수
|
||||||
|
*/
|
||||||
|
static async getBatchStats(req: AuthenticatedRequest, res: Response) {
|
||||||
|
try {
|
||||||
|
const companyCode = req.user?.companyCode;
|
||||||
|
|
||||||
|
// 전체/활성 배치 수
|
||||||
|
let configQuery: string;
|
||||||
|
let configParams: any[] = [];
|
||||||
|
if (companyCode === "*") {
|
||||||
|
configQuery = `
|
||||||
|
SELECT
|
||||||
|
COUNT(*)::int AS total,
|
||||||
|
COUNT(*) FILTER (WHERE is_active = 'Y')::int AS active
|
||||||
|
FROM batch_configs
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
configQuery = `
|
||||||
|
SELECT
|
||||||
|
COUNT(*)::int AS total,
|
||||||
|
COUNT(*) FILTER (WHERE is_active = 'Y')::int AS active
|
||||||
|
FROM batch_configs
|
||||||
|
WHERE company_code = $1
|
||||||
|
`;
|
||||||
|
configParams = [companyCode];
|
||||||
|
}
|
||||||
|
const configResult = await query<{ total: number; active: number }>(
|
||||||
|
configQuery,
|
||||||
|
configParams
|
||||||
|
);
|
||||||
|
|
||||||
|
// 오늘/어제 실행·실패 수 (KST 기준 날짜)
|
||||||
|
const logParams: any[] = [];
|
||||||
|
let logWhere = "";
|
||||||
|
if (companyCode && companyCode !== "*") {
|
||||||
|
logWhere = " AND company_code = $1";
|
||||||
|
logParams.push(companyCode);
|
||||||
|
}
|
||||||
|
const todayLogQuery = `
|
||||||
|
SELECT
|
||||||
|
COUNT(*)::int AS today_executions,
|
||||||
|
COUNT(*) FILTER (WHERE execution_status = 'FAILED')::int AS today_failures
|
||||||
|
FROM batch_execution_logs
|
||||||
|
WHERE (start_time AT TIME ZONE 'Asia/Seoul')::date = (NOW() AT TIME ZONE 'Asia/Seoul')::date
|
||||||
|
${logWhere}
|
||||||
|
`;
|
||||||
|
const prevDayLogQuery = `
|
||||||
|
SELECT
|
||||||
|
COUNT(*)::int AS prev_executions,
|
||||||
|
COUNT(*) FILTER (WHERE execution_status = 'FAILED')::int AS prev_failures
|
||||||
|
FROM batch_execution_logs
|
||||||
|
WHERE (start_time AT TIME ZONE 'Asia/Seoul')::date = (NOW() AT TIME ZONE 'Asia/Seoul')::date - INTERVAL '1 day'
|
||||||
|
${logWhere}
|
||||||
|
`;
|
||||||
|
const [todayResult, prevResult] = await Promise.all([
|
||||||
|
query<{ today_executions: number; today_failures: number }>(
|
||||||
|
todayLogQuery,
|
||||||
|
logParams
|
||||||
|
),
|
||||||
|
query<{ prev_executions: number; prev_failures: number }>(
|
||||||
|
prevDayLogQuery,
|
||||||
|
logParams
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const config = configResult[0];
|
||||||
|
const today = todayResult[0];
|
||||||
|
const prev = prevResult[0];
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
totalBatches: config?.total ?? 0,
|
||||||
|
activeBatches: config?.active ?? 0,
|
||||||
|
todayExecutions: today?.today_executions ?? 0,
|
||||||
|
todayFailures: today?.today_failures ?? 0,
|
||||||
|
prevDayExecutions: prev?.prev_executions ?? 0,
|
||||||
|
prevDayFailures: prev?.prev_failures ?? 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("배치 통계 조회 오류:", error);
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "배치 통계 조회 실패",
|
||||||
|
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배치별 최근 24시간 스파크라인 (1시간 단위 집계)
|
||||||
|
* GET /api/batch-management/batch-configs/:id/sparkline
|
||||||
|
* 멀티테넌시: company_code 필터링 필수
|
||||||
|
*/
|
||||||
|
static async getBatchSparkline(req: AuthenticatedRequest, res: Response) {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const companyCode = req.user?.companyCode;
|
||||||
|
const batchId = Number(id);
|
||||||
|
if (!id || isNaN(batchId)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "올바른 배치 ID를 제공해주세요.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const params: any[] = [batchId];
|
||||||
|
let companyFilter = "";
|
||||||
|
if (companyCode && companyCode !== "*") {
|
||||||
|
companyFilter = " AND bel.company_code = $2";
|
||||||
|
params.push(companyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// KST 기준 최근 24시간 1시간 단위 슬롯 + 집계 (generate_series로 24개 보장)
|
||||||
|
const sparklineQuery = `
|
||||||
|
WITH kst_slots AS (
|
||||||
|
SELECT to_char(s, 'YYYY-MM-DD"T"HH24:00:00') AS hour
|
||||||
|
FROM generate_series(
|
||||||
|
(NOW() AT TIME ZONE 'Asia/Seoul') - INTERVAL '23 hours',
|
||||||
|
(NOW() AT TIME ZONE 'Asia/Seoul'),
|
||||||
|
INTERVAL '1 hour'
|
||||||
|
) AS s
|
||||||
|
),
|
||||||
|
agg AS (
|
||||||
|
SELECT
|
||||||
|
to_char(date_trunc('hour', (bel.start_time AT TIME ZONE 'Asia/Seoul')) AT TIME ZONE 'Asia/Seoul', 'YYYY-MM-DD"T"HH24:00:00') AS hour,
|
||||||
|
COUNT(*) FILTER (WHERE bel.execution_status = 'SUCCESS')::int AS success,
|
||||||
|
COUNT(*) FILTER (WHERE bel.execution_status = 'FAILED')::int AS failed
|
||||||
|
FROM batch_execution_logs bel
|
||||||
|
WHERE bel.batch_config_id = $1
|
||||||
|
AND bel.start_time >= (NOW() AT TIME ZONE 'Asia/Seoul') - INTERVAL '24 hours'
|
||||||
|
${companyFilter}
|
||||||
|
GROUP BY date_trunc('hour', (bel.start_time AT TIME ZONE 'Asia/Seoul'))
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
k.hour,
|
||||||
|
COALESCE(a.success, 0) AS success,
|
||||||
|
COALESCE(a.failed, 0) AS failed
|
||||||
|
FROM kst_slots k
|
||||||
|
LEFT JOIN agg a ON k.hour = a.hour
|
||||||
|
ORDER BY k.hour
|
||||||
|
`;
|
||||||
|
const data = await query<{
|
||||||
|
hour: string;
|
||||||
|
success: number;
|
||||||
|
failed: number;
|
||||||
|
}>(sparklineQuery, params);
|
||||||
|
|
||||||
|
return res.json({ success: true, data });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("스파크라인 조회 오류:", error);
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "스파크라인 데이터 조회 실패",
|
||||||
|
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배치별 최근 실행 로그 (최대 20건)
|
||||||
|
* GET /api/batch-management/batch-configs/:id/recent-logs
|
||||||
|
* 멀티테넌시: company_code 필터링 필수
|
||||||
|
*/
|
||||||
|
static async getBatchRecentLogs(req: AuthenticatedRequest, res: Response) {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const companyCode = req.user?.companyCode;
|
||||||
|
const batchId = Number(id);
|
||||||
|
const limit = Math.min(Number(req.query.limit) || 20, 20);
|
||||||
|
if (!id || isNaN(batchId)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "올바른 배치 ID를 제공해주세요.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let logsQuery: string;
|
||||||
|
let logsParams: any[];
|
||||||
|
if (companyCode === "*") {
|
||||||
|
logsQuery = `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
start_time AS started_at,
|
||||||
|
end_time AS finished_at,
|
||||||
|
execution_status AS status,
|
||||||
|
total_records,
|
||||||
|
success_records,
|
||||||
|
failed_records,
|
||||||
|
error_message,
|
||||||
|
duration_ms
|
||||||
|
FROM batch_execution_logs
|
||||||
|
WHERE batch_config_id = $1
|
||||||
|
ORDER BY start_time DESC
|
||||||
|
LIMIT $2
|
||||||
|
`;
|
||||||
|
logsParams = [batchId, limit];
|
||||||
|
} else {
|
||||||
|
logsQuery = `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
start_time AS started_at,
|
||||||
|
end_time AS finished_at,
|
||||||
|
execution_status AS status,
|
||||||
|
total_records,
|
||||||
|
success_records,
|
||||||
|
failed_records,
|
||||||
|
error_message,
|
||||||
|
duration_ms
|
||||||
|
FROM batch_execution_logs
|
||||||
|
WHERE batch_config_id = $1 AND company_code = $2
|
||||||
|
ORDER BY start_time DESC
|
||||||
|
LIMIT $3
|
||||||
|
`;
|
||||||
|
logsParams = [batchId, companyCode, limit];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await query(logsQuery, logsParams);
|
||||||
|
return res.json({ success: true, data: result });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("최근 실행 이력 조회 오류:", error);
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "최근 실행 이력 조회 실패",
|
||||||
|
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,13 @@ import { authenticateToken } from "../middleware/authMiddleware";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/batch-management/stats
|
||||||
|
* 배치 대시보드 통계 (전체/활성 배치 수, 오늘·어제 실행/실패 수)
|
||||||
|
* 반드시 /batch-configs 보다 위에 등록 (/:id로 잡히지 않도록)
|
||||||
|
*/
|
||||||
|
router.get("/stats", authenticateToken, BatchManagementController.getBatchStats);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/batch-management/connections
|
* GET /api/batch-management/connections
|
||||||
* 사용 가능한 커넥션 목록 조회
|
* 사용 가능한 커넥션 목록 조회
|
||||||
|
|
@ -55,6 +62,18 @@ router.get("/batch-configs", authenticateToken, BatchManagementController.getBat
|
||||||
*/
|
*/
|
||||||
router.get("/batch-configs/:id", authenticateToken, BatchManagementController.getBatchConfigById);
|
router.get("/batch-configs/:id", authenticateToken, BatchManagementController.getBatchConfigById);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/batch-management/batch-configs/:id/sparkline
|
||||||
|
* 해당 배치 최근 24시간 1시간 단위 실행 집계
|
||||||
|
*/
|
||||||
|
router.get("/batch-configs/:id/sparkline", authenticateToken, BatchManagementController.getBatchSparkline);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/batch-management/batch-configs/:id/recent-logs
|
||||||
|
* 해당 배치 최근 실행 로그 (최대 20건)
|
||||||
|
*/
|
||||||
|
router.get("/batch-configs/:id/recent-logs", authenticateToken, BatchManagementController.getBatchRecentLogs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/batch-management/batch-configs/:id
|
* PUT /api/batch-management/batch-configs/:id
|
||||||
* 배치 설정 업데이트
|
* 배치 설정 업데이트
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue