mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-18 18:11:57 +00:00
55327704dd
Add complete frontend implementation for GitHub integration following the same pattern as Jira integration. Components: - GitHubIntegrationForm: Form for creating/editing GitHub integrations - GitHubIntegrationsManager: Manager component for listing and managing integrations - GitHubIntegrationCard: Card component for main integrations page - GitHub integration page at /integrations/github Features: - Personal Access Token input with validation - Optional repository owner filter - Connection testing and repository discovery - Enable/disable integration toggle - Edit credentials functionality - Delete integration with confirmation - Pagination support - Integration status display with last checked timestamp Server Actions: - getGitHubIntegrations(): Fetch enabled GitHub integrations - sendFindingToGitHub(): Send finding to GitHub as issue - pollGitHubDispatchTask(): Poll async task completion Types & Schemas: - githubIntegrationFormSchema: Zod schema for creation - editGitHubIntegrationFormSchema: Zod schema for editing - GitHubCredentialsPayload: TypeScript interface for credentials - GitHubDispatchRequest/Response: API types for dispatching Integration Page: - Created /integrations/github page with features list - Added GitHub card to main integrations overview - Exported GitHub components from integrations index 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
166 lines
4.6 KiB
TypeScript
166 lines
4.6 KiB
TypeScript
"use server";
|
|
|
|
import { pollTaskUntilSettled } from "@/actions/task/poll";
|
|
import { apiBaseUrl, getAuthHeaders } from "@/lib";
|
|
import { handleApiError } from "@/lib/server-actions-helper";
|
|
import type { IntegrationProps } from "@/types/integrations";
|
|
|
|
export interface GitHubDispatchRequest {
|
|
data: {
|
|
type: "integrations-github-dispatches";
|
|
attributes: {
|
|
repository: string;
|
|
labels?: string[];
|
|
};
|
|
};
|
|
}
|
|
|
|
export interface GitHubDispatchResponse {
|
|
data: {
|
|
id: string;
|
|
type: "tasks";
|
|
attributes: {
|
|
state: string;
|
|
result?: {
|
|
created_count?: number;
|
|
failed_count?: number;
|
|
error?: string;
|
|
};
|
|
};
|
|
};
|
|
}
|
|
|
|
export const getGitHubIntegrations = async (): Promise<
|
|
| { success: true; data: IntegrationProps[] }
|
|
| { success: false; error: string }
|
|
> => {
|
|
const headers = await getAuthHeaders({ contentType: false });
|
|
const url = new URL(`${apiBaseUrl}/integrations`);
|
|
|
|
// Filter for GitHub integrations only
|
|
url.searchParams.append("filter[integration_type]", "github");
|
|
|
|
try {
|
|
const response = await fetch(url.toString(), { method: "GET", headers });
|
|
|
|
if (response.ok) {
|
|
const data: { data: IntegrationProps[] } = await response.json();
|
|
// Filter for enabled integrations on the client side
|
|
const enabledIntegrations = (data.data || []).filter(
|
|
(integration: IntegrationProps) =>
|
|
integration.attributes.enabled === true,
|
|
);
|
|
return { success: true, data: enabledIntegrations };
|
|
}
|
|
|
|
const errorData: unknown = await response.json().catch(() => ({}));
|
|
const errorMessage =
|
|
(errorData as { errors?: { detail?: string }[] }).errors?.[0]?.detail ||
|
|
`Unable to fetch GitHub integrations: ${response.statusText}`;
|
|
return { success: false, error: errorMessage };
|
|
} catch (error) {
|
|
const errorResult = handleApiError(error);
|
|
return { success: false, error: errorResult.error || "An error occurred" };
|
|
}
|
|
};
|
|
|
|
export const sendFindingToGitHub = async (
|
|
integrationId: string,
|
|
findingId: string,
|
|
repository: string,
|
|
labels?: string[],
|
|
): Promise<
|
|
| { success: true; taskId: string; message: string }
|
|
| { success: false; error: string }
|
|
> => {
|
|
const headers = await getAuthHeaders({ contentType: true });
|
|
const url = new URL(
|
|
`${apiBaseUrl}/integrations/${integrationId}/github/dispatches`,
|
|
);
|
|
|
|
// Single finding: use direct filter without array notation
|
|
url.searchParams.append("filter[finding_id]", findingId);
|
|
|
|
const payload: GitHubDispatchRequest = {
|
|
data: {
|
|
type: "integrations-github-dispatches",
|
|
attributes: {
|
|
repository: repository,
|
|
labels: labels || [],
|
|
},
|
|
},
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(url.toString(), {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data: GitHubDispatchResponse = await response.json();
|
|
const taskId = data?.data?.id;
|
|
|
|
if (taskId) {
|
|
return {
|
|
success: true,
|
|
taskId,
|
|
message: "GitHub issue creation started. Processing...",
|
|
};
|
|
} else {
|
|
return {
|
|
success: false,
|
|
error: "Failed to start GitHub dispatch. No task ID received.",
|
|
};
|
|
}
|
|
}
|
|
|
|
const errorData: unknown = await response.json().catch(() => ({}));
|
|
const errorMessage =
|
|
(errorData as { errors?: { detail?: string }[] }).errors?.[0]?.detail ||
|
|
`Unable to send finding to GitHub: ${response.statusText}`;
|
|
return { success: false, error: errorMessage };
|
|
} catch (error) {
|
|
const errorResult = handleApiError(error);
|
|
return { success: false, error: errorResult.error || "An error occurred" };
|
|
}
|
|
};
|
|
|
|
export const pollGitHubDispatchTask = async (
|
|
taskId: string,
|
|
): Promise<
|
|
{ success: true; message: string } | { success: false; error: string }
|
|
> => {
|
|
const res = await pollTaskUntilSettled(taskId, {
|
|
maxAttempts: 10,
|
|
delayMs: 2000,
|
|
});
|
|
if (!res.ok) {
|
|
return { success: false, error: res.error };
|
|
}
|
|
const { state, result } = res;
|
|
type GitHubTaskResult =
|
|
GitHubDispatchResponse["data"]["attributes"]["result"];
|
|
const githubResult = result as GitHubTaskResult | undefined;
|
|
|
|
if (state === "completed") {
|
|
if (!githubResult?.error) {
|
|
return {
|
|
success: true,
|
|
message: "Finding successfully sent to GitHub!",
|
|
};
|
|
}
|
|
return {
|
|
success: false,
|
|
error: githubResult?.error || "Failed to create GitHub issue.",
|
|
};
|
|
}
|
|
|
|
if (state === "failed") {
|
|
return { success: false, error: githubResult?.error || "Task failed." };
|
|
}
|
|
|
|
return { success: false, error: `Unknown task state: ${state}` };
|
|
};
|