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 - z A - Z 0 - 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