@@ -685,6 +685,56 @@ const readFolderOwnerFromRow = (row) => {
685685 }
686686 return '' ;
687687} ;
688+ const isVmFolderMemberPrimaryRow = ( row ) => ! ! ( row && row . nodeType === 1 && row . matches ( 'tr.fv-vm-member-row' ) ) ;
689+ const isVmFolderBoundaryRow = ( row ) => ! ! ( row && row . nodeType === 1 && ( row . matches ( 'tr.folder' ) || isVmFolderMemberPrimaryRow ( row ) ) ) ;
690+ const collectVmMemberRowGroup = ( row ) => {
691+ if ( ! isVmFolderMemberPrimaryRow ( row ) ) {
692+ return [ ] ;
693+ }
694+ const group = [ row ] ;
695+ let cursor = row . nextElementSibling ;
696+ while ( cursor && ! isVmFolderBoundaryRow ( cursor ) ) {
697+ group . push ( cursor ) ;
698+ cursor = cursor . nextElementSibling ;
699+ }
700+ return group ;
701+ } ;
702+ const applyVmMemberRowGroupOwnership = ( row , folderId ) => {
703+ const id = String ( folderId || '' ) . trim ( ) ;
704+ if ( ! id || ! isVmFolderMemberPrimaryRow ( row ) ) {
705+ return [ ] ;
706+ }
707+ const ownerClass = `folder-${ id } -element` ;
708+ const group = collectVmMemberRowGroup ( row ) ;
709+ group . forEach ( ( entry , index ) => {
710+ if ( ! entry || entry . nodeType !== 1 ) {
711+ return ;
712+ }
713+ entry . classList . add ( ownerClass ) ;
714+ if ( index > 0 ) {
715+ entry . classList . add ( 'fv-vm-member-detail-row' ) ;
716+ }
717+ } ) ;
718+ return group ;
719+ } ;
720+ const placeVmManagedDetailRowsAfterOwner = ( ownerRow , detailRows , folderId ) => {
721+ if ( ! isVmFolderMemberPrimaryRow ( ownerRow ) ) {
722+ return ;
723+ }
724+ const id = String ( folderId || readFolderOwnerFromRow ( ownerRow ) || '' ) . trim ( ) ;
725+ if ( ! id ) {
726+ return ;
727+ }
728+ let insertAfter = collectVmMemberRowGroup ( ownerRow ) . slice ( - 1 ) [ 0 ] || ownerRow ;
729+ for ( const detailRow of detailRows ) {
730+ if ( ! detailRow || detailRow . nodeType !== 1 || isVmFolderBoundaryRow ( detailRow ) ) {
731+ continue ;
732+ }
733+ detailRow . classList . add ( `folder-${ id } -element` , 'fv-vm-member-detail-row' ) ;
734+ $ ( insertAfter ) . after ( detailRow ) ;
735+ insertAfter = detailRow ;
736+ }
737+ } ;
688738const getFocusedFolderVisibleSet = ( folderId ) => {
689739 const id = String ( folderId || '' ) . trim ( ) ;
690740 if ( ! id || ! globalFolders [ id ] ) {
@@ -1101,6 +1151,73 @@ const hideVmRuntimeLoadingRow = () => {
11011151 $ ( 'tbody#kvm_list tr.fv-runtime-loading-row' ) . remove ( ) ;
11021152} ;
11031153
1154+ const rememberPendingVmDetailOwner = ( row ) => {
1155+ if ( ! isVmFolderMemberPrimaryRow ( row ) ) {
1156+ vmPendingExpandedDetailOwner = null ;
1157+ return ;
1158+ }
1159+ vmPendingExpandedDetailOwner = {
1160+ row,
1161+ folderId : readFolderOwnerFromRow ( row ) ,
1162+ expiresAt : Date . now ( ) + 1500
1163+ } ;
1164+ } ;
1165+
1166+ const readPendingVmDetailOwner = ( ) => {
1167+ if ( ! vmPendingExpandedDetailOwner ) {
1168+ return null ;
1169+ }
1170+ if ( vmPendingExpandedDetailOwner . expiresAt < Date . now ( ) ) {
1171+ vmPendingExpandedDetailOwner = null ;
1172+ return null ;
1173+ }
1174+ const row = vmPendingExpandedDetailOwner . row ;
1175+ if ( ! isVmFolderMemberPrimaryRow ( row ) || ! document . body . contains ( row ) ) {
1176+ vmPendingExpandedDetailOwner = null ;
1177+ return null ;
1178+ }
1179+ return vmPendingExpandedDetailOwner ;
1180+ } ;
1181+
1182+ const ensureVmFolderDetailObserver = ( ) => {
1183+ if ( vmFolderDetailObserver || typeof MutationObserver !== 'function' ) {
1184+ return ;
1185+ }
1186+ const tbody = document . querySelector ( 'tbody#kvm_list' ) ;
1187+ if ( ! tbody ) {
1188+ return ;
1189+ }
1190+ $ ( tbody )
1191+ . off ( 'click.fvVmMemberDetailOwner' )
1192+ . on ( 'click.fvVmMemberDetailOwner' , 'tr.fv-vm-member-row td.vm-name, tr.fv-vm-member-row td.vm-name a, tr.fv-vm-member-row td.vm-name button, tr.fv-vm-member-row td.vm-name span' , function ( ) {
1193+ rememberPendingVmDetailOwner ( this . closest ( 'tr' ) ) ;
1194+ } ) ;
1195+ vmFolderDetailObserver = new MutationObserver ( ( mutations ) => {
1196+ const pendingOwner = readPendingVmDetailOwner ( ) ;
1197+ if ( ! pendingOwner || ! pendingOwner . folderId ) {
1198+ return ;
1199+ }
1200+ const addedRows = [ ] ;
1201+ for ( const mutation of mutations ) {
1202+ for ( const node of mutation . addedNodes || [ ] ) {
1203+ if ( ! node || node . nodeType !== 1 || node . tagName !== 'TR' ) {
1204+ continue ;
1205+ }
1206+ if ( isVmFolderBoundaryRow ( node ) || readFolderOwnerFromRow ( node ) ) {
1207+ continue ;
1208+ }
1209+ addedRows . push ( node ) ;
1210+ }
1211+ }
1212+ if ( ! addedRows . length ) {
1213+ return ;
1214+ }
1215+ placeVmManagedDetailRowsAfterOwner ( pendingOwner . row , addedRows , pendingOwner . folderId ) ;
1216+ vmPendingExpandedDetailOwner = null ;
1217+ } ) ;
1218+ vmFolderDetailObserver . observe ( tbody , { childList : true } ) ;
1219+ } ;
1220+
11041221let createFoldersInFlight = false ;
11051222let createFoldersQueued = false ;
11061223
@@ -1112,6 +1229,7 @@ const createFolders = async () => {
11121229 showVmRuntimeLoadingRow ( ) ;
11131230 setVmFatalBannerPhase ( 'bootstrap-data' ) ;
11141231 try {
1232+ ensureVmFolderDetailObserver ( ) ;
11151233 ensureVmExpandedStateLifecycleHooks ( ) ;
11161234 markVmFatalBannerStep ( 'VM runtime lifecycle hooks ready' ) ;
11171235 persistVmExpandedStateFromDom ( ) ;
@@ -1557,7 +1675,9 @@ const createFolder = (folder, id, position, order, vmInfo, foldersDone, matchCac
15571675 let $vmTR = $ ( '#kvm_list > tr.sortable' ) . filter ( function ( ) {
15581676 return $ ( this ) . find ( 'td.vm-name span.outer span.inner a' ) . first ( ) . text ( ) . trim ( ) === container ;
15591677 } ) . first ( ) ;
1560- $ ( `tr.folder-id-${ id } div.folder-storage` ) . append ( $vmTR . addClass ( `folder-${ id } -element` ) . addClass ( `folder-element` ) . removeClass ( 'sortable' ) ) ;
1678+ $vmTR . addClass ( 'fv-vm-member-row' ) . addClass ( `folder-${ id } -element` ) . addClass ( 'folder-element' ) . removeClass ( 'sortable' ) ;
1679+ const vmRowGroup = applyVmMemberRowGroupOwnership ( $vmTR . get ( 0 ) , id ) ;
1680+ $ ( `tr.folder-id-${ id } div.folder-storage` ) . append ( vmRowGroup . length ? $ ( vmRowGroup ) : $vmTR ) ;
15611681
15621682 if ( folderDebugMode ) {
15631683 vmDebugLog ( `${ newFolder [ container ] . id } (${ offsetIndex } , ${ index } ) => ${ id } ` ) ;
@@ -2689,6 +2809,8 @@ let folderDebugMode = false;
26892809let folderDebugModeWindow = [ ] ;
26902810let folderReq = [ ] ;
26912811let folderTypePrefs = utils . normalizePrefs ( { } ) ;
2812+ let vmPendingExpandedDetailOwner = null ;
2813+ let vmFolderDetailObserver = null ;
26922814let liveRefreshTimer = null ;
26932815let liveRefreshMs = 0 ;
26942816let liveRefreshInFlight = false ;
0 commit comments