@@ -15,6 +15,12 @@ let __aclSourcesCache = null;
1515let __aclSourceId = '' ;
1616
1717function getFolderAccessSourceId ( ) {
18+ const groupModal = document . getElementById ( 'groupAclModal' ) ;
19+ if ( groupModal && groupModal . style . display !== 'none' ) {
20+ const groupSel = document . getElementById ( 'groupAclSourceSelect' ) ;
21+ const groupId = groupSel && groupSel . value ? String ( groupSel . value ) : '' ;
22+ return groupId || __aclSourceId || '' ;
23+ }
1824 const sel = document . getElementById ( 'folderAccessSourceSelect' ) ;
1925 const id = sel && sel . value ? String ( sel . value ) : '' ;
2026 return id || __aclSourceId || '' ;
@@ -60,28 +66,35 @@ function populateFolderAccessSourceSelect(selectEl, sources, activeId) {
6066 selectEl . value = hasActive ? activeId : selectEl . options [ 0 ] . value ;
6167}
6268
63- async function initFolderAccessSourceSelector ( ) {
64- const row = document . getElementById ( 'folderAccessSourceRow' ) ;
65- const selectEl = document . getElementById ( 'folderAccessSourceSelect' ) ;
69+ async function initFolderAccessSourceSelector ( opts = { } ) {
70+ const rowId = opts . rowId || 'folderAccessSourceRow' ;
71+ const selectId = opts . selectId || 'folderAccessSourceSelect' ;
72+ const onChange = typeof opts . onChange === 'function' ? opts . onChange : null ;
73+ const row = document . getElementById ( rowId ) ;
74+ const selectEl = document . getElementById ( selectId ) ;
6675 if ( ! row || ! selectEl ) return ;
6776
6877 const data = await loadFolderAccessSources ( ) ;
6978 if ( ! data || ! Array . isArray ( data . sources ) || data . sources . length <= 1 ) {
7079 row . style . display = 'none' ;
7180 __aclSourceId = ( data && data . activeId ) ? String ( data . activeId ) : '' ;
81+ selectEl . __onSourceChange = onChange ;
7282 return ;
7383 }
7484
7585 row . style . display = '' ;
7686 populateFolderAccessSourceSelect ( selectEl , data . sources , data . activeId || '' ) ;
7787 __aclSourceId = selectEl . value || '' ;
88+ selectEl . __onSourceChange = onChange ;
7889
7990 if ( ! selectEl . __wired ) {
8091 selectEl . __wired = true ;
8192 selectEl . addEventListener ( 'change' , ( ) => {
8293 __aclSourceId = selectEl . value || '' ;
8394 __allFoldersCache = new Map ( ) ;
84- loadUserPermissionsList ( ) ;
95+ if ( typeof selectEl . __onSourceChange === 'function' ) {
96+ selectEl . __onSourceChange ( __aclSourceId ) ;
97+ }
8598 } ) ;
8699 }
87100}
@@ -792,17 +805,74 @@ async function saveAllGroups(groups) {
792805
793806let __groupsCache = { } ;
794807
795- function computeGroupGrantMaskForUser ( username , folders = [ ] ) {
808+ function normalizeSourceId ( sourceId ) {
809+ return String ( sourceId || '' ) . trim ( ) ;
810+ }
811+
812+ function isLocalSourceId ( sourceId ) {
813+ const sid = normalizeSourceId ( sourceId ) ;
814+ if ( ! sid || sid === 'local' ) return true ;
815+ const data = __aclSourcesCache ;
816+ if ( data && Array . isArray ( data . sources ) ) {
817+ const match = data . sources . find ( src => String ( src . id || '' ) === sid ) ;
818+ if ( match ) return String ( match . type || '' ) . toLowerCase ( ) === 'local' ;
819+ }
820+ return false ;
821+ }
822+
823+ function pickGroupGrantsForSource ( group , sourceId ) {
824+ if ( ! group || typeof group !== 'object' ) return { } ;
825+ const sid = normalizeSourceId ( sourceId ) ;
826+ const bySource = ( group . grantsBySource && typeof group . grantsBySource === 'object' && ! Array . isArray ( group . grantsBySource ) )
827+ ? group . grantsBySource
828+ : null ;
829+ if ( bySource ) {
830+ if ( sid && Object . prototype . hasOwnProperty . call ( bySource , sid ) ) {
831+ const candidate = bySource [ sid ] ;
832+ return ( candidate && typeof candidate === 'object' && ! Array . isArray ( candidate ) ) ? candidate : { } ;
833+ }
834+ if ( ! sid && Object . prototype . hasOwnProperty . call ( bySource , 'local' ) ) {
835+ const candidate = bySource . local ;
836+ return ( candidate && typeof candidate === 'object' && ! Array . isArray ( candidate ) ) ? candidate : { } ;
837+ }
838+ if ( isLocalSourceId ( sid ) ) {
839+ const legacy = group . grants ;
840+ return ( legacy && typeof legacy === 'object' && ! Array . isArray ( legacy ) ) ? legacy : { } ;
841+ }
842+ return { } ;
843+ }
844+ if ( isLocalSourceId ( sid ) ) {
845+ const legacy = group . grants ;
846+ return ( legacy && typeof legacy === 'object' && ! Array . isArray ( legacy ) ) ? legacy : { } ;
847+ }
848+ return { } ;
849+ }
850+
851+ function setGroupGrantsForSource ( group , sourceId , grants ) {
852+ if ( ! group || typeof group !== 'object' ) return ;
853+ const sid = normalizeSourceId ( sourceId ) ;
854+ if ( ! sid ) {
855+ group . grants = grants ;
856+ return ;
857+ }
858+ if ( ! group . grantsBySource || typeof group . grantsBySource !== 'object' || Array . isArray ( group . grantsBySource ) ) {
859+ group . grantsBySource = { } ;
860+ }
861+ group . grantsBySource [ sid ] = grants ;
862+ }
863+
864+ function computeGroupGrantMaskForUser ( username , folders = [ ] , sourceId = null ) {
796865 const mask = { } ;
797866 if ( ! username || ! __groupsCache ) return mask ;
867+ const sid = normalizeSourceId ( sourceId == null ? getFolderAccessSourceId ( ) : sourceId ) ;
798868 const uname = String ( username ) . toLowerCase ( ) ;
799869
800870 const userGroups = Object . keys ( __groupsCache || { } ) . map ( groupName => {
801871 const g = __groupsCache [ groupName ] || { } ;
802872 const members = Array . isArray ( g . members ) ? g . members : [ ] ;
803873 const inGroup = members . some ( m => String ( m || "" ) . toLowerCase ( ) === uname ) ;
804874 if ( ! inGroup ) return null ;
805- return { name : groupName , grants : g . grants || { } } ;
875+ return { name : groupName , grants : pickGroupGrantsForSource ( g , sid ) } ;
806876 } ) . filter ( Boolean ) ;
807877
808878 if ( ! userGroups . length ) return mask ;
@@ -1091,7 +1161,9 @@ export function openUserPermissionsModal(initialUser = null) {
10911161 userPermissionsModal . style . display = "flex" ;
10921162 }
10931163
1094- initFolderAccessSourceSelector ( ) . finally ( ( ) => {
1164+ initFolderAccessSourceSelector ( {
1165+ onChange : ( ) => loadUserPermissionsList ( )
1166+ } ) . finally ( ( ) => {
10951167 loadUserPermissionsList ( ) ;
10961168 } ) ;
10971169}
@@ -1109,7 +1181,7 @@ export async function openUserGroupsModal() {
11091181 modal . id = 'userGroupsModal' ;
11101182 modal . style . cssText = `
11111183 position:fixed; inset:0; background:${ overlayBg } ;
1112- display:flex; align-items:center; justify-content:center; z-index:3650 ;
1184+ display:flex; align-items:center; justify-content:center; z-index:3750 ;
11131185 ` ;
11141186 modal . innerHTML = `
11151187 <div class="modal-content"
@@ -1260,7 +1332,7 @@ async function loadUserPermissionsList() {
12601332 if ( grantsBox . dataset . loaded === "1" ) return ;
12611333 try {
12621334 const group = __groupsCache [ name ] || { } ;
1263- const grants = group . grants || { } ;
1335+ const grants = pickGroupGrantsForSource ( group , sourceId ) ;
12641336
12651337 renderFolderGrantsUI (
12661338 name ,
@@ -1386,7 +1458,7 @@ async function loadUserPermissionsList() {
13861458 ) ;
13871459
13881460 if ( ! isAdmin && groupNamesForUser . length ) {
1389- const groupMask = computeGroupGrantMaskForUser ( username , orderedFolders ) ;
1461+ const groupMask = computeGroupGrantMaskForUser ( username , orderedFolders , sourceId ) ;
13901462 applyGroupLocksForUser ( username , grantsBox , groupMask , groupNamesForUser ) ;
13911463 }
13921464
@@ -1656,13 +1728,16 @@ async function saveUserGroupsFromUI() {
16561728 const label = ( labelEl && labelEl . value || '' ) . trim ( ) || name ;
16571729 const members = Array . from ( membersSel && membersSel . selectedOptions || [ ] ) . map ( o => o . value ) ;
16581730
1659- const existing = __groupsCache [ oldName ] || __groupsCache [ name ] || { grants : { } } ;
1731+ const existing = __groupsCache [ oldName ] || __groupsCache [ name ] || { grants : { } , grantsBySource : { } } ;
16601732 groups [ name ] = {
16611733 name,
16621734 label,
16631735 members,
16641736 grants : existing . grants || { }
16651737 } ;
1738+ if ( existing . grantsBySource && typeof existing . grantsBySource === 'object' && ! Array . isArray ( existing . grantsBySource ) ) {
1739+ groups [ name ] . grantsBySource = existing . grantsBySource ;
1740+ }
16661741 } ) ;
16671742
16681743 if ( status ) {
@@ -1710,7 +1785,7 @@ async function openGroupAclEditor(groupName) {
17101785 modal . id = 'groupAclModal' ;
17111786 modal . style . cssText = `
17121787 position:fixed; inset:0; background:${ overlayBg } ;
1713- display:flex; align-items:center; justify-content:center; z-index:3700 ;
1788+ display:flex; align-items:center; justify-content:center; z-index:3800 ;
17141789 ` ;
17151790 modal . innerHTML = `
17161791 <div class="modal-content"
@@ -1727,6 +1802,11 @@ async function openGroupAclEditor(groupName) {
17271802 Group grants are merged with each member’s own folder access. They never reduce access.
17281803 </div>
17291804
1805+ <div class="modal-source-row" id="groupAclSourceRow" style="display:none;">
1806+ <label for="groupAclSourceSelect">${ tf ( "storage_source" , "Source" ) } </label>
1807+ <select id="groupAclSourceSelect" class="form-control form-control-sm"></select>
1808+ </div>
1809+
17301810 <div id="groupAclBody" style="max-height:70vh; overflow-y:auto; margin-bottom:12px;"></div>
17311811
17321812 <div style="display:flex; justify-content:flex-end; gap:8px;">
@@ -1759,19 +1839,30 @@ async function openGroupAclEditor(groupName) {
17591839 modal . dataset . groupName = groupName ;
17601840 modal . style . display = 'flex' ;
17611841
1762- const sourceId = getFolderAccessSourceId ( ) ;
1763- const folders = await getAllFolders ( true , sourceId ) ;
1764- const grants = ( __groupsCache [ groupName ] && __groupsCache [ groupName ] . grants ) || { } ;
1842+ const renderGroupAcl = async ( ) => {
1843+ const bodyEl = document . getElementById ( 'groupAclBody' ) ;
1844+ if ( ! bodyEl ) return ;
1845+ bodyEl . textContent = `${ t ( 'loading' ) } …` ;
17651846
1766- if ( body ) {
1767- body . textContent = '' ;
1847+ const sourceId = getFolderAccessSourceId ( ) ;
1848+ const folders = await getAllFolders ( true , sourceId ) ;
1849+ const grants = pickGroupGrantsForSource ( __groupsCache [ groupName ] || { } , sourceId ) ;
1850+
1851+ bodyEl . textContent = '' ;
17681852 const box = document . createElement ( 'div' ) ;
17691853 box . className = 'folder-grants-box' ;
1770- body . appendChild ( box ) ;
1854+ bodyEl . appendChild ( box ) ;
17711855
17721856 renderFolderGrantsUI ( groupName , box , [ 'root' , ...folders . filter ( f => f !== 'root' ) ] , grants ) ;
17731857 box . __grantsFallback = grants ;
1774- }
1858+ } ;
1859+
1860+ await initFolderAccessSourceSelector ( {
1861+ rowId : 'groupAclSourceRow' ,
1862+ selectId : 'groupAclSourceSelect' ,
1863+ onChange : renderGroupAcl
1864+ } ) ;
1865+ await renderGroupAcl ( ) ;
17751866}
17761867
17771868function saveGroupAclFromUI ( ) {
@@ -1786,10 +1877,11 @@ function saveGroupAclFromUI() {
17861877 if ( ! box ) return ;
17871878
17881879 const grants = collectGrantsFrom ( box , box . __grantsFallback || { } ) ;
1880+ const sourceId = getFolderAccessSourceId ( ) ;
17891881 if ( ! __groupsCache [ groupName ] ) {
17901882 __groupsCache [ groupName ] = { name : groupName , label : groupName , members : [ ] , grants : { } } ;
17911883 }
1792- __groupsCache [ groupName ] . grants = grants ;
1884+ setGroupGrantsForSource ( __groupsCache [ groupName ] , sourceId , grants ) ;
17931885
17941886 showToast ( t ( 'admin_group_access_updated' ) ) ;
17951887 modal . style . display = 'none' ;
0 commit comments