vme-mcp-server 0.1.5 → 0.1.7
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/dist/lib/api-utils.js +251 -0
- package/dist/lib/capability-cache.js +131 -0
- package/dist/lib/capability-discovery.js +240 -0
- package/dist/lib/intent-recognition.js +159 -0
- package/dist/lib/name-resolver.js +174 -0
- package/dist/lib/session.js +59 -0
- package/dist/lib/vm-parsing.js +82 -0
- package/dist/server.js +36 -918
- package/dist/server_old.js +933 -0
- package/dist/tools/check-capability.js +84 -0
- package/dist/tools/create-vm.js +253 -0
- package/dist/tools/discover-capabilities.js +79 -0
- package/dist/tools/export-training-data.js +115 -0
- package/dist/tools/get-cache-status.js +123 -0
- package/dist/tools/get-resources.js +40 -0
- package/dist/tools/index.js +68 -0
- package/dist/tools/parse-intent.js +52 -0
- package/dist/tools/provide-feedback.js +61 -0
- package/dist/tools/query-resources.js +248 -0
- package/dist/types/interfaces.js +2 -0
- package/package.json +4 -4
@@ -0,0 +1,159 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.parseVMIntent = void 0;
|
4
|
+
const vm_parsing_js_1 = require("./vm-parsing.js");
|
5
|
+
// Intent recognition system for natural language processing
|
6
|
+
function parseVMIntent(naturalLanguageInput) {
|
7
|
+
const input = naturalLanguageInput.toLowerCase().trim();
|
8
|
+
const result = {
|
9
|
+
action: 'unknown',
|
10
|
+
confidence: 0,
|
11
|
+
entities: {},
|
12
|
+
originalText: naturalLanguageInput
|
13
|
+
};
|
14
|
+
// Intent Classification with confidence scoring
|
15
|
+
const intentPatterns = {
|
16
|
+
create: [
|
17
|
+
/\b(create|provision|deploy|launch|start|build|make|setup)\b/,
|
18
|
+
/\bneed\s+(\d+\s+)?(vm|server|machine)/,
|
19
|
+
/\bcan\s+you\s+(create|provision|deploy)/
|
20
|
+
],
|
21
|
+
modify: [
|
22
|
+
/\b(modify|change|update|edit|alter|resize|scale)\b/,
|
23
|
+
/\badd\s+(more\s+)?(cpu|memory|disk|storage)/,
|
24
|
+
/\bchange\s+the\s+(size|plan|configuration)/
|
25
|
+
],
|
26
|
+
monitor: [
|
27
|
+
/\b(show|list|display|check|status|monitor|view)\b(?!.*\b(available|options|resources|templates|plans)\b)/,
|
28
|
+
/\bwhat\s+(vm|server|machine)/,
|
29
|
+
/\bhow\s+many\s+(vm|server|machine)/
|
30
|
+
],
|
31
|
+
destroy: [
|
32
|
+
/\b(delete|remove|destroy|terminate|stop|kill)\b/,
|
33
|
+
/\btear\s+down/,
|
34
|
+
/\bshut\s+down/
|
35
|
+
],
|
36
|
+
discover: [
|
37
|
+
/\b(available|options|resources|templates|plans)\b/,
|
38
|
+
/\bshow\s+me\s+(all|available)/,
|
39
|
+
/\bwhat\s+(are|is)\s+(available|possible)/,
|
40
|
+
/\bwhat\s+can\s+i\s+(create|do)/
|
41
|
+
]
|
42
|
+
};
|
43
|
+
// Calculate confidence for each intent
|
44
|
+
let maxConfidence = 0;
|
45
|
+
let detectedAction = 'unknown';
|
46
|
+
for (const [action, patterns] of Object.entries(intentPatterns)) {
|
47
|
+
let matches = 0;
|
48
|
+
for (const pattern of patterns) {
|
49
|
+
if (pattern.test(input)) {
|
50
|
+
matches++;
|
51
|
+
}
|
52
|
+
}
|
53
|
+
const confidence = patterns.length > 0 ? matches / patterns.length : 0;
|
54
|
+
if (confidence > maxConfidence) {
|
55
|
+
maxConfidence = confidence;
|
56
|
+
detectedAction = action;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
result.action = detectedAction;
|
60
|
+
result.confidence = maxConfidence;
|
61
|
+
// Entity Extraction for VM creation
|
62
|
+
if (result.action === 'create') {
|
63
|
+
// Extract VM names and count
|
64
|
+
const namePatterns = [
|
65
|
+
/\b(?:vm|server|machine)s?\s+(?:named\s+)?["']?([a-zA-Z0-9\-_]+(?:\s*->\s*[a-zA-Z0-9\-_]+)?)["']?/,
|
66
|
+
/\b(?:named\s+)?["']?([a-zA-Z0-9\-_]+(?:\s*->\s*[a-zA-Z0-9\-_]+)?)["']?\s+(?:vm|server|machine)/,
|
67
|
+
/\bcreate\s+(?:(?:vm|server|machine)s?\s+)?["']?([a-zA-Z0-9\-_]+(?:\s*->\s*[a-zA-Z0-9\-_]+)?)["']?/,
|
68
|
+
/\b(?:vm|server|machine)\s+named\s+["']?([a-zA-Z0-9\-_]+(?:\s*->\s*[a-zA-Z0-9\-_]+)?)["']?/
|
69
|
+
];
|
70
|
+
for (const pattern of namePatterns) {
|
71
|
+
const match = input.match(pattern);
|
72
|
+
if (match) {
|
73
|
+
const nameInput = match[1] || match[2];
|
74
|
+
result.entities.vmNames = (0, vm_parsing_js_1.parseVMNames)(nameInput);
|
75
|
+
break;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
// Extract count
|
79
|
+
const countMatch = input.match(/\b(\d+)\s+(?:vm|server|machine)/);
|
80
|
+
if (countMatch) {
|
81
|
+
result.entities.count = parseInt(countMatch[1]);
|
82
|
+
}
|
83
|
+
// Extract OS template
|
84
|
+
const osPatterns = [
|
85
|
+
/\b(ubuntu|rocky|centos|debian|rhel|red\s*hat|alma|almalinux)(?:\s+(\d+(?:\.\d+)?))?/,
|
86
|
+
/\busing\s+([a-zA-Z0-9\-_]+)\s+(?:template|os|operating)/,
|
87
|
+
/\b(?:template|os|operating)\s+([a-zA-Z0-9\-_]+)/
|
88
|
+
];
|
89
|
+
for (const pattern of osPatterns) {
|
90
|
+
const match = input.match(pattern);
|
91
|
+
if (match) {
|
92
|
+
result.entities.osTemplate = match[0];
|
93
|
+
break;
|
94
|
+
}
|
95
|
+
}
|
96
|
+
// Extract size/memory
|
97
|
+
const sizePatterns = [
|
98
|
+
/\b(small|medium|large|tiny)\b/,
|
99
|
+
/\b(\d+)\s*gb\s+(?:memory|ram)\b/,
|
100
|
+
/\b(\d+)\s*cpu[s]?\s*,?\s*(\d+)\s*gb/,
|
101
|
+
/\bwith\s+(\d+(?:gb|cpu))\b/,
|
102
|
+
/\b(\d+gb)\b/
|
103
|
+
];
|
104
|
+
for (const pattern of sizePatterns) {
|
105
|
+
const match = input.match(pattern);
|
106
|
+
if (match) {
|
107
|
+
result.entities.size = match[0];
|
108
|
+
break;
|
109
|
+
}
|
110
|
+
}
|
111
|
+
// Extract group/environment
|
112
|
+
const groupPatterns = [
|
113
|
+
/\bin\s+(?:the\s+)?([a-zA-Z0-9\-_]+)\s+(?:group|environment|site)/,
|
114
|
+
/\b(?:group|environment|site)\s+([a-zA-Z0-9\-_]+)/,
|
115
|
+
/\bfor\s+([a-zA-Z0-9\-_]+)\s+(?:environment|testing|production)/
|
116
|
+
];
|
117
|
+
for (const pattern of groupPatterns) {
|
118
|
+
const match = input.match(pattern);
|
119
|
+
if (match) {
|
120
|
+
result.entities.group = match[1];
|
121
|
+
break;
|
122
|
+
}
|
123
|
+
}
|
124
|
+
// Extract zone/cloud
|
125
|
+
const zonePatterns = [
|
126
|
+
/\bon\s+([a-zA-Z0-9\-_]+)\s+(?:zone|cloud)/,
|
127
|
+
/\b(?:zone|cloud)\s+([a-zA-Z0-9\-_]+)/,
|
128
|
+
/\bin\s+([a-zA-Z0-9\-_]+)\s+(?:zone|cloud)/,
|
129
|
+
/\b(?:deploy|launch|create)\s+(?:in\s+)?([a-zA-Z0-9\-_]+)\s+zone/
|
130
|
+
];
|
131
|
+
for (const pattern of zonePatterns) {
|
132
|
+
const match = input.match(pattern);
|
133
|
+
if (match) {
|
134
|
+
result.entities.zone = match[1];
|
135
|
+
break;
|
136
|
+
}
|
137
|
+
}
|
138
|
+
// Extract distribution strategy
|
139
|
+
if (input.includes('spread') || input.includes('distribute') || input.includes('across')) {
|
140
|
+
result.entities.distribution = 'spread';
|
141
|
+
}
|
142
|
+
else if (input.includes('each node') || input.includes('all nodes')) {
|
143
|
+
result.entities.distribution = 'spread';
|
144
|
+
}
|
145
|
+
else if (input.includes('node') && input.match(/node[s]?\s*[\d,\s]+/)) {
|
146
|
+
const nodeMatch = input.match(/node[s]?\s*([\d,\s]+)/);
|
147
|
+
if (nodeMatch) {
|
148
|
+
result.entities.distribution = nodeMatch[1].replace(/\s+/g, '');
|
149
|
+
}
|
150
|
+
}
|
151
|
+
// Boost confidence if we extracted entities
|
152
|
+
const entityCount = Object.keys(result.entities).length;
|
153
|
+
if (entityCount > 0) {
|
154
|
+
result.confidence = Math.min(0.95, result.confidence + (entityCount * 0.1));
|
155
|
+
}
|
156
|
+
}
|
157
|
+
return result;
|
158
|
+
}
|
159
|
+
exports.parseVMIntent = parseVMIntent;
|
@@ -0,0 +1,174 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.nameResolver = exports.NameResolver = void 0;
|
4
|
+
const capability_discovery_js_1 = require("./capability-discovery.js");
|
5
|
+
// Generic name->ID resolver using capability discovery data
|
6
|
+
class NameResolver {
|
7
|
+
constructor() {
|
8
|
+
this.capabilitiesCache = null;
|
9
|
+
this.cacheTimestamp = 0;
|
10
|
+
this.CACHE_TTL = 300000; // 5 minutes
|
11
|
+
}
|
12
|
+
async getCapabilities() {
|
13
|
+
const now = Date.now();
|
14
|
+
if (!this.capabilitiesCache || (now - this.cacheTimestamp) > this.CACHE_TTL) {
|
15
|
+
this.capabilitiesCache = await capability_discovery_js_1.capabilityDiscovery.discoverCapabilities(['all']);
|
16
|
+
this.cacheTimestamp = now;
|
17
|
+
}
|
18
|
+
return this.capabilitiesCache;
|
19
|
+
}
|
20
|
+
async resolveNameToId(resourceType, name) {
|
21
|
+
if (!name)
|
22
|
+
return null;
|
23
|
+
const capabilities = await this.getCapabilities();
|
24
|
+
const nameLower = name.toLowerCase();
|
25
|
+
switch (resourceType) {
|
26
|
+
case 'virtualImage':
|
27
|
+
case 'image':
|
28
|
+
return this.resolveImageName(capabilities, nameLower);
|
29
|
+
case 'group':
|
30
|
+
case 'site':
|
31
|
+
return this.resolveGroupName(capabilities, nameLower);
|
32
|
+
case 'servicePlan':
|
33
|
+
case 'plan':
|
34
|
+
return this.resolvePlanName(capabilities, nameLower);
|
35
|
+
case 'zone':
|
36
|
+
case 'cloud':
|
37
|
+
return this.resolveZoneName(capabilities, nameLower);
|
38
|
+
case 'instanceType':
|
39
|
+
return this.resolveInstanceTypeName(capabilities, nameLower);
|
40
|
+
default:
|
41
|
+
return null;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
resolveImageName(capabilities, nameLower) {
|
45
|
+
const images = capabilities.compute?.available_images || [];
|
46
|
+
// Try exact name match first
|
47
|
+
let found = images.find((img) => img.name?.toLowerCase() === nameLower);
|
48
|
+
if (found)
|
49
|
+
return found.id;
|
50
|
+
// Try partial name match
|
51
|
+
found = images.find((img) => img.name?.toLowerCase().includes(nameLower));
|
52
|
+
if (found)
|
53
|
+
return found.id;
|
54
|
+
// Try category + version matching (e.g., "ubuntu 20.04" -> ubuntu-2004)
|
55
|
+
for (const img of images) {
|
56
|
+
const imgName = img.name?.toLowerCase() || '';
|
57
|
+
const category = img.category?.toLowerCase() || '';
|
58
|
+
const version = img.version?.toString() || '';
|
59
|
+
// Match patterns like "ubuntu 20.04" to "ubuntu-2004"
|
60
|
+
if (nameLower.includes(category) && nameLower.includes(version)) {
|
61
|
+
return img.id;
|
62
|
+
}
|
63
|
+
// Match patterns like "ubuntu" to first ubuntu image
|
64
|
+
if (nameLower === category) {
|
65
|
+
return img.id;
|
66
|
+
}
|
67
|
+
}
|
68
|
+
return null;
|
69
|
+
}
|
70
|
+
resolveGroupName(capabilities, nameLower) {
|
71
|
+
const groups = capabilities.platform?.groups || [];
|
72
|
+
// Try exact match
|
73
|
+
let found = groups.find((g) => g.name?.toLowerCase() === nameLower);
|
74
|
+
if (found)
|
75
|
+
return found.id;
|
76
|
+
// Try partial match
|
77
|
+
found = groups.find((g) => g.name?.toLowerCase().includes(nameLower));
|
78
|
+
if (found)
|
79
|
+
return found.id;
|
80
|
+
return null;
|
81
|
+
}
|
82
|
+
resolvePlanName(capabilities, nameLower) {
|
83
|
+
const plans = capabilities.compute?.service_plans || [];
|
84
|
+
// Try exact match
|
85
|
+
let found = plans.find((p) => p.name?.toLowerCase() === nameLower);
|
86
|
+
if (found)
|
87
|
+
return found.id;
|
88
|
+
// Try size-based matching
|
89
|
+
if (nameLower.includes('small') || nameLower.includes('1 cpu') || nameLower.includes('4gb')) {
|
90
|
+
found = plans.find((p) => p.name?.toLowerCase().includes('1 cpu') ||
|
91
|
+
p.name?.toLowerCase().includes('4gb') ||
|
92
|
+
p.max_cpu === 1);
|
93
|
+
if (found)
|
94
|
+
return found.id;
|
95
|
+
}
|
96
|
+
if (nameLower.includes('medium') || nameLower.includes('2 cpu') || nameLower.includes('8gb')) {
|
97
|
+
found = plans.find((p) => p.name?.toLowerCase().includes('2 cpu') ||
|
98
|
+
p.name?.toLowerCase().includes('8gb') ||
|
99
|
+
p.max_cpu === 2);
|
100
|
+
if (found)
|
101
|
+
return found.id;
|
102
|
+
}
|
103
|
+
if (nameLower.includes('large') || nameLower.includes('4 cpu') || nameLower.includes('16gb')) {
|
104
|
+
found = plans.find((p) => p.name?.toLowerCase().includes('4 cpu') ||
|
105
|
+
p.name?.toLowerCase().includes('16gb') ||
|
106
|
+
p.max_cpu === 4);
|
107
|
+
if (found)
|
108
|
+
return found.id;
|
109
|
+
}
|
110
|
+
// Try partial match
|
111
|
+
found = plans.find((p) => p.name?.toLowerCase().includes(nameLower));
|
112
|
+
if (found)
|
113
|
+
return found.id;
|
114
|
+
return null;
|
115
|
+
}
|
116
|
+
resolveZoneName(capabilities, nameLower) {
|
117
|
+
const zones = capabilities.networking?.zones || [];
|
118
|
+
// Try exact match
|
119
|
+
let found = zones.find((z) => z.name?.toLowerCase() === nameLower);
|
120
|
+
if (found)
|
121
|
+
return found.id;
|
122
|
+
// Try partial match
|
123
|
+
found = zones.find((z) => z.name?.toLowerCase().includes(nameLower));
|
124
|
+
if (found)
|
125
|
+
return found.id;
|
126
|
+
return null;
|
127
|
+
}
|
128
|
+
resolveInstanceTypeName(capabilities, nameLower) {
|
129
|
+
const instanceTypes = capabilities.compute?.instance_types || [];
|
130
|
+
// Try exact match
|
131
|
+
let found = instanceTypes.find((t) => t.name?.toLowerCase() === nameLower);
|
132
|
+
if (found)
|
133
|
+
return found.id;
|
134
|
+
// Try code match
|
135
|
+
found = instanceTypes.find((t) => t.code?.toLowerCase() === nameLower);
|
136
|
+
if (found)
|
137
|
+
return found.id;
|
138
|
+
// Try partial match
|
139
|
+
found = instanceTypes.find((t) => t.name?.toLowerCase().includes(nameLower));
|
140
|
+
if (found)
|
141
|
+
return found.id;
|
142
|
+
return null;
|
143
|
+
}
|
144
|
+
// Helper method to get available names for a resource type
|
145
|
+
async getAvailableNames(resourceType) {
|
146
|
+
const capabilities = await this.getCapabilities();
|
147
|
+
switch (resourceType) {
|
148
|
+
case 'virtualImage':
|
149
|
+
return capabilities.compute?.available_images?.map((img) => img.name) || [];
|
150
|
+
case 'group':
|
151
|
+
return capabilities.platform?.groups?.map((g) => g.name) || [];
|
152
|
+
case 'servicePlan':
|
153
|
+
return capabilities.compute?.service_plans?.map((p) => p.name) || [];
|
154
|
+
case 'zone':
|
155
|
+
return capabilities.networking?.zones?.map((z) => z.name) || [];
|
156
|
+
case 'instanceType':
|
157
|
+
return capabilities.compute?.instance_types?.map((t) => t.name) || [];
|
158
|
+
default:
|
159
|
+
return [];
|
160
|
+
}
|
161
|
+
}
|
162
|
+
// Helper to resolve multiple resources at once
|
163
|
+
async resolveMultiple(resources) {
|
164
|
+
const results = {};
|
165
|
+
for (const resource of resources) {
|
166
|
+
const key = `${resource.type}_${resource.name}`;
|
167
|
+
results[key] = await this.resolveNameToId(resource.type, resource.name);
|
168
|
+
}
|
169
|
+
return results;
|
170
|
+
}
|
171
|
+
}
|
172
|
+
exports.NameResolver = NameResolver;
|
173
|
+
// Global resolver instance
|
174
|
+
exports.nameResolver = new NameResolver();
|
@@ -0,0 +1,59 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.logInteraction = exports.generateSessionId = void 0;
|
4
|
+
const fs_1 = require("fs");
|
5
|
+
const path_1 = require("path");
|
6
|
+
// AI Training Data Collection Configuration
|
7
|
+
const AI_TRAINING_ENABLED = process.env.ENABLE_AI_TRAINING_DATA === 'true';
|
8
|
+
const AI_TRAINING_FIELDS = (process.env.AI_TRAINING_DATA_FIELDS || 'user_input,parsed_output,success_metrics').split(',');
|
9
|
+
const AI_TRAINING_RETENTION_DAYS = parseInt(process.env.AI_TRAINING_DATA_RETENTION_DAYS || '30');
|
10
|
+
// Generate session ID for interaction tracking
|
11
|
+
function generateSessionId() {
|
12
|
+
return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
13
|
+
}
|
14
|
+
exports.generateSessionId = generateSessionId;
|
15
|
+
// Privacy-aware interaction logging
|
16
|
+
function logInteraction(interaction) {
|
17
|
+
if (!AI_TRAINING_ENABLED) {
|
18
|
+
return; // Respect user privacy choice
|
19
|
+
}
|
20
|
+
try {
|
21
|
+
// Ensure logs directory exists
|
22
|
+
const logsDir = (0, path_1.join)(process.cwd(), 'ai-training-logs');
|
23
|
+
if (!(0, fs_1.existsSync)(logsDir)) {
|
24
|
+
(0, fs_1.mkdirSync)(logsDir, { recursive: true });
|
25
|
+
}
|
26
|
+
// Filter fields based on user preferences
|
27
|
+
const filteredInteraction = {
|
28
|
+
id: interaction.id || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
29
|
+
timestamp: new Date().toISOString(),
|
30
|
+
session_id: interaction.session_id || 'unknown',
|
31
|
+
tool_name: interaction.tool_name || 'unknown'
|
32
|
+
};
|
33
|
+
// Only include fields user has opted into
|
34
|
+
if (AI_TRAINING_FIELDS.includes('user_input') && interaction.user_input) {
|
35
|
+
filteredInteraction.user_input = interaction.user_input;
|
36
|
+
}
|
37
|
+
if (AI_TRAINING_FIELDS.includes('parsed_output') && interaction.parsed_output) {
|
38
|
+
filteredInteraction.parsed_output = interaction.parsed_output;
|
39
|
+
}
|
40
|
+
if (AI_TRAINING_FIELDS.includes('api_calls') && interaction.api_calls) {
|
41
|
+
filteredInteraction.api_calls = interaction.api_calls;
|
42
|
+
}
|
43
|
+
if (AI_TRAINING_FIELDS.includes('success_metrics') && interaction.success_metrics) {
|
44
|
+
filteredInteraction.success_metrics = interaction.success_metrics;
|
45
|
+
}
|
46
|
+
if (AI_TRAINING_FIELDS.includes('timing') && interaction.timing) {
|
47
|
+
filteredInteraction.timing = interaction.timing;
|
48
|
+
}
|
49
|
+
// Append to daily log file (JSONL format)
|
50
|
+
const logFile = (0, path_1.join)(logsDir, `interactions-${new Date().toISOString().split('T')[0]}.jsonl`);
|
51
|
+
const logEntry = JSON.stringify(filteredInteraction) + '\n';
|
52
|
+
(0, fs_1.writeFileSync)(logFile, logEntry, { flag: 'a' });
|
53
|
+
}
|
54
|
+
catch (error) {
|
55
|
+
// Fail silently to not disrupt user experience
|
56
|
+
console.warn('AI training data logging failed:', error);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
exports.logInteraction = logInteraction;
|
@@ -0,0 +1,82 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.calculateNodeAssignments = exports.parseVMNames = void 0;
|
4
|
+
// Helper function to parse VM name patterns
|
5
|
+
function parseVMNames(nameInput, count) {
|
6
|
+
// Handle range patterns like "web01->web03" or "web01 -> web03"
|
7
|
+
const rangeMatch = nameInput.match(/^(.+?)(\d+)\s*->\s*(.+?)(\d+)$/);
|
8
|
+
if (rangeMatch) {
|
9
|
+
const [, prefix1, start, prefix2, end] = rangeMatch;
|
10
|
+
if (prefix1.trim() === prefix2.trim()) {
|
11
|
+
const basePrefix = prefix1.trim();
|
12
|
+
const startNum = parseInt(start);
|
13
|
+
const endNum = parseInt(end);
|
14
|
+
const names = [];
|
15
|
+
for (let i = startNum; i <= endNum; i++) {
|
16
|
+
const paddedNum = i.toString().padStart(start.length, '0');
|
17
|
+
names.push(`${basePrefix}${paddedNum}`);
|
18
|
+
}
|
19
|
+
return names;
|
20
|
+
}
|
21
|
+
}
|
22
|
+
// Handle count-based generation
|
23
|
+
if (count && count > 1) {
|
24
|
+
const names = [];
|
25
|
+
// Extract base name and number pattern
|
26
|
+
const match = nameInput.match(/^(.+?)(\d+)$/);
|
27
|
+
if (match) {
|
28
|
+
const [, prefix, startStr] = match;
|
29
|
+
const startNum = parseInt(startStr);
|
30
|
+
for (let i = 0; i < count; i++) {
|
31
|
+
const num = startNum + i;
|
32
|
+
const paddedNum = num.toString().padStart(startStr.length, '0');
|
33
|
+
names.push(`${prefix}${paddedNum}`);
|
34
|
+
}
|
35
|
+
return names;
|
36
|
+
}
|
37
|
+
else {
|
38
|
+
// If no number pattern, append numbers
|
39
|
+
for (let i = 1; i <= count; i++) {
|
40
|
+
const paddedNum = i.toString().padStart(2, '0');
|
41
|
+
names.push(`${nameInput}${paddedNum}`);
|
42
|
+
}
|
43
|
+
return names;
|
44
|
+
}
|
45
|
+
}
|
46
|
+
// Single VM
|
47
|
+
return [nameInput];
|
48
|
+
}
|
49
|
+
exports.parseVMNames = parseVMNames;
|
50
|
+
// Determine node assignment strategy for VMs
|
51
|
+
function calculateNodeAssignments(vmNames, nodes, distribution) {
|
52
|
+
let nodeAssignments = [];
|
53
|
+
if (distribution === 'spread' || (vmNames.length > 1 && !distribution)) {
|
54
|
+
// Distribute VMs across all available nodes
|
55
|
+
vmNames.forEach((vmName, index) => {
|
56
|
+
const nodeId = nodes[index % nodes.length];
|
57
|
+
nodeAssignments.push({ name: vmName, kvmHostId: nodeId });
|
58
|
+
});
|
59
|
+
}
|
60
|
+
else if (distribution && distribution !== 'auto') {
|
61
|
+
// Parse specific node assignments like "1,2,3" or "node1,node2,node3"
|
62
|
+
const specifiedNodes = distribution.split(',').map((n) => {
|
63
|
+
const trimmed = n.trim();
|
64
|
+
return trimmed.startsWith('node') ? parseInt(trimmed.replace('node', '')) : parseInt(trimmed);
|
65
|
+
}).filter((n) => !isNaN(n));
|
66
|
+
if (specifiedNodes.length > 0) {
|
67
|
+
vmNames.forEach((vmName, index) => {
|
68
|
+
const nodeId = specifiedNodes[index % specifiedNodes.length];
|
69
|
+
nodeAssignments.push({ name: vmName, kvmHostId: nodeId });
|
70
|
+
});
|
71
|
+
}
|
72
|
+
else {
|
73
|
+
nodeAssignments = vmNames.map(vmName => ({ name: vmName }));
|
74
|
+
}
|
75
|
+
}
|
76
|
+
else {
|
77
|
+
// Auto-placement (no kvmHostId specified)
|
78
|
+
nodeAssignments = vmNames.map(vmName => ({ name: vmName }));
|
79
|
+
}
|
80
|
+
return nodeAssignments;
|
81
|
+
}
|
82
|
+
exports.calculateNodeAssignments = calculateNodeAssignments;
|