Files
SRCmail/lib/admin/plugin-config.ts
T
Linus Rath aaa283357e feat: implemented plugin configuration UI and calendar event action slot
- Add configSchema support to plugin manifest and ServerPlugin registry
- Add schema-driven admin config page (string, secret, boolean, number, select fields)
- Add per-plugin config storage backend (JSON files + REST API)
- Add calendar-event-actions and admin-plugin-page slot names to plugin store
- Add registerCalendarEventAction and registerAdminPage to plugin API
- Add calendarFormHooks (onCalendarEventFormOpen/Save) to hook bus
- Add PluginSlot in calendar event modal for plugin action buttons
- Style calendar event action buttons to match Bulwark outline button design
- Add Configure link per plugin in admin plugins dashboard
- Add Jitsi Meet plugin with tests (repos/plugins/jitsi-meet)
- Exclude data/admin/plugins from ESLint (deployed plugin bundles)
2026-03-30 21:12:05 +02:00

82 lines
2.4 KiB
TypeScript

import { readFile, writeFile, mkdir, rename, unlink } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import path from 'node:path';
import { logger } from '@/lib/logger';
function getAdminDir(): string {
return process.env.ADMIN_DATA_DIR || path.join(process.cwd(), 'data', 'admin');
}
function getPluginConfigDir(): string {
return path.join(getAdminDir(), 'plugin-config');
}
function configPath(pluginId: string): string {
return path.join(getPluginConfigDir(), `${pluginId}.json`);
}
async function ensureDir(dir: string): Promise<void> {
if (!existsSync(dir)) {
await mkdir(dir, { recursive: true });
}
}
/**
* Get all config for a plugin.
*/
export async function getPluginConfig(pluginId: string): Promise<Record<string, unknown>> {
try {
const raw = await readFile(configPath(pluginId), 'utf-8');
return JSON.parse(raw);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return {};
logger.warn(`Failed to read plugin config for ${pluginId}`, {
error: error instanceof Error ? error.message : 'Unknown error',
});
return {};
}
}
/**
* Set a single config key for a plugin.
*/
export async function setPluginConfig(pluginId: string, key: string, value: unknown): Promise<void> {
const dir = getPluginConfigDir();
await ensureDir(dir);
const config = await getPluginConfig(pluginId);
config[key] = value;
const filePath = configPath(pluginId);
const tmpPath = filePath + '.tmp';
await writeFile(tmpPath, JSON.stringify(config, null, 2), 'utf-8');
await rename(tmpPath, filePath);
}
/**
* Delete a single config key for a plugin.
*/
export async function deletePluginConfigKey(pluginId: string, key: string): Promise<void> {
const config = await getPluginConfig(pluginId);
delete config[key];
if (Object.keys(config).length === 0) {
try { await unlink(configPath(pluginId)); } catch { /* ok if missing */ }
return;
}
const dir = getPluginConfigDir();
await ensureDir(dir);
const filePath = configPath(pluginId);
const tmpPath = filePath + '.tmp';
await writeFile(tmpPath, JSON.stringify(config, null, 2), 'utf-8');
await rename(tmpPath, filePath);
}
/**
* Delete all config for a plugin (used when uninstalling).
*/
export async function deleteAllPluginConfig(pluginId: string): Promise<void> {
try { await unlink(configPath(pluginId)); } catch { /* ok if missing */ }
}