mcp-config-manager 1.0.2
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/README.md +235 -0
- package/docs/images/kanban-view.png +0 -0
- package/docs/images/list-view.png +0 -0
- package/docs/images/remote-mcp-modal.png +0 -0
- package/docs/images/server-view.png +0 -0
- package/package.json +52 -0
- package/public/index.html +325 -0
- package/public/js/api.js +108 -0
- package/public/js/clientView.js +211 -0
- package/public/js/kanbanView.js +224 -0
- package/public/js/main.js +126 -0
- package/public/js/modals.js +822 -0
- package/public/js/serverView.js +299 -0
- package/public/js/utils.js +185 -0
- package/public/style.css +640 -0
- package/src/cli.js +394 -0
- package/src/clients.js +113 -0
- package/src/config-manager.js +520 -0
- package/src/server.js +214 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { getAllServersApi, updateServerEnvApi, addServerToMultipleClientsApi } from './api.js';
|
|
2
|
+
import { addEnvVarRow } from './utils.js';
|
|
3
|
+
import { showServerModal, deleteServer, copyToClipboard, exportServer, removeFromAll } from './modals.js'; // Import from modals.js
|
|
4
|
+
|
|
5
|
+
let clients = []; // This will be passed from main.js
|
|
6
|
+
let loadClientsCallback = null; // Callback to main.js to reload all clients
|
|
7
|
+
|
|
8
|
+
export function initServerView(allClients, loadClientsFn) {
|
|
9
|
+
clients = allClients;
|
|
10
|
+
loadClientsCallback = loadClientsFn;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function renderAllServers() {
|
|
14
|
+
const allServersList = document.getElementById('allServersList');
|
|
15
|
+
allServersList.innerHTML = '<p>Loading servers...</p>';
|
|
16
|
+
try {
|
|
17
|
+
const allServers = await getAllServersApi();
|
|
18
|
+
allServersList.innerHTML = ''; // Clear loading message
|
|
19
|
+
|
|
20
|
+
if (Object.keys(allServers).length === 0) {
|
|
21
|
+
allServersList.innerHTML = '<p style="color: #7f8c8d;">No servers found across all clients.</p>';
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const [serverName, serverData] of Object.entries(allServers)) {
|
|
26
|
+
const card = document.createElement('div');
|
|
27
|
+
card.className = 'server-card-all';
|
|
28
|
+
|
|
29
|
+
let clientsHtml = '';
|
|
30
|
+
if (serverData.clients && serverData.clients.length > 0) {
|
|
31
|
+
clientsHtml = '<div class="server-clients"><strong>Clients:</strong> ';
|
|
32
|
+
clientsHtml += serverData.clients.map(c => c.name).join(', ');
|
|
33
|
+
clientsHtml += '</div>';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let envHtml = '';
|
|
37
|
+
if (serverData.config && serverData.config.env && Object.keys(serverData.config.env).length > 0) {
|
|
38
|
+
envHtml = '<div class="env-list"><strong>Environment:</strong><br>';
|
|
39
|
+
for (const [key, value] of Object.entries(serverData.config.env)) {
|
|
40
|
+
const displayValue = value.includes('KEY') || value.includes('SECRET')
|
|
41
|
+
? value.substring(0, 4) + '***'
|
|
42
|
+
: value;
|
|
43
|
+
envHtml += `${key}: ${displayValue}<br>`;
|
|
44
|
+
}
|
|
45
|
+
envHtml += '</div>';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
card.innerHTML = `
|
|
49
|
+
<div class="server-header-all">
|
|
50
|
+
<span class="server-name-all">${serverName}</span>
|
|
51
|
+
${serverData.global ? '<span class="global-tag">Global</span>' : ''}
|
|
52
|
+
<div class="server-actions-all">
|
|
53
|
+
<button class="btn btn-small btn-secondary edit-server-full-btn" data-server-name="${serverName}" data-clients='${JSON.stringify(serverData.clients)}' data-server-config='${JSON.stringify(serverData.config)}'>Edit</button>
|
|
54
|
+
<button class="btn btn-small btn-primary add-to-clients-btn" data-server-name="${serverName}" data-server-config='${JSON.stringify(serverData.config)}'>Add to More Clients</button>
|
|
55
|
+
<button class="btn btn-small btn-secondary copy-to-clipboard-server-view-btn" data-server-name="${serverName}" data-server-config='${JSON.stringify(serverData.config)}'>📋</button>
|
|
56
|
+
<button class="btn btn-small btn-secondary export-server-view-btn" data-server-name="${serverName}">💾</button>
|
|
57
|
+
<button class="btn btn-small btn-danger delete-server-view-btn" data-server-name="${serverName}">Delete</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="server-details-all">
|
|
61
|
+
${serverData.config && serverData.config.command ? `<div class="detail-row"><strong>Command:</strong> ${serverData.config.command}</div>` : ''}
|
|
62
|
+
${serverData.config && serverData.config.args ? `<div class="detail-row"><strong>Args:</strong> ${serverData.config.args.join(' ')}</div>` : ''}
|
|
63
|
+
${envHtml}
|
|
64
|
+
${clientsHtml}
|
|
65
|
+
</div>
|
|
66
|
+
`;
|
|
67
|
+
allServersList.appendChild(card);
|
|
68
|
+
}
|
|
69
|
+
attachServerViewEventListeners();
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Failed to load all servers:', error);
|
|
72
|
+
allServersList.innerHTML = '<p style="color: #e74c3c;">Error loading servers.</p>';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function attachServerViewEventListeners() {
|
|
77
|
+
document.querySelectorAll('.edit-server-full-btn').forEach(button => {
|
|
78
|
+
button.addEventListener('click', (e) => {
|
|
79
|
+
const serverName = e.currentTarget.dataset.serverName;
|
|
80
|
+
const serverConfig = JSON.parse(e.currentTarget.dataset.serverConfig);
|
|
81
|
+
const clients = JSON.parse(e.currentTarget.dataset.clients);
|
|
82
|
+
const clientIds = clients.map(c => c.id);
|
|
83
|
+
showServerModal(serverName, serverConfig, renderAllServers, null, clientIds, loadClientsCallback);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
document.querySelectorAll('.add-to-clients-btn').forEach(button => {
|
|
88
|
+
button.addEventListener('click', (e) => {
|
|
89
|
+
const serverName = e.target.dataset.serverName;
|
|
90
|
+
const serverConfig = JSON.parse(e.target.dataset.serverConfig);
|
|
91
|
+
showAddServerToClientsModal(serverName, serverConfig);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
document.querySelectorAll('.copy-to-clipboard-server-view-btn').forEach(button => {
|
|
96
|
+
button.addEventListener('click', (e) => {
|
|
97
|
+
const serverName = e.target.dataset.serverName;
|
|
98
|
+
const serverConfig = JSON.parse(e.target.dataset.serverConfig);
|
|
99
|
+
copyToClipboard(serverName, e, serverConfig); // Pass serverConfig
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
document.querySelectorAll('.export-server-view-btn').forEach(button => {
|
|
104
|
+
button.addEventListener('click', (e) => {
|
|
105
|
+
const serverName = e.target.dataset.serverName;
|
|
106
|
+
exportServer(serverName);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
document.querySelectorAll('.delete-server-view-btn').forEach(button => {
|
|
111
|
+
button.addEventListener('click', (e) => {
|
|
112
|
+
const serverName = e.target.dataset.serverName;
|
|
113
|
+
if (!confirm(`Are you sure you want to delete the server "${serverName}" from ALL clients?`)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
removeFromAll(serverName, null, renderAllServers, loadClientsCallback); // Pass renderAllServers to re-render
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const showEditServerEnvModal = (serverName, currentEnv) => {
|
|
122
|
+
const modal = document.getElementById('editServerEnvModal');
|
|
123
|
+
document.getElementById('editServerEnvTitle').textContent = `Edit Environment Variable for Server ${serverName}`;
|
|
124
|
+
document.getElementById('editServerEnvName').textContent = serverName;
|
|
125
|
+
|
|
126
|
+
const editEnvVarsDiv = document.getElementById('editEnvVars');
|
|
127
|
+
editEnvVarsDiv.innerHTML = `
|
|
128
|
+
<div class="form-group">
|
|
129
|
+
<label for="envVarKeySelect">Environment Variable Key</label>
|
|
130
|
+
<select id="envVarKeySelect">
|
|
131
|
+
<option value="">-- Select or type new --</option>
|
|
132
|
+
${Object.keys(currentEnv).map(key => `<option value="${key}">${key}</option>`).join('')}
|
|
133
|
+
</select>
|
|
134
|
+
<input type="text" id="newEnvVarKeyInput" placeholder="Type new key if not in list" style="margin-top: 5px;">
|
|
135
|
+
</div>
|
|
136
|
+
<div class="form-group">
|
|
137
|
+
<label for="envVarValueInput">Environment Variable Value</label>
|
|
138
|
+
<input type="text" id="envVarValueInput">
|
|
139
|
+
</div>
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
// Pre-fill value if an existing key is selected
|
|
143
|
+
document.getElementById('envVarKeySelect').onchange = (e) => {
|
|
144
|
+
const selectedKey = e.target.value;
|
|
145
|
+
document.getElementById('newEnvVarKeyInput').value = selectedKey;
|
|
146
|
+
document.getElementById('envVarValueInput').value = currentEnv[selectedKey] || '';
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Sync new key input with select
|
|
150
|
+
document.getElementById('newEnvVarKeyInput').oninput = (e) => {
|
|
151
|
+
const inputVal = e.target.value;
|
|
152
|
+
const select = document.getElementById('envVarKeySelect');
|
|
153
|
+
let found = false;
|
|
154
|
+
for (let i = 0; i < select.options.length; i++) {
|
|
155
|
+
if (select.options[i].value === inputVal) {
|
|
156
|
+
select.value = inputVal;
|
|
157
|
+
found = true;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!found) {
|
|
162
|
+
select.value = ""; // Reset select if input doesn't match an option
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const editEnvClientsDiv = document.getElementById('editEnvClients');
|
|
167
|
+
editEnvClientsDiv.innerHTML = '';
|
|
168
|
+
clients.forEach(client => {
|
|
169
|
+
const item = document.createElement('div');
|
|
170
|
+
item.className = 'checkbox-item';
|
|
171
|
+
item.innerHTML = `
|
|
172
|
+
<input type="checkbox" id="editEnvClient-${client.id}" value="${client.id}" checked>
|
|
173
|
+
<label for="editEnvClient-${client.id}">${client.name}</label>
|
|
174
|
+
`;
|
|
175
|
+
editEnvClientsDiv.appendChild(item);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
document.getElementById('selectAllEditEnvClients').onclick = () => {
|
|
179
|
+
editEnvClientsDiv.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = true);
|
|
180
|
+
};
|
|
181
|
+
document.getElementById('selectNoneEditEnvClients').onclick = () => {
|
|
182
|
+
editEnvClientsDiv.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Add 'Apply to all servers' checkbox
|
|
186
|
+
const applyToAllServersDiv = document.createElement('div');
|
|
187
|
+
applyToAllServersDiv.className = 'form-group checkbox-item';
|
|
188
|
+
applyToAllServersDiv.innerHTML = `
|
|
189
|
+
<input type="checkbox" id="applyToAllServers" value="true">
|
|
190
|
+
<label for="applyToAllServers">Apply to all servers in selected clients</label>
|
|
191
|
+
`;
|
|
192
|
+
editEnvClientsDiv.parentNode.insertBefore(applyToAllServersDiv, editEnvClientsDiv.nextSibling);
|
|
193
|
+
|
|
194
|
+
modal.style.display = 'flex';
|
|
195
|
+
|
|
196
|
+
document.getElementById('editServerEnvForm').onsubmit = async (e) => {
|
|
197
|
+
e.preventDefault();
|
|
198
|
+
const envKey = document.getElementById('newEnvVarKeyInput').value;
|
|
199
|
+
const envValue = document.getElementById('envVarValueInput').value;
|
|
200
|
+
const applyToAll = document.getElementById('applyToAllServers').checked;
|
|
201
|
+
await saveEditedServerEnv(serverName, envKey, envValue, applyToAll);
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const saveEditedServerEnv = async (serverName, envKey, envValue, applyToAll) => {
|
|
206
|
+
if (!envKey) {
|
|
207
|
+
alert('Environment variable key cannot be empty.');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const selectedClientIds = Array.from(document.querySelectorAll('#editEnvClients input[type="checkbox"]:checked'))
|
|
212
|
+
.map(cb => cb.value);
|
|
213
|
+
|
|
214
|
+
if (selectedClientIds.length === 0) {
|
|
215
|
+
alert('Please select at least one client to apply changes.');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
if (applyToAll) {
|
|
221
|
+
// Apply to all servers in selected clients
|
|
222
|
+
for (const clientId of selectedClientIds) {
|
|
223
|
+
const clientConfig = await getClientConfigApi(clientId);
|
|
224
|
+
for (const sName of Object.keys(clientConfig.servers)) {
|
|
225
|
+
await updateServerEnvApi(sName, envKey, envValue, [clientId]);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
// Apply only to the specific server in selected clients
|
|
230
|
+
const response = await updateServerEnvApi(serverName, envKey, envValue, selectedClientIds);
|
|
231
|
+
if (!response.success) {
|
|
232
|
+
throw new Error(`Failed to update ${envKey}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
document.getElementById('editServerEnvModal').style.display = 'none';
|
|
237
|
+
alert('Environment variable updated successfully!');
|
|
238
|
+
renderAllServers(); // Re-render to show changes
|
|
239
|
+
} catch (error) {
|
|
240
|
+
alert('Failed to save environment variable: ' + error.message);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export const showAddServerToClientsModal = (serverName, serverConfig) => {
|
|
245
|
+
const modal = document.getElementById('addServerToClientsModal');
|
|
246
|
+
document.getElementById('addServerToClientsTitle').textContent = `Add Server ${serverName} to Clients`;
|
|
247
|
+
document.getElementById('addServerToClientsName').textContent = serverName;
|
|
248
|
+
document.getElementById('addServerToClientsConfig').value = JSON.stringify(serverConfig, null, 2);
|
|
249
|
+
|
|
250
|
+
const addServerToClientsListDiv = document.getElementById('addServerToClientsList');
|
|
251
|
+
addServerToClientsListDiv.innerHTML = '';
|
|
252
|
+
clients.forEach(client => {
|
|
253
|
+
const item = document.createElement('div');
|
|
254
|
+
item.className = 'checkbox-item';
|
|
255
|
+
item.innerHTML = `
|
|
256
|
+
<input type="checkbox" id="addServerClient-${client.id}" value="${client.id}" checked>
|
|
257
|
+
<label for="addServerClient-${client.id}">${client.name}</label>
|
|
258
|
+
`;
|
|
259
|
+
addServerToClientsListDiv.appendChild(item);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
document.getElementById('selectAllAddServerClients').onclick = () => {
|
|
263
|
+
addServerToClientsListDiv.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = true);
|
|
264
|
+
};
|
|
265
|
+
document.getElementById('selectNoneAddServerClients').onclick = () => {
|
|
266
|
+
addServerToClientsListDiv.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
modal.style.display = 'flex';
|
|
270
|
+
|
|
271
|
+
document.getElementById('addServerToClientsForm').onsubmit = async (e) => {
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
await addServerToSelectedClients(serverName, serverConfig);
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const addServerToSelectedClients = async (serverName, serverConfig) => {
|
|
278
|
+
const selectedClientIds = Array.from(document.querySelectorAll('#addServerToClientsList input[type="checkbox"]:checked'))
|
|
279
|
+
.map(cb => cb.value);
|
|
280
|
+
|
|
281
|
+
if (selectedClientIds.length === 0) {
|
|
282
|
+
alert('Please select at least one client to add the server to.');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const response = await addServerToMultipleClientsApi(serverName, serverConfig, selectedClientIds);
|
|
288
|
+
|
|
289
|
+
if (response.success) {
|
|
290
|
+
document.getElementById('addServerToClientsModal').style.display = 'none';
|
|
291
|
+
alert(`Server ${serverName} added to selected clients successfully!`);
|
|
292
|
+
renderAllServers(); // Re-render to show changes
|
|
293
|
+
} else {
|
|
294
|
+
throw new Error('Failed to add server to clients');
|
|
295
|
+
}
|
|
296
|
+
} catch (error) {
|
|
297
|
+
alert('Failed to add server to clients: ' + error.message);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export function addEnvVarRow(envVarsDiv, key = '', value = '') {
|
|
2
|
+
const row = document.createElement('div');
|
|
3
|
+
row.className = 'env-var-row';
|
|
4
|
+
|
|
5
|
+
row.innerHTML = `
|
|
6
|
+
<input type="text" placeholder="Key" value="${key}" class="env-key">
|
|
7
|
+
<input type="text" placeholder="Value" value="${value}" class="env-value">
|
|
8
|
+
<button type="button" class="icon-btn secondary copy-env-var-btn" data-key="${key}" data-value="${value}" title="Copy">➡️</button>
|
|
9
|
+
<button type="button" class="icon-btn danger" onclick="this.parentElement.remove()" title="Remove">🗑️</button>
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
envVarsDiv.appendChild(row);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function switchTab(tab) {
|
|
16
|
+
document.querySelectorAll('.tab-btn').forEach(btn => {
|
|
17
|
+
btn.classList.toggle('active', btn.dataset.tab === tab);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
document.getElementById('formTab').style.display = tab === 'form' ? 'block' : 'none';
|
|
21
|
+
document.getElementById('jsonTab').style.display = tab === 'json' ? 'block' : 'none';
|
|
22
|
+
|
|
23
|
+
// Disable/enable form validation based on active tab
|
|
24
|
+
const formControls = document.querySelectorAll('#formTab input, #formTab textarea');
|
|
25
|
+
const jsonControls = document.querySelectorAll('#jsonTab textarea');
|
|
26
|
+
|
|
27
|
+
if (tab === 'json') {
|
|
28
|
+
// Disable validation on form tab controls when JSON tab is active
|
|
29
|
+
formControls.forEach(control => {
|
|
30
|
+
control.setAttribute('data-original-required', control.hasAttribute('required'));
|
|
31
|
+
control.removeAttribute('required');
|
|
32
|
+
});
|
|
33
|
+
// Ensure JSON editor doesn't have required attribute either
|
|
34
|
+
jsonControls.forEach(control => {
|
|
35
|
+
control.removeAttribute('required');
|
|
36
|
+
});
|
|
37
|
+
} else {
|
|
38
|
+
// Restore validation on form tab controls when form tab is active
|
|
39
|
+
formControls.forEach(control => {
|
|
40
|
+
if (control.getAttribute('data-original-required') === 'true') {
|
|
41
|
+
control.setAttribute('required', '');
|
|
42
|
+
}
|
|
43
|
+
control.removeAttribute('data-original-required');
|
|
44
|
+
});
|
|
45
|
+
// Disable validation on JSON tab controls
|
|
46
|
+
jsonControls.forEach(control => {
|
|
47
|
+
control.removeAttribute('required');
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Sync data between tabs
|
|
52
|
+
if (tab === 'json') {
|
|
53
|
+
// When switching to JSON tab, update only the fields that form controls
|
|
54
|
+
try {
|
|
55
|
+
const existingJson = JSON.parse(document.getElementById('jsonEditor').value);
|
|
56
|
+
const formConfig = buildConfigFromForm();
|
|
57
|
+
|
|
58
|
+
// Merge form data into existing JSON, preserving other properties
|
|
59
|
+
const mergedConfig = { ...existingJson };
|
|
60
|
+
|
|
61
|
+
// Update command - if empty in form, remove from JSON
|
|
62
|
+
if (formConfig.command) {
|
|
63
|
+
mergedConfig.command = formConfig.command;
|
|
64
|
+
} else if (document.getElementById('serverCommand').value === '') {
|
|
65
|
+
delete mergedConfig.command;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Update args - if empty in form, remove from JSON
|
|
69
|
+
if (formConfig.args && formConfig.args.length > 0) {
|
|
70
|
+
mergedConfig.args = formConfig.args;
|
|
71
|
+
} else if (document.getElementById('serverArgs').value === '') {
|
|
72
|
+
delete mergedConfig.args;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// For env vars, only update/remove the ones that were shown in form
|
|
76
|
+
// Preserve any env vars that weren't displayed in the form
|
|
77
|
+
if (mergedConfig.env || formConfig.env) {
|
|
78
|
+
// Start with ALL existing env vars (including ones not shown in form)
|
|
79
|
+
const updatedEnv = { ...(mergedConfig.env || {}) };
|
|
80
|
+
|
|
81
|
+
// Get the keys that were initially loaded into the form
|
|
82
|
+
const initialFormKeys = document.getElementById('envVars').dataset.initialKeys;
|
|
83
|
+
const initialKeys = initialFormKeys ? JSON.parse(initialFormKeys) : [];
|
|
84
|
+
|
|
85
|
+
// Create a set of initial keys for quick lookup
|
|
86
|
+
const initialKeysSet = new Set(initialKeys);
|
|
87
|
+
|
|
88
|
+
// Get the current state of form env vars
|
|
89
|
+
const currentFormEnvs = {};
|
|
90
|
+
const currentFormKeys = new Set();
|
|
91
|
+
document.querySelectorAll('.env-var-row').forEach(row => {
|
|
92
|
+
const key = row.querySelector('.env-key').value;
|
|
93
|
+
const value = row.querySelector('.env-value').value;
|
|
94
|
+
if (key) {
|
|
95
|
+
currentFormKeys.add(key);
|
|
96
|
+
currentFormEnvs[key] = value;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Update or remove only the env vars that were initially shown in the form
|
|
101
|
+
initialKeys.forEach(key => {
|
|
102
|
+
if (currentFormKeys.has(key)) {
|
|
103
|
+
// Key still exists in form, update its value
|
|
104
|
+
updatedEnv[key] = currentFormEnvs[key];
|
|
105
|
+
} else {
|
|
106
|
+
// Key was removed from form, delete it
|
|
107
|
+
delete updatedEnv[key];
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Add any NEW env vars that were added in the form
|
|
112
|
+
currentFormKeys.forEach(key => {
|
|
113
|
+
if (!initialKeysSet.has(key)) {
|
|
114
|
+
// This is a new key added in the form
|
|
115
|
+
updatedEnv[key] = currentFormEnvs[key];
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (Object.keys(updatedEnv).length > 0) {
|
|
120
|
+
mergedConfig.env = updatedEnv;
|
|
121
|
+
} else {
|
|
122
|
+
delete mergedConfig.env;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
document.getElementById('jsonEditor').value = JSON.stringify(mergedConfig, null, 2);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
// If existing JSON is invalid, just use form data
|
|
129
|
+
const config = buildConfigFromForm();
|
|
130
|
+
document.getElementById('jsonEditor').value = JSON.stringify(config, null, 2);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
// When switching to form tab, only update form with supported fields
|
|
134
|
+
try {
|
|
135
|
+
const config = JSON.parse(document.getElementById('jsonEditor').value);
|
|
136
|
+
updateFormFromConfig(config);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
// Invalid JSON, don't update form
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function buildConfigFromForm() {
|
|
144
|
+
const config = {};
|
|
145
|
+
const command = document.getElementById('serverCommand').value;
|
|
146
|
+
const argsText = document.getElementById('serverArgs').value;
|
|
147
|
+
const args = argsText ? argsText.split('\n').filter(a => a.trim()) : [];
|
|
148
|
+
|
|
149
|
+
if (command) config.command = command;
|
|
150
|
+
if (args.length > 0) config.args = args;
|
|
151
|
+
|
|
152
|
+
const envVarRows = document.querySelectorAll('.env-var-row');
|
|
153
|
+
const env = {};
|
|
154
|
+
envVarRows.forEach(row => {
|
|
155
|
+
const key = row.querySelector('.env-key').value;
|
|
156
|
+
const value = row.querySelector('.env-value').value;
|
|
157
|
+
if (key && value) {
|
|
158
|
+
env[key] = value;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
if (Object.keys(env).length > 0) config.env = env;
|
|
162
|
+
|
|
163
|
+
return config;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function updateFormFromConfig(config) {
|
|
167
|
+
// Only sync the supported fields to the form, ignore any other JSON properties
|
|
168
|
+
document.getElementById('serverCommand').value = config.command || '';
|
|
169
|
+
document.getElementById('serverArgs').value = config.args?.join('\n') || '';
|
|
170
|
+
|
|
171
|
+
const envVarsDiv = document.getElementById('envVars');
|
|
172
|
+
envVarsDiv.innerHTML = '';
|
|
173
|
+
|
|
174
|
+
// Track which env keys we're loading into the form
|
|
175
|
+
const loadedEnvKeys = [];
|
|
176
|
+
if (config.env && typeof config.env === 'object') {
|
|
177
|
+
for (const [key, value] of Object.entries(config.env)) {
|
|
178
|
+
addEnvVarRow(envVarsDiv, key, value);
|
|
179
|
+
loadedEnvKeys.push(key);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Store the initially loaded keys so we know what to update later
|
|
184
|
+
envVarsDiv.dataset.initialKeys = JSON.stringify(loadedEnvKeys);
|
|
185
|
+
}
|