feat(plugin): scope secret-ref config by company

This commit is contained in:
Paperclip Bot 2026-06-03 06:31:01 +00:00 committed by Alkim Ake Gozen
parent 62863126a3
commit db0ef46900
19 changed files with 587 additions and 102 deletions

View file

@ -357,8 +357,10 @@ export const pluginsApi = {
*
* @param pluginId - UUID of the plugin.
*/
getConfig: (pluginId: string) =>
api.get<PluginConfig | null>(`/plugins/${pluginId}/config`),
getConfig: (pluginId: string, companyId?: string | null) => {
const qs = companyId ? `?companyId=${encodeURIComponent(companyId)}` : "";
return api.get<PluginConfig | null>(`/plugins/${pluginId}/config${qs}`);
},
/**
* Save (create or update) the configuration for a plugin.
@ -369,8 +371,8 @@ export const pluginsApi = {
* @param pluginId - UUID of the plugin.
* @param configJson - Configuration values matching the plugin's `instanceConfigSchema`.
*/
saveConfig: (pluginId: string, configJson: Record<string, unknown>) =>
api.post<PluginConfig>(`/plugins/${pluginId}/config`, { configJson }),
saveConfig: (pluginId: string, configJson: Record<string, unknown>, companyId?: string | null) =>
api.post<PluginConfig>(`/plugins/${pluginId}/config`, { configJson, companyId }),
/**
* Call the plugin's `validateConfig` RPC method to test the configuration

View file

@ -199,7 +199,8 @@ export const queryKeys = {
detail: (pluginId: string) => ["plugins", pluginId] as const,
health: (pluginId: string) => ["plugins", pluginId, "health"] as const,
uiContributions: ["plugins", "ui-contributions"] as const,
config: (pluginId: string) => ["plugins", pluginId, "config"] as const,
config: (pluginId: string, companyId?: string | null) =>
["plugins", pluginId, "config", companyId ?? null] as const,
localFolders: (pluginId: string, companyId: string) =>
["plugins", pluginId, "companies", companyId, "local-folders"] as const,
dashboard: (pluginId: string) => ["plugins", pluginId, "dashboard"] as const,

View file

@ -47,8 +47,8 @@ import {
* - `GET /api/plugins/:pluginId/health` health diagnostics (polling).
* Only fetched when `plugin.status === "ready"`.
* - `GET /api/plugins/:pluginId/dashboard` aggregated runtime dashboard data (polling).
* - `GET /api/plugins/:pluginId/config` current config values.
* - `POST /api/plugins/:pluginId/config` save config values.
* - `GET /api/plugins/:pluginId/config?companyId=...` current company config values.
* - `POST /api/plugins/:pluginId/config` save company config values.
* - `POST /api/plugins/:pluginId/config/test` test configuration.
*
* URL params:
@ -97,9 +97,9 @@ export function PluginSettings() {
const hasConfigSchema = configSchema && configSchema.properties && Object.keys(configSchema.properties).length > 0;
const { data: configData, isLoading: configLoading } = useQuery({
queryKey: queryKeys.plugins.config(pluginId!),
queryFn: () => pluginsApi.getConfig(pluginId!),
enabled: !!pluginId && !!hasConfigSchema,
queryKey: queryKeys.plugins.config(pluginId!, selectedCompanyId),
queryFn: () => pluginsApi.getConfig(pluginId!, selectedCompanyId),
enabled: !!pluginId && !!hasConfigSchema && !!selectedCompanyId,
});
const { slots } = usePluginSlots({
@ -245,6 +245,7 @@ export function PluginSettings() {
) : hasConfigSchema ? (
<PluginConfigForm
pluginId={pluginId!}
companyId={selectedCompanyId}
schema={configSchema!}
initialValues={configData?.configJson}
isLoading={configLoading}
@ -919,6 +920,7 @@ function isLikelyAbsolutePath(pathValue: string) {
interface PluginConfigFormProps {
pluginId: string;
companyId?: string | null;
schema: JsonSchemaNode;
initialValues?: Record<string, unknown>;
isLoading?: boolean;
@ -935,7 +937,7 @@ interface PluginConfigFormProps {
* Separated from PluginSettings to isolate re-render scope only the form
* re-renders on field changes, not the entire page.
*/
function PluginConfigForm({ pluginId, schema, initialValues, isLoading, pluginStatus, supportsConfigTest }: PluginConfigFormProps) {
function PluginConfigForm({ pluginId, companyId, schema, initialValues, isLoading, pluginStatus, supportsConfigTest }: PluginConfigFormProps) {
const queryClient = useQueryClient();
// Form values: start with saved values, fall back to schema defaults
@ -971,11 +973,11 @@ function PluginConfigForm({ pluginId, schema, initialValues, isLoading, pluginSt
// Save mutation
const saveMutation = useMutation({
mutationFn: (configJson: Record<string, unknown>) =>
pluginsApi.saveConfig(pluginId, configJson),
pluginsApi.saveConfig(pluginId, configJson, companyId),
onSuccess: () => {
setSaveMessage({ type: "success", text: "Configuration saved." });
setTestResult(null);
queryClient.invalidateQueries({ queryKey: queryKeys.plugins.config(pluginId) });
queryClient.invalidateQueries({ queryKey: queryKeys.plugins.config(pluginId, companyId) });
// Clear success message after 3s
setTimeout(() => setSaveMessage(null), 3000);
},