173 lines
5.3 KiB
TypeScript
173 lines
5.3 KiB
TypeScript
|
|
import { useEffect, useRef } from 'react';
|
||
|
|
import SwaggerUI from 'swagger-ui-react';
|
||
|
|
import 'swagger-ui-react/swagger-ui.css';
|
||
|
|
import { env } from '@/config/env';
|
||
|
|
import { logger } from '@/utils/logger';
|
||
|
|
|
||
|
|
interface SwaggerUIProps {
|
||
|
|
specUrl?: string;
|
||
|
|
spec?: object;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Composant Swagger UI pour afficher la documentation API
|
||
|
|
* Charge le fichier OpenAPI depuis le backend ou utilise un spec fourni
|
||
|
|
*/
|
||
|
|
export function SwaggerUIDoc({ specUrl, spec }: SwaggerUIProps) {
|
||
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
||
|
|
|
||
|
|
// Construire l'URL du fichier OpenAPI/Swagger
|
||
|
|
const getOpenApiUrl = () => {
|
||
|
|
if (specUrl) return specUrl;
|
||
|
|
|
||
|
|
// Si API_URL est relatif, construire l'URL complète
|
||
|
|
const apiBase = env.API_URL.startsWith('http')
|
||
|
|
? env.API_URL
|
||
|
|
: `${window.location.origin}${env.API_URL}`;
|
||
|
|
|
||
|
|
// Le backend sert Swagger à /swagger/doc.json ou /docs/swagger.json
|
||
|
|
// Retirer /api/v1 et ajouter /swagger/doc.json
|
||
|
|
const baseUrl = apiBase.replace(/\/api\/v1$/, '');
|
||
|
|
// Essayer d'abord /swagger/doc.json (endpoint Swagger standard)
|
||
|
|
return `${baseUrl}/swagger/doc.json`;
|
||
|
|
};
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (containerRef.current) {
|
||
|
|
logger.debug('Swagger UI initialized', {
|
||
|
|
specUrl: specUrl || getOpenApiUrl(),
|
||
|
|
hasSpec: !!spec,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}, [specUrl, spec]);
|
||
|
|
|
||
|
|
const swaggerConfig = {
|
||
|
|
url: spec ? undefined : getOpenApiUrl(),
|
||
|
|
spec: spec,
|
||
|
|
deepLinking: true,
|
||
|
|
displayOperationId: false,
|
||
|
|
defaultModelsExpandDepth: 1,
|
||
|
|
defaultModelExpandDepth: 1,
|
||
|
|
docExpansion: 'list' as const,
|
||
|
|
filter: true,
|
||
|
|
showExtensions: true,
|
||
|
|
showCommonExtensions: true,
|
||
|
|
tryItOutEnabled: true,
|
||
|
|
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
|
||
|
|
requestInterceptor: (request: any) => {
|
||
|
|
// Ajouter le token d'authentification si disponible
|
||
|
|
const token = localStorage.getItem('access_token');
|
||
|
|
if (token && request.headers) {
|
||
|
|
request.headers['Authorization'] = `Bearer ${token}`;
|
||
|
|
}
|
||
|
|
// Ajouter le CSRF token si disponible
|
||
|
|
const csrfToken = localStorage.getItem('csrf_token');
|
||
|
|
if (csrfToken && request.headers) {
|
||
|
|
request.headers['X-CSRF-Token'] = csrfToken;
|
||
|
|
}
|
||
|
|
return request;
|
||
|
|
},
|
||
|
|
onComplete: () => {
|
||
|
|
logger.debug('Swagger UI loaded successfully', {
|
||
|
|
url: getOpenApiUrl(),
|
||
|
|
});
|
||
|
|
},
|
||
|
|
onFailure: (error: Error) => {
|
||
|
|
logger.error('Failed to load Swagger UI', {
|
||
|
|
error: error.message,
|
||
|
|
stack: error.stack,
|
||
|
|
url: getOpenApiUrl(),
|
||
|
|
});
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
ref={containerRef}
|
||
|
|
className="swagger-ui-container"
|
||
|
|
style={{
|
||
|
|
height: '100%',
|
||
|
|
minHeight: '600px',
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<style>{`
|
||
|
|
.swagger-ui-container .swagger-ui {
|
||
|
|
background: transparent;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .topbar {
|
||
|
|
display: none;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .info {
|
||
|
|
margin: 20px 0;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .info .title {
|
||
|
|
color: #fff;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .scheme-container {
|
||
|
|
background: rgba(255, 255, 255, 0.05);
|
||
|
|
padding: 10px;
|
||
|
|
border-radius: 8px;
|
||
|
|
margin: 20px 0;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .opblock {
|
||
|
|
background: rgba(255, 255, 255, 0.03);
|
||
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
|
|
border-radius: 8px;
|
||
|
|
margin: 10px 0;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .opblock.opblock-post {
|
||
|
|
border-color: rgba(102, 252, 241, 0.3);
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .opblock.opblock-get {
|
||
|
|
border-color: rgba(102, 252, 241, 0.3);
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .opblock.opblock-put {
|
||
|
|
border-color: rgba(255, 193, 7, 0.3);
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .opblock.opblock-delete {
|
||
|
|
border-color: rgba(244, 67, 54, 0.3);
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .opblock .opblock-summary {
|
||
|
|
color: #fff;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .opblock .opblock-description-wrapper {
|
||
|
|
color: rgba(255, 255, 255, 0.8);
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .parameter__name {
|
||
|
|
color: #66fcf1;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .response-col_status {
|
||
|
|
color: #fff;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .response-col_description {
|
||
|
|
color: rgba(255, 255, 255, 0.8);
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui input,
|
||
|
|
.swagger-ui-container .swagger-ui select,
|
||
|
|
.swagger-ui-container .swagger-ui textarea {
|
||
|
|
background: rgba(255, 255, 255, 0.1);
|
||
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
|
|
color: #fff;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .btn {
|
||
|
|
background: #66fcf1;
|
||
|
|
color: #000;
|
||
|
|
border: none;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .btn:hover {
|
||
|
|
background: #52e8e0;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .btn.execute {
|
||
|
|
background: #66fcf1;
|
||
|
|
color: #000;
|
||
|
|
}
|
||
|
|
.swagger-ui-container .swagger-ui .btn.cancel {
|
||
|
|
background: rgba(255, 255, 255, 0.1);
|
||
|
|
color: #fff;
|
||
|
|
}
|
||
|
|
`}</style>
|
||
|
|
<SwaggerUI {...swaggerConfig} />
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|