Merge pull request #331 from bipproduction/bagas/25-feb-25
fix gitignore
This commit is contained in:
16
.gitignore
vendored
16
.gitignore
vendored
@@ -36,13 +36,15 @@ yarn-error.log*
|
|||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|
||||||
# logs
|
# logs - mengabaikan isi logs tetapi menyimpan struktur folder
|
||||||
logs/
|
logs/*
|
||||||
# **/logs/
|
!logs/.gitkeep
|
||||||
# *.log
|
!logs/backend/
|
||||||
# **/*.log
|
!logs/frontend/
|
||||||
# log/
|
logs/backend/*
|
||||||
# **/log/
|
!logs/backend/.gitkeep
|
||||||
|
logs/frontend/*
|
||||||
|
!logs/frontend/.gitkeep
|
||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.2.61](https://github.com/bipproduction/hipmi/compare/v1.2.60...v1.2.61) (2025-02-25)
|
||||||
|
|
||||||
|
## [1.2.60](https://github.com/bipproduction/hipmi/compare/v1.2.59...v1.2.60) (2025-02-25)
|
||||||
|
|
||||||
|
## [1.2.59](https://github.com/bipproduction/hipmi/compare/v1.2.58...v1.2.59) (2025-02-25)
|
||||||
|
|
||||||
## [1.2.58](https://github.com/bipproduction/hipmi/compare/v1.2.57...v1.2.58) (2025-02-25)
|
## [1.2.58](https://github.com/bipproduction/hipmi/compare/v1.2.57...v1.2.58) (2025-02-25)
|
||||||
|
|
||||||
## [1.2.57](https://github.com/bipproduction/hipmi/compare/v1.2.56...v1.2.57) (2025-02-20)
|
## [1.2.57](https://github.com/bipproduction/hipmi/compare/v1.2.56...v1.2.57) (2025-02-20)
|
||||||
|
|||||||
0
logs/.gitkeep
Normal file
0
logs/.gitkeep
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"keep": {
|
||||||
|
"days": true,
|
||||||
|
"amount": 14
|
||||||
|
},
|
||||||
|
"auditLog": "logs/backend/.31d2357fa2026a78b8cb786880c7c733460d7dbe-audit.json",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"date": 1739242050497,
|
||||||
|
"name": "logs/backend/combined-2025-02-11.log",
|
||||||
|
"hash": "13ef770bd22a1c0c4412156ffbf32e0a38fa2d61caa14054dc71d579374e8f56"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1739327386336,
|
||||||
|
"name": "logs/backend/combined-2025-02-12.log",
|
||||||
|
"hash": "e37b3d0427223c278c22797248e12d501c266188b48b4edee0591037415ce990"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1739413125862,
|
||||||
|
"name": "logs/backend/combined-2025-02-13.log",
|
||||||
|
"hash": "51e5ed7dd711ae987cc602dcb0f736c750ff1d7cde4811768c710a70f1fef1f0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1739480222362,
|
||||||
|
"name": "logs/backend/combined-2025-02-14.log",
|
||||||
|
"hash": "7a9bec4aa4fc886c924f82f5195f3e538f10ed5c253513d236a87d55e1e5ff5d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1739758512710,
|
||||||
|
"name": "logs/backend/combined-2025-02-17.log",
|
||||||
|
"hash": "b7bd9b84067ca29a2f86552cba92af2bb1ade78f3e51335cd082380be588d540"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1739845002511,
|
||||||
|
"name": "logs/backend/combined-2025-02-18.log",
|
||||||
|
"hash": "cef0033bca146808caf3dc3c2c1fa6bb71ddfeaa9bb67c640b71de0213a00a3b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1739931481701,
|
||||||
|
"name": "logs/backend/combined-2025-02-19.log",
|
||||||
|
"hash": "94785112f47679d7874b8ce2f99c1d35036a2a29e8389a4a619b9acda671014b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1740017684018,
|
||||||
|
"name": "logs/backend/combined-2025-02-20.log",
|
||||||
|
"hash": "1b367b338d7a84a4fcd35ac0325ac29c3dde5a9118fb7ae213c0c7cca1702297"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1740091704357,
|
||||||
|
"name": "logs/backend/combined-2025-02-21.log",
|
||||||
|
"hash": "528834ada61c18f4a953032166ff0fa8da2aa0ae4dda620da736fa82fb23c2fb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1740284206845,
|
||||||
|
"name": "logs/backend/combined-2025-02-23.log",
|
||||||
|
"hash": "cc2343e5d09b7ba94f004007d8bfa91037df96ea9528bd7bbfc8f2d692aaa420"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1740363413968,
|
||||||
|
"name": "logs/backend/combined-2025-02-24.log",
|
||||||
|
"hash": "e22f3d5ec43b767fdc876b8943a50e13648a7bf1845ffb1b41d5197e5a5ab599"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1740450562875,
|
||||||
|
"name": "logs/backend/combined-2025-02-25.log",
|
||||||
|
"hash": "1ab2165b4d456ebed03006c88d9ce4b4f7be61bd38246e306fecbc73f72f8fdd"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hashType": "sha256"
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"keep": {
|
||||||
|
"days": true,
|
||||||
|
"amount": 14
|
||||||
|
},
|
||||||
|
"auditLog": "logs/backend/.5d9a990e0c6075347623def466341a14f8ba4a12-audit.json",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"date": 1739242050493,
|
||||||
|
"name": "logs/backend/error-2025-02-11.log",
|
||||||
|
"hash": "a346dd5f2327d9f40439c851473199450ec0918d7f0bd4b280343540a0b5ccb0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1739327386330,
|
||||||
|
"name": "logs/backend/error-2025-02-12.log",
|
||||||
|
"hash": "0d834a4ac0d78d547969ae0e55bc53a760b5a11942ed2efb7a08b50b2a16e7ee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1739413125859,
|
||||||
|
"name": "logs/backend/error-2025-02-13.log",
|
||||||
|
"hash": "f3c7e022a48b20a4f445135bd7df00f011051233c423f0129d519b295b105973"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1739480222359,
|
||||||
|
"name": "logs/backend/error-2025-02-14.log",
|
||||||
|
"hash": "41112251dc042a1d8bbcafc529fa6693c3b6ad18a65808ea6c2a0ef90926a44d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1739758512705,
|
||||||
|
"name": "logs/backend/error-2025-02-17.log",
|
||||||
|
"hash": "7bd665435d8c40208c98d79febf882214c107d4888992e38d7b26b795341b955"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1739845002507,
|
||||||
|
"name": "logs/backend/error-2025-02-18.log",
|
||||||
|
"hash": "b1bd0117ab00fe43a68ddb4d8a30eccfe8d7b77e2694a1c6aa518ada1b20a5c8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1739931481684,
|
||||||
|
"name": "logs/backend/error-2025-02-19.log",
|
||||||
|
"hash": "29cfa7b37c18aab735a86b256b6ed3b137a47d66d7a2bdb9293f513197b7d70f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1740017684016,
|
||||||
|
"name": "logs/backend/error-2025-02-20.log",
|
||||||
|
"hash": "70ccdce58b4d0c5e67b8353cdfa67ec8f51704045fc862b873722e2e001eac30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1740091704347,
|
||||||
|
"name": "logs/backend/error-2025-02-21.log",
|
||||||
|
"hash": "0d97111aa4834a1079551e4757e3837ff0343aa9bb86e46ade2e978fc9204874"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1740284206833,
|
||||||
|
"name": "logs/backend/error-2025-02-23.log",
|
||||||
|
"hash": "f89165d25c3eaad0cf77b60059a19439274cd469677494d4d8f8a2d48b17d826"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1740363413963,
|
||||||
|
"name": "logs/backend/error-2025-02-24.log",
|
||||||
|
"hash": "a01e20567b60d4b3c2d2c46728d358bd7c36b078c13e840723e7e0541d3b89c6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1740450562872,
|
||||||
|
"name": "logs/backend/error-2025-02-25.log",
|
||||||
|
"hash": "189996570b83d8dbc20aac1eaf0583edf1472e1d1ac0dc83be0fb9b20a20ee07"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hashType": "sha256"
|
||||||
|
}
|
||||||
0
logs/backend/.gitkeep
Normal file
0
logs/backend/.gitkeep
Normal file
0
logs/frontend/.gitkeep
Normal file
0
logs/frontend/.gitkeep
Normal file
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hipmi",
|
"name": "hipmi",
|
||||||
"version": "1.2.58",
|
"version": "1.2.61",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"prisma": {
|
"prisma": {
|
||||||
|
|||||||
138
src/app/(admin)/logs/logs.module.css
Normal file
138
src/app/(admin)/logs/logs.module.css
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/* app/admin/logs/logs.module.css */
|
||||||
|
.container {
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterContainer {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logsContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logItem {
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorLog {
|
||||||
|
background-color: #fef2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warnLog {
|
||||||
|
background-color: #fefce8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoLog {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorLevel {
|
||||||
|
background-color: #fee2e2;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warnLevel {
|
||||||
|
background-color: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoLevel {
|
||||||
|
background-color: #dbeafe;
|
||||||
|
color: #1e40af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #dc2626;
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover effects */
|
||||||
|
.logItem:hover {
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select:hover {
|
||||||
|
border-color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus states */
|
||||||
|
.select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #2563eb;
|
||||||
|
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logHeader {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/app/(admin)/logs/page.tsx
Normal file
106
src/app/(admin)/logs/page.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
// app/admin/logs/page.tsx
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import styles from "./logs.module.css";
|
||||||
|
|
||||||
|
interface LogEntry {
|
||||||
|
timestamp: string;
|
||||||
|
level: string;
|
||||||
|
message: string;
|
||||||
|
metadata?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LogsPage() {
|
||||||
|
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [filter, setFilter] = useState<"all" | "error" | "info" | "warn">(
|
||||||
|
"all"
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchLogs();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function fetchLogs() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/logs/view");
|
||||||
|
if (!response.ok) throw new Error("Failed to fetch logs");
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
setLogs(data.logs);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : "Error fetching logs");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredLogs = logs.filter((log) =>
|
||||||
|
filter === "all" ? true : log.level === filter
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loading) return <div className={styles.loading}>Loading logs...</div>;
|
||||||
|
if (error) return <div className={styles.error}>Error: {error}</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<h1 className={styles.title}>System Logs</h1>
|
||||||
|
|
||||||
|
<div className={styles.filterContainer}>
|
||||||
|
<select
|
||||||
|
value={filter}
|
||||||
|
onChange={(e) => setFilter(e.target.value as any)}
|
||||||
|
className={styles.select}
|
||||||
|
>
|
||||||
|
<option value="all">All Logs</option>
|
||||||
|
<option value="error">Errors</option>
|
||||||
|
<option value="warn">Warnings</option>
|
||||||
|
<option value="info">Info</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.logsContainer}>
|
||||||
|
{filteredLogs.map((log, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`${styles.logItem} ${
|
||||||
|
log.level === "error"
|
||||||
|
? styles.errorLog
|
||||||
|
: log.level === "warn"
|
||||||
|
? styles.warnLog
|
||||||
|
: styles.infoLog
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className={styles.logHeader}>
|
||||||
|
<span className={styles.timestamp}>
|
||||||
|
{format(new Date(log.timestamp), "yyyy-MM-dd HH:mm:ss")}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`${styles.level} ${
|
||||||
|
log.level === "error"
|
||||||
|
? styles.errorLevel
|
||||||
|
: log.level === "warn"
|
||||||
|
? styles.warnLevel
|
||||||
|
: styles.infoLevel
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{log.level.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.message}>{log.message}</div>
|
||||||
|
|
||||||
|
{log.metadata && (
|
||||||
|
<pre className={styles.metadata}>
|
||||||
|
{JSON.stringify(log.metadata, null, 2)}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
71
src/app/api/logs/route.ts
Normal file
71
src/app/api/logs/route.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// app/api/logs/view/route.ts
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
interface LogEntry {
|
||||||
|
timestamp: string;
|
||||||
|
level: string;
|
||||||
|
message: string;
|
||||||
|
metadata?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readLogFiles(directory: string): Promise<LogEntry[]> {
|
||||||
|
try {
|
||||||
|
const logPath = path.join(process.cwd(), directory);
|
||||||
|
const files = await fs.readdir(logPath);
|
||||||
|
const logFiles = files.filter((file) => file.endsWith(".log"));
|
||||||
|
|
||||||
|
const allLogs: LogEntry[] = [];
|
||||||
|
|
||||||
|
for (const file of logFiles) {
|
||||||
|
const filePath = path.join(logPath, file);
|
||||||
|
const content = await fs.readFile(filePath, "utf-8");
|
||||||
|
|
||||||
|
// Parse setiap baris log
|
||||||
|
const logs = content
|
||||||
|
.split("\n")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((line) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(line);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
allLogs.push(...logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort berdasarkan timestamp, terbaru di atas
|
||||||
|
return allLogs.sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error reading log files:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
// Baca logs dari frontend dan backend
|
||||||
|
const frontendLogs = await readLogFiles("logs/frontend");
|
||||||
|
const backendLogs = await readLogFiles("logs/backend");
|
||||||
|
|
||||||
|
// Gabungkan dan sort semua logs
|
||||||
|
const allLogs = [...frontendLogs, ...backendLogs].sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json({ logs: allLogs });
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Failed to fetch logs" },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/app/api/logs/view/route.ts
Normal file
71
src/app/api/logs/view/route.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// app/api/logs/view/route.ts
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
interface LogEntry {
|
||||||
|
timestamp: string;
|
||||||
|
level: string;
|
||||||
|
message: string;
|
||||||
|
metadata?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readLogFiles(directory: string): Promise<LogEntry[]> {
|
||||||
|
try {
|
||||||
|
const logPath = path.join(process.cwd(), directory);
|
||||||
|
const files = await fs.readdir(logPath);
|
||||||
|
const logFiles = files.filter((file) => file.endsWith(".log"));
|
||||||
|
|
||||||
|
const allLogs: LogEntry[] = [];
|
||||||
|
|
||||||
|
for (const file of logFiles) {
|
||||||
|
const filePath = path.join(logPath, file);
|
||||||
|
const content = await fs.readFile(filePath, "utf-8");
|
||||||
|
|
||||||
|
// Parse setiap baris log
|
||||||
|
const logs = content
|
||||||
|
.split("\n")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((line) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(line);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
allLogs.push(...logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort berdasarkan timestamp, terbaru di atas
|
||||||
|
return allLogs.sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error reading log files:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
// Baca logs dari frontend dan backend
|
||||||
|
const frontendLogs = await readLogFiles("logs/frontend");
|
||||||
|
const backendLogs = await readLogFiles("logs/backend");
|
||||||
|
|
||||||
|
// Gabungkan dan sort semua logs
|
||||||
|
const allLogs = [...frontendLogs, ...backendLogs].sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json({ logs: allLogs });
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Failed to fetch logs" },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +1,9 @@
|
|||||||
import { Admin_Investasi } from "@/app_modules/admin/investasi";
|
import { Admin_Investasi } from "@/app_modules/admin/investasi";
|
||||||
import Admin_CountStatusInvestasi from "@/app_modules/admin/investasi/fun/count_status";
|
|
||||||
import Admin_funGetAllInvestasi from "@/app_modules/admin/investasi/fun/get_all_investasi";
|
|
||||||
import Admin_getPublishProgresInvestasi from "@/app_modules/admin/investasi/fun/get_publish_progres";
|
|
||||||
import Admin_getTotalInvestasiByUser from "@/app_modules/admin/investasi/fun/get_total_investasi_by_user";
|
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const listInvestasi = await Admin_funGetAllInvestasi();
|
|
||||||
// const countDraft = await Admin_CountStatusInvestasi(1);
|
|
||||||
// const countReview = await Admin_CountStatusInvestasi(2);
|
|
||||||
// const countPublish = await Admin_CountStatusInvestasi(3);
|
|
||||||
// const countReject = await Admin_CountStatusInvestasi(4);
|
|
||||||
const totalInvestasiByUser = await Admin_getTotalInvestasiByUser()
|
|
||||||
const publishProgres = await Admin_getPublishProgresInvestasi()
|
|
||||||
// console.log(targetTerbesar)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Admin_Investasi
|
<Admin_Investasi />
|
||||||
listInvestasi={listInvestasi as any}
|
|
||||||
// countDraft={countDraft}
|
|
||||||
// countReview={countReview}
|
|
||||||
// countPublish={countPublish}
|
|
||||||
// countReject={countReject}
|
|
||||||
totalInvestasiByUser={totalInvestasiByUser}
|
|
||||||
publishProgres={publishProgres}
|
|
||||||
|
|
||||||
/>
|
|
||||||
{/* <pre>{JSON.stringify(totalInvestasiByUser, null,2)}</pre> */}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,25 +52,14 @@ import global_limit from "@/lib/limit";
|
|||||||
import { useShallowEffect } from "@mantine/hooks";
|
import { useShallowEffect } from "@mantine/hooks";
|
||||||
import { apiGetAdminInvestasiCountDashboard } from "../_lib/api_fetch_admin_investasi";
|
import { apiGetAdminInvestasiCountDashboard } from "../_lib/api_fetch_admin_investasi";
|
||||||
|
|
||||||
|
export default function Admin_Investasi() {
|
||||||
export default function Admin_Investasi({
|
|
||||||
listInvestasi,
|
|
||||||
totalInvestasiByUser,
|
|
||||||
publishProgres,
|
|
||||||
}: {
|
|
||||||
listInvestasi: MODEL_INVESTASI[];
|
|
||||||
totalInvestasiByUser: any[];
|
|
||||||
publishProgres: any[];
|
|
||||||
}) {
|
|
||||||
const [investasi, setInvestasi] = useState(listInvestasi);
|
|
||||||
const router = useRouter();
|
|
||||||
const [countPublish, setCountPublish] = useState<number | null>(null);
|
const [countPublish, setCountPublish] = useState<number | null>(null);
|
||||||
const [countReview, setCountReview] = useState<number | null>(null);
|
const [countReview, setCountReview] = useState<number | null>(null);
|
||||||
const [countReject, setCountReject] = useState<number | null>(null);
|
const [countReject, setCountReject] = useState<number | null>(null);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
handlerLoadData();
|
handlerLoadData();
|
||||||
}, [])
|
}, []);
|
||||||
async function handlerLoadData() {
|
async function handlerLoadData() {
|
||||||
try {
|
try {
|
||||||
const listLoadData = [
|
const listLoadData = [
|
||||||
@@ -79,7 +68,6 @@ export default function Admin_Investasi({
|
|||||||
global_limit(() => onLoadCountReject()),
|
global_limit(() => onLoadCountReject()),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
const result = await Promise.all(listLoadData);
|
const result = await Promise.all(listLoadData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
clientLogger.error("Error handler load data", error);
|
clientLogger.error("Error handler load data", error);
|
||||||
@@ -88,13 +76,10 @@ export default function Admin_Investasi({
|
|||||||
|
|
||||||
async function onLoadCountPublish() {
|
async function onLoadCountPublish() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const response = await apiGetAdminInvestasiCountDashboard({
|
const response = await apiGetAdminInvestasiCountDashboard({
|
||||||
name: "Publish",
|
name: "Publish",
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Response Publish", response);
|
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
setCountPublish(response.data);
|
setCountPublish(response.data);
|
||||||
}
|
}
|
||||||
@@ -148,31 +133,32 @@ export default function Admin_Investasi({
|
|||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Review",
|
name: "Review",
|
||||||
jumlah: countReview == null ? (
|
jumlah:
|
||||||
<CustomSkeleton height={40} width={40} />
|
countReview == null ? (
|
||||||
) : countReview ? (
|
<CustomSkeleton height={40} width={40} />
|
||||||
countReview
|
) : countReview ? (
|
||||||
) : (
|
countReview
|
||||||
"-"
|
) : (
|
||||||
),
|
"-"
|
||||||
|
),
|
||||||
path: RouterAdminInvestasi_OLD.table_status_review,
|
path: RouterAdminInvestasi_OLD.table_status_review,
|
||||||
color: MainColor.orange,
|
color: MainColor.orange,
|
||||||
icon: <IconBookmark size={18} color="#FF7043" />
|
icon: <IconBookmark size={18} color="#FF7043" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
name: "Reject",
|
name: "Reject",
|
||||||
jumlah: countReject == null ? (
|
jumlah:
|
||||||
<CustomSkeleton height={40} width={40} />
|
countReject == null ? (
|
||||||
) : countReject ? (
|
<CustomSkeleton height={40} width={40} />
|
||||||
countReject
|
) : countReject ? (
|
||||||
) : (
|
countReject
|
||||||
"-"
|
) : (
|
||||||
),
|
"-"
|
||||||
|
),
|
||||||
path: RouterAdminInvestasi_OLD.table_status_reject,
|
path: RouterAdminInvestasi_OLD.table_status_reject,
|
||||||
color: MainColor.red,
|
color: MainColor.red,
|
||||||
icon: <IconAlertTriangle size={18} color="#FF4B4C" />
|
icon: <IconAlertTriangle size={18} color="#FF4B4C" />,
|
||||||
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -197,19 +183,23 @@ export default function Admin_Investasi({
|
|||||||
shadow="md"
|
shadow="md"
|
||||||
radius="md"
|
radius="md"
|
||||||
p="md"
|
p="md"
|
||||||
// sx={{ borderColor: e.color, borderStyle: "solid" }}
|
// sx={{ borderColor: e.color, borderStyle: "solid" }}
|
||||||
>
|
>
|
||||||
|
|
||||||
<Stack spacing={0}>
|
<Stack spacing={0}>
|
||||||
<Text fw={"bold"} color={AccentColor.white}>{e.name}</Text>
|
<Text fw={"bold"} color={AccentColor.white}>
|
||||||
|
{e.name}
|
||||||
|
</Text>
|
||||||
<Flex align={"center"} justify={"space-between"}>
|
<Flex align={"center"} justify={"space-between"}>
|
||||||
<Title c={AccentColor.white}>{e.jumlah}</Title>
|
<Title c={AccentColor.white}>{e.jumlah}</Title>
|
||||||
<ThemeIcon radius={"xl"} size={"md"} color={AccentColor.white}>
|
<ThemeIcon
|
||||||
|
radius={"xl"}
|
||||||
|
size={"md"}
|
||||||
|
color={AccentColor.white}
|
||||||
|
>
|
||||||
{e.icon}
|
{e.icon}
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
</Paper>
|
</Paper>
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
|||||||
Reference in New Issue
Block a user