Skip to content
Open
121 changes: 75 additions & 46 deletions forge/ee/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,95 @@
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)
} else if (app.license.get('tier') === 'fleet') {
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 {
// old "Pro" license
}

// Set the Team Library Feature Flag
Expand Down
70 changes: 52 additions & 18 deletions forge/ee/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand All @@ -18,31 +42,41 @@ module.exports = async function (app) {

await app.register(require('./flowBlueprints'), { prefix: '/api/v1/flow-blueprints', logLevel: app.config.logging.http })

let enableSSO = false

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 })

enableSSO = true
} 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 })

enableSSO = true
} 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 })

enableSSO = true
} 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 })

enableSSO = true
} else {
// old Pro license
}

if (enableSSO) {
// Important: keep SSO last to avoid its error handling polluting other routes.
await app.register(require('./sso'), { logLevel: app.config.logging.http })
}
Expand Down
2 changes: 1 addition & 1 deletion forge/licensing/license-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const { v4: uuidv4 } = require('uuid')
replace: '*'
})

const licenseTier = await promptly.choose('License tier (teams*, enterprise): ', ['teams', 'enterprise'], { default: 'teams', trim: true })
const licenseTier = await promptly.choose('License tier (teams, enterprise, hub*, edge, fleet): ', ['teams', 'enterprise', 'hub', 'edge', 'fleet'], { default: 'hub', trim: true })

const licenseHolder = await promptly.prompt('License holder name: ')

Expand Down
Loading
Loading