diff --git a/docker-compose.yml b/docker-compose.yml
index d088854..3f6c03c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,7 +1,9 @@
services:
dashboard:
build:
- context: .
+ # Les sources actives sont versionnées sous src/. Le contexte racine
+ # pouvait réutiliser d’anciens dossiers backend/frontend restés sur l’hôte.
+ context: ./src
dockerfile: Dockerfile
container_name: manus-dashboard
# Le dashboard doit repartir seul après un arrêt anormal du processus.
diff --git a/src/frontend/src/pages/GiteaPage.jsx b/src/frontend/src/pages/GiteaPage.jsx
index e36f657..42b9ac1 100644
--- a/src/frontend/src/pages/GiteaPage.jsx
+++ b/src/frontend/src/pages/GiteaPage.jsx
@@ -9,8 +9,11 @@ import {
Clock,
User,
Code,
+ CheckCircle2,
+ XCircle,
+ Loader2,
} from 'lucide-react';
-import { getGiteaRepos, getGiteaCommits } from '../utils/api';
+import { getGiteaRepos, getGiteaCommits, getGiteaWorkflowRuns } from '../utils/api';
function formatDate(dateStr) {
if (!dateStr) return 'N/A';
@@ -27,11 +30,25 @@ function formatSize(kb) {
return `${(kb / 1024).toFixed(1)} MB`;
}
+function formatDuration(seconds) {
+ if (!Number.isFinite(seconds)) return 'En attente';
+ const minutes = Math.floor(seconds / 60);
+ const remainingSeconds = seconds % 60;
+ return minutes > 0 ? `${minutes} min ${remainingSeconds.toString().padStart(2, '0')} s` : `${remainingSeconds} s`;
+}
+
+function CiStatus({ run }) {
+ if (run.status !== 'completed') return En cours;
+ if (run.conclusion === 'success') return Réussi;
+ return {run.conclusion || 'Échec'};
+}
+
export default function GiteaPage() {
const [repos, setRepos] = useState([]);
const [loading, setLoading] = useState(true);
const [selectedRepo, setSelectedRepo] = useState(null);
const [commits, setCommits] = useState([]);
+ const [workflowRuns, setWorkflowRuns] = useState([]);
const [loadingCommits, setLoadingCommits] = useState(false);
const fetchRepos = async () => {
@@ -51,11 +68,16 @@ export default function GiteaPage() {
setSelectedRepo(fullName);
try {
const [owner, repo] = fullName.split('/');
- const res = await getGiteaCommits(owner, repo);
- setCommits(res.data);
+ const [commitsResponse, runsResponse] = await Promise.all([
+ getGiteaCommits(owner, repo),
+ getGiteaWorkflowRuns(owner, repo),
+ ]);
+ setCommits(commitsResponse.data);
+ setWorkflowRuns(runsResponse.data);
} catch (err) {
console.error('Erreur chargement commits:', err);
setCommits([]);
+ setWorkflowRuns([]);
} finally {
setLoadingCommits(false);
}
@@ -191,6 +213,27 @@ export default function GiteaPage() {
) : (
+
+
Dernières validations CI
+ {workflowRuns.length === 0 ? (
+
Aucune validation CI trouvée pour ce dépôt.
+ ) : (
+
+ {workflowRuns.slice(0, 5).map((run) => (
+
+
+
{run.name}
+
{run.commit?.slice(0, 7) || 'Commit inconnu'} · {formatDate(run.createdAt)}
+
+
+
+ {formatDuration(run.durationSeconds)}
+
+
+ ))}
+
+ )}
+
{commits.map((commit, idx) => (
diff --git a/src/frontend/src/utils/api.js b/src/frontend/src/utils/api.js
index 322ad77..d3c675e 100644
--- a/src/frontend/src/utils/api.js
+++ b/src/frontend/src/utils/api.js
@@ -59,6 +59,8 @@ export const getDeployments = (appId) =>
export const getGiteaRepos = () => api.get('/gitea/repos');
export const getGiteaCommits = (owner, repo) =>
api.get(`/gitea/repos/${owner}/${repo}/commits`);
+export const getGiteaWorkflowRuns = (owner, repo) =>
+ api.get(`/gitea/repos/${owner}/${repo}/actions/runs`);
// Docker
export const getContainers = () => api.get('/docker/containers');