Skip to content

Commit baad2c9

Browse files
committed
feat: add role permissions form tool and fix merge logic
- Added standalone `role-permissions-form.html` utility to generate role definitions from a permissions map JSON - Added `glob` dependency to support improved file discovery - Updated README example (opensearch-report.json → opensearch-reports.json) - Refactored mergeSolutions to remove buggy recursive dependency loop that caused incorrect module merging - Cleaned up commented-out legacy merge code for clarity These changes improve the reliability of solution composition and provide a practical helper for permission management.
1 parent 50827c4 commit baad2c9

44 files changed

Lines changed: 1752 additions & 1176 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ initData: {}
214214
```json
215215
{
216216
"modules": [
217-
"opensearch-report.json"
217+
"opensearch-reports.json"
218218
]
219219
}
220220
```

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"dependencies": {
33
"axios": "^1.13.6",
4+
"glob": "^13.0.6",
45
"js-yaml": "^4.1.0",
56
"jszip": "^3.10.1",
67
"mime-types": "^3.0.1",

role-permissions-form.html

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Role Permissions Form</title>
7+
<style>
8+
body { font-family: Arial, sans-serif; margin: 20px; }
9+
.form-group { margin-bottom: 15px; }
10+
label { display: block; margin-bottom: 5px; }
11+
input[type="text"] { width: 100%; padding: 8px; box-sizing: border-box; }
12+
.module { margin-bottom: 20px; }
13+
.module summary { font-weight: bold; cursor: pointer; }
14+
.permissions { margin-left: 20px; }
15+
.permission { margin-bottom: 5px; }
16+
button { padding: 10px 20px; background-color: #007bff; color: white; border: none; cursor: pointer; }
17+
button:hover { background-color: #0056b3; }
18+
</style>
19+
</head>
20+
<body>
21+
<h1>Role Permissions Form</h1>
22+
<div class="form-group">
23+
<label for="permissionsFile">Upload Permissions Map JSON:</label>
24+
<input type="file" id="permissionsFile" accept=".json" required>
25+
</div>
26+
<form id="roleForm" style="display: none;">
27+
<div class="form-group">
28+
<label for="roleCode">Role Code:</label>
29+
<input type="text" id="roleCode" required>
30+
</div>
31+
<div class="form-group">
32+
<label for="roleName">Role Name:</label>
33+
<input type="text" id="roleName" required>
34+
</div>
35+
<div id="permissionsContainer">
36+
<!-- Permissions will be loaded here -->
37+
</div>
38+
<button type="button" id="saveButton">Save Role</button>
39+
</form>
40+
41+
<script>
42+
const permissionsFileInput = document.getElementById('permissionsFile');
43+
const roleForm = document.getElementById('roleForm');
44+
const permissionsContainer = document.getElementById('permissionsContainer');
45+
const saveButton = document.getElementById('saveButton');
46+
const roleCodeInput = document.getElementById('roleCode');
47+
const roleNameInput = document.getElementById('roleName');
48+
49+
permissionsFileInput.addEventListener('change', (event) => {
50+
const file = event.target.files[0];
51+
if (file) {
52+
const reader = new FileReader();
53+
reader.onload = (e) => {
54+
try {
55+
const permissionsMap = JSON.parse(e.target.result);
56+
if (typeof permissionsMap !== 'object' || permissionsMap === null) {
57+
throw new Error('Invalid structure: must be an object.');
58+
}
59+
for (const module in permissionsMap) {
60+
if (!Array.isArray(permissionsMap[module])) {
61+
throw new Error(`Invalid module "${module}": must be an array.`);
62+
}
63+
for (const perm of permissionsMap[module]) {
64+
if (typeof perm !== 'string') {
65+
throw new Error(`Invalid permission in module "${module}": must be a string.`);
66+
}
67+
}
68+
}
69+
renderPermissions(permissionsMap);
70+
roleForm.style.display = 'block';
71+
} catch (error) {
72+
alert('Invalid JSON file. Please upload a valid module_permissions_map.json with nested structure.');
73+
}
74+
};
75+
reader.readAsText(file);
76+
}
77+
});
78+
79+
function updateModuleState(details) {
80+
const moduleCb = details.querySelector('input[type="checkbox"]');
81+
const permCbs = details.querySelectorAll('.permissions input[type="checkbox"]');
82+
const checkedCount = Array.from(permCbs).filter(cb => cb.checked).length;
83+
if (checkedCount === 0) {
84+
moduleCb.checked = false;
85+
moduleCb.indeterminate = false;
86+
} else if (checkedCount === permCbs.length) {
87+
moduleCb.checked = true;
88+
moduleCb.indeterminate = false;
89+
} else {
90+
moduleCb.checked = false;
91+
moduleCb.indeterminate = true;
92+
}
93+
}
94+
95+
function renderPermissions(permissionsMap) {
96+
// Clear previous content
97+
permissionsContainer.innerHTML = '';
98+
99+
// Render grouped permissions using top-level keys as modules
100+
Object.keys(permissionsMap).sort().forEach(module => {
101+
const details = document.createElement('details');
102+
details.className = 'module';
103+
104+
const summary = document.createElement('summary');
105+
const moduleCheckbox = document.createElement('input');
106+
moduleCheckbox.type = 'checkbox';
107+
moduleCheckbox.id = `moduleCheckbox-${module.replace(/[^a-zA-Z0-9]/g, '_')}`;
108+
summary.dataset.module = module;
109+
summary.appendChild(moduleCheckbox);
110+
summary.appendChild(document.createTextNode(' ' + module));
111+
details.appendChild(summary);
112+
113+
const permissionsDiv = document.createElement('div');
114+
permissionsDiv.className = 'permissions';
115+
116+
permissionsMap[module].forEach(perm => {
117+
const label = document.createElement('label');
118+
label.className = 'permission';
119+
120+
const checkbox = document.createElement('input');
121+
checkbox.type = 'checkbox';
122+
checkbox.value = perm;
123+
124+
const displayText = perm;
125+
label.appendChild(checkbox);
126+
label.appendChild(document.createTextNode(' ' + displayText));
127+
128+
permissionsDiv.appendChild(label);
129+
});
130+
131+
details.appendChild(permissionsDiv);
132+
permissionsContainer.appendChild(details);
133+
});
134+
}
135+
136+
// Attach permission checkbox change listener for indeterminate state
137+
permissionsContainer.addEventListener('change', (e) => {
138+
if (e.target.type === 'checkbox' && e.target.closest('.permissions')) {
139+
const details = e.target.closest('details');
140+
updateModuleState(details);
141+
}else if (e.target.type === 'checkbox' && e.target.id.startsWith("moduleCheckbox-")){
142+
const details = e.target.closest('details');
143+
const permCheckboxes = details.querySelectorAll('.permissions input[type="checkbox"]');
144+
permCheckboxes.forEach(permCb => {
145+
permCb.checked = e.target.checked;
146+
});
147+
// Update the module state after toggling
148+
updateModuleState(details);
149+
}
150+
});
151+
152+
saveButton.addEventListener('click', () => {
153+
const roleCode = roleCodeInput.value.trim();
154+
const roleName = roleNameInput.value.trim();
155+
156+
if (!roleCode || !roleName) {
157+
alert('Please enter both role code and name.');
158+
return;
159+
}
160+
161+
const permissions = [];
162+
const checkboxes = document.querySelectorAll('.permissions input[type="checkbox"]:checked');
163+
checkboxes.forEach(cb => {
164+
permissions.push(cb.value);
165+
});
166+
167+
const roleData = {
168+
code: roleCode,
169+
name: roleName,
170+
permissions: permissions
171+
};
172+
173+
const roles = {roles: [roleData]}
174+
175+
// Download as JSON
176+
const blob = new Blob([JSON.stringify(roles, null, 2)], { type: 'application/json' });
177+
const url = URL.createObjectURL(blob);
178+
const a = document.createElement('a');
179+
a.href = url;
180+
a.download = `${roleCode}-role.json`;
181+
document.body.appendChild(a);
182+
a.click();
183+
document.body.removeChild(a);
184+
URL.revokeObjectURL(url);
185+
});
186+
</script>
187+
</body>
188+
</html>

0 commit comments

Comments
 (0)