Implement unified API standard across backend viewsets and serializers, enhancing error handling and response formatting. Update AccountModelViewSet to standardize CRUD operations with success and error responses. Refactor various viewsets to inherit from AccountModelViewSet, ensuring compliance with the new standard. Improve frontend components to handle API responses consistently and update configuration for better user experience.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
interface ComponentCardProps {
|
||||
title: string;
|
||||
title: string | React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
className?: string; // Additional custom classes for styling
|
||||
desc?: string; // Description text
|
||||
desc?: string | React.ReactNode; // Description text
|
||||
}
|
||||
|
||||
const ComponentCard: React.FC<ComponentCardProps> = ({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { useLocation } from "react-router";
|
||||
import { API_BASE_URL } from "../../services/api";
|
||||
import { useAuthStore } from "../../store/authStore";
|
||||
|
||||
@@ -37,7 +38,7 @@ const endpointGroups = [
|
||||
},
|
||||
{
|
||||
name: "Planner Module",
|
||||
abbreviation: "PL",
|
||||
abbreviation: "PM",
|
||||
endpoints: [
|
||||
{ path: "/v1/planner/keywords/", method: "GET" },
|
||||
{ path: "/v1/planner/keywords/auto_cluster/", method: "POST" },
|
||||
@@ -49,7 +50,7 @@ const endpointGroups = [
|
||||
},
|
||||
{
|
||||
name: "Writer Module",
|
||||
abbreviation: "WR",
|
||||
abbreviation: "WM",
|
||||
endpoints: [
|
||||
{ path: "/v1/writer/tasks/", method: "GET" },
|
||||
{ path: "/v1/writer/tasks/auto_generate_content/", method: "POST" },
|
||||
@@ -60,6 +61,48 @@ const endpointGroups = [
|
||||
{ path: "/v1/writer/images/generate_images/", method: "POST" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "CRUD Operations - Planner",
|
||||
abbreviation: "PC",
|
||||
endpoints: [
|
||||
{ path: "/v1/planner/keywords/", method: "GET" },
|
||||
{ path: "/v1/planner/keywords/", method: "POST" },
|
||||
{ path: "/v1/planner/keywords/1/", method: "GET" },
|
||||
{ path: "/v1/planner/keywords/1/", method: "PUT" },
|
||||
{ path: "/v1/planner/keywords/1/", method: "DELETE" },
|
||||
{ path: "/v1/planner/clusters/", method: "GET" },
|
||||
{ path: "/v1/planner/clusters/", method: "POST" },
|
||||
{ path: "/v1/planner/clusters/1/", method: "GET" },
|
||||
{ path: "/v1/planner/clusters/1/", method: "PUT" },
|
||||
{ path: "/v1/planner/clusters/1/", method: "DELETE" },
|
||||
{ path: "/v1/planner/ideas/", method: "GET" },
|
||||
{ path: "/v1/planner/ideas/", method: "POST" },
|
||||
{ path: "/v1/planner/ideas/1/", method: "GET" },
|
||||
{ path: "/v1/planner/ideas/1/", method: "PUT" },
|
||||
{ path: "/v1/planner/ideas/1/", method: "DELETE" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "CRUD Operations - Writer",
|
||||
abbreviation: "WC",
|
||||
endpoints: [
|
||||
{ path: "/v1/writer/tasks/", method: "GET" },
|
||||
{ path: "/v1/writer/tasks/", method: "POST" },
|
||||
{ path: "/v1/writer/tasks/1/", method: "GET" },
|
||||
{ path: "/v1/writer/tasks/1/", method: "PUT" },
|
||||
{ path: "/v1/writer/tasks/1/", method: "DELETE" },
|
||||
{ path: "/v1/writer/content/", method: "GET" },
|
||||
{ path: "/v1/writer/content/", method: "POST" },
|
||||
{ path: "/v1/writer/content/1/", method: "GET" },
|
||||
{ path: "/v1/writer/content/1/", method: "PUT" },
|
||||
{ path: "/v1/writer/content/1/", method: "DELETE" },
|
||||
{ path: "/v1/writer/images/", method: "GET" },
|
||||
{ path: "/v1/writer/images/", method: "POST" },
|
||||
{ path: "/v1/writer/images/1/", method: "GET" },
|
||||
{ path: "/v1/writer/images/1/", method: "PUT" },
|
||||
{ path: "/v1/writer/images/1/", method: "DELETE" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "System & Billing",
|
||||
abbreviation: "SY",
|
||||
@@ -67,7 +110,7 @@ const endpointGroups = [
|
||||
{ path: "/v1/system/prompts/", method: "GET" },
|
||||
{ path: "/v1/system/author-profiles/", method: "GET" },
|
||||
{ path: "/v1/system/strategies/", method: "GET" },
|
||||
{ path: "/v1/system/settings/integrations/1/test/", method: "POST" },
|
||||
{ path: "/v1/system/settings/integrations/openai/test/", method: "POST" },
|
||||
{ path: "/v1/system/settings/account/", method: "GET" },
|
||||
{ path: "/v1/billing/credits/balance/balance/", method: "GET" },
|
||||
{ path: "/v1/billing/credits/usage/", method: "GET" },
|
||||
@@ -79,6 +122,7 @@ const endpointGroups = [
|
||||
|
||||
export default function ApiStatusIndicator() {
|
||||
const { user } = useAuthStore();
|
||||
const location = useLocation();
|
||||
const [groupStatuses, setGroupStatuses] = useState<GroupStatus[]>([]);
|
||||
const [isChecking, setIsChecking] = useState(false);
|
||||
const intervalRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
@@ -86,8 +130,11 @@ export default function ApiStatusIndicator() {
|
||||
// Only show and run for aws-admin accounts
|
||||
const isAwsAdmin = user?.account?.slug === 'aws-admin';
|
||||
|
||||
// Return null if not aws-admin account
|
||||
if (!isAwsAdmin) {
|
||||
// Only run API checks on API monitor page to avoid console errors on other pages
|
||||
const isApiMonitorPage = location.pathname === '/settings/api-monitor';
|
||||
|
||||
// Return null if not aws-admin account or not on API monitor page
|
||||
if (!isAwsAdmin || !isApiMonitorPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -141,8 +188,17 @@ export default function ApiStatusIndicator() {
|
||||
body = { username: 'test', password: 'test' };
|
||||
} else if (path.includes('/register/')) {
|
||||
body = { username: 'test', email: 'test@test.com', password: 'test' };
|
||||
} else if (path.includes('/bulk_delete/')) {
|
||||
body = { ids: [] }; // Empty array to trigger validation error
|
||||
} else if (path.includes('/bulk_update/')) {
|
||||
body = { ids: [] }; // Empty array to trigger validation error
|
||||
}
|
||||
fetchOptions.body = JSON.stringify(body);
|
||||
} else if (method === 'PUT' || method === 'DELETE') {
|
||||
// For PUT/DELETE, we need to send a body for PUT or handle DELETE
|
||||
if (method === 'PUT') {
|
||||
fetchOptions.body = JSON.stringify({}); // Empty object to trigger validation
|
||||
}
|
||||
}
|
||||
|
||||
// Suppress console errors for expected 400 responses (validation errors from test data)
|
||||
@@ -154,11 +210,13 @@ export default function ApiStatusIndicator() {
|
||||
path.includes('/test/')
|
||||
);
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}${path}`, fetchOptions);
|
||||
|
||||
// Suppress console errors for expected 400 responses
|
||||
if (!isExpected400 || response.status !== 400) {
|
||||
// Only log if it's not an expected 400
|
||||
// Use a silent fetch that won't log to console for expected errors
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(`${API_BASE_URL}${path}`, fetchOptions);
|
||||
} catch (fetchError) {
|
||||
// Network errors are real errors
|
||||
return 'error';
|
||||
}
|
||||
|
||||
if (actualMethod === 'OPTIONS') {
|
||||
@@ -176,24 +234,31 @@ export default function ApiStatusIndicator() {
|
||||
} else if (response.status === 401 || response.status === 403) {
|
||||
return 'warning';
|
||||
} else if (response.status === 404) {
|
||||
return 'error';
|
||||
// For GET requests to specific resource IDs (e.g., /v1/planner/keywords/1/),
|
||||
// 404 is expected and healthy (resource doesn't exist, but endpoint works correctly)
|
||||
// For other GET requests (like list endpoints), 404 means endpoint doesn't exist
|
||||
const isResourceByIdRequest = /\/\d+\/?$/.test(path); // Path ends with /number/ or /number
|
||||
if (isResourceByIdRequest) {
|
||||
return 'healthy'; // GET to specific ID returning 404 is healthy (endpoint exists, resource doesn't)
|
||||
}
|
||||
return 'error'; // Endpoint doesn't exist
|
||||
} else if (response.status >= 500) {
|
||||
return 'error';
|
||||
}
|
||||
return 'warning';
|
||||
} else if (method === 'POST') {
|
||||
// Suppress console errors for expected 400 responses (validation errors from test data)
|
||||
// CRUD POST endpoints (like /v1/planner/keywords/, /v1/writer/tasks/) return 400 for empty/invalid test data
|
||||
const isExpected400 = path.includes('/login/') ||
|
||||
path.includes('/register/') ||
|
||||
path.includes('/bulk_') ||
|
||||
path.includes('/test/');
|
||||
path.includes('/test/') ||
|
||||
// CRUD CREATE endpoints - POST to list endpoints (no ID in path, ends with / or exact match)
|
||||
/\/v1\/(planner|writer)\/(keywords|clusters|ideas|tasks|content|images)\/?$/.test(path);
|
||||
|
||||
if (response.status === 400) {
|
||||
// 400 is expected for test requests - endpoint is working
|
||||
if (!isExpected400) {
|
||||
// Only log if it's unexpected
|
||||
console.warn(`[ApiStatusIndicator] ${method} ${path}: 400 (unexpected)`);
|
||||
}
|
||||
// Don't log warnings for expected 400s - they're normal validation errors
|
||||
return 'healthy';
|
||||
} else if (response.status >= 200 && response.status < 300) {
|
||||
return 'healthy';
|
||||
|
||||
Reference in New Issue
Block a user