diff --git a/forge/ee/lib/index.js b/forge/ee/lib/index.js index dff8995d87..81ca1f0b92 100644 --- a/forge/ee/lib/index.js +++ b/forge/ee/lib/index.js @@ -1,66 +1,99 @@ const fp = require('fastify-plugin') +// common features across all higher tier (enterpise, hub, edge, fleet) +async function commonFeatures(app, opts) { + app.decorate('sso', await require('./sso').init(app)) + await require('./teamBroker').init(app) + // Set the MFA Feature Flag + app.config.features.register('mfa', true, true) + // Set the Project History timeline Feature Flag + app.config.features.register('projectHistory', true, true) + // Set the Bill of Materials Feature Flag + app.config.features.register('bom', true, true) + if (app.config.npmRegistry?.enabled) { + // Set npm Feature Flag + app.config.features.register('npm', true, true) + } + if (app.config.tables?.enabled) { + app.decorate('tables', await require('./tables').init(app)) + } + app.config.features.register('certifiedNodes', true, true) + app.config.features.register('ffNodes', true, true) + // Application level RBAC + app.config.features.register('rbacApplication', true, true) + require('./autoUpdateStacks').init(app) + + // Expert + await app.register(require('./expert')) + + // Set the AI Features Flag (global gate for all AI features) + const isAiEnabled = !!(app.config?.ai?.enabled ?? true) + app.config.features.register('ai', isAiEnabled, true) + + // Set the Generate Snapshot Description Feature Flag + const isAssistantConfigured = isAiEnabled && app.config.assistant?.enabled === true && !!app.config.assistant?.service?.url + app.config.features.register('generatedSnapshotDescription', isAssistantConfigured, true) + + // Set the assistant inline completions Feature Flag + app.config.features.register('assistantInlineCompletions', isAssistantConfigured, true) + + // Set the expert platform automation Feature Flag (MCP platform tools server) + app.config.features.register('expertPlatformAutomation', isAiEnabled && (app.config?.expert?.enabled ?? false), true) + + // Set the expert assistant Feature Flag + app.config.features.register('expertAssistant', isAiEnabled && (app.config?.expert?.enabled ?? false), true) + + // Set the Expert Insights flag + const isInsightsEnabled = isAiEnabled && + !!app.config?.expert?.enabled && + (Object.prototype.hasOwnProperty.call(app.config?.expert ?? {}, 'insights') ? !!app.config?.expert?.insights?.enabled : true) + + app.config.features.register('expertInsights', isInsightsEnabled ?? false, true) +} + module.exports = fp(async function (app, opts) { if (app.config.billing) { app.decorate('billing', await require('./billing').init(app)) } + // common features for all license levels (inc Pro) require('./projectComms').init(app) require('./deviceEditor').init(app) require('./alerts').init(app) if (app.license.get('tier') === 'enterprise') { + await commonFeatures(app, opts) + // HA require('./ha').init(app) + // Protected Instances require('./protectedInstance').init(app) + // Git Opps + app.decorate('gitops', await require('./gitops').init(app)) require('./customHostnames').init(app) - app.decorate('sso', await require('./sso').init(app)) - await require('./teamBroker').init(app) + + // Set the Device Groups Feature Flag + app.config.features.register('deviceGroups', true, true) + + } else if (app.license.get('tier') === 'hub') { + await commonFeatures(app, opts) + // HA + require('./ha').init(app) + // Protected Instances + require('./protectedInstance').init(app) + // Git Opps app.decorate('gitops', await require('./gitops').init(app)) - // Set the MFA Feature Flag - app.config.features.register('mfa', true, true) + + } else if (app.license.get('tier') === 'edge') { + await commonFeatures(app, opts) // Set the Device Groups Feature Flag app.config.features.register('deviceGroups', true, true) - // Set the Project History timeline Feature Flag - app.config.features.register('projectHistory', true, true) - // Set the Bill of Materials Feature Flag - app.config.features.register('bom', true, true) - if (app.config.npmRegistry?.enabled) { - // Set npm Feature Flag - app.config.features.register('npm', true, true) - } - if (app.config.tables?.enabled) { - app.decorate('tables', await require('./tables').init(app)) - } - app.config.features.register('certifiedNodes', true, true) - app.config.features.register('ffNodes', true, true) - app.config.features.register('rbacApplication', true, true) - require('./autoUpdateStacks').init(app) - - // Expert - await app.register(require('./expert')) - - // Set the AI Features Flag (global gate for all AI features) - const isAiEnabled = !!(app.config?.ai?.enabled ?? true) - app.config.features.register('ai', isAiEnabled, true) - - // Set the Generate Snapshot Description Feature Flag - const isAssistantConfigured = isAiEnabled && app.config.assistant?.enabled === true && !!app.config.assistant?.service?.url - app.config.features.register('generatedSnapshotDescription', isAssistantConfigured, true) - - // Set the assistant inline completions Feature Flag - app.config.features.register('assistantInlineCompletions', isAssistantConfigured, true) - - // Set the expert platform automation Feature Flag (MCP platform tools server) - app.config.features.register('expertPlatformAutomation', isAiEnabled && (app.config?.expert?.enabled ?? false), true) - - // Set the expert assistant Feature Flag - app.config.features.register('expertAssistant', isAiEnabled && (app.config?.expert?.enabled ?? false), true) - - // Set the Expert Insights flag - const isInsightsEnabled = isAiEnabled && - !!app.config?.expert?.enabled && - (Object.prototype.hasOwnProperty.call(app.config?.expert ?? {}, 'insights') ? !!app.config?.expert?.insights?.enabled : true) - - app.config.features.register('expertInsights', isInsightsEnabled ?? false, true) + + } else if (app.license.get('tier') === 'fleet') { + await commonFeatures(app, opts) + // Set the Device Groups Feature Flag + app.config.features.register('deviceGroups', true, true) + + } else { + // old "Pro" license } // Set the Team Library Feature Flag diff --git a/forge/ee/routes/index.js b/forge/ee/routes/index.js index 14ef4c5b03..1c3a967657 100644 --- a/forge/ee/routes/index.js +++ b/forge/ee/routes/index.js @@ -4,11 +4,35 @@ * @namespace api * @memberof forge.ee */ + +// common features across all higher tier (enterpise, hub, edge, fleet) +async function commonFeatures(app) { + await app.register(require('./expert'), { prefix: '/api/v1/expert', logLevel: app.config.logging.http }) + await app.register(require('./mcp'), { logLevel: app.config.logging.http }) + await app.register(require('./teamBroker'), { prefix: '/api/v1/teams/:teamId/broker', logLevel: app.config.logging.http }) + await app.register(require('./teamBroker/3rdPartyBroker'), { prefix: '/api/v1/teams/:teamId/brokers', logLevel: app.config.logging.http }) + if (app.config.npmRegistry?.enabled) { + await app.register(require('./catalogues'), { prefix: '/api/v1/teams/:teamId', logLevel: app.config.logging.http }) + } + await app.register(require('./mfa'), { prefix: '/api/v1', logLevel: app.config.logging.http }) + if (app.config.tables?.enabled) { + await app.register(require('./tables'), { prefix: '/api/v1/teams/:teamId/databases', logLevel: app.config.logging.http }) + } + await app.register(require('./resource'), { prefix: '/api/v1/projects/:instanceId/resources', logLevel: app.config.logging.http }) + await app.register(require('./autoUpdateStacks'), { prefix: '/api/v1/projects/:projectId/autoUpdateStack', logLevel: app.config.logging.http }) + await app.register(require('./httpTokens'), { prefix: '/api/v1', logLevel: app.config.logging.http }) + await app.register(require('./customHostnames'), { prefix: '/api/v1/projects/:projectId/customHostname', logLevel: app.config.logging.http }) + await app.register(require('./staticAssets'), { prefix: '/api/v1/projects/:projectId/files', logLevel: app.config.logging.http }) + await app.register(require('./projectHistory'), { prefix: '/api/v1/projects/:instanceId/history', logLevel: app.config.logging.http }) + await app.register(require('./deviceHistory'), { prefix: '/api/v1/devices/:deviceId/history', logLevel: app.config.logging.http }) +} + module.exports = async function (app) { app.addHook('preHandler', app.verifySession) if (app.config.billing) { await app.register(require('./billing'), { prefix: '/ee/billing', logLevel: app.config.logging.http }) } + // Features provied to any license level (inc pro) await app.register(require('./sharedLibrary'), { logLevel: app.config.logging.http }) await app.register(require('./pipeline'), { prefix: '/api/v1', logLevel: app.config.logging.http }) await app.register(require('./pipeline/teamPipelines.js'), { prefix: '/api/v1/teams/:teamId/pipelines', logLevel: app.config.logging.http }) @@ -19,31 +43,38 @@ module.exports = async function (app) { await app.register(require('./flowBlueprints'), { prefix: '/api/v1/flow-blueprints', logLevel: app.config.logging.http }) if (app.license.get('tier') === 'enterprise') { + await commonFeatures(app) await app.register(require('./applicationDeviceGroups'), { prefix: '/api/v1/applications/:applicationId/device-groups', logLevel: app.config.logging.http }) await app.register(require('./teamDeviceGroups'), { prefix: '/api/v1/teams/:teamId/device-groups', logLevel: app.config.logging.http }) await app.register(require('./ha'), { prefix: '/api/v1/projects/:projectId/ha', logLevel: app.config.logging.http }) await app.register(require('./protectedInstance'), { prefix: '/api/v1/projects/:projectId/protectInstance', logLevel: app.config.logging.http }) - await app.register(require('./mfa'), { prefix: '/api/v1', logLevel: app.config.logging.http }) - await app.register(require('./httpTokens'), { prefix: '/api/v1', logLevel: app.config.logging.http }) - await app.register(require('./customHostnames'), { prefix: '/api/v1/projects/:projectId/customHostname', logLevel: app.config.logging.http }) - await app.register(require('./staticAssets'), { prefix: '/api/v1/projects/:projectId/files', logLevel: app.config.logging.http }) - await app.register(require('./projectHistory'), { prefix: '/api/v1/projects/:instanceId/history', logLevel: app.config.logging.http }) - await app.register(require('./deviceHistory'), { prefix: '/api/v1/devices/:deviceId/history', logLevel: app.config.logging.http }) - await app.register(require('./teamBroker'), { prefix: '/api/v1/teams/:teamId/broker', logLevel: app.config.logging.http }) - await app.register(require('./teamBroker/3rdPartyBroker'), { prefix: '/api/v1/teams/:teamId/brokers', logLevel: app.config.logging.http }) - if (app.config.npmRegistry?.enabled) { - await app.register(require('./catalogues'), { prefix: '/api/v1/teams/:teamId', logLevel: app.config.logging.http }) - } await app.register(require('./gitops'), { prefix: '/api/v1/teams/:teamId/git', logLevel: app.config.logging.http }) - await app.register(require('./resource'), { prefix: '/api/v1/projects/:instanceId/resources', logLevel: app.config.logging.http }) - if (app.config.tables?.enabled) { - await app.register(require('./tables'), { prefix: '/api/v1/teams/:teamId/databases', logLevel: app.config.logging.http }) - } - await app.register(require('./mcp'), { logLevel: app.config.logging.http }) - await app.register(require('./autoUpdateStacks'), { prefix: '/api/v1/projects/:projectId/autoUpdateStack', logLevel: app.config.logging.http }) - await app.register(require('./expert'), { prefix: '/api/v1/expert', logLevel: app.config.logging.http }) + + // Important: keep SSO last to avoid its error handling polluting other routes. + await app.register(require('./sso'), { logLevel: app.config.logging.http }) + } else if (app.license.get('tier') === 'hub') { + await commonFeatures(app) + await app.register(require('./gitops'), { prefix: '/api/v1/teams/:teamId/git', logLevel: app.config.logging.http }) + await app.register(require('./ha'), { prefix: '/api/v1/projects/:projectId/ha', logLevel: app.config.logging.http }) + await app.register(require('./protectedInstance'), { prefix: '/api/v1/projects/:projectId/protectInstance', logLevel: app.config.logging.http }) + + // Important: keep SSO last to avoid its error handling polluting other routes. + await app.register(require('./sso'), { logLevel: app.config.logging.http }) + } else if (app.license.get('tier') === 'edge') { + await commonFeatures(app) + await app.register(require('./applicationDeviceGroups'), { prefix: '/api/v1/applications/:applicationId/device-groups', logLevel: app.config.logging.http }) + await app.register(require('./teamDeviceGroups'), { prefix: '/api/v1/teams/:teamId/device-groups', logLevel: app.config.logging.http }) + + // Important: keep SSO last to avoid its error handling polluting other routes. + await app.register(require('./sso'), { logLevel: app.config.logging.http }) + } else if (app.license.get('tier') === 'fleet') { + await commonFeatures(app) + await app.register(require('./applicationDeviceGroups'), { prefix: '/api/v1/applications/:applicationId/device-groups', logLevel: app.config.logging.http }) + await app.register(require('./teamDeviceGroups'), { prefix: '/api/v1/teams/:teamId/device-groups', logLevel: app.config.logging.http }) // Important: keep SSO last to avoid its error handling polluting other routes. await app.register(require('./sso'), { logLevel: app.config.logging.http }) + } else { + // old Pro license } }