@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
|
Binary file
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Main entry point - imports and initializes all modules
|
|
2
|
+
import { initializeCodeMirror, initializeIparamsEditors } from './modules/editors.js';
|
|
3
|
+
import { setupTabNavigation, setupModalListeners, switchTab } from './modules/ui-utils.js';
|
|
4
|
+
import {
|
|
5
|
+
handleEventTypeChange,
|
|
6
|
+
loadAvailableEventTypes,
|
|
7
|
+
resetToOriginalData,
|
|
8
|
+
testEvent
|
|
9
|
+
} from './modules/events.js';
|
|
10
|
+
import {
|
|
11
|
+
enableIparamsEditing,
|
|
12
|
+
cancelIparamsEditing,
|
|
13
|
+
saveIparams
|
|
14
|
+
} from './modules/iparams.js';
|
|
15
|
+
import {
|
|
16
|
+
enableInputsEditing,
|
|
17
|
+
cancelInputsEditing,
|
|
18
|
+
toggleInputsView,
|
|
19
|
+
saveIparamsInputs
|
|
20
|
+
} from './modules/iparams-inputs.js';
|
|
21
|
+
import {
|
|
22
|
+
eventTypeSelect,
|
|
23
|
+
resetBtn,
|
|
24
|
+
testDataBtn,
|
|
25
|
+
saveIparamsBtn,
|
|
26
|
+
saveInputsBtn,
|
|
27
|
+
editIparamsBtn,
|
|
28
|
+
editInputsBtn,
|
|
29
|
+
cancelIparamsBtn,
|
|
30
|
+
cancelInputsBtn,
|
|
31
|
+
toggleViewBtn
|
|
32
|
+
} from './modules/constants.js';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Sets up event listeners for user interactions
|
|
36
|
+
*/
|
|
37
|
+
function setupEventListeners() {
|
|
38
|
+
eventTypeSelect.addEventListener('change', handleEventTypeChange);
|
|
39
|
+
resetBtn.addEventListener('click', resetToOriginalData);
|
|
40
|
+
testDataBtn.addEventListener('click', testEvent);
|
|
41
|
+
saveIparamsBtn.addEventListener('click', saveIparams);
|
|
42
|
+
saveInputsBtn.addEventListener('click', saveIparamsInputs);
|
|
43
|
+
editIparamsBtn.addEventListener('click', enableIparamsEditing);
|
|
44
|
+
editInputsBtn.addEventListener('click', enableInputsEditing);
|
|
45
|
+
cancelIparamsBtn.addEventListener('click', cancelIparamsEditing);
|
|
46
|
+
cancelInputsBtn.addEventListener('click', cancelInputsEditing);
|
|
47
|
+
toggleViewBtn.addEventListener('click', toggleInputsView);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Initialize the application
|
|
51
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
52
|
+
initializeCodeMirror();
|
|
53
|
+
initializeIparamsEditors();
|
|
54
|
+
setupEventListeners();
|
|
55
|
+
setupTabNavigation();
|
|
56
|
+
setupModalListeners();
|
|
57
|
+
|
|
58
|
+
// Load available event types and select the first one by default
|
|
59
|
+
loadAvailableEventTypes();
|
|
60
|
+
});
|
|
61
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API utility functions for making fetch requests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { EMPTY_IPARAM_DEFNS } from './constants.js';
|
|
6
|
+
|
|
7
|
+
/** Fetches available event types from manifest */
|
|
8
|
+
export async function fetchEventTypes() {
|
|
9
|
+
const response = await fetch('/api/manifest');
|
|
10
|
+
if (!response.ok) throw new Error('Failed to load event types');
|
|
11
|
+
return await response.json();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Fetches test data for a specific event type */
|
|
15
|
+
export async function fetchTestData(eventType) {
|
|
16
|
+
const response = await fetch(`/api/test-data/${eventType}`);
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
if (response.status === 404) return null;
|
|
19
|
+
const errorData = await response.json();
|
|
20
|
+
throw new Error(errorData.message || 'Failed to load sample data');
|
|
21
|
+
}
|
|
22
|
+
return await response.json();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Invokes the handler with event data */
|
|
26
|
+
export async function invokeHandler(payload) {
|
|
27
|
+
const response = await fetch('/api/invoke', {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
body: JSON.stringify(payload)
|
|
31
|
+
});
|
|
32
|
+
const responseText = await response.text();
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
try {
|
|
35
|
+
return { error: JSON.parse(responseText), status: response.status };
|
|
36
|
+
} catch {
|
|
37
|
+
return { error: { message: `Server returned status ${response.status}` }, status: response.status };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
return { data: JSON.parse(responseText), status: response.status };
|
|
42
|
+
} catch {
|
|
43
|
+
return { data: { message: 'Success' }, status: response.status };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Fetches iparams */
|
|
48
|
+
export async function fetchIparams() {
|
|
49
|
+
const response = await fetch('/api/iparams');
|
|
50
|
+
const data = await response.json();
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const error = new Error(data.message || data.error || 'Failed to load iparams.json');
|
|
53
|
+
error.rawContent = data.rawContent;
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
return data.iparams || EMPTY_IPARAM_DEFNS;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Saves iparams */
|
|
60
|
+
export async function saveIparams(iparams) {
|
|
61
|
+
const response = await fetch('/api/iparams', {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: { 'Content-Type': 'application/json' },
|
|
64
|
+
body: JSON.stringify({ iparams })
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const errorData = await response.json();
|
|
68
|
+
throw new Error(errorData.message || errorData.error || 'Unknown error');
|
|
69
|
+
}
|
|
70
|
+
return await response.json();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Fetches iparams inputs */
|
|
74
|
+
export async function fetchIparamsInputs() {
|
|
75
|
+
const response = await fetch('/api/iparams/inputs');
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const errorData = await response.json();
|
|
78
|
+
throw new Error(errorData.message || errorData.error || 'Failed to load iparams.local.json');
|
|
79
|
+
}
|
|
80
|
+
const data = await response.json();
|
|
81
|
+
return data.inputs || {};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Saves iparams inputs (returns body with success or valid: false + missing/message on validation error) */
|
|
85
|
+
export async function saveIparamsInputs(inputs) {
|
|
86
|
+
const response = await fetch('/api/iparams/inputs', {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: { 'Content-Type': 'application/json' },
|
|
89
|
+
body: JSON.stringify({ inputs })
|
|
90
|
+
});
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const errorData = await response.json();
|
|
93
|
+
throw new Error(errorData.message || errorData.error || 'Unknown error');
|
|
94
|
+
}
|
|
95
|
+
return await response.json();
|
|
96
|
+
}
|
|
97
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Global state variables - using object to allow mutation
|
|
2
|
+
export const EMPTY_IPARAM_DEFNS = {};
|
|
3
|
+
|
|
4
|
+
export const state = {
|
|
5
|
+
editor: null,
|
|
6
|
+
iparamsEditor: null,
|
|
7
|
+
iparamsInputsEditor: null,
|
|
8
|
+
currentEventType: '',
|
|
9
|
+
originalEventData: null,
|
|
10
|
+
originalIparamsData: null,
|
|
11
|
+
originalInputsData: null,
|
|
12
|
+
isIparamsEditing: false,
|
|
13
|
+
isInputsEditing: false,
|
|
14
|
+
isFormView: true, // true for form view, false for JSON view
|
|
15
|
+
iparamDefns: EMPTY_IPARAM_DEFNS // Store iparam definition for form generation
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// DOM elements
|
|
19
|
+
export const eventTypeSelect = document.getElementById('eventType');
|
|
20
|
+
export const jsonEditor = document.getElementById('jsonEditor');
|
|
21
|
+
export const resetBtn = document.getElementById('resetBtn');
|
|
22
|
+
export const testDataBtn = document.getElementById('testDataBtn');
|
|
23
|
+
export const statusBlock = document.getElementById('statusBlock');
|
|
24
|
+
export const statusText = document.getElementById('statusText');
|
|
25
|
+
export const saveIparamsBtn = document.getElementById('saveIparamsBtn');
|
|
26
|
+
export const saveInputsBtn = document.getElementById('saveInputsBtn');
|
|
27
|
+
export const editIparamsBtn = document.getElementById('editIparamsBtn');
|
|
28
|
+
export const editInputsBtn = document.getElementById('editInputsBtn');
|
|
29
|
+
export const cancelIparamsBtn = document.getElementById('cancelIparamsBtn');
|
|
30
|
+
export const cancelInputsBtn = document.getElementById('cancelInputsBtn');
|
|
31
|
+
export const toggleViewBtn = document.getElementById('toggleViewBtn');
|
|
32
|
+
export const iparamsInputsForm = document.getElementById('iparamsInputsForm');
|
|
33
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { state } from './constants.js';
|
|
2
|
+
|
|
3
|
+
const editorOptions = {
|
|
4
|
+
enableBasicAutocompletion: true,
|
|
5
|
+
enableLiveAutocompletion: true,
|
|
6
|
+
enableSnippets: true,
|
|
7
|
+
fontSize: "13px",
|
|
8
|
+
showPrintMargin: false,
|
|
9
|
+
showGutter: true,
|
|
10
|
+
highlightActiveLine: true,
|
|
11
|
+
wrap: true,
|
|
12
|
+
tabSize: 2,
|
|
13
|
+
useSoftTabs: true,
|
|
14
|
+
showInvisibles: false
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Initializes the Ace code editor with JSON syntax highlighting */
|
|
18
|
+
export function initializeCodeMirror() {
|
|
19
|
+
state.editor = ace.edit("jsonEditor");
|
|
20
|
+
state.editor.setTheme("ace/theme/github");
|
|
21
|
+
state.editor.session.setMode("ace/mode/json");
|
|
22
|
+
state.editor.setOptions(editorOptions);
|
|
23
|
+
state.editor.setValue('{\n \n}');
|
|
24
|
+
const editorEl = document.getElementById('jsonEditor');
|
|
25
|
+
state.editor.on('focus', () => editorEl.classList.add('ace-focused'));
|
|
26
|
+
state.editor.on('blur', () => editorEl.classList.remove('ace-focused'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Initializes the Ace code editors for iparams and inputs */
|
|
30
|
+
export function initializeIparamsEditors() {
|
|
31
|
+
state.iparamsEditor = ace.edit("iparamsEditor");
|
|
32
|
+
state.iparamsEditor.setTheme("ace/theme/github");
|
|
33
|
+
state.iparamsEditor.session.setMode("ace/mode/json");
|
|
34
|
+
state.iparamsEditor.setOptions({ ...editorOptions, readOnly: true });
|
|
35
|
+
|
|
36
|
+
state.iparamsInputsEditor = ace.edit("iparamsInputsEditor");
|
|
37
|
+
state.iparamsInputsEditor.setTheme("ace/theme/github");
|
|
38
|
+
state.iparamsInputsEditor.session.setMode("ace/mode/json");
|
|
39
|
+
state.iparamsInputsEditor.setOptions({ ...editorOptions, readOnly: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { state, eventTypeSelect, resetBtn, testDataBtn } from './constants.js';
|
|
2
|
+
import { showStatus, showMissingParamsPopup } from './ui-utils.js';
|
|
3
|
+
import { fetchEventTypes, fetchTestData, invokeHandler } from './api.js';
|
|
4
|
+
|
|
5
|
+
/** Handles changes to the event type selection dropdown */
|
|
6
|
+
export function handleEventTypeChange() {
|
|
7
|
+
state.currentEventType = eventTypeSelect.value;
|
|
8
|
+
if (state.currentEventType) {
|
|
9
|
+
resetBtn.disabled = false;
|
|
10
|
+
testDataBtn.disabled = false;
|
|
11
|
+
loadSampleData();
|
|
12
|
+
} else {
|
|
13
|
+
resetBtn.disabled = true;
|
|
14
|
+
testDataBtn.disabled = true;
|
|
15
|
+
state.editor.setValue('{\n \n}');
|
|
16
|
+
state.originalEventData = null;
|
|
17
|
+
showStatus('Select an event type to get started');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Loads sample data for the currently selected event type */
|
|
22
|
+
export async function loadSampleData() {
|
|
23
|
+
if (!state.currentEventType || typeof state.currentEventType !== 'string') return;
|
|
24
|
+
try {
|
|
25
|
+
const data = await fetchTestData(state.currentEventType);
|
|
26
|
+
if (data) {
|
|
27
|
+
state.originalEventData = data;
|
|
28
|
+
state.editor.setValue(JSON.stringify(data, null, 2));
|
|
29
|
+
showStatus(`Sample data loaded for ${state.currentEventType}!`);
|
|
30
|
+
} else {
|
|
31
|
+
state.editor.setValue(`{\n // No sample data available for this event\n // Please create a test_data/${state.currentEventType}.json file\n}`);
|
|
32
|
+
showStatus(`No test data found for ${state.currentEventType}. Create test_data/${state.currentEventType}.json`);
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
showStatus(`Error loading sample data for ${state.currentEventType}: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Resets the editor content to the original sample data */
|
|
40
|
+
export function resetToOriginalData() {
|
|
41
|
+
if (!state.currentEventType || !state.originalEventData) {
|
|
42
|
+
showStatus('No original data to reset to. Please select an event type first.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
state.editor.setValue(JSON.stringify(state.originalEventData, null, 2));
|
|
46
|
+
showStatus('Data reset to original sample data!');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Tests the current event by sending it to the handler function */
|
|
50
|
+
export async function testEvent() {
|
|
51
|
+
if (!state.currentEventType) return;
|
|
52
|
+
try {
|
|
53
|
+
const jsonData = state.editor.getValue();
|
|
54
|
+
let eventData;
|
|
55
|
+
try {
|
|
56
|
+
eventData = JSON.parse(jsonData);
|
|
57
|
+
} catch (parseError) {
|
|
58
|
+
showStatus('Invalid JSON format. Please check your JSON syntax.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const payload = { event_type: state.currentEventType, ...eventData };
|
|
62
|
+
showStatus('Testing event...');
|
|
63
|
+
testDataBtn.disabled = true;
|
|
64
|
+
const result = await invokeHandler(payload);
|
|
65
|
+
|
|
66
|
+
if (result.status === 200) {
|
|
67
|
+
const eventAnalysis = await fetchEventTypes();
|
|
68
|
+
const currentEvent = eventAnalysis.events?.find(event => event.event_type === state.currentEventType);
|
|
69
|
+
const handlerName = currentEvent?.handler?.name || 'unknown';
|
|
70
|
+
showStatus(`Event handler '${handlerName}' executed successfully! Check the output in the terminal.`);
|
|
71
|
+
} else {
|
|
72
|
+
const errorData = result.error;
|
|
73
|
+
if (result.status === 400) {
|
|
74
|
+
if (errorData.showPopup && errorData.missingParams) {
|
|
75
|
+
showMissingParamsPopup(errorData.missingParams, errorData.message);
|
|
76
|
+
showStatus(`Error: ${errorData.message}`);
|
|
77
|
+
} else if (errorData.message?.includes('Handler function') && errorData.message.includes('not found in handler.js')) {
|
|
78
|
+
const handlerMatch = errorData.message.match(/Handler function '([^']+)'/);
|
|
79
|
+
const handlerName = handlerMatch ? handlerMatch[1] : 'unknown';
|
|
80
|
+
showStatus(`Error: Handler function '${handlerName}' not found in handler.js. Please create this function in your handler.js file.`);
|
|
81
|
+
} else if (errorData.message?.includes('No handler configured')) {
|
|
82
|
+
showStatus(`Error: No handler configured for event type '${state.currentEventType}' in manifest.json.`);
|
|
83
|
+
} else {
|
|
84
|
+
showStatus(`Error: ${errorData.message || 'Bad request'}`);
|
|
85
|
+
}
|
|
86
|
+
} else if (result.status === 404) {
|
|
87
|
+
if (errorData.message?.includes('handler.js not found')) {
|
|
88
|
+
showStatus('Error: handler.js file not found. Please create a handler.js file in your app directory.');
|
|
89
|
+
} else if (errorData.message?.includes('manifest.json not found')) {
|
|
90
|
+
showStatus('Error: manifest.json file not found. Please create a manifest.json file in your app directory.');
|
|
91
|
+
} else {
|
|
92
|
+
showStatus(`Error: ${errorData.message || 'Resource not found'}`);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
showStatus(`Error: ${errorData.message || 'Unknown error'}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
|
100
|
+
showStatus('Error: Cannot connect to server. Please check if the server is running.');
|
|
101
|
+
} else if (error.name === 'SyntaxError') {
|
|
102
|
+
showStatus('Error: Invalid response from server. Please try again.');
|
|
103
|
+
} else {
|
|
104
|
+
showStatus(`Error: ${error.message}`);
|
|
105
|
+
}
|
|
106
|
+
} finally {
|
|
107
|
+
testDataBtn.disabled = false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Loads and populates the dropdown with available event types from the server */
|
|
112
|
+
export async function loadAvailableEventTypes() {
|
|
113
|
+
try {
|
|
114
|
+
const data = await fetchEventTypes();
|
|
115
|
+
const events = data.events || [];
|
|
116
|
+
eventTypeSelect.innerHTML = '<option value="">Choose an event type...</option>';
|
|
117
|
+
events.forEach(cbEvent => {
|
|
118
|
+
const option = document.createElement('option');
|
|
119
|
+
option.value = cbEvent.event_type;
|
|
120
|
+
option.textContent = cbEvent.event_type;
|
|
121
|
+
eventTypeSelect.appendChild(option);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (events.length > 0) {
|
|
125
|
+
const incompleteEvents = events.filter(event => event.status === 'incomplete');
|
|
126
|
+
if (incompleteEvents.length > 0) {
|
|
127
|
+
const missingItems = incompleteEvents.map(event => {
|
|
128
|
+
const issues = [];
|
|
129
|
+
if (!event.handler.exists) {
|
|
130
|
+
issues.push(event.handler.name ? `handler function '${event.handler.name}'` : 'handler mapping');
|
|
131
|
+
}
|
|
132
|
+
if (!event.has_test_data) issues.push('test data');
|
|
133
|
+
return `${event.event_type} (missing: ${issues.join(', ')})`;
|
|
134
|
+
});
|
|
135
|
+
showStatus(`Events configured: ${events.length}. Missing components: ${missingItems.join(', ')}`);
|
|
136
|
+
} else {
|
|
137
|
+
showStatus(`All ${events.length} events are properly configured with handlers and test data.`);
|
|
138
|
+
}
|
|
139
|
+
const firstEventType = events[0].event_type;
|
|
140
|
+
eventTypeSelect.value = firstEventType;
|
|
141
|
+
state.currentEventType = firstEventType;
|
|
142
|
+
resetBtn.disabled = false;
|
|
143
|
+
testDataBtn.disabled = false;
|
|
144
|
+
saveToLocalStorage();
|
|
145
|
+
loadSampleData();
|
|
146
|
+
} else {
|
|
147
|
+
showStatus('No events configured in manifest.json. Please add events to your manifest file.');
|
|
148
|
+
}
|
|
149
|
+
} catch (error) {
|
|
150
|
+
showStatus('Error loading event types. Please check your connection.');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Saves the current editor state to localStorage for persistence */
|
|
155
|
+
export function saveToLocalStorage() {
|
|
156
|
+
if (state.currentEventType) {
|
|
157
|
+
localStorage.setItem('serverless', JSON.stringify({
|
|
158
|
+
eventType: state.currentEventType,
|
|
159
|
+
jsonData: state.editor.getValue()
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Loads saved editor state from localStorage */
|
|
165
|
+
export function loadFromLocalStorage() {
|
|
166
|
+
const saved = localStorage.getItem('serverless');
|
|
167
|
+
if (saved) {
|
|
168
|
+
try {
|
|
169
|
+
const data = JSON.parse(saved);
|
|
170
|
+
eventTypeSelect.value = data.eventType;
|
|
171
|
+
state.currentEventType = data.eventType;
|
|
172
|
+
state.editor.setValue(data.jsonData);
|
|
173
|
+
resetBtn.disabled = false;
|
|
174
|
+
testDataBtn.disabled = false;
|
|
175
|
+
loadSampleData();
|
|
176
|
+
} catch (error) {
|
|
177
|
+
// Ignore localStorage errors
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Keyboard shortcuts
|
|
183
|
+
document.addEventListener('keydown', function(e) {
|
|
184
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter' && !testDataBtn.disabled) {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
testEvent();
|
|
187
|
+
}
|
|
188
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'r' && !resetBtn.disabled) {
|
|
189
|
+
e.preventDefault();
|
|
190
|
+
resetToOriginalData();
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
window.addEventListener('load', loadFromLocalStorage);
|
|
195
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { state } from './constants.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns sections from the stored iparam definition.
|
|
5
|
+
* @returns {Array} Section definitions
|
|
6
|
+
*/
|
|
7
|
+
export function getIparamSections() {
|
|
8
|
+
return state.iparamDefns?.installation_parameters?.sections ?? [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builds a unique DOM id for a parameter input within a section.
|
|
13
|
+
* @param {string} sectionName - Section name
|
|
14
|
+
* @param {string} paramName - Parameter name
|
|
15
|
+
* @returns {string} Unique element id
|
|
16
|
+
*/
|
|
17
|
+
export function getIparamInputId(sectionName, paramName) {
|
|
18
|
+
return `iparam-${sectionName}-${paramName}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Merges sectioned inputs with defaults from iparam definitions.
|
|
23
|
+
* @param {Object} inputs - Provided input values keyed by section
|
|
24
|
+
* @param {Object} iparamDefns - Iparams definition object
|
|
25
|
+
* @returns {Object} Merged inputs with defaults applied
|
|
26
|
+
*/
|
|
27
|
+
export function mergeInputsWithDefaults(inputs, iparamDefns) {
|
|
28
|
+
const merged = {};
|
|
29
|
+
const sections = iparamDefns?.installation_parameters?.sections ?? [];
|
|
30
|
+
|
|
31
|
+
sections.forEach(section => {
|
|
32
|
+
const sectionInputs = inputs?.[section.name] ?? {};
|
|
33
|
+
const mergedSection = {};
|
|
34
|
+
|
|
35
|
+
section.parameters.forEach(param => {
|
|
36
|
+
const value = sectionInputs[param.name];
|
|
37
|
+
const hasValue = value !== undefined && value !== null && value !== '';
|
|
38
|
+
if (hasValue && !(Array.isArray(value) && value.length === 0)) {
|
|
39
|
+
mergedSection[param.name] = value;
|
|
40
|
+
}
|
|
41
|
+
if (mergedSection[param.name] === undefined && param.default !== undefined && param.default !== null) {
|
|
42
|
+
mergedSection[param.name] = param.default;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (Object.keys(mergedSection).length > 0) {
|
|
47
|
+
merged[section.name] = mergedSection;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return merged;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Updates the multiselect dropdown button display */
|
|
55
|
+
export function updateMultiselectDisplay(button, panel, paramName) {
|
|
56
|
+
const selected = Array.from(panel.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value);
|
|
57
|
+
const isOpen = button.classList.contains('open');
|
|
58
|
+
button.innerHTML = '';
|
|
59
|
+
if (selected.length > 0) {
|
|
60
|
+
button.textContent = selected.join(', ');
|
|
61
|
+
button.classList.remove('placeholder');
|
|
62
|
+
} else {
|
|
63
|
+
button.textContent = '-- Select options --';
|
|
64
|
+
button.classList.add('placeholder');
|
|
65
|
+
}
|
|
66
|
+
const arrow = document.createElement('span');
|
|
67
|
+
arrow.className = 'select-dropdown-arrow';
|
|
68
|
+
button.appendChild(arrow);
|
|
69
|
+
if (isOpen) button.classList.add('open');
|
|
70
|
+
}
|