crm-summit/app/Views/gitea_dashboard.php
mahdahar ec5f2fc385 feat(gitea): add database-backed sync, API, and dashboard views
Add Gitea sync service with full and incremental modes, paged API fetch, upsert logic for users/repos/commits/PRs, and error aggregation.

Add migration for git_users, git_repositories, git_commits, git_pull_requests with indexes and unique constraints; add models and sync scripts for full/incremental jobs.

Update Gitea UI and dashboard filters (user/repo/date), aggregate commit loading across repositories, and wire routes/controllers/sidebar for dashboard and sync endpoints.
2026-04-22 16:39:30 +07:00

260 lines
7.6 KiB
PHP

<?= $this->extend('layouts/main.php') ?>
<?= $this->section('content') ?>
<div class="page-wrapper">
<div class="container-fluid">
<div class="row page-titles">
<div class="col-md-6 align-self-center">
<h4 class="text-themecolor">Gitea Dashboard (DB)</h4>
</div>
<div class="col-md-6 text-end">
<?php if (!empty($isAdmin)): ?>
<button id="btnSync" class="btn btn-primary">Sync Now</button>
<?php endif; ?>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-3">
<label class="form-label">User</label>
<select id="filterUser" class="form-select"><option value="">All User</option></select>
</div>
<div class="col-md-3">
<label class="form-label">Repository</label>
<select id="filterRepo" class="form-select"><option value="">All Repository</option></select>
</div>
<div class="col-md-2">
<label class="form-label">Start Date</label>
<input type="date" id="filterStart" class="form-control">
</div>
<div class="col-md-2">
<label class="form-label">End Date</label>
<input type="date" id="filterEnd" class="form-control">
</div>
<div class="col-md-2 d-flex align-items-end">
<button id="btnApplyFilter" class="btn btn-success w-100">Apply</button>
</div>
</div>
<div class="row">
<div class="col-12 mb-3">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="mb-0">Latest Commits</h5>
<div><strong>Total Commits:</strong> <span id="totalCommits">0</span></div>
</div>
<div class="table-responsive">
<table class="table table-sm table-bordered" id="tableCommits">
<thead>
<tr>
<th>Date</th>
<th>Repository</th>
<th>User</th>
<th>SHA</th>
<th>Message</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-body">
<h5>Latest Pull Requests</h5>
<div class="table-responsive">
<table class="table table-sm table-bordered" id="tablePrs">
<thead>
<tr>
<th>Updated</th>
<th>Repository</th>
<th>User</th>
<th>PR</th>
<th>State</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('script') ?>
<script>
document.addEventListener('DOMContentLoaded', async () => {
const totalCommits = document.getElementById('totalCommits');
const filterRepo = document.getElementById('filterRepo');
const filterUser = document.getElementById('filterUser');
const filterStart = document.getElementById('filterStart');
const filterEnd = document.getElementById('filterEnd');
const btnApplyFilter = document.getElementById('btnApplyFilter');
const btnSync = document.getElementById('btnSync');
setDefaultDateRange();
await loadUsers();
await loadRepos();
await loadTables();
btnApplyFilter.addEventListener('click', loadTables);
if (btnSync) {
btnSync.addEventListener('click', async () => {
btnSync.disabled = true;
btnSync.innerText = 'Syncing...';
try {
const response = await fetch(`<?= base_url('api/git/sync') ?>`, {
method: 'POST'
});
const result = await response.json();
if (response.ok) {
alert('Sync complete');
await loadTables();
} else {
alert(result.message || 'Sync failed');
}
} catch (error) {
alert('Sync request failed');
}
btnSync.disabled = false;
btnSync.innerText = 'Sync Now';
});
}
function setDefaultDateRange() {
const today = new Date();
const startOfYear = new Date(today.getFullYear(), 0, 1);
filterStart.value = toInputDate(startOfYear);
filterEnd.value = toInputDate(today);
}
function toInputDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
async function loadUsers() {
const response = await fetch(`<?= base_url('api/git/users') ?>`);
const result = await response.json();
if (!response.ok || !Array.isArray(result.data)) {
return;
}
result.data.forEach(item => {
const option = document.createElement('option');
option.value = item.id;
option.textContent = item.username;
filterUser.appendChild(option);
});
}
async function loadRepos() {
const response = await fetch(`<?= base_url('api/git/repositories') ?>`);
const result = await response.json();
if (!response.ok || !Array.isArray(result.data)) {
return;
}
result.data.forEach(item => {
const option = document.createElement('option');
option.value = item.id;
option.textContent = item.full_name;
filterRepo.appendChild(option);
});
}
async function loadTables() {
await loadCommits();
await loadPullRequests();
}
function buildParams() {
const params = new URLSearchParams();
if (filterRepo.value) params.set('repo_id', filterRepo.value);
if (filterUser.value) params.set('user_id', filterUser.value);
if (filterStart.value) params.set('start_date', filterStart.value);
if (filterEnd.value) params.set('end_date', filterEnd.value);
params.set('limit', '200');
return params.toString();
}
async function loadCommits() {
const tbody = document.querySelector('#tableCommits tbody');
tbody.innerHTML = '<tr><td colspan="5">Loading...</td></tr>';
const response = await fetch(`<?= base_url('api/git/commits') ?>?${buildParams()}`);
const result = await response.json();
if (!response.ok || !Array.isArray(result.data)) {
totalCommits.innerText = '0';
tbody.innerHTML = '<tr><td colspan="5">Failed loading commits</td></tr>';
return;
}
totalCommits.innerText = String(result.data.length);
if (!result.data.length) {
tbody.innerHTML = '<tr><td colspan="5">No commits found</td></tr>';
return;
}
tbody.innerHTML = result.data.map(item => {
const msg = (item.message || '').replace(/</g, '&lt;').replace(/>/g, '&gt;').slice(0, 120);
const sha = item.short_sha || '';
const link = item.html_url ? `<a href="${item.html_url}" target="_blank">${sha}</a>` : sha;
return `<tr>
<td>${item.committed_at || ''}</td>
<td>${item.repository_full_name || ''}</td>
<td>${item.user_username || item.author_name || ''}</td>
<td>${link}</td>
<td>${msg}</td>
</tr>`;
}).join('');
}
async function loadPullRequests() {
const tbody = document.querySelector('#tablePrs tbody');
tbody.innerHTML = '<tr><td colspan="5">Loading...</td></tr>';
const response = await fetch(`<?= base_url('api/git/pull-requests') ?>?${buildParams()}`);
const result = await response.json();
if (!response.ok || !Array.isArray(result.data)) {
tbody.innerHTML = '<tr><td colspan="5">Failed loading pull requests</td></tr>';
return;
}
if (!result.data.length) {
tbody.innerHTML = '<tr><td colspan="5">No pull requests found</td></tr>';
return;
}
tbody.innerHTML = result.data.map(item => {
const title = (item.title || '').replace(/</g, '&lt;').replace(/>/g, '&gt;').slice(0, 100);
const prText = `#${item.number} ${title}`;
const link = item.html_url ? `<a href="${item.html_url}" target="_blank">${prText}</a>` : prText;
return `<tr>
<td>${item.updated_at_gitea || ''}</td>
<td>${item.repository_full_name || ''}</td>
<td>${item.user_username || ''}</td>
<td>${link}</td>
<td>${item.state || ''}</td>
</tr>`;
}).join('');
}
});
</script>
<?= $this->endSection() ?>