feat(dns): add custom DNS provider plugin system

- Add plugin interface with lifecycle hooks (Init/Cleanup)
- Implement thread-safe provider registry
- Add plugin loader with SHA-256 signature verification
- Migrate 10 built-in providers to registry pattern
- Add multi-credential support to plugin interface
- Create plugin management UI with enable/disable controls
- Add dynamic credential fields based on provider metadata
- Include PowerDNS example plugin
- Add comprehensive user & developer documentation
- Fix frontend test hang (33min → 1.5min, 22x faster)

Platform: Linux/macOS only (Go plugin limitation)
Security: Signature verification, directory permission checks

Backend coverage: 85.1%
Frontend coverage: 85.31%

Closes: DNS Challenge Future Features - Phase 5
This commit is contained in:
GitHub Actions
2026-01-07 02:54:01 +00:00
parent 048b0c10a7
commit b86aa3921b
48 changed files with 8152 additions and 117 deletions

109
frontend/src/api/plugins.ts Normal file
View File

@@ -0,0 +1,109 @@
import client from './client'
/** Plugin status types */
export type PluginStatus = 'pending' | 'loaded' | 'error'
/** Plugin information */
export interface PluginInfo {
id: number
uuid: string
name: string
type: string
enabled: boolean
status: PluginStatus
error?: string
version?: string
author?: string
is_built_in: boolean
description?: string
documentation_url?: string
loaded_at?: string
created_at: string
updated_at: string
}
/** Credential field specification */
export interface CredentialFieldSpec {
name: string
label: string
type: 'text' | 'password' | 'textarea' | 'select'
placeholder?: string
hint?: string
required?: boolean
options?: Array<{
value: string
label: string
}>
}
/** Provider metadata response */
export interface ProviderFieldsResponse {
type: string
name: string
required_fields: CredentialFieldSpec[]
optional_fields: CredentialFieldSpec[]
}
/**
* Fetches all plugins (built-in and external).
* @returns Promise resolving to array of plugin info
* @throws {AxiosError} If the request fails
*/
export async function getPlugins(): Promise<PluginInfo[]> {
const response = await client.get<PluginInfo[]>('/admin/plugins')
return response.data
}
/**
* Fetches a single plugin by ID.
* @param id - The plugin ID
* @returns Promise resolving to the plugin info
* @throws {AxiosError} If not found or request fails
*/
export async function getPlugin(id: number): Promise<PluginInfo> {
const response = await client.get<PluginInfo>(`/admin/plugins/${id}`)
return response.data
}
/**
* Enables a disabled plugin.
* @param id - The plugin ID
* @returns Promise resolving to success message
* @throws {AxiosError} If not found or request fails
*/
export async function enablePlugin(id: number): Promise<{ message: string }> {
const response = await client.post<{ message: string }>(`/admin/plugins/${id}/enable`)
return response.data
}
/**
* Disables an active plugin.
* @param id - The plugin ID
* @returns Promise resolving to success message
* @throws {AxiosError} If not found, in use, or request fails
*/
export async function disablePlugin(id: number): Promise<{ message: string }> {
const response = await client.post<{ message: string }>(`/admin/plugins/${id}/disable`)
return response.data
}
/**
* Reloads all plugins from the plugin directory.
* @returns Promise resolving to success message and count
* @throws {AxiosError} If request fails
*/
export async function reloadPlugins(): Promise<{ message: string; count: number }> {
const response = await client.post<{ message: string; count: number }>('/admin/plugins/reload')
return response.data
}
/**
* Fetches credential field definitions for a DNS provider type.
* @param providerType - The provider type (e.g., "cloudflare", "powerdns")
* @returns Promise resolving to field specifications
* @throws {AxiosError} If provider type not found or request fails
*/
export async function getProviderFields(providerType: string): Promise<ProviderFieldsResponse> {
const response = await client.get<ProviderFieldsResponse>(`/dns-providers/types/${providerType}/fields`)
return response.data
}