penambahan dan perbaikan untuk log
This commit is contained in:
No files matched your search
@@ -159,7 +159,12 @@ func (h *AdminHandler) GetPage(c *gin.Context) {
|
||||
.toast.show { opacity: 1; }
|
||||
.toast-ok { background: #2ecc71; }
|
||||
.toast-err { background: #e74c3c; }
|
||||
.charts-row { display: flex; gap: 16px; margin-bottom: 16px; }
|
||||
.chart-card { background: white; border-radius: 8px; padding: 16px 20px; flex: 1; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.chart-card h3 { font-size: 14px; font-weight: 600; color: #555; margin-bottom: 12px; }
|
||||
@media (max-width: 768px) { .charts-row { flex-direction: column; } }
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -184,6 +189,17 @@ func (h *AdminHandler) GetPage(c *gin.Context) {
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="charts-row">
|
||||
<div class="chart-card">
|
||||
<h3>Status Mapping</h3>
|
||||
<canvas id="chartPie" height="200"></canvas>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h3>Kapasitas per Kelas</h3>
|
||||
<canvas id="chartBar" height="200"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-bar">
|
||||
<input type="text" id="search" placeholder="🔍 Cari nama ruangan..." oninput="filterTable()">
|
||||
</div>
|
||||
@@ -409,6 +425,76 @@ document.querySelectorAll('.modal-overlay').forEach(el => {
|
||||
});
|
||||
|
||||
init();
|
||||
initCharts();
|
||||
|
||||
function initCharts() {
|
||||
const sudah = DATA.filter(r => r.sudah_mapping).length;
|
||||
const belum = DATA.filter(r => !r.sudah_mapping).length;
|
||||
|
||||
// Pie chart — sudah vs belum mapping
|
||||
new Chart(document.getElementById('chartPie'), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['Sudah Mapping', 'Belum Mapping'],
|
||||
datasets: [{
|
||||
data: [sudah, belum],
|
||||
backgroundColor: ['#2ecc71', '#f4a261'],
|
||||
borderWidth: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { position: 'bottom', labels: { font: { size: 12 } } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Bar chart — kapasitas per kelas
|
||||
const kelasSummary = {};
|
||||
DATA.forEach(r => {
|
||||
const kelas = r.kode_kelas || 'Belum';
|
||||
if (!kelasSummary[kelas]) kelasSummary[kelas] = { kapasitas: 0, ruangan: 0 };
|
||||
kelasSummary[kelas].kapasitas += r.jumlah_tt;
|
||||
kelasSummary[kelas].ruangan += 1;
|
||||
});
|
||||
|
||||
const labels = Object.keys(kelasSummary).sort();
|
||||
const kapasitas = labels.map(k => kelasSummary[k].kapasitas);
|
||||
const colors = labels.map((_, i) => 'hsl(' + ((i * 47) % 360) + ', 65%, 55%)');
|
||||
|
||||
new Chart(document.getElementById('chartBar'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Total Kapasitas',
|
||||
data: kapasitas,
|
||||
backgroundColor: colors,
|
||||
borderRadius: 6,
|
||||
borderWidth: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
afterLabel: ctx => {
|
||||
const kelas = labels[ctx.dataIndex];
|
||||
return 'Ruangan: ' + kelasSummary[kelas].ruangan;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: { beginAtZero: true, grid: { color: '#f0f0f0' } },
|
||||
x: { grid: { display: false } }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -20,19 +21,25 @@ type SyncLog struct {
|
||||
ResponseMs int64 `json:"response_ms,omitempty"`
|
||||
}
|
||||
|
||||
var logPath = "./logs/sync.log"
|
||||
// logPath sekarang fungsi yang mengembalikan path berdasarkan tanggal
|
||||
func getLogPath() string {
|
||||
// Format tanggal: 2026-06-11
|
||||
dateStr := time.Now().Format("2006-01-02")
|
||||
return filepath.Join("./logs", fmt.Sprintf("sync-%s.log", dateStr))
|
||||
}
|
||||
|
||||
func init() {
|
||||
_ = os.MkdirAll("./logs", 0755)
|
||||
}
|
||||
|
||||
// WriteLog menulis 1 entry log ke sync.log
|
||||
// WriteLog menulis 1 entry log ke file berdasarkan tanggal hari ini
|
||||
func WriteLog(entry SyncLog) {
|
||||
entry.Timestamp = time.Now().Format(time.RFC3339)
|
||||
writeToFile(entry)
|
||||
logPath := getLogPath()
|
||||
writeToFile(entry, logPath)
|
||||
}
|
||||
|
||||
// WriteBatchLog menulis ringkasan 1 run sync
|
||||
// WriteBatchLog menulis ringkasan 1 run sync ke file berdasarkan tanggal hari ini
|
||||
func WriteBatchLog(result *SyncResult) {
|
||||
if result == nil {
|
||||
return
|
||||
@@ -57,6 +64,7 @@ func WriteBatchLog(result *SyncResult) {
|
||||
"errors": result.Errors,
|
||||
}
|
||||
|
||||
logPath := getLogPath()
|
||||
f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -66,11 +74,10 @@ func WriteBatchLog(result *SyncResult) {
|
||||
line, _ := json.Marshal(summary)
|
||||
_, _ = f.Write(append(line, '\n'))
|
||||
|
||||
// Rotasi log — jaga ukuran file max 5MB
|
||||
rotateLogs()
|
||||
// Rotasi log tidak diperlukan karena sudah otomatis terpisah per hari
|
||||
}
|
||||
|
||||
func writeToFile(entry SyncLog) {
|
||||
func writeToFile(entry SyncLog, logPath string) {
|
||||
f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("gagal buka log file: %v\n", err)
|
||||
@@ -82,15 +89,4 @@ func writeToFile(entry SyncLog) {
|
||||
_, _ = f.Write(append(line, '\n'))
|
||||
}
|
||||
|
||||
// rotateLogs — kalau file > 5MB, rename jadi sync.log.old
|
||||
func rotateLogs() {
|
||||
info, err := os.Stat(logPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 5MB
|
||||
if info.Size() > 5*1024*1024 {
|
||||
_ = os.Rename(logPath, logPath+".old")
|
||||
}
|
||||
}
|
||||
// rotateLogs dihapus karena sudah otomatis terpisah per hari
|
||||
Reference in New Issue
Block a user