@chargebee/chargebee-apps-libs 0.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/LICENSE +24 -0
- package/README.md +270 -0
- package/SECURITY.md +8 -0
- package/config/README.md +83 -0
- package/config/allowed-modules.json +24 -0
- package/dist/config/config-loader.d.ts +24 -0
- package/dist/config/config-loader.js +95 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +21 -0
- package/dist/libs/port-service.d.ts +18 -0
- package/dist/libs/port-service.js +27 -0
- package/dist/logger/cb-public-logger.d.ts +59 -0
- package/dist/logger/cb-public-logger.js +158 -0
- package/dist/sandbox/public-sandbox-wrapper.d.ts +41 -0
- package/dist/sandbox/public-sandbox-wrapper.js +208 -0
- package/package.json +44 -0
- package/templates/serverless-node-starter-app/.env +4 -0
- package/templates/serverless-node-starter-app/README.md +290 -0
- package/templates/serverless-node-starter-app/handler/handler.js +34 -0
- package/templates/serverless-node-starter-app/jsconfig.json +35 -0
- package/templates/serverless-node-starter-app/manifest.json +15 -0
- package/templates/serverless-node-starter-app/test_data/customer_created.json +51 -0
- package/templates/serverless-node-starter-app/test_data/invoice_generated.json +112 -0
- package/templates/serverless-node-starter-app/test_data/subscription_created.json +173 -0
- package/templates/serverless-node-starter-app/types/types.d.ts +45 -0
- package/templates/serverless-node-starter-app-with-iparams/.env +4 -0
- package/templates/serverless-node-starter-app-with-iparams/README.md +717 -0
- package/templates/serverless-node-starter-app-with-iparams/handler/handler.js +39 -0
- package/templates/serverless-node-starter-app-with-iparams/iparams.json +41 -0
- package/templates/serverless-node-starter-app-with-iparams/iparams.local.json +9 -0
- package/templates/serverless-node-starter-app-with-iparams/jsconfig.json +35 -0
- package/templates/serverless-node-starter-app-with-iparams/manifest.json +15 -0
- package/templates/serverless-node-starter-app-with-iparams/test_data/customer_created.json +51 -0
- package/templates/serverless-node-starter-app-with-iparams/test_data/invoice_generated.json +112 -0
- package/templates/serverless-node-starter-app-with-iparams/test_data/subscription_created.json +173 -0
- package/templates/serverless-node-starter-app-with-iparams/types/types.d.ts +63 -0
- package/ui/README.md +118 -0
- package/ui/web/assets/css/main.css +1121 -0
- package/ui/web/assets/images/Chargebee-logo.png +0 -0
- package/ui/web/assets/js/main.js +61 -0
- package/ui/web/assets/js/modules/api.js +97 -0
- package/ui/web/assets/js/modules/constants.js +33 -0
- package/ui/web/assets/js/modules/editors.js +41 -0
- package/ui/web/assets/js/modules/events.js +195 -0
- package/ui/web/assets/js/modules/form-utils.js +70 -0
- package/ui/web/assets/js/modules/iparams-inputs.js +495 -0
- package/ui/web/assets/js/modules/iparams.js +82 -0
- package/ui/web/assets/js/modules/ui-utils.js +87 -0
- package/ui/web/index.html +164 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import { state, editInputsBtn, saveInputsBtn, cancelInputsBtn, toggleViewBtn, iparamsInputsForm } from './constants.js';
|
|
2
|
+
import { showStatus } from './ui-utils.js';
|
|
3
|
+
import { fetchIparams, fetchIparamsInputs, saveIparamsInputs as saveInputsAPI } from './api.js';
|
|
4
|
+
import { mergeInputsWithDefaults, updateMultiselectDisplay, getIparamSections, getIparamInputId } from './form-utils.js';
|
|
5
|
+
|
|
6
|
+
/** Creates a dropdown container with arrow */
|
|
7
|
+
function createDropdownContainer(selectElement) {
|
|
8
|
+
const container = document.createElement('div');
|
|
9
|
+
container.className = 'select-dropdown-container';
|
|
10
|
+
const arrow = document.createElement('span');
|
|
11
|
+
arrow.className = 'select-dropdown-arrow';
|
|
12
|
+
container.appendChild(selectElement);
|
|
13
|
+
container.appendChild(arrow);
|
|
14
|
+
return container;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Creates a form field for a single parameter */
|
|
18
|
+
function createParamField(section, param, sectionInputs) {
|
|
19
|
+
const formField = document.createElement('div');
|
|
20
|
+
formField.className = 'iparam-field';
|
|
21
|
+
const inputId = getIparamInputId(section.name, param.name);
|
|
22
|
+
const paramValue = sectionInputs?.[param.name];
|
|
23
|
+
|
|
24
|
+
const label = document.createElement('label');
|
|
25
|
+
label.textContent = param.display_name || param.name;
|
|
26
|
+
if (param.required) label.innerHTML += ' <span class="required">*</span>';
|
|
27
|
+
label.setAttribute('for', inputId);
|
|
28
|
+
|
|
29
|
+
const description = document.createElement('p');
|
|
30
|
+
description.className = 'iparam-description';
|
|
31
|
+
description.textContent = param.description;
|
|
32
|
+
|
|
33
|
+
let input;
|
|
34
|
+
|
|
35
|
+
switch (param.type) {
|
|
36
|
+
case 'BOOLEAN': {
|
|
37
|
+
const booleanSelect = document.createElement('select');
|
|
38
|
+
booleanSelect.id = inputId;
|
|
39
|
+
booleanSelect.name = param.name;
|
|
40
|
+
[['', '-- Select --'], ['true', 'True'], ['false', 'False']].forEach(([val, text]) => {
|
|
41
|
+
const opt = document.createElement('option');
|
|
42
|
+
opt.value = val;
|
|
43
|
+
opt.textContent = text;
|
|
44
|
+
booleanSelect.appendChild(opt);
|
|
45
|
+
});
|
|
46
|
+
booleanSelect.value = paramValue !== undefined
|
|
47
|
+
? (paramValue === true || paramValue === 'true' ? 'true' : 'false')
|
|
48
|
+
: '';
|
|
49
|
+
input = createDropdownContainer(booleanSelect);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case 'NUMBER':
|
|
53
|
+
input = document.createElement('input');
|
|
54
|
+
input.type = 'number';
|
|
55
|
+
input.id = inputId;
|
|
56
|
+
input.name = param.name;
|
|
57
|
+
input.value = paramValue !== undefined ? paramValue : '';
|
|
58
|
+
break;
|
|
59
|
+
case 'DROPDOWN': {
|
|
60
|
+
const dropdownSelect = document.createElement('select');
|
|
61
|
+
dropdownSelect.id = inputId;
|
|
62
|
+
dropdownSelect.name = param.name;
|
|
63
|
+
const emptyOpt = document.createElement('option');
|
|
64
|
+
emptyOpt.value = '';
|
|
65
|
+
emptyOpt.textContent = '-- Select option --';
|
|
66
|
+
dropdownSelect.appendChild(emptyOpt);
|
|
67
|
+
if (param.options) {
|
|
68
|
+
param.options.forEach(option => {
|
|
69
|
+
const opt = document.createElement('option');
|
|
70
|
+
opt.value = option;
|
|
71
|
+
opt.textContent = option;
|
|
72
|
+
if (paramValue === option) opt.selected = true;
|
|
73
|
+
dropdownSelect.appendChild(opt);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (paramValue === undefined || paramValue === '') {
|
|
77
|
+
dropdownSelect.value = '';
|
|
78
|
+
}
|
|
79
|
+
input = createDropdownContainer(dropdownSelect);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case 'MULTISELECT_DROPDOWN': {
|
|
83
|
+
const dropdownContainer = document.createElement('div');
|
|
84
|
+
dropdownContainer.className = 'multiselect-dropdown-container';
|
|
85
|
+
dropdownContainer.id = inputId;
|
|
86
|
+
|
|
87
|
+
const dropdownButton = document.createElement('div');
|
|
88
|
+
dropdownButton.className = 'multiselect-dropdown-button iparam-input';
|
|
89
|
+
dropdownButton.setAttribute('role', 'button');
|
|
90
|
+
dropdownButton.tabIndex = state.isInputsEditing ? 0 : -1;
|
|
91
|
+
if (!state.isInputsEditing) {
|
|
92
|
+
dropdownButton.style.pointerEvents = 'none';
|
|
93
|
+
dropdownButton.classList.add('readonly');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const selectedValues = Array.isArray(paramValue) ? paramValue : [];
|
|
97
|
+
dropdownButton.textContent = selectedValues.length > 0 ? selectedValues.join(', ') : '-- Select options --';
|
|
98
|
+
if (selectedValues.length === 0) dropdownButton.classList.add('placeholder');
|
|
99
|
+
|
|
100
|
+
const arrow = document.createElement('span');
|
|
101
|
+
arrow.className = 'select-dropdown-arrow';
|
|
102
|
+
dropdownButton.appendChild(arrow);
|
|
103
|
+
|
|
104
|
+
const dropdownPanel = document.createElement('div');
|
|
105
|
+
dropdownPanel.className = 'multiselect-dropdown-panel';
|
|
106
|
+
dropdownPanel.style.display = 'none';
|
|
107
|
+
|
|
108
|
+
if (param.options) {
|
|
109
|
+
param.options.forEach(option => {
|
|
110
|
+
const wrapper = document.createElement('div');
|
|
111
|
+
wrapper.className = 'checkbox-option';
|
|
112
|
+
|
|
113
|
+
const checkbox = document.createElement('input');
|
|
114
|
+
checkbox.type = 'checkbox';
|
|
115
|
+
checkbox.id = `${inputId}-${option}`;
|
|
116
|
+
checkbox.name = inputId;
|
|
117
|
+
checkbox.value = option;
|
|
118
|
+
checkbox.checked = selectedValues.includes(option);
|
|
119
|
+
checkbox.disabled = !state.isInputsEditing;
|
|
120
|
+
checkbox.className = 'iparam-input';
|
|
121
|
+
checkbox.addEventListener('change', () => updateMultiselectDisplay(dropdownButton, dropdownPanel, param.name));
|
|
122
|
+
|
|
123
|
+
const optionLabel = document.createElement('label');
|
|
124
|
+
optionLabel.htmlFor = `${inputId}-${option}`;
|
|
125
|
+
optionLabel.textContent = option;
|
|
126
|
+
|
|
127
|
+
wrapper.appendChild(checkbox);
|
|
128
|
+
wrapper.appendChild(optionLabel);
|
|
129
|
+
dropdownPanel.appendChild(wrapper);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
dropdownButton.addEventListener('click', function(e) {
|
|
134
|
+
if (!state.isInputsEditing) return;
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
e.preventDefault();
|
|
137
|
+
const isOpen = dropdownPanel.style.display === 'block';
|
|
138
|
+
dropdownPanel.style.display = isOpen ? 'none' : 'block';
|
|
139
|
+
dropdownButton.classList.toggle('open', !isOpen);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
dropdownPanel.addEventListener('click', e => e.stopPropagation());
|
|
143
|
+
dropdownContainer.appendChild(dropdownButton);
|
|
144
|
+
dropdownContainer.appendChild(dropdownPanel);
|
|
145
|
+
|
|
146
|
+
if (!window.multiselectClickHandler) {
|
|
147
|
+
window.multiselectClickHandler = function(e) {
|
|
148
|
+
document.querySelectorAll('.multiselect-dropdown-panel').forEach(panel => {
|
|
149
|
+
const container = panel.closest('.multiselect-dropdown-container');
|
|
150
|
+
if (container && !container.contains(e.target)) {
|
|
151
|
+
panel.style.display = 'none';
|
|
152
|
+
const button = container.querySelector('.multiselect-dropdown-button');
|
|
153
|
+
if (button) button.classList.remove('open');
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
document.addEventListener('click', window.multiselectClickHandler);
|
|
158
|
+
}
|
|
159
|
+
input = dropdownContainer;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case 'DATE':
|
|
163
|
+
input = document.createElement('input');
|
|
164
|
+
input.type = 'date';
|
|
165
|
+
input.id = inputId;
|
|
166
|
+
input.name = param.name;
|
|
167
|
+
if (paramValue) {
|
|
168
|
+
let dateStr = '';
|
|
169
|
+
if (typeof paramValue === 'string') {
|
|
170
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(paramValue)) {
|
|
171
|
+
dateStr = paramValue;
|
|
172
|
+
} else if (paramValue.includes('T')) {
|
|
173
|
+
dateStr = paramValue.split('T')[0];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!dateStr) {
|
|
177
|
+
const date = new Date(paramValue);
|
|
178
|
+
if (!isNaN(date.getTime())) {
|
|
179
|
+
dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (dateStr && /^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
|
183
|
+
input.value = dateStr;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
case 'SECRET':
|
|
188
|
+
case 'TEXT':
|
|
189
|
+
case 'URL':
|
|
190
|
+
default:
|
|
191
|
+
input = document.createElement('input');
|
|
192
|
+
input.type = param.type === 'URL' ? 'url' : 'text';
|
|
193
|
+
input.id = inputId;
|
|
194
|
+
input.name = param.name;
|
|
195
|
+
input.value = paramValue !== undefined ? paramValue : '';
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (input.tagName === 'INPUT' || input.tagName === 'SELECT' || input.tagName === 'TEXTAREA') {
|
|
200
|
+
input.className = 'iparam-input';
|
|
201
|
+
input.disabled = !state.isInputsEditing;
|
|
202
|
+
} else if (input.tagName === 'DIV' && input.classList.contains('select-dropdown-container')) {
|
|
203
|
+
const selectElement = input.querySelector('select');
|
|
204
|
+
if (selectElement) {
|
|
205
|
+
selectElement.className = 'iparam-input';
|
|
206
|
+
selectElement.disabled = !state.isInputsEditing;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
formField.appendChild(label);
|
|
211
|
+
formField.appendChild(description);
|
|
212
|
+
formField.appendChild(input);
|
|
213
|
+
return formField;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Updates the form view with current inputs */
|
|
217
|
+
export function updateInputsForm(inputs) {
|
|
218
|
+
if (!iparamsInputsForm) return;
|
|
219
|
+
iparamsInputsForm.innerHTML = '';
|
|
220
|
+
const sections = getIparamSections();
|
|
221
|
+
if (sections.length === 0) {
|
|
222
|
+
iparamsInputsForm.innerHTML = '<p style="color: #6b7280; padding: 20px; text-align: center;">No parameters defined. Define parameters in the Iparams tab first.</p>';
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
sections.forEach(section => {
|
|
227
|
+
const sectionBlock = document.createElement('div');
|
|
228
|
+
sectionBlock.className = 'iparam-section';
|
|
229
|
+
|
|
230
|
+
const sectionHeader = document.createElement('div');
|
|
231
|
+
sectionHeader.className = 'iparam-section-header';
|
|
232
|
+
|
|
233
|
+
const sectionTitle = document.createElement('h3');
|
|
234
|
+
sectionTitle.className = 'iparam-section-title';
|
|
235
|
+
sectionTitle.textContent = section.display_name || section.name;
|
|
236
|
+
|
|
237
|
+
const sectionDescription = document.createElement('p');
|
|
238
|
+
sectionDescription.className = 'iparam-section-description';
|
|
239
|
+
sectionDescription.textContent = section.description;
|
|
240
|
+
|
|
241
|
+
sectionHeader.appendChild(sectionTitle);
|
|
242
|
+
sectionHeader.appendChild(sectionDescription);
|
|
243
|
+
sectionBlock.appendChild(sectionHeader);
|
|
244
|
+
|
|
245
|
+
const sectionInputs = inputs?.[section.name] ?? {};
|
|
246
|
+
section.parameters.forEach(param => {
|
|
247
|
+
sectionBlock.appendChild(createParamField(section, param, sectionInputs));
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
iparamsInputsForm.appendChild(sectionBlock);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Reads a single parameter value from the form */
|
|
255
|
+
function readParamValueFromForm(section, param) {
|
|
256
|
+
const input = document.getElementById(getIparamInputId(section.name, param.name));
|
|
257
|
+
if (!input) return undefined;
|
|
258
|
+
|
|
259
|
+
switch (param.type) {
|
|
260
|
+
case 'BOOLEAN': {
|
|
261
|
+
const booleanSelect = input.tagName === 'SELECT' ? input : input.querySelector('select');
|
|
262
|
+
return booleanSelect && booleanSelect.value && booleanSelect.value !== ''
|
|
263
|
+
? booleanSelect.value === 'true'
|
|
264
|
+
: undefined;
|
|
265
|
+
}
|
|
266
|
+
case 'NUMBER':
|
|
267
|
+
return input.value && input.value.trim() !== '' ? Number(input.value) : undefined;
|
|
268
|
+
case 'MULTISELECT_DROPDOWN': {
|
|
269
|
+
const dropdownPanel = input.querySelector('.multiselect-dropdown-panel');
|
|
270
|
+
if (dropdownPanel) {
|
|
271
|
+
const selected = Array.from(dropdownPanel.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value);
|
|
272
|
+
return selected.length > 0 ? selected : undefined;
|
|
273
|
+
}
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
case 'DATE':
|
|
277
|
+
case 'SECRET':
|
|
278
|
+
case 'TEXT':
|
|
279
|
+
case 'URL':
|
|
280
|
+
return input.value && input.value.trim() !== '' ? input.value.trim() : undefined;
|
|
281
|
+
case 'DROPDOWN': {
|
|
282
|
+
const dropdownSelect = input.tagName === 'SELECT' ? input : input.querySelector('select');
|
|
283
|
+
return dropdownSelect && dropdownSelect.value && dropdownSelect.value.trim() !== ''
|
|
284
|
+
? dropdownSelect.value.trim()
|
|
285
|
+
: undefined;
|
|
286
|
+
}
|
|
287
|
+
default:
|
|
288
|
+
return input.value && input.value.trim() !== '' ? input.value.trim() : undefined;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Gets inputs from form view - merges defaults for fields that don't have values */
|
|
293
|
+
export function getInputsFromForm() {
|
|
294
|
+
if (!iparamsInputsForm) return mergeInputsWithDefaults({}, state.iparamDefns);
|
|
295
|
+
const rawInputs = {};
|
|
296
|
+
|
|
297
|
+
getIparamSections().forEach(section => {
|
|
298
|
+
const sectionInputs = {};
|
|
299
|
+
section.parameters.forEach(param => {
|
|
300
|
+
const value = readParamValueFromForm(section, param);
|
|
301
|
+
if (value !== undefined && value !== null && value !== '' && (!Array.isArray(value) || value.length > 0)) {
|
|
302
|
+
sectionInputs[param.name] = value;
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
if (Object.keys(sectionInputs).length > 0) {
|
|
306
|
+
rawInputs[section.name] = sectionInputs;
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
return mergeInputsWithDefaults(rawInputs, state.iparamDefns);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Reloads inputs from server and updates views, preserving editing state */
|
|
314
|
+
async function reloadInputsAndUpdateViews() {
|
|
315
|
+
const wasEditing = state.isInputsEditing;
|
|
316
|
+
try {
|
|
317
|
+
const iparamDefns = await fetchIparams();
|
|
318
|
+
state.iparamDefns = iparamDefns;
|
|
319
|
+
const inputs = await fetchIparamsInputs();
|
|
320
|
+
const mergedInputs = mergeInputsWithDefaults(inputs, iparamDefns);
|
|
321
|
+
const inputsJson = JSON.stringify(mergedInputs, null, 2);
|
|
322
|
+
state.iparamsInputsEditor.setValue(inputsJson);
|
|
323
|
+
state.originalInputsData = inputsJson;
|
|
324
|
+
updateInputsForm(mergedInputs);
|
|
325
|
+
state.iparamsInputsEditor.resize();
|
|
326
|
+
if (wasEditing) {
|
|
327
|
+
setInputsReadonly(false);
|
|
328
|
+
} else {
|
|
329
|
+
setInputsReadonly(true);
|
|
330
|
+
}
|
|
331
|
+
} catch (error) {
|
|
332
|
+
state.iparamsInputsEditor.setValue('{}');
|
|
333
|
+
state.originalInputsData = '{}';
|
|
334
|
+
updateInputsForm({});
|
|
335
|
+
showStatus(`Error loading iparams inputs: ${error.message}`);
|
|
336
|
+
setInputsReadonly(true);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Toggles between form and JSON view - reloads from server discarding unsaved changes */
|
|
341
|
+
export async function toggleInputsView() {
|
|
342
|
+
state.isFormView = !state.isFormView;
|
|
343
|
+
const editorElement = document.getElementById('iparamsInputsEditor');
|
|
344
|
+
if (state.isFormView) {
|
|
345
|
+
if (editorElement) editorElement.style.display = 'none';
|
|
346
|
+
if (iparamsInputsForm) iparamsInputsForm.style.display = 'block';
|
|
347
|
+
if (toggleViewBtn) toggleViewBtn.textContent = 'Switch to JSON';
|
|
348
|
+
} else {
|
|
349
|
+
if (iparamsInputsForm) iparamsInputsForm.style.display = 'none';
|
|
350
|
+
if (editorElement) editorElement.style.display = 'block';
|
|
351
|
+
if (toggleViewBtn) toggleViewBtn.textContent = 'Switch to Form';
|
|
352
|
+
}
|
|
353
|
+
await reloadInputsAndUpdateViews();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Enables editing mode for inputs editor */
|
|
357
|
+
export function enableInputsEditing() {
|
|
358
|
+
state.isInputsEditing = true;
|
|
359
|
+
state.iparamsInputsEditor.setReadOnly(false);
|
|
360
|
+
editInputsBtn.style.display = 'none';
|
|
361
|
+
saveInputsBtn.style.display = 'inline-block';
|
|
362
|
+
cancelInputsBtn.style.display = 'inline-block';
|
|
363
|
+
if (iparamsInputsForm) {
|
|
364
|
+
iparamsInputsForm.querySelectorAll('.iparam-input').forEach(input => input.disabled = false);
|
|
365
|
+
iparamsInputsForm.querySelectorAll('.multiselect-dropdown-button').forEach(button => {
|
|
366
|
+
button.tabIndex = 0;
|
|
367
|
+
button.style.pointerEvents = 'auto';
|
|
368
|
+
button.classList.remove('readonly');
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Cancels editing and restores original inputs */
|
|
374
|
+
export function cancelInputsEditing() {
|
|
375
|
+
if (state.originalInputsData !== null) {
|
|
376
|
+
state.iparamsInputsEditor.setValue(state.originalInputsData);
|
|
377
|
+
}
|
|
378
|
+
setInputsReadonly(true);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Sets inputs editor to readonly mode */
|
|
382
|
+
export function setInputsReadonly(readonly) {
|
|
383
|
+
state.isInputsEditing = !readonly;
|
|
384
|
+
state.iparamsInputsEditor.setReadOnly(readonly);
|
|
385
|
+
const editorElement = document.getElementById('iparamsInputsEditor');
|
|
386
|
+
editorElement.classList.toggle('readonly', readonly);
|
|
387
|
+
editInputsBtn.style.display = readonly ? 'inline-block' : 'none';
|
|
388
|
+
saveInputsBtn.style.display = readonly ? 'none' : 'inline-block';
|
|
389
|
+
cancelInputsBtn.style.display = readonly ? 'none' : 'inline-block';
|
|
390
|
+
if (iparamsInputsForm) {
|
|
391
|
+
iparamsInputsForm.querySelectorAll('.iparam-input').forEach(input => input.disabled = readonly);
|
|
392
|
+
iparamsInputsForm.querySelectorAll('.multiselect-dropdown-button').forEach(button => {
|
|
393
|
+
if (readonly) {
|
|
394
|
+
button.tabIndex = -1;
|
|
395
|
+
button.style.pointerEvents = 'none';
|
|
396
|
+
button.classList.add('readonly');
|
|
397
|
+
} else {
|
|
398
|
+
button.tabIndex = 0;
|
|
399
|
+
button.style.pointerEvents = 'auto';
|
|
400
|
+
button.classList.remove('readonly');
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
if (readonly) {
|
|
404
|
+
iparamsInputsForm.querySelectorAll('.multiselect-dropdown-panel').forEach(panel => panel.style.display = 'none');
|
|
405
|
+
iparamsInputsForm.querySelectorAll('.multiselect-dropdown-button').forEach(button => button.classList.remove('open'));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** Loads iparams inputs from the server */
|
|
411
|
+
export async function loadIparamsInputs() {
|
|
412
|
+
try {
|
|
413
|
+
const iparamDefns = await fetchIparams();
|
|
414
|
+
state.iparamDefns = iparamDefns;
|
|
415
|
+
const inputs = await fetchIparamsInputs();
|
|
416
|
+
const mergedInputs = mergeInputsWithDefaults(inputs, iparamDefns);
|
|
417
|
+
const inputsJson = JSON.stringify(mergedInputs, null, 2);
|
|
418
|
+
state.iparamsInputsEditor.setValue(inputsJson);
|
|
419
|
+
state.originalInputsData = inputsJson;
|
|
420
|
+
updateInputsForm(mergedInputs);
|
|
421
|
+
state.iparamsInputsEditor.resize();
|
|
422
|
+
} catch (error) {
|
|
423
|
+
state.iparamsInputsEditor.setValue('{}');
|
|
424
|
+
state.originalInputsData = '{}';
|
|
425
|
+
updateInputsForm({});
|
|
426
|
+
showStatus(`Error loading iparams inputs: ${error.message}`);
|
|
427
|
+
}
|
|
428
|
+
setInputsReadonly(true);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** Saves iparams inputs to the server */
|
|
432
|
+
export async function saveIparamsInputs() {
|
|
433
|
+
try {
|
|
434
|
+
let inputs;
|
|
435
|
+
const isCurrentlyFormView = iparamsInputsForm && iparamsInputsForm.style.display !== 'none';
|
|
436
|
+
if (isCurrentlyFormView) {
|
|
437
|
+
inputs = getInputsFromForm();
|
|
438
|
+
} else {
|
|
439
|
+
if (state.iparamsInputsEditor) {
|
|
440
|
+
try {
|
|
441
|
+
inputs = JSON.parse(state.iparamsInputsEditor.getValue());
|
|
442
|
+
const cleanedInputs = {};
|
|
443
|
+
for (const [sectionName, sectionValues] of Object.entries(inputs)) {
|
|
444
|
+
if (!sectionValues || typeof sectionValues !== 'object' || Array.isArray(sectionValues)) continue;
|
|
445
|
+
const cleanedSection = {};
|
|
446
|
+
for (const [key, value] of Object.entries(sectionValues)) {
|
|
447
|
+
if (value !== undefined && value !== null && value !== '' && !(Array.isArray(value) && value.length === 0)) {
|
|
448
|
+
cleanedSection[key] = value;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (Object.keys(cleanedSection).length > 0) {
|
|
452
|
+
cleanedInputs[sectionName] = cleanedSection;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
inputs = cleanedInputs;
|
|
456
|
+
} catch (parseError) {
|
|
457
|
+
showStatus('Invalid JSON format. Please check your JSON syntax.');
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
inputs = {};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (typeof inputs !== 'object' || Array.isArray(inputs)) {
|
|
465
|
+
showStatus('Inputs must be an object keyed by section name.');
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
showStatus('Saving iparams inputs...');
|
|
469
|
+
saveInputsBtn.disabled = true;
|
|
470
|
+
const data = await saveInputsAPI(inputs);
|
|
471
|
+
if (data.valid === false) {
|
|
472
|
+
if (data.message) {
|
|
473
|
+
showStatus(data.message);
|
|
474
|
+
} else {
|
|
475
|
+
const missing = data.missing || [];
|
|
476
|
+
if (missing.length > 0) {
|
|
477
|
+
const paramText = missing.length > 1 ? 'parameters' : 'parameter';
|
|
478
|
+
showStatus(`Missing required ${paramText}: ${missing.join(', ')}.`);
|
|
479
|
+
} else {
|
|
480
|
+
showStatus('Validation failed. Please check your inputs.');
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
saveInputsBtn.disabled = false;
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
487
|
+
await loadIparamsInputs();
|
|
488
|
+
setInputsReadonly(true);
|
|
489
|
+
showStatus('Iparams inputs saved successfully!');
|
|
490
|
+
} catch (error) {
|
|
491
|
+
showStatus(`Error saving inputs: ${error.message}`);
|
|
492
|
+
} finally {
|
|
493
|
+
saveInputsBtn.disabled = false;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { state, editIparamsBtn, saveIparamsBtn, cancelIparamsBtn, EMPTY_IPARAM_DEFNS } from './constants.js';
|
|
2
|
+
import { showStatus } from './ui-utils.js';
|
|
3
|
+
import { fetchIparams, saveIparams as saveIparamsAPI } from './api.js';
|
|
4
|
+
|
|
5
|
+
/** Loads iparams from the server */
|
|
6
|
+
export async function loadIparams() {
|
|
7
|
+
try {
|
|
8
|
+
const iparamDefns = await fetchIparams();
|
|
9
|
+
state.iparamDefns = iparamDefns;
|
|
10
|
+
const iparamsJson = JSON.stringify(iparamDefns, null, 2);
|
|
11
|
+
state.iparamsEditor.setValue(iparamsJson);
|
|
12
|
+
state.originalIparamsData = iparamsJson;
|
|
13
|
+
state.iparamsEditor.resize();
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error.rawContent !== null && error.rawContent !== undefined) {
|
|
16
|
+
state.iparamDefns = null;
|
|
17
|
+
const rawJson = JSON.stringify(error.rawContent, null, 2);
|
|
18
|
+
state.iparamsEditor.setValue(rawJson);
|
|
19
|
+
state.originalIparamsData = rawJson;
|
|
20
|
+
} else {
|
|
21
|
+
state.iparamDefns = EMPTY_IPARAM_DEFNS;
|
|
22
|
+
const emptyJson = JSON.stringify(EMPTY_IPARAM_DEFNS, null, 2);
|
|
23
|
+
state.iparamsEditor.setValue(emptyJson);
|
|
24
|
+
state.originalIparamsData = emptyJson;
|
|
25
|
+
}
|
|
26
|
+
showStatus(`Error loading iparams: ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
setIparamsReadonly(true);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Enables editing mode for iparams editor */
|
|
32
|
+
export function enableIparamsEditing() {
|
|
33
|
+
state.isIparamsEditing = true;
|
|
34
|
+
state.iparamsEditor.setReadOnly(false);
|
|
35
|
+
editIparamsBtn.style.display = 'none';
|
|
36
|
+
saveIparamsBtn.style.display = 'inline-block';
|
|
37
|
+
cancelIparamsBtn.style.display = 'inline-block';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Cancels editing and restores original iparams */
|
|
41
|
+
export function cancelIparamsEditing() {
|
|
42
|
+
if (state.originalIparamsData !== null) {
|
|
43
|
+
state.iparamsEditor.setValue(state.originalIparamsData);
|
|
44
|
+
}
|
|
45
|
+
setIparamsReadonly(true);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Sets iparams editor to readonly mode */
|
|
49
|
+
export function setIparamsReadonly(readonly) {
|
|
50
|
+
state.isIparamsEditing = !readonly;
|
|
51
|
+
state.iparamsEditor.setReadOnly(readonly);
|
|
52
|
+
const editorElement = document.getElementById('iparamsEditor');
|
|
53
|
+
editorElement.classList.toggle('readonly', readonly);
|
|
54
|
+
editIparamsBtn.style.display = readonly ? 'inline-block' : 'none';
|
|
55
|
+
saveIparamsBtn.style.display = readonly ? 'none' : 'inline-block';
|
|
56
|
+
cancelIparamsBtn.style.display = readonly ? 'none' : 'inline-block';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Saves iparams to the server */
|
|
60
|
+
export async function saveIparams() {
|
|
61
|
+
try {
|
|
62
|
+
const jsonData = state.iparamsEditor.getValue();
|
|
63
|
+
let iparamDefns;
|
|
64
|
+
try {
|
|
65
|
+
iparamDefns = JSON.parse(jsonData);
|
|
66
|
+
} catch (parseError) {
|
|
67
|
+
showStatus('Invalid JSON format. Please check your JSON syntax.');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
showStatus('Saving iparams...');
|
|
71
|
+
saveIparamsBtn.disabled = true;
|
|
72
|
+
await saveIparamsAPI(iparamDefns);
|
|
73
|
+
state.iparamDefns = iparamDefns;
|
|
74
|
+
state.originalIparamsData = jsonData;
|
|
75
|
+
setIparamsReadonly(true);
|
|
76
|
+
showStatus('Iparams saved successfully!');
|
|
77
|
+
} catch (error) {
|
|
78
|
+
showStatus(`Error saving iparams: ${error.message}`);
|
|
79
|
+
} finally {
|
|
80
|
+
saveIparamsBtn.disabled = false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { statusText } from './constants.js';
|
|
2
|
+
|
|
3
|
+
/** Displays a status message to the user */
|
|
4
|
+
export function showStatus(message) {
|
|
5
|
+
statusText.textContent = message;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Sets up tab navigation */
|
|
9
|
+
export function setupTabNavigation() {
|
|
10
|
+
document.querySelectorAll('.tab-btn').forEach(btn => {
|
|
11
|
+
btn.addEventListener('click', function() {
|
|
12
|
+
switchTab(this.getAttribute('data-tab'));
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Switches between tabs */
|
|
18
|
+
export function switchTab(tabName) {
|
|
19
|
+
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
|
20
|
+
document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
|
|
21
|
+
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
|
22
|
+
document.getElementById(`${tabName}-tab`).classList.add('active');
|
|
23
|
+
|
|
24
|
+
if (tabName === 'iparams') {
|
|
25
|
+
showStatus('Define or update parameter');
|
|
26
|
+
setTimeout(async () => {
|
|
27
|
+
const module = await import('./iparams.js');
|
|
28
|
+
module.loadIparams();
|
|
29
|
+
}, 100);
|
|
30
|
+
} else if (tabName === 'iparams-inputs') {
|
|
31
|
+
showStatus('Provide values for parameters');
|
|
32
|
+
setTimeout(async () => {
|
|
33
|
+
const module = await import('./iparams-inputs.js');
|
|
34
|
+
module.loadIparamsInputs();
|
|
35
|
+
}, 100);
|
|
36
|
+
} else if (tabName === 'events') {
|
|
37
|
+
showStatus('Select an event type and click "Test Data" to test your function');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Shows popup modal for missing required parameters */
|
|
42
|
+
export function showMissingParamsPopup(missingParams, message) {
|
|
43
|
+
const modal = document.getElementById('errorModal');
|
|
44
|
+
const missingParamsList = document.getElementById('missingParamsList');
|
|
45
|
+
if (!modal || !missingParamsList) return;
|
|
46
|
+
missingParamsList.innerHTML = '';
|
|
47
|
+
if (missingParams && missingParams.length > 0) {
|
|
48
|
+
missingParams.forEach(param => {
|
|
49
|
+
const li = document.createElement('li');
|
|
50
|
+
li.textContent = param;
|
|
51
|
+
missingParamsList.appendChild(li);
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
const li = document.createElement('li');
|
|
55
|
+
li.textContent = message || 'Unknown parameter';
|
|
56
|
+
missingParamsList.appendChild(li);
|
|
57
|
+
}
|
|
58
|
+
modal.style.display = 'block';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Closes the error modal */
|
|
62
|
+
export function closeErrorModal() {
|
|
63
|
+
const modal = document.getElementById('errorModal');
|
|
64
|
+
if (modal) modal.style.display = 'none';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Sets up modal event listeners */
|
|
68
|
+
export function setupModalListeners() {
|
|
69
|
+
const closeModalBtn = document.getElementById('closeModal');
|
|
70
|
+
const closeModalBtn2 = document.getElementById('closeModalBtn');
|
|
71
|
+
const goToIparamsBtn = document.getElementById('goToIparamsBtn');
|
|
72
|
+
const modal = document.getElementById('errorModal');
|
|
73
|
+
if (closeModalBtn) closeModalBtn.addEventListener('click', closeErrorModal);
|
|
74
|
+
if (closeModalBtn2) closeModalBtn2.addEventListener('click', closeErrorModal);
|
|
75
|
+
if (goToIparamsBtn) {
|
|
76
|
+
goToIparamsBtn.addEventListener('click', function() {
|
|
77
|
+
closeErrorModal();
|
|
78
|
+
switchTab('iparams-inputs');
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (modal) {
|
|
82
|
+
window.addEventListener('click', function(event) {
|
|
83
|
+
if (event.target === modal) closeErrorModal();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|