'use strict'; // PluginInstance — the registry of configured, loadable plugin copies. // // The SSO plugin system (see nodejs/services/plugin_registry.js) distinguishes // **plugin types** (the .js modules under nodejs/plugins//.js) // from **plugin instances** — a configured, loadable/unloadable *copy* of a // type. You can have several instances of the same type (e.g. two Proxmox // endpoints with their own URLs + tokens), each on its own schedule. // // This table holds the *non-secret* per-instance state: which type it is, its // schedule (cron), whether it's loaded (enabled), and its non-secret config. // Per-instance **secrets** (the configSchema fields flagged `secret:true`, // e.g. a Proxmox `tokenSecret` or UniFi `password`) live in OpenBao at // `secret/plugins//conf` (see nodejs/utils/plugin_secrets.js) — never in // the DB. The DB row's `config` JSON column holds only non-secret field values. // // `slug` is the discovery source name passed to DiscoveryReconciler.reconcile, // so a discovery instance's resources are attributed to a stable, human-chosen // name rather than its uuid. Unique, so two instances can't shadow each other // in the resource graph's `discovery_sources`. // // Like Resource/AccessRequest, there is no ORM auto-timestamp hook: the route // handler stamps created_by/on + updated_by/on explicitly on every write (see // routes/api_plugins.js). `id` (uuid) is generated by the ORM on create. const { Model } = require('@simpleworkjs/orm'); const STATUS = { OK: 'ok', ERROR: 'error', RUNNING: 'running', }; class PluginInstance extends Model { static fields = { id: { type: 'uuid', primaryKey: true }, // A registered plugin type slug (matches a manifest `type`). Validated // against the registry before a row is created. pluginType: { type: 'string', isRequired: true, min: 1, max: 64 }, // The plugin's category (e.g. 'discovery'). Copied from the manifest at // create time so the scheduler can dispatch without re-reading the registry // on every run (and so a later type removal still shows what the instance was). category: { type: 'string', isRequired: true, default: 'discovery', min: 1, max: 64 }, // Human label for the instance. name: { type: 'string', isRequired: true, min: 1, max: 120 }, // Stable handle: discovery source name + unique constraint. Lowercase // alnum + hyphen/underscore to stay safe as a resource-graph slug. slug: { type: 'string', isRequired: true, unique: true, min: 1, max: 64 }, // Loaded into the scheduler? `false` = unloaded (no scheduled runs). enabled: { type: 'boolean', default: true }, // Cron schedule (5-field). The scheduler turns this into a BullMQ // repeatable JobScheduler. cron: { type: 'string', isRequired: true, default: '0 * * * *' }, // Non-secret configSchema field values. Secret fields are NOT here. config: { type: 'json', default: {} }, // Last-run bookkeeping, updated by the scheduler worker. lastRunAt: { type: 'integer' }, lastStatus: { type: 'string' }, lastError: { type: 'text' }, lastLog: { type: 'text' }, // Audit stamps (set by the route handler, not by an ORM hook). created_by: { type: 'string' }, created_on: { type: 'integer' }, updated_by: { type: 'string' }, updated_on: { type: 'integer' }, }; // All instances the scheduler should run: enabled only. Loaded fresh each // boot / load; not cached on the model (the scheduler is the source of truth // for what's actually scheduled). static async listEnabled() { return this.list({ where: { enabled: true } }); } // Look up by slug — used by tests + the reconciler when only a slug is known. static async getBySlug(slug) { const rows = await this.list({ where: { slug } }); return rows[0] || null; } } module.exports = { PluginInstance, STATUS };