feat(FN-009): implement config and secrets management with runtime injection

- Add RuntimeInjectionService for scope-based config/secret resolution
- Mount configs as JSON files at /app/config/ with 0400 permissions
- Inject secrets as environment variables with uppercase keys
- Implement scope hierarchy: instance > project > user > global
- Create ConfigListPage and SecretListPage frontend components
- Mask secret values in API responses (never expose decrypted)
- Validate secrets exist before spawning containers
- Add comprehensive tests for runtime injection service
- Update documentation with config/secrets workflow
This commit is contained in:
2026-05-15 16:44:26 +02:00
parent 78aaddb2b5
commit 51d93d9dc6
12 changed files with 838 additions and 86 deletions
+55 -1
View File
@@ -1,4 +1,4 @@
import type { Project, ProjectCreate, ProjectUpdate, ToolDefinition, ToolInstance, User } from '../types/api.ts'
import type { Config, ConfigCreate, Project, ProjectCreate, ProjectUpdate, Secret, SecretCreate, ToolDefinition, ToolInstance, User } from '../types/api.ts'
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
@@ -141,4 +141,58 @@ export const api = {
const response = await fetchWithAuth('/tools')
return response.json()
},
getConfigs: async (projectId: string): Promise<Config[]> => {
const response = await fetchWithAuth(`/configs?scope_type=project&scope_id=${projectId}`)
return response.json()
},
createConfig: async (data: ConfigCreate): Promise<Config> => {
const response = await fetchWithAuth('/configs', {
method: 'POST',
body: JSON.stringify(data),
})
return response.json()
},
updateConfig: async (id: string, data: { value: unknown }): Promise<Config> => {
const response = await fetchWithAuth(`/configs/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
})
return response.json()
},
deleteConfig: async (id: string): Promise<void> => {
await fetchWithAuth(`/configs/${id}`, {
method: 'DELETE',
})
},
getSecrets: async (projectId: string): Promise<Secret[]> => {
const response = await fetchWithAuth(`/secrets?scope_type=project&scope_id=${projectId}`)
return response.json()
},
createSecret: async (data: SecretCreate): Promise<Secret> => {
const response = await fetchWithAuth('/secrets', {
method: 'POST',
body: JSON.stringify(data),
})
return response.json()
},
updateSecret: async (id: string, data: { value: string }): Promise<Secret> => {
const response = await fetchWithAuth(`/secrets/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
})
return response.json()
},
deleteSecret: async (id: string): Promise<void> => {
await fetchWithAuth(`/secrets/${id}`, {
method: 'DELETE',
})
},
}