@aifabrix/builder 2.9.0 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/app-config.js +39 -8
- package/lib/app-register.js +28 -13
- package/lib/commands/login.js +7 -1
- package/lib/schema/env-config.yaml +9 -1
- package/lib/templates.js +43 -7
- package/lib/utils/api-error-handler.js +12 -12
- package/lib/utils/device-code.js +65 -2
- package/lib/utils/env-template.js +5 -4
- package/package.json +1 -1
- package/templates/applications/keycloak/env.template +8 -2
- package/templates/applications/keycloak/variables.yaml +3 -3
- package/templates/applications/miso-controller/env.template +23 -10
- package/templates/applications/miso-controller/rbac.yaml +263 -213
- package/templates/applications/miso-controller/variables.yaml +3 -3
package/lib/app-config.js
CHANGED
|
@@ -46,23 +46,54 @@ async function generateVariablesYamlFile(appPath, appName, config) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Generates env.template content for external systems based on authentication type
|
|
51
|
+
* @param {Object} config - Application configuration with authType and systemKey
|
|
52
|
+
* @param {string} appName - Application name (used as fallback for systemKey)
|
|
53
|
+
* @returns {string} Environment template content
|
|
54
|
+
*/
|
|
55
|
+
function generateExternalSystemEnvTemplate(config, appName) {
|
|
56
|
+
const systemKey = config.systemKey || appName;
|
|
57
|
+
const authType = config.authType || 'apikey';
|
|
58
|
+
const lines = [
|
|
59
|
+
`# ${systemKey} ${authType.toUpperCase()} Configuration`,
|
|
60
|
+
'# These values are set via the Miso Controller interface or Dataplane portal',
|
|
61
|
+
'# Values are stored in Key Vault automatically by the platform',
|
|
62
|
+
''
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
if (authType === 'oauth2') {
|
|
66
|
+
lines.push('CLIENTID=kv://' + systemKey + '-clientidKeyVault');
|
|
67
|
+
lines.push('CLIENTSECRET=kv://' + systemKey + '-clientsecretKeyVault');
|
|
68
|
+
lines.push('TOKENURL=https://api.example.com/oauth/token');
|
|
69
|
+
lines.push('REDIRECT_URI=kv://' + systemKey + '-redirect-uriKeyVault');
|
|
70
|
+
} else if (authType === 'apikey') {
|
|
71
|
+
lines.push('API_KEY=kv://' + systemKey + '-api-keyKeyVault');
|
|
72
|
+
} else if (authType === 'basic') {
|
|
73
|
+
lines.push('USERNAME=kv://' + systemKey + '-usernameKeyVault');
|
|
74
|
+
lines.push('PASSWORD=kv://' + systemKey + '-passwordKeyVault');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return lines.join('\n');
|
|
78
|
+
}
|
|
79
|
+
|
|
49
80
|
/**
|
|
50
81
|
* Generates env.template file if it doesn't exist
|
|
51
82
|
* @async
|
|
52
83
|
* @param {string} appPath - Path to application directory
|
|
84
|
+
* @param {string} appName - Application name
|
|
53
85
|
* @param {Object} config - Application configuration
|
|
54
86
|
* @param {Object} existingEnv - Existing environment variables
|
|
55
87
|
*/
|
|
56
|
-
async function generateEnvTemplateFile(appPath, config, existingEnv) {
|
|
57
|
-
// Skip env.template for external type
|
|
58
|
-
if (config.type === 'external') {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
88
|
+
async function generateEnvTemplateFile(appPath, appName, config, existingEnv) {
|
|
62
89
|
const envTemplatePath = path.join(appPath, 'env.template');
|
|
63
90
|
if (!(await fileExists(envTemplatePath))) {
|
|
64
91
|
let envTemplate;
|
|
65
|
-
|
|
92
|
+
|
|
93
|
+
if (config.type === 'external') {
|
|
94
|
+
// Generate env.template for external systems based on authType
|
|
95
|
+
envTemplate = generateExternalSystemEnvTemplate(config, appName);
|
|
96
|
+
} else if (existingEnv) {
|
|
66
97
|
const envResult = await generateEnvTemplateFromReader(config, existingEnv);
|
|
67
98
|
envTemplate = envResult.template;
|
|
68
99
|
|
|
@@ -155,7 +186,7 @@ async function generateDeployJsonFile(appPath, appName, config) {
|
|
|
155
186
|
async function generateConfigFiles(appPath, appName, config, existingEnv) {
|
|
156
187
|
try {
|
|
157
188
|
await generateVariablesYamlFile(appPath, appName, config);
|
|
158
|
-
await generateEnvTemplateFile(appPath, config, existingEnv);
|
|
189
|
+
await generateEnvTemplateFile(appPath, appName, config, existingEnv);
|
|
159
190
|
await generateRbacYamlFile(appPath, appName, config);
|
|
160
191
|
await generateDeployJsonFile(appPath, appName, config);
|
|
161
192
|
await generateReadmeMdFile(appPath, appName, config);
|
package/lib/app-register.js
CHANGED
|
@@ -21,6 +21,7 @@ const { updateEnvTemplate } = require('./utils/env-template');
|
|
|
21
21
|
const { getOrRefreshDeviceToken } = require('./utils/token-manager');
|
|
22
22
|
const { detectAppType } = require('./utils/paths');
|
|
23
23
|
const { generateEnvFile } = require('./secrets');
|
|
24
|
+
const applicationSchema = require('./schema/application-schema.json');
|
|
24
25
|
|
|
25
26
|
// Import createApp to auto-generate config if missing
|
|
26
27
|
let createApp;
|
|
@@ -30,8 +31,17 @@ try {
|
|
|
30
31
|
createApp = null;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
// Extract valid enum values from application schema
|
|
35
|
+
const validTypes = applicationSchema.properties.type.enum || [];
|
|
36
|
+
const validRegistryModes = applicationSchema.properties.registryMode.enum || [];
|
|
37
|
+
const portConstraints = {
|
|
38
|
+
minimum: applicationSchema.properties.port?.minimum || 1,
|
|
39
|
+
maximum: applicationSchema.properties.port?.maximum || 65535
|
|
40
|
+
};
|
|
41
|
+
|
|
33
42
|
/**
|
|
34
43
|
* Validation schema for application registration
|
|
44
|
+
* Validates according to application-schema.json
|
|
35
45
|
*/
|
|
36
46
|
const registerApplicationSchema = {
|
|
37
47
|
environmentId: (val) => {
|
|
@@ -44,10 +54,12 @@ const registerApplicationSchema = {
|
|
|
44
54
|
if (!val || val.length < 1) {
|
|
45
55
|
throw new Error('Application key is required');
|
|
46
56
|
}
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
const keyPattern = applicationSchema.properties.key.pattern;
|
|
58
|
+
const keyMaxLength = applicationSchema.properties.key.maxLength || 50;
|
|
59
|
+
if (val.length > keyMaxLength) {
|
|
60
|
+
throw new Error(`Application key must be at most ${keyMaxLength} characters`);
|
|
49
61
|
}
|
|
50
|
-
if (
|
|
62
|
+
if (keyPattern && !new RegExp(keyPattern).test(val)) {
|
|
51
63
|
throw new Error('Application key must contain only lowercase letters, numbers, and hyphens');
|
|
52
64
|
}
|
|
53
65
|
return val;
|
|
@@ -56,25 +68,28 @@ const registerApplicationSchema = {
|
|
|
56
68
|
if (!val || val.length < 1) {
|
|
57
69
|
throw new Error('Display name is required');
|
|
58
70
|
}
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
const displayNameMaxLength = applicationSchema.properties.displayName.maxLength || 100;
|
|
72
|
+
if (val.length > displayNameMaxLength) {
|
|
73
|
+
throw new Error(`Display name must be at most ${displayNameMaxLength} characters`);
|
|
61
74
|
}
|
|
62
75
|
return val;
|
|
63
76
|
},
|
|
64
77
|
description: (val) => val || undefined,
|
|
65
78
|
configuration: (val) => {
|
|
66
|
-
const validTypes = ['webapp', 'api', 'service', 'functionapp'];
|
|
67
|
-
const validRegistryModes = ['acr', 'external', 'public'];
|
|
68
|
-
|
|
69
79
|
if (!val || !val.type || !validTypes.includes(val.type)) {
|
|
70
|
-
throw new Error(
|
|
80
|
+
throw new Error(`Configuration type must be one of: ${validTypes.join(', ')}`);
|
|
71
81
|
}
|
|
72
82
|
if (!val.registryMode || !validRegistryModes.includes(val.registryMode)) {
|
|
73
|
-
throw new Error(
|
|
83
|
+
throw new Error(`Registry mode must be one of: ${validRegistryModes.join(', ')}`);
|
|
74
84
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
// Port validation: skip for external type (external systems don't need ports)
|
|
86
|
+
// For other types, port is required and must be valid
|
|
87
|
+
if (val.type !== 'external') {
|
|
88
|
+
if (val.port === undefined || val.port === null) {
|
|
89
|
+
throw new Error('Port is required for non-external application types');
|
|
90
|
+
}
|
|
91
|
+
if (!Number.isInteger(val.port) || val.port < portConstraints.minimum || val.port > portConstraints.maximum) {
|
|
92
|
+
throw new Error(`Port must be an integer between ${portConstraints.minimum} and ${portConstraints.maximum}`);
|
|
78
93
|
}
|
|
79
94
|
}
|
|
80
95
|
return val;
|
package/lib/commands/login.js
CHANGED
|
@@ -359,7 +359,13 @@ async function handleDeviceCodeLogin(controllerUrl, environment, offline, scope)
|
|
|
359
359
|
);
|
|
360
360
|
|
|
361
361
|
} catch (deviceError) {
|
|
362
|
-
|
|
362
|
+
// Display formatted error if available (includes detailed validation info)
|
|
363
|
+
if (deviceError.formattedError) {
|
|
364
|
+
logger.error(chalk.red('\n❌ Device code flow failed:'));
|
|
365
|
+
logger.log(deviceError.formattedError);
|
|
366
|
+
} else {
|
|
367
|
+
logger.error(chalk.red(`\n❌ Device code flow failed: ${deviceError.message}`));
|
|
368
|
+
}
|
|
363
369
|
process.exit(1);
|
|
364
370
|
}
|
|
365
371
|
}
|
|
@@ -11,6 +11,10 @@ environments:
|
|
|
11
11
|
MISO_PORT: 3000
|
|
12
12
|
KEYCLOAK_HOST: keycloak
|
|
13
13
|
KEYCLOAK_PORT: 8082
|
|
14
|
+
NODE_ENV: production
|
|
15
|
+
PYTHONUNBUFFERED: 1
|
|
16
|
+
PYTHONDONTWRITEBYTECODE: 1
|
|
17
|
+
PYTHONIOENCODING: utf-8
|
|
14
18
|
|
|
15
19
|
local:
|
|
16
20
|
DB_HOST: localhost
|
|
@@ -20,4 +24,8 @@ environments:
|
|
|
20
24
|
MISO_HOST: localhost
|
|
21
25
|
MISO_PORT: 3010
|
|
22
26
|
KEYCLOAK_HOST: localhost
|
|
23
|
-
KEYCLOAK_PORT: 8082
|
|
27
|
+
KEYCLOAK_PORT: 8082
|
|
28
|
+
NODE_ENV: development
|
|
29
|
+
PYTHONUNBUFFERED: 1
|
|
30
|
+
PYTHONDONTWRITEBYTECODE: 1
|
|
31
|
+
PYTHONIOENCODING: utf-8
|
package/lib/templates.js
CHANGED
|
@@ -20,11 +20,16 @@ function generateVariablesYaml(appName, config) {
|
|
|
20
20
|
|
|
21
21
|
// For external type, create minimal variables.yaml
|
|
22
22
|
if (appType === 'external') {
|
|
23
|
+
// Use config values if provided, otherwise use defaults consistent with prompts
|
|
24
|
+
const systemKey = config.systemKey || appName;
|
|
25
|
+
const systemDisplayName = config.systemDisplayName || displayName;
|
|
26
|
+
const systemDescription = config.systemDescription || `External system integration for ${appName}`;
|
|
27
|
+
|
|
23
28
|
const variables = {
|
|
24
29
|
app: {
|
|
25
|
-
key:
|
|
26
|
-
displayName:
|
|
27
|
-
description:
|
|
30
|
+
key: systemKey,
|
|
31
|
+
displayName: systemDisplayName,
|
|
32
|
+
description: systemDescription,
|
|
28
33
|
type: 'external'
|
|
29
34
|
},
|
|
30
35
|
deployment: {
|
|
@@ -134,13 +139,31 @@ function generateVariablesYaml(appName, config) {
|
|
|
134
139
|
*/
|
|
135
140
|
function buildCoreEnv(config) {
|
|
136
141
|
return {
|
|
137
|
-
'NODE_ENV': '
|
|
142
|
+
'NODE_ENV': '${NODE_ENV}',
|
|
138
143
|
'PORT': config.port || 3000,
|
|
139
144
|
'APP_NAME': config.appName || 'myapp',
|
|
140
145
|
'LOG_LEVEL': 'info'
|
|
141
146
|
};
|
|
142
147
|
}
|
|
143
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Builds Python-specific environment variables
|
|
151
|
+
* @param {Object} config - Configuration options
|
|
152
|
+
* @returns {Object} Python environment variables
|
|
153
|
+
*/
|
|
154
|
+
function buildPythonEnv(config) {
|
|
155
|
+
const language = config.language || 'typescript';
|
|
156
|
+
if (language !== 'python') {
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
'PYTHONUNBUFFERED': '${PYTHONUNBUFFERED}',
|
|
162
|
+
'PYTHONDONTWRITEBYTECODE': '${PYTHONDONTWRITEBYTECODE}',
|
|
163
|
+
'PYTHONIOENCODING': '${PYTHONIOENCODING}'
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
144
167
|
function buildDatabaseEnv(config) {
|
|
145
168
|
if (!config.database) {
|
|
146
169
|
return {};
|
|
@@ -207,8 +230,9 @@ function buildMonitoringEnv(config) {
|
|
|
207
230
|
return {
|
|
208
231
|
'MISO_CONTROLLER_URL': config.controllerUrl || 'https://controller.aifabrix.ai',
|
|
209
232
|
'MISO_ENVIRONMENT': 'dev',
|
|
210
|
-
'MISO_CLIENTID': 'kv://miso-
|
|
211
|
-
'MISO_CLIENTSECRET': 'kv://miso-
|
|
233
|
+
'MISO_CLIENTID': 'kv://miso-controller-client-idKeyVault',
|
|
234
|
+
'MISO_CLIENTSECRET': 'kv://miso-controller-client-secretKeyVault',
|
|
235
|
+
'MISO_WEB_SERVER_URL': 'kv://miso-controller-web-server-url'
|
|
212
236
|
};
|
|
213
237
|
}
|
|
214
238
|
|
|
@@ -220,10 +244,17 @@ function buildMonitoringEnv(config) {
|
|
|
220
244
|
function addCoreVariables(lines, envVars) {
|
|
221
245
|
Object.entries(envVars).forEach(([key, value]) => {
|
|
222
246
|
if (key.startsWith('NODE_ENV') || key.startsWith('PORT') ||
|
|
223
|
-
key.startsWith('APP_NAME') || key.startsWith('LOG_LEVEL')
|
|
247
|
+
key.startsWith('APP_NAME') || key.startsWith('LOG_LEVEL') ||
|
|
248
|
+
key.startsWith('PYTHON')) {
|
|
224
249
|
lines.push(`${key}=${value}`);
|
|
225
250
|
}
|
|
226
251
|
});
|
|
252
|
+
|
|
253
|
+
// Add ALLOWED_ORIGINS and WEB_SERVER_URL after PORT variable
|
|
254
|
+
// ALLOWED_ORIGINS: My application public address
|
|
255
|
+
lines.push('ALLOWED_ORIGINS=http://localhost:*,');
|
|
256
|
+
// WEB_SERVER_URL: Miso public address (uses ${PORT} template variable)
|
|
257
|
+
lines.push('WEB_SERVER_URL=http://localhost:${PORT},');
|
|
227
258
|
}
|
|
228
259
|
|
|
229
260
|
function addMonitoringSection(lines, envVars) {
|
|
@@ -232,6 +263,10 @@ function addMonitoringSection(lines, envVars) {
|
|
|
232
263
|
lines.push(`MISO_ENVIRONMENT=${envVars['MISO_ENVIRONMENT']}`);
|
|
233
264
|
lines.push(`MISO_CLIENTID=${envVars['MISO_CLIENTID']}`);
|
|
234
265
|
lines.push(`MISO_CLIENTSECRET=${envVars['MISO_CLIENTSECRET']}`);
|
|
266
|
+
// MISO_WEB_SERVER_URL: Miso public address
|
|
267
|
+
if (envVars['MISO_WEB_SERVER_URL']) {
|
|
268
|
+
lines.push(`MISO_WEB_SERVER_URL=${envVars['MISO_WEB_SERVER_URL']}`);
|
|
269
|
+
}
|
|
235
270
|
}
|
|
236
271
|
|
|
237
272
|
function addDatabaseSection(lines, envVars) {
|
|
@@ -316,6 +351,7 @@ function addAuthenticationSection(lines, envVars) {
|
|
|
316
351
|
function generateEnvTemplate(config, existingEnv = {}) {
|
|
317
352
|
const envVars = {
|
|
318
353
|
...buildCoreEnv(config),
|
|
354
|
+
...buildPythonEnv(config),
|
|
319
355
|
...buildDatabaseEnv(config),
|
|
320
356
|
...buildRedisEnv(config),
|
|
321
357
|
...buildStorageEnv(config),
|
|
@@ -73,13 +73,11 @@ function formatPermissionError(errorData) {
|
|
|
73
73
|
|
|
74
74
|
const requiredPerms = extractRequiredPermissions(errorData);
|
|
75
75
|
addPermissionList(lines, requiredPerms, 'Required permissions');
|
|
76
|
-
|
|
77
76
|
const requestUrl = errorData.instance || errorData.url;
|
|
78
77
|
const method = errorData.method || 'POST';
|
|
79
78
|
if (requestUrl) {
|
|
80
79
|
lines.push(chalk.gray(`Request: ${method} ${requestUrl}`));
|
|
81
80
|
}
|
|
82
|
-
|
|
83
81
|
if (errorData.correlationId) {
|
|
84
82
|
lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
|
|
85
83
|
}
|
|
@@ -97,18 +95,27 @@ function formatValidationError(errorData) {
|
|
|
97
95
|
lines.push(chalk.red('❌ Validation Error\n'));
|
|
98
96
|
|
|
99
97
|
// Handle RFC 7807 Problem Details format
|
|
100
|
-
// Priority: detail > title > message
|
|
98
|
+
// Priority: detail > title > errorDescription > message > error
|
|
101
99
|
if (errorData.detail) {
|
|
102
100
|
lines.push(chalk.yellow(errorData.detail));
|
|
103
101
|
lines.push('');
|
|
104
102
|
} else if (errorData.title) {
|
|
105
103
|
lines.push(chalk.yellow(errorData.title));
|
|
106
104
|
lines.push('');
|
|
105
|
+
} else if (errorData.errorDescription) {
|
|
106
|
+
// Handle Keycloak-style error format
|
|
107
|
+
lines.push(chalk.yellow(errorData.errorDescription));
|
|
108
|
+
if (errorData.error) {
|
|
109
|
+
lines.push(chalk.gray(`Error code: ${errorData.error}`));
|
|
110
|
+
}
|
|
111
|
+
lines.push('');
|
|
107
112
|
} else if (errorData.message) {
|
|
108
113
|
lines.push(chalk.yellow(errorData.message));
|
|
109
114
|
lines.push('');
|
|
115
|
+
} else if (errorData.error) {
|
|
116
|
+
lines.push(chalk.yellow(errorData.error));
|
|
117
|
+
lines.push('');
|
|
110
118
|
}
|
|
111
|
-
|
|
112
119
|
// Handle errors array - this is the most important part
|
|
113
120
|
if (errorData.errors && Array.isArray(errorData.errors) && errorData.errors.length > 0) {
|
|
114
121
|
lines.push(chalk.yellow('Validation errors:'));
|
|
@@ -124,17 +131,14 @@ function formatValidationError(errorData) {
|
|
|
124
131
|
});
|
|
125
132
|
lines.push('');
|
|
126
133
|
}
|
|
127
|
-
|
|
128
134
|
// Show instance (endpoint) if available (RFC 7807)
|
|
129
135
|
if (errorData.instance) {
|
|
130
136
|
lines.push(chalk.gray(`Endpoint: ${errorData.instance}`));
|
|
131
137
|
}
|
|
132
|
-
|
|
133
138
|
// Show correlation ID if available
|
|
134
139
|
if (errorData.correlationId) {
|
|
135
140
|
lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
|
|
136
141
|
}
|
|
137
|
-
|
|
138
142
|
return lines.join('\n');
|
|
139
143
|
}
|
|
140
144
|
/**
|
|
@@ -145,7 +149,6 @@ function formatValidationError(errorData) {
|
|
|
145
149
|
function formatAuthenticationError(errorData) {
|
|
146
150
|
const lines = [];
|
|
147
151
|
lines.push(chalk.red('❌ Authentication Failed\n'));
|
|
148
|
-
|
|
149
152
|
if (errorData.message) {
|
|
150
153
|
lines.push(chalk.yellow(errorData.message));
|
|
151
154
|
} else {
|
|
@@ -153,11 +156,9 @@ function formatAuthenticationError(errorData) {
|
|
|
153
156
|
}
|
|
154
157
|
lines.push('');
|
|
155
158
|
lines.push(chalk.gray('Run: aifabrix login'));
|
|
156
|
-
|
|
157
159
|
if (errorData.correlationId) {
|
|
158
160
|
lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
|
|
159
161
|
}
|
|
160
|
-
|
|
161
162
|
return lines.join('\n');
|
|
162
163
|
}
|
|
163
164
|
/**
|
|
@@ -379,7 +380,7 @@ function createErrorResult(type, message, formatted, data) {
|
|
|
379
380
|
* @returns {string} Error message
|
|
380
381
|
*/
|
|
381
382
|
function getErrorMessage(errorData, defaultMessage) {
|
|
382
|
-
return errorData.detail || errorData.title || errorData.message || defaultMessage;
|
|
383
|
+
return errorData.detail || errorData.title || errorData.errorDescription || errorData.message || errorData.error || defaultMessage;
|
|
383
384
|
}
|
|
384
385
|
|
|
385
386
|
/**
|
|
@@ -486,7 +487,6 @@ function formatApiError(apiResponse) {
|
|
|
486
487
|
const parsed = parseErrorResponse(errorResponse, statusCode, isNetworkError);
|
|
487
488
|
return parsed.formatted;
|
|
488
489
|
}
|
|
489
|
-
|
|
490
490
|
module.exports = {
|
|
491
491
|
parseErrorResponse,
|
|
492
492
|
formatApiError,
|
package/lib/utils/device-code.js
CHANGED
|
@@ -140,15 +140,61 @@ function parseTokenResponse(response) {
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Creates a validation error with detailed information
|
|
145
|
+
* @function createValidationError
|
|
146
|
+
* @param {Object} response - Full API response object
|
|
147
|
+
* @returns {Error} Validation error with formattedError and errorData attached
|
|
148
|
+
*/
|
|
149
|
+
function createValidationError(response) {
|
|
150
|
+
const validationError = new Error('Token polling failed: Validation error');
|
|
151
|
+
|
|
152
|
+
// Attach formatted error if available (includes detailed validation info with ANSI colors)
|
|
153
|
+
if (response && response.formattedError) {
|
|
154
|
+
validationError.formattedError = response.formattedError;
|
|
155
|
+
validationError.message = `Token polling failed:\n${response.formattedError}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Attach error data for programmatic access
|
|
159
|
+
if (response && response.errorData) {
|
|
160
|
+
validationError.errorData = response.errorData;
|
|
161
|
+
validationError.errorType = response.errorType || 'validation';
|
|
162
|
+
|
|
163
|
+
// Build detailed message if formattedError not available
|
|
164
|
+
if (!validationError.formattedError) {
|
|
165
|
+
const errorData = response.errorData;
|
|
166
|
+
const detail = errorData.detail || errorData.title || errorData.message || 'Validation error';
|
|
167
|
+
let errorMsg = `Token polling failed: ${detail}`;
|
|
168
|
+
// Add validation errors if available
|
|
169
|
+
if (errorData.errors && Array.isArray(errorData.errors) && errorData.errors.length > 0) {
|
|
170
|
+
errorMsg += '\n\nValidation errors:';
|
|
171
|
+
errorData.errors.forEach(err => {
|
|
172
|
+
const field = err.field || err.path || 'validation';
|
|
173
|
+
const message = err.message || 'Invalid value';
|
|
174
|
+
if (field === 'validation' || field === 'unknown') {
|
|
175
|
+
errorMsg += `\n • ${message}`;
|
|
176
|
+
} else {
|
|
177
|
+
errorMsg += `\n • ${field}: ${message}`;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
validationError.message = errorMsg;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return validationError;
|
|
186
|
+
}
|
|
187
|
+
|
|
143
188
|
/**
|
|
144
189
|
* Handles polling errors
|
|
145
190
|
* @function handlePollingErrors
|
|
146
191
|
* @param {string} error - Error code
|
|
147
192
|
* @param {number} status - HTTP status code
|
|
193
|
+
* @param {Object} response - Full API response object (for accessing formattedError and errorData)
|
|
148
194
|
* @throws {Error} For fatal errors
|
|
149
195
|
* @returns {boolean} True if should continue polling
|
|
150
196
|
*/
|
|
151
|
-
function handlePollingErrors(error, status) {
|
|
197
|
+
function handlePollingErrors(error, status, response) {
|
|
152
198
|
if (error === 'authorization_pending' || status === 202) {
|
|
153
199
|
return true;
|
|
154
200
|
}
|
|
@@ -166,6 +212,11 @@ function handlePollingErrors(error, status) {
|
|
|
166
212
|
return true;
|
|
167
213
|
}
|
|
168
214
|
|
|
215
|
+
// Handle validation errors with detailed message
|
|
216
|
+
if (error === 'validation_error' || status === 400) {
|
|
217
|
+
throw createValidationError(response);
|
|
218
|
+
}
|
|
219
|
+
|
|
169
220
|
throw new Error(`Token polling failed: ${error}`);
|
|
170
221
|
}
|
|
171
222
|
|
|
@@ -187,6 +238,18 @@ async function waitForNextPoll(interval, slowDown) {
|
|
|
187
238
|
* @returns {string} Error code or 'Unknown error'
|
|
188
239
|
*/
|
|
189
240
|
function extractPollingError(response) {
|
|
241
|
+
// Check for structured error data first (from api-error-handler)
|
|
242
|
+
if (response.errorData) {
|
|
243
|
+
const errorData = response.errorData;
|
|
244
|
+
// For validation errors, return the error type so we can handle it specially
|
|
245
|
+
if (response.errorType === 'validation') {
|
|
246
|
+
return 'validation_error';
|
|
247
|
+
}
|
|
248
|
+
// Return the error message from structured error
|
|
249
|
+
return errorData.detail || errorData.title || errorData.message || errorData.error || response.error || 'Unknown error';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Fallback to original extraction logic
|
|
190
253
|
const apiResponse = response.data || {};
|
|
191
254
|
const errorData = typeof apiResponse === 'object' ? apiResponse : {};
|
|
192
255
|
return errorData.error || response.error || 'Unknown error';
|
|
@@ -229,7 +292,7 @@ async function processPollingResponse(response, interval) {
|
|
|
229
292
|
}
|
|
230
293
|
|
|
231
294
|
const error = extractPollingError(response);
|
|
232
|
-
const shouldContinue = handlePollingErrors(error, response.status);
|
|
295
|
+
const shouldContinue = handlePollingErrors(error, response.status, response);
|
|
233
296
|
|
|
234
297
|
if (shouldContinue) {
|
|
235
298
|
const slowDown = error === 'slow_down';
|
|
@@ -20,10 +20,11 @@ const logger = require('../utils/logger');
|
|
|
20
20
|
* @param {string} appKey - Application key
|
|
21
21
|
* @param {string} clientIdKey - Secret key for client ID (e.g., 'myapp-client-idKeyVault')
|
|
22
22
|
* @param {string} clientSecretKey - Secret key for client secret (e.g., 'myapp-client-secretKeyVault')
|
|
23
|
-
* @param {string}
|
|
23
|
+
* @param {string} _controllerUrl - Controller URL (e.g., 'http://localhost:3010' or 'https://controller.aifabrix.ai')
|
|
24
|
+
* Note: This parameter is accepted for compatibility but the template format http://${MISO_HOST}:${MISO_PORT} is used instead
|
|
24
25
|
* @returns {Promise<void>} Resolves when template is updated
|
|
25
26
|
*/
|
|
26
|
-
async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey,
|
|
27
|
+
async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, _controllerUrl) {
|
|
27
28
|
const envTemplatePath = path.join(process.cwd(), 'builder', appKey, 'env.template');
|
|
28
29
|
|
|
29
30
|
if (!fsSync.existsSync(envTemplatePath)) {
|
|
@@ -49,7 +50,7 @@ async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, controlle
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
if (hasControllerUrl) {
|
|
52
|
-
content = content.replace(/^MISO_CONTROLLER_URL\s*=.*$/m,
|
|
53
|
+
content = content.replace(/^MISO_CONTROLLER_URL\s*=.*$/m, 'MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}');
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
// Add missing entries
|
|
@@ -62,7 +63,7 @@ async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, controlle
|
|
|
62
63
|
missingEntries.push(`MISO_CLIENTSECRET=kv://${clientSecretKey}`);
|
|
63
64
|
}
|
|
64
65
|
if (!hasControllerUrl) {
|
|
65
|
-
missingEntries.push(
|
|
66
|
+
missingEntries.push('MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}');
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
const misoSection = `# MISO Application Client Credentials (per application)
|
package/package.json
CHANGED
|
@@ -26,13 +26,19 @@ KC_HTTP_MANAGEMENT_HEALTH_ENABLED=false
|
|
|
26
26
|
# =============================================================================
|
|
27
27
|
# DATABASE CONFIGURATION
|
|
28
28
|
# =============================================================================
|
|
29
|
+
|
|
30
|
+
# MISO Application Client Credentials (per application)
|
|
31
|
+
MISO_CLIENTID=kv://keycloak-client-idKeyVault
|
|
32
|
+
MISO_CLIENTSECRET=kv://keycloak-client-secretKeyVault
|
|
33
|
+
MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}
|
|
34
|
+
MISO_WEB_SERVER_URL=kv://miso-controller-web-server-url
|
|
35
|
+
|
|
29
36
|
# Connects to postgres service in Docker network (postgres) or localhost (local)
|
|
30
37
|
|
|
31
38
|
KC_DB=postgres
|
|
32
39
|
KC_DB_URL_HOST=${DB_HOST}
|
|
33
|
-
KC_DB_URL_PORT
|
|
40
|
+
KC_DB_URL_PORT=${DB_PORT}
|
|
34
41
|
KC_DB_URL_DATABASE=keycloak
|
|
35
42
|
KC_DB_USERNAME=keycloak_user
|
|
36
43
|
KC_DB_PASSWORD=kv://databases-keycloak-0-passwordKeyVault
|
|
37
44
|
DB_0_PASSWORD=kv://databases-keycloak-0-passwordKeyVault
|
|
38
|
-
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Application Metadata
|
|
2
2
|
app:
|
|
3
3
|
key: keycloak
|
|
4
|
-
displayName:
|
|
5
|
-
description:
|
|
4
|
+
displayName: 'AI Fabrix Keycloak'
|
|
5
|
+
description: 'Identity and Access Management'
|
|
6
6
|
type: webapp
|
|
7
7
|
|
|
8
8
|
# Image Configuration
|
|
9
9
|
image:
|
|
10
10
|
name: aifabrix/keycloak
|
|
11
|
-
tag:
|
|
11
|
+
tag: 'latest'
|
|
12
12
|
registry: devflowiseacr.azurecr.io
|
|
13
13
|
registryMode: acr
|
|
14
14
|
|
|
@@ -28,13 +28,21 @@ ONBOARDING_ADMIN_EMAIL=kv://miso-controller-admin-emailKeyVault
|
|
|
28
28
|
# APPLICATION ENVIRONMENT
|
|
29
29
|
# =============================================================================
|
|
30
30
|
|
|
31
|
-
NODE_ENV
|
|
32
|
-
|
|
31
|
+
# NODE_ENV: production for Docker (serves pre-built static files), development for local dev
|
|
32
|
+
# In Docker, this should be production to prevent Vite dev server initialization
|
|
33
|
+
NODE_ENV=${NODE_ENV}
|
|
34
|
+
PORT=${MISO_PORT}
|
|
33
35
|
AUTO_CREATE_TABLES=true
|
|
34
36
|
FAST_STARTUP=false
|
|
35
|
-
ALLOWED_ORIGINS=http://localhost
|
|
37
|
+
ALLOWED_ORIGINS=http://localhost:*
|
|
36
38
|
ENABLE_API_DOCS=true
|
|
37
39
|
|
|
40
|
+
# Rate Limiting Configuration (for local development)
|
|
41
|
+
# Set DISABLE_RATE_LIMIT=true to disable rate limiting entirely (local development only)
|
|
42
|
+
DISABLE_RATE_LIMIT=true
|
|
43
|
+
# RATE_LIMIT_WINDOW_MS=900000 # 15 minutes in milliseconds (default: 900000)
|
|
44
|
+
# RATE_LIMIT_MAX=100 # Max requests per window (default: 100)
|
|
45
|
+
|
|
38
46
|
# Package Version (auto-set by npm/pnpm, optional override)
|
|
39
47
|
# npm_package_version=1.0.0
|
|
40
48
|
|
|
@@ -72,6 +80,7 @@ REDIS_PERMISSIONS_TTL=900
|
|
|
72
80
|
|
|
73
81
|
KEYCLOAK_REALM=aifabrix
|
|
74
82
|
KEYCLOAK_SERVER_URL=kv://keycloak-server-urlKeyVault
|
|
83
|
+
KEYCLOAK_PUBLIC_SERVER_URL=kv://keycloak-public-server-urlKeyVault
|
|
75
84
|
KEYCLOAK_CLIENT_ID=miso-controller
|
|
76
85
|
KEYCLOAK_CLIENT_SECRET=kv://keycloak-client-secretKeyVault
|
|
77
86
|
KEYCLOAK_ADMIN_USERNAME=admin
|
|
@@ -96,8 +105,9 @@ AZURE_SERVICE_NAME=kv://azure-service-nameKeyVault
|
|
|
96
105
|
AZURE_CLIENT_ID=kv://azure-client-idKeyVault
|
|
97
106
|
AZURE_CLIENT_SECRET=kv://azure-client-secretKeyVault
|
|
98
107
|
|
|
99
|
-
# Mock Mode (
|
|
100
|
-
MOCK=true
|
|
108
|
+
# Mock Mode (defaults to false - set to true only for testing/development)
|
|
109
|
+
# Set MOCK=true to prevent actual Azure resource creation (for testing)
|
|
110
|
+
MOCK=false
|
|
101
111
|
|
|
102
112
|
# =============================================================================
|
|
103
113
|
# SECURITY & ENCRYPTION
|
|
@@ -117,11 +127,14 @@ API_KEY=kv://miso-controller-api-key-secretKeyVault
|
|
|
117
127
|
# =============================================================================
|
|
118
128
|
|
|
119
129
|
# MISO Controller URL
|
|
120
|
-
MISO_CONTROLLER_URL=
|
|
121
|
-
|
|
122
|
-
# Web Server URL (for OpenAPI documentation server URLs)
|
|
123
|
-
#
|
|
124
|
-
|
|
130
|
+
MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}
|
|
131
|
+
|
|
132
|
+
# Web Server URL (for OpenAPI documentation server URLs and Keycloak callbacks)
|
|
133
|
+
# This is the PUBLIC-FACING URL that browsers/users access (e.g., http://localhost:3100)
|
|
134
|
+
# Used to generate correct server URLs in OpenAPI spec and Keycloak callback URLs
|
|
135
|
+
# For Docker: use localhost with mapped port (e.g., localhost:3100)
|
|
136
|
+
# For production: use public domain (e.g., https://miso.example.com)
|
|
137
|
+
MISO_WEB_SERVER_URL=kv://miso-controller-web-server-url
|
|
125
138
|
|
|
126
139
|
# MISO Environment Configuration (miso, dev, tst, pro)
|
|
127
140
|
MISO_ENVIRONMENT=miso
|
|
@@ -1,230 +1,280 @@
|
|
|
1
1
|
roles:
|
|
2
|
-
- name:
|
|
3
|
-
value:
|
|
4
|
-
description:
|
|
5
|
-
Groups: [
|
|
6
|
-
|
|
7
|
-
- name:
|
|
8
|
-
value:
|
|
9
|
-
description:
|
|
10
|
-
Groups: [
|
|
11
|
-
|
|
12
|
-
- name:
|
|
13
|
-
value:
|
|
14
|
-
description:
|
|
15
|
-
Groups: [
|
|
16
|
-
|
|
17
|
-
- name:
|
|
18
|
-
value:
|
|
19
|
-
description:
|
|
20
|
-
Groups: [
|
|
21
|
-
|
|
22
|
-
- name:
|
|
23
|
-
value:
|
|
24
|
-
description:
|
|
25
|
-
Groups: [
|
|
26
|
-
|
|
27
|
-
- name:
|
|
28
|
-
value:
|
|
29
|
-
description:
|
|
30
|
-
Groups: [
|
|
31
|
-
|
|
32
|
-
- name:
|
|
33
|
-
value:
|
|
34
|
-
description:
|
|
35
|
-
Groups: [
|
|
2
|
+
- name: 'AI Fabrix Platform Admin'
|
|
3
|
+
value: 'aifabrix-platform-admin'
|
|
4
|
+
description: 'Full platform infrastructure management and enterprise controller access'
|
|
5
|
+
Groups: ['AI-Fabrix-Platform-Admins']
|
|
6
|
+
|
|
7
|
+
- name: 'AI Fabrix Security Admin'
|
|
8
|
+
value: 'aifabrix-security-admin'
|
|
9
|
+
description: 'Security and compliance management for enterprise controller'
|
|
10
|
+
Groups: ['AI-Fabrix-Security-Admins']
|
|
11
|
+
|
|
12
|
+
- name: 'AI Fabrix Infrastructure Admin'
|
|
13
|
+
value: 'aifabrix-infrastructure-admin'
|
|
14
|
+
description: 'Infrastructure deployment and management across environments'
|
|
15
|
+
Groups: ['AI-Fabrix-Infrastructure-Admins']
|
|
16
|
+
|
|
17
|
+
- name: 'AI Fabrix Deployment Admin'
|
|
18
|
+
value: 'aifabrix-deployment-admin'
|
|
19
|
+
description: 'Application deployment orchestration and environment management'
|
|
20
|
+
Groups: ['AI-Fabrix-Deployment-Admins']
|
|
21
|
+
|
|
22
|
+
- name: 'AI Fabrix Compliance Admin'
|
|
23
|
+
value: 'aifabrix-compliance-admin'
|
|
24
|
+
description: 'ISO 27001 compliance monitoring and audit management'
|
|
25
|
+
Groups: ['AI-Fabrix-Compliance-Admins']
|
|
26
|
+
|
|
27
|
+
- name: 'AI Fabrix Developer'
|
|
28
|
+
value: 'aifabrix-developer'
|
|
29
|
+
description: 'Developer access to deploy applications via GitHub Actions'
|
|
30
|
+
Groups: ['AI-Fabrix-Developers']
|
|
31
|
+
|
|
32
|
+
- name: 'AI Fabrix Observer'
|
|
33
|
+
value: 'aifabrix-observer'
|
|
34
|
+
description: 'Read-only access to monitoring, logs, and compliance reports'
|
|
35
|
+
Groups: ['AI-Fabrix-Observers']
|
|
36
36
|
|
|
37
37
|
permissions:
|
|
38
38
|
# Service User Management
|
|
39
|
-
- name:
|
|
40
|
-
roles: [
|
|
41
|
-
description:
|
|
42
|
-
|
|
43
|
-
- name:
|
|
44
|
-
roles: [
|
|
45
|
-
description:
|
|
46
|
-
|
|
47
|
-
- name:
|
|
48
|
-
roles: [
|
|
49
|
-
description:
|
|
50
|
-
|
|
51
|
-
- name:
|
|
52
|
-
roles: [
|
|
53
|
-
description:
|
|
54
|
-
|
|
39
|
+
- name: 'service-user:create'
|
|
40
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
|
|
41
|
+
description: 'Create service users and API clients'
|
|
42
|
+
|
|
43
|
+
- name: 'service-user:read'
|
|
44
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-observer']
|
|
45
|
+
description: 'View service users and their configurations'
|
|
46
|
+
|
|
47
|
+
- name: 'service-user:update'
|
|
48
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
|
|
49
|
+
description: 'Update service user configurations and regenerate secrets'
|
|
50
|
+
|
|
51
|
+
- name: 'service-user:delete'
|
|
52
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
|
|
53
|
+
description: 'Deactivate service users'
|
|
54
|
+
|
|
55
55
|
# User Management
|
|
56
|
-
- name:
|
|
57
|
-
roles: [
|
|
58
|
-
description:
|
|
59
|
-
|
|
60
|
-
- name:
|
|
61
|
-
roles: [
|
|
62
|
-
description:
|
|
63
|
-
|
|
64
|
-
- name:
|
|
65
|
-
roles: [
|
|
66
|
-
description:
|
|
67
|
-
|
|
68
|
-
- name:
|
|
69
|
-
roles: [
|
|
70
|
-
description:
|
|
71
|
-
|
|
56
|
+
- name: 'users:create'
|
|
57
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
|
|
58
|
+
description: 'Create new users'
|
|
59
|
+
|
|
60
|
+
- name: 'users:read'
|
|
61
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-observer']
|
|
62
|
+
description: 'View user information and profiles'
|
|
63
|
+
|
|
64
|
+
- name: 'users:update'
|
|
65
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
|
|
66
|
+
description: 'Update user information and manage group memberships'
|
|
67
|
+
|
|
68
|
+
- name: 'users:delete'
|
|
69
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
|
|
70
|
+
description: 'Delete users'
|
|
71
|
+
|
|
72
72
|
# Group Management
|
|
73
|
-
- name:
|
|
74
|
-
roles: [
|
|
75
|
-
description:
|
|
76
|
-
|
|
77
|
-
- name:
|
|
78
|
-
roles: [
|
|
79
|
-
description:
|
|
80
|
-
|
|
81
|
-
- name:
|
|
82
|
-
roles: [
|
|
83
|
-
description:
|
|
84
|
-
|
|
85
|
-
- name:
|
|
86
|
-
roles: [
|
|
87
|
-
description:
|
|
88
|
-
|
|
73
|
+
- name: 'groups:create'
|
|
74
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
|
|
75
|
+
description: 'Create new groups'
|
|
76
|
+
|
|
77
|
+
- name: 'groups:read'
|
|
78
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-observer']
|
|
79
|
+
description: 'View group information and members'
|
|
80
|
+
|
|
81
|
+
- name: 'groups:update'
|
|
82
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
|
|
83
|
+
description: 'Update group information'
|
|
84
|
+
|
|
85
|
+
- name: 'groups:delete'
|
|
86
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
|
|
87
|
+
description: 'Delete groups'
|
|
88
|
+
|
|
89
89
|
# Administrative Permissions
|
|
90
|
-
- name:
|
|
91
|
-
roles: [
|
|
92
|
-
description:
|
|
93
|
-
|
|
94
|
-
- name:
|
|
95
|
-
roles: [
|
|
96
|
-
description:
|
|
97
|
-
|
|
98
|
-
- name:
|
|
99
|
-
roles: [
|
|
100
|
-
description:
|
|
101
|
-
|
|
90
|
+
- name: 'admin:read'
|
|
91
|
+
roles: ['aifabrix-platform-admin']
|
|
92
|
+
description: 'Administrative read access to all resources'
|
|
93
|
+
|
|
94
|
+
- name: 'admin:write'
|
|
95
|
+
roles: ['aifabrix-platform-admin']
|
|
96
|
+
description: 'Administrative write access to all resources'
|
|
97
|
+
|
|
98
|
+
- name: 'admin:delete'
|
|
99
|
+
roles: ['aifabrix-platform-admin']
|
|
100
|
+
description: 'Administrative delete access to all resources'
|
|
101
|
+
|
|
102
102
|
# Template Applications (environment = null)
|
|
103
|
-
- name:
|
|
104
|
-
roles: [
|
|
105
|
-
description:
|
|
106
|
-
|
|
107
|
-
- name:
|
|
108
|
-
roles:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
103
|
+
- name: 'applications:create'
|
|
104
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin', 'aifabrix-deployment-admin']
|
|
105
|
+
description: 'Register new application templates'
|
|
106
|
+
|
|
107
|
+
- name: 'applications:read'
|
|
108
|
+
roles:
|
|
109
|
+
[
|
|
110
|
+
'aifabrix-platform-admin',
|
|
111
|
+
'aifabrix-infrastructure-admin',
|
|
112
|
+
'aifabrix-deployment-admin',
|
|
113
|
+
'aifabrix-developer',
|
|
114
|
+
'aifabrix-observer'
|
|
115
|
+
]
|
|
116
|
+
description: 'View application templates'
|
|
117
|
+
|
|
118
|
+
- name: 'applications:update'
|
|
119
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin', 'aifabrix-deployment-admin']
|
|
120
|
+
description: 'Update application templates'
|
|
121
|
+
|
|
122
|
+
- name: 'applications:delete'
|
|
123
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin']
|
|
124
|
+
description: 'Remove application templates'
|
|
125
|
+
|
|
119
126
|
# Environments
|
|
120
|
-
- name:
|
|
121
|
-
roles: [
|
|
122
|
-
description:
|
|
123
|
-
|
|
124
|
-
- name:
|
|
125
|
-
roles:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
127
|
+
- name: 'environments:create'
|
|
128
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin']
|
|
129
|
+
description: 'Create new environments (dev, tst, pro, miso)'
|
|
130
|
+
|
|
131
|
+
- name: 'environments:read'
|
|
132
|
+
roles:
|
|
133
|
+
[
|
|
134
|
+
'aifabrix-platform-admin',
|
|
135
|
+
'aifabrix-infrastructure-admin',
|
|
136
|
+
'aifabrix-deployment-admin',
|
|
137
|
+
'aifabrix-developer',
|
|
138
|
+
'aifabrix-observer'
|
|
139
|
+
]
|
|
140
|
+
description: 'View environments and their status'
|
|
141
|
+
|
|
142
|
+
- name: 'environments:update'
|
|
143
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin']
|
|
144
|
+
description: 'Update environment configuration'
|
|
145
|
+
|
|
146
|
+
- name: 'environments:delete'
|
|
147
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin']
|
|
148
|
+
description: 'Delete environments'
|
|
149
|
+
|
|
136
150
|
# Environment Applications
|
|
137
|
-
- name:
|
|
138
|
-
roles: [
|
|
139
|
-
description:
|
|
140
|
-
|
|
141
|
-
- name:
|
|
142
|
-
roles:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
- name: 'environments-applications:create'
|
|
152
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-deployment-admin', 'aifabrix-developer']
|
|
153
|
+
description: 'Create applications within environments'
|
|
154
|
+
|
|
155
|
+
- name: 'environments-applications:read'
|
|
156
|
+
roles:
|
|
157
|
+
[
|
|
158
|
+
'aifabrix-platform-admin',
|
|
159
|
+
'aifabrix-deployment-admin',
|
|
160
|
+
'aifabrix-developer',
|
|
161
|
+
'aifabrix-observer'
|
|
162
|
+
]
|
|
163
|
+
description: 'View applications within environments'
|
|
164
|
+
|
|
165
|
+
- name: 'environments-applications:update'
|
|
166
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-deployment-admin', 'aifabrix-developer']
|
|
167
|
+
description: 'Update applications within environments'
|
|
168
|
+
|
|
169
|
+
- name: 'environments-applications:delete'
|
|
170
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-deployment-admin']
|
|
171
|
+
description: 'Remove applications from environments'
|
|
172
|
+
|
|
153
173
|
# Pipeline & Deployment
|
|
154
|
-
- name:
|
|
155
|
-
roles: [
|
|
156
|
-
description:
|
|
157
|
-
|
|
158
|
-
- name:
|
|
159
|
-
roles:
|
|
160
|
-
|
|
161
|
-
|
|
174
|
+
- name: 'applications:deploy'
|
|
175
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-deployment-admin', 'aifabrix-developer']
|
|
176
|
+
description: 'Deploy applications to environments'
|
|
177
|
+
|
|
178
|
+
- name: 'deployments:read'
|
|
179
|
+
roles:
|
|
180
|
+
[
|
|
181
|
+
'aifabrix-platform-admin',
|
|
182
|
+
'aifabrix-deployment-admin',
|
|
183
|
+
'aifabrix-developer',
|
|
184
|
+
'aifabrix-observer'
|
|
185
|
+
]
|
|
186
|
+
description: 'View deployment history and status'
|
|
187
|
+
|
|
162
188
|
# Controller Operations
|
|
163
|
-
- name:
|
|
164
|
-
roles: [
|
|
165
|
-
description:
|
|
166
|
-
|
|
167
|
-
- name:
|
|
168
|
-
roles: [
|
|
169
|
-
description:
|
|
170
|
-
|
|
171
|
-
- name:
|
|
172
|
-
roles: [
|
|
173
|
-
description:
|
|
174
|
-
|
|
175
|
-
- name:
|
|
176
|
-
roles: [
|
|
177
|
-
description:
|
|
178
|
-
|
|
189
|
+
- name: 'controller:admin'
|
|
190
|
+
roles: ['aifabrix-platform-admin']
|
|
191
|
+
description: 'Full administrative access to controller operations'
|
|
192
|
+
|
|
193
|
+
- name: 'controller:deploy'
|
|
194
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin', 'aifabrix-deployment-admin']
|
|
195
|
+
description: 'Deploy infrastructure and manage environments'
|
|
196
|
+
|
|
197
|
+
- name: 'controller:monitor'
|
|
198
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-observer']
|
|
199
|
+
description: 'Monitor system health and view logs'
|
|
200
|
+
|
|
201
|
+
- name: 'controller:compliance'
|
|
202
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-compliance-admin']
|
|
203
|
+
description: 'Access compliance reports and audit logs'
|
|
204
|
+
|
|
179
205
|
# Authentication & Authorization
|
|
180
|
-
- name:
|
|
181
|
-
roles:
|
|
182
|
-
|
|
183
|
-
|
|
206
|
+
- name: 'auth:read'
|
|
207
|
+
roles:
|
|
208
|
+
[
|
|
209
|
+
'aifabrix-platform-admin',
|
|
210
|
+
'aifabrix-security-admin',
|
|
211
|
+
'aifabrix-developer',
|
|
212
|
+
'aifabrix-observer'
|
|
213
|
+
]
|
|
214
|
+
description: 'View user roles and permissions'
|
|
215
|
+
|
|
184
216
|
# Logs
|
|
185
|
-
- name:
|
|
186
|
-
roles:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
217
|
+
- name: 'logs:read'
|
|
218
|
+
roles:
|
|
219
|
+
[
|
|
220
|
+
'aifabrix-platform-admin',
|
|
221
|
+
'aifabrix-security-admin',
|
|
222
|
+
'aifabrix-compliance-admin',
|
|
223
|
+
'aifabrix-observer'
|
|
224
|
+
]
|
|
225
|
+
description: 'View application and audit logs'
|
|
226
|
+
|
|
227
|
+
- name: 'logs:write'
|
|
228
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-developer']
|
|
229
|
+
description: 'Write audit and error logs'
|
|
230
|
+
|
|
231
|
+
- name: 'logs:export'
|
|
232
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-compliance-admin']
|
|
233
|
+
description: 'Export logs for archival and compliance'
|
|
234
|
+
|
|
235
|
+
- name: 'audit:read'
|
|
236
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-compliance-admin']
|
|
237
|
+
description: 'View audit trail logs'
|
|
238
|
+
|
|
239
|
+
- name: 'jobs:read'
|
|
240
|
+
roles:
|
|
241
|
+
[
|
|
242
|
+
'aifabrix-platform-admin',
|
|
243
|
+
'aifabrix-infrastructure-admin',
|
|
244
|
+
'aifabrix-deployment-admin',
|
|
245
|
+
'aifabrix-observer'
|
|
246
|
+
]
|
|
247
|
+
description: 'View job and performance logs'
|
|
248
|
+
|
|
249
|
+
- name: 'admin:export'
|
|
250
|
+
roles: ['aifabrix-platform-admin']
|
|
251
|
+
description: 'Administrative export access to all data'
|
|
252
|
+
|
|
209
253
|
# Admin Operations
|
|
210
|
-
- name:
|
|
211
|
-
roles: [
|
|
212
|
-
description:
|
|
213
|
-
|
|
214
|
-
- name:
|
|
215
|
-
roles: [
|
|
216
|
-
description:
|
|
217
|
-
|
|
254
|
+
- name: 'admin:sync'
|
|
255
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin']
|
|
256
|
+
description: 'Full system synchronization operations'
|
|
257
|
+
|
|
258
|
+
- name: 'admin:keycloak'
|
|
259
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
|
|
260
|
+
description: 'Keycloak administration and configuration'
|
|
261
|
+
|
|
218
262
|
# Cache Management
|
|
219
|
-
- name:
|
|
220
|
-
roles: [
|
|
221
|
-
description:
|
|
222
|
-
|
|
223
|
-
- name:
|
|
224
|
-
roles: [
|
|
225
|
-
description:
|
|
226
|
-
|
|
263
|
+
- name: 'cache:read'
|
|
264
|
+
roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-observer']
|
|
265
|
+
description: 'View cache statistics and performance metrics'
|
|
266
|
+
|
|
267
|
+
- name: 'cache:admin'
|
|
268
|
+
roles: ['aifabrix-platform-admin']
|
|
269
|
+
description: 'Manage cache (clear, invalidate patterns)'
|
|
270
|
+
|
|
227
271
|
# Dashboard
|
|
228
|
-
- name:
|
|
229
|
-
roles:
|
|
230
|
-
|
|
272
|
+
- name: 'dashboard:read'
|
|
273
|
+
roles:
|
|
274
|
+
[
|
|
275
|
+
'aifabrix-platform-admin',
|
|
276
|
+
'aifabrix-deployment-admin',
|
|
277
|
+
'aifabrix-developer',
|
|
278
|
+
'aifabrix-observer'
|
|
279
|
+
]
|
|
280
|
+
description: 'View dashboard summaries and aggregates'
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Application Metadata
|
|
2
2
|
app:
|
|
3
3
|
key: miso-controller
|
|
4
|
-
displayName:
|
|
5
|
-
description:
|
|
4
|
+
displayName: 'Miso Controller'
|
|
5
|
+
description: 'AI Fabrix Miso Controller - Backend API and orchestration service'
|
|
6
6
|
type: webapp
|
|
7
7
|
|
|
8
8
|
# Image Configuration
|
|
@@ -34,7 +34,7 @@ healthCheck:
|
|
|
34
34
|
|
|
35
35
|
# Authentication
|
|
36
36
|
authentication:
|
|
37
|
-
type:
|
|
37
|
+
type: keycloak
|
|
38
38
|
enableSSO: true
|
|
39
39
|
requiredRoles:
|
|
40
40
|
- aifabrix-user
|