@@ -1109,6 +1109,274 @@ const hideVmRuntimeLoadingRow = () => {
11091109 $ ( 'tbody#kvm_list tr.fv-runtime-loading-row' ) . remove ( ) ;
11101110} ;
11111111
1112+ const VM_NATIVE_DETAIL_ROW_SELECTOR = 'tr[id^="name-"]:not([child-id])' ;
1113+ const VM_NATIVE_TOGGLE_SELECTOR = 'a[onclick*="toggle_id("]' ;
1114+ const VM_NATIVE_DETAIL_REQUEST_WINDOW_MS = 1600 ;
1115+ let vmNativeDetailRowObserver = null ;
1116+ let vmNativeDetailRowObserverHost = null ;
1117+ let vmNativeToggleClickHost = null ;
1118+ let vmNativeDetailAdoptionSuspendDepth = 0 ;
1119+ let vmZebraRefreshTimer = null ;
1120+ let vmLastNativeDetailRequest = {
1121+ detailId : '' ,
1122+ row : null ,
1123+ requestedAt : 0
1124+ } ;
1125+
1126+ const applyVmZebra = ( ) => {
1127+ let visibleIndex = 0 ;
1128+ $ ( '#kvm_table tbody tr' ) . each ( function applyVmZebraRow ( ) {
1129+ const $row = $ ( this ) ;
1130+ if ( ! $row . is ( ':visible' ) ) {
1131+ return ;
1132+ }
1133+ if ( $row . hasClass ( 'fv-runtime-loading-row' ) ) {
1134+ this . style . backgroundColor = '' ;
1135+ return ;
1136+ }
1137+ this . style . backgroundColor = ( visibleIndex % 2 === 1 )
1138+ ? 'var(--fvplus-vm-row-alt-bg, var(--dynamix-tablesorter-tbody-row-alt-bg-color, transparent))'
1139+ : 'var(--fvplus-vm-row-bg, transparent)' ;
1140+ visibleIndex += 1 ;
1141+ } ) ;
1142+ } ;
1143+
1144+ const scheduleVmZebraRefresh = ( delayMs = 32 ) => {
1145+ if ( vmZebraRefreshTimer !== null ) {
1146+ window . clearTimeout ( vmZebraRefreshTimer ) ;
1147+ }
1148+ vmZebraRefreshTimer = window . setTimeout ( ( ) => {
1149+ vmZebraRefreshTimer = null ;
1150+ applyVmZebra ( ) ;
1151+ } , Math . max ( 0 , Number ( delayMs ) || 0 ) ) ;
1152+ } ;
1153+
1154+ const isVmNativeDetailRow = ( row ) => (
1155+ row instanceof HTMLTableRowElement
1156+ && String ( row . id || '' ) . startsWith ( 'name-' )
1157+ && ! row . hasAttribute ( 'child-id' )
1158+ ) ;
1159+
1160+ const getVmNativeDetailRowId = ( row ) => (
1161+ isVmNativeDetailRow ( row )
1162+ ? String ( row . id || '' ) . trim ( )
1163+ : ''
1164+ ) ;
1165+
1166+ const extractVmNativeToggleDetailId = ( value ) => {
1167+ const match = String ( value || '' ) . match ( / t o g g l e _ i d \( ( [ ' " ] ) ( n a m e - [ ^ ' " ) ] + ) \1\) / ) ;
1168+ return match ? String ( match [ 2 ] || '' ) . trim ( ) : '' ;
1169+ } ;
1170+
1171+ const clearVmFolderElementOwnership = ( row ) => {
1172+ if ( ! ( row instanceof Element ) ) {
1173+ return ;
1174+ }
1175+ Array . from ( row . classList )
1176+ . filter ( ( token ) => / ^ f o l d e r - .+ - e l e m e n t $ / . test ( token ) )
1177+ . forEach ( ( token ) => row . classList . remove ( token ) ) ;
1178+ row . classList . remove ( 'folder-element' ) ;
1179+ } ;
1180+
1181+ const applyVmFolderElementOwnership = ( row , folderId ) => {
1182+ if ( ! ( row instanceof Element ) ) {
1183+ return ;
1184+ }
1185+ clearVmFolderElementOwnership ( row ) ;
1186+ const safeFolderId = String ( folderId || '' ) . trim ( ) ;
1187+ if ( ! safeFolderId ) {
1188+ return ;
1189+ }
1190+ row . classList . add ( `folder-${ safeFolderId } -element` , 'folder-element' ) ;
1191+ } ;
1192+
1193+ const findVmFolderOwnerIdForRow = ( row ) => {
1194+ if ( ! ( row instanceof Element ) ) {
1195+ return '' ;
1196+ }
1197+ const token = Array . from ( row . classList ) . find ( ( value ) => / ^ f o l d e r - .+ - e l e m e n t $ / . test ( value ) ) ;
1198+ if ( ! token ) {
1199+ return '' ;
1200+ }
1201+ const match = token . match ( / ^ f o l d e r - ( .+ ) - e l e m e n t $ / ) ;
1202+ return match ? String ( match [ 1 ] || '' ) . trim ( ) : '' ;
1203+ } ;
1204+
1205+ const isVmFolderExpanded = ( folderId ) => (
1206+ $ ( `.dropDown-${ String ( folderId || '' ) . trim ( ) } ` ) . attr ( 'active' ) === 'true'
1207+ ) ;
1208+
1209+ const findVmNativeToggleAnchorForDetailId = ( detailId ) => {
1210+ const targetDetailId = String ( detailId || '' ) . trim ( ) ;
1211+ if ( ! targetDetailId ) {
1212+ return null ;
1213+ }
1214+ const anchors = Array . from ( document . querySelectorAll ( VM_NATIVE_TOGGLE_SELECTOR ) ) ;
1215+ return anchors . find ( ( anchor ) => extractVmNativeToggleDetailId ( anchor . getAttribute ( 'onclick' ) ) === targetDetailId ) || null ;
1216+ } ;
1217+
1218+ const findVmRuntimeRowForDetailId = ( detailId ) => {
1219+ const targetDetailId = String ( detailId || '' ) . trim ( ) ;
1220+ if ( ! targetDetailId ) {
1221+ return null ;
1222+ }
1223+ const directAnchor = findVmNativeToggleAnchorForDetailId ( targetDetailId ) ;
1224+ const directRow = directAnchor instanceof Element ? directAnchor . closest ( 'tr' ) : null ;
1225+ if ( directRow instanceof HTMLTableRowElement ) {
1226+ return directRow ;
1227+ }
1228+ const requestedAt = Number ( vmLastNativeDetailRequest . requestedAt || 0 ) ;
1229+ const requestedRecently = requestedAt > 0 && ( Date . now ( ) - requestedAt ) <= VM_NATIVE_DETAIL_REQUEST_WINDOW_MS ;
1230+ if (
1231+ requestedRecently
1232+ && vmLastNativeDetailRequest . detailId === targetDetailId
1233+ && vmLastNativeDetailRequest . row instanceof HTMLTableRowElement
1234+ && vmLastNativeDetailRequest . row . isConnected
1235+ ) {
1236+ return vmLastNativeDetailRequest . row ;
1237+ }
1238+ return null ;
1239+ } ;
1240+
1241+ const collectExistingVmDetailRowsForVmRow = ( vmRow ) => {
1242+ if ( ! ( vmRow instanceof HTMLTableRowElement ) ) {
1243+ return [ ] ;
1244+ }
1245+ const detailId = extractVmNativeToggleDetailId ( vmRow . querySelector ( VM_NATIVE_TOGGLE_SELECTOR ) ?. getAttribute ( 'onclick' ) ) ;
1246+ if ( ! detailId ) {
1247+ return [ ] ;
1248+ }
1249+ const detailRows = [ ] ;
1250+ let sibling = vmRow . nextElementSibling ;
1251+ while ( sibling instanceof HTMLTableRowElement && isVmNativeDetailRow ( sibling ) ) {
1252+ const siblingDetailId = getVmNativeDetailRowId ( sibling ) ;
1253+ if ( siblingDetailId !== detailId ) {
1254+ break ;
1255+ }
1256+ detailRows . push ( sibling ) ;
1257+ sibling = sibling . nextElementSibling ;
1258+ }
1259+ return detailRows ;
1260+ } ;
1261+
1262+ const withVmNativeDetailAdoptionSuspended = ( callback ) => {
1263+ vmNativeDetailAdoptionSuspendDepth += 1 ;
1264+ try {
1265+ return callback ( ) ;
1266+ } finally {
1267+ vmNativeDetailAdoptionSuspendDepth = Math . max ( 0 , vmNativeDetailAdoptionSuspendDepth - 1 ) ;
1268+ }
1269+ } ;
1270+
1271+ const placeVmNativeDetailRowForOwner = ( detailRow , vmRow ) => {
1272+ if ( ! isVmNativeDetailRow ( detailRow ) || ! ( vmRow instanceof HTMLTableRowElement ) ) {
1273+ return false ;
1274+ }
1275+ const folderId = findVmFolderOwnerIdForRow ( vmRow ) ;
1276+ return withVmNativeDetailAdoptionSuspended ( ( ) => {
1277+ if ( folderId ) {
1278+ applyVmFolderElementOwnership ( detailRow , folderId ) ;
1279+ detailRow . dataset . fvplusVmDetailAdopted = '1' ;
1280+ if ( isVmFolderExpanded ( folderId ) ) {
1281+ vmRow . after ( detailRow ) ;
1282+ } else {
1283+ const storage = document . querySelector ( `tr.folder-id-${ folderId } .folder-storage` ) ;
1284+ if ( storage instanceof Element ) {
1285+ storage . appendChild ( detailRow ) ;
1286+ } else {
1287+ vmRow . after ( detailRow ) ;
1288+ }
1289+ }
1290+ return true ;
1291+ }
1292+ clearVmFolderElementOwnership ( detailRow ) ;
1293+ detailRow . dataset . fvplusVmDetailAdopted = '1' ;
1294+ vmRow . after ( detailRow ) ;
1295+ return true ;
1296+ } ) ;
1297+ } ;
1298+
1299+ const adoptVmNativeDetailRows = ( rows = [ ] ) => {
1300+ let adoptedCount = 0 ;
1301+ rows . forEach ( ( row ) => {
1302+ if ( ! isVmNativeDetailRow ( row ) ) {
1303+ return ;
1304+ }
1305+ const detailId = getVmNativeDetailRowId ( row ) ;
1306+ const vmRow = findVmRuntimeRowForDetailId ( detailId ) ;
1307+ if ( ! ( vmRow instanceof HTMLTableRowElement ) ) {
1308+ return ;
1309+ }
1310+ if ( placeVmNativeDetailRowForOwner ( row , vmRow ) ) {
1311+ adoptedCount += 1 ;
1312+ }
1313+ } ) ;
1314+ if ( adoptedCount > 0 ) {
1315+ scheduleVmZebraRefresh ( ) ;
1316+ }
1317+ } ;
1318+
1319+ const ensureVmNativeDetailRowObserver = ( ) => {
1320+ const tbody = document . querySelector ( 'tbody#kvm_list' ) ;
1321+ if ( ! ( tbody instanceof HTMLTableSectionElement ) ) {
1322+ return ;
1323+ }
1324+ if ( vmNativeDetailRowObserver && vmNativeDetailRowObserverHost === tbody ) {
1325+ return ;
1326+ }
1327+ if ( vmNativeDetailRowObserver ) {
1328+ vmNativeDetailRowObserver . disconnect ( ) ;
1329+ vmNativeDetailRowObserver = null ;
1330+ vmNativeDetailRowObserverHost = null ;
1331+ }
1332+ vmNativeDetailRowObserverHost = tbody ;
1333+ vmNativeDetailRowObserver = new MutationObserver ( ( mutations ) => {
1334+ if ( vmNativeDetailAdoptionSuspendDepth > 0 ) {
1335+ return ;
1336+ }
1337+ const detailRows = [ ] ;
1338+ mutations . forEach ( ( mutation ) => {
1339+ mutation . addedNodes . forEach ( ( node ) => {
1340+ if ( isVmNativeDetailRow ( node ) ) {
1341+ detailRows . push ( node ) ;
1342+ }
1343+ } ) ;
1344+ } ) ;
1345+ if ( detailRows . length > 0 ) {
1346+ adoptVmNativeDetailRows ( detailRows ) ;
1347+ }
1348+ } ) ;
1349+ vmNativeDetailRowObserver . observe ( tbody , { childList : true } ) ;
1350+ } ;
1351+
1352+ const ensureVmNativeDetailInteractionHooks = ( ) => {
1353+ const table = document . getElementById ( 'kvm_table' ) ;
1354+ if ( ! ( table instanceof HTMLTableElement ) || vmNativeToggleClickHost === table ) {
1355+ return ;
1356+ }
1357+ if ( vmNativeToggleClickHost instanceof HTMLTableElement ) {
1358+ vmNativeToggleClickHost . removeEventListener ( 'click' , handleVmNativeToggleClick , true ) ;
1359+ }
1360+ vmNativeToggleClickHost = table ;
1361+ table . addEventListener ( 'click' , handleVmNativeToggleClick , true ) ;
1362+ } ;
1363+
1364+ function handleVmNativeToggleClick ( event ) {
1365+ const target = event . target instanceof Element ? event . target : null ;
1366+ const anchor = target ? target . closest ( VM_NATIVE_TOGGLE_SELECTOR ) : null ;
1367+ if ( ! ( anchor instanceof Element ) ) {
1368+ return ;
1369+ }
1370+ const detailId = extractVmNativeToggleDetailId ( anchor . getAttribute ( 'onclick' ) ) ;
1371+ const vmRow = anchor . closest ( 'tr' ) ;
1372+ vmLastNativeDetailRequest = {
1373+ detailId,
1374+ row : vmRow instanceof HTMLTableRowElement ? vmRow : null ,
1375+ requestedAt : Date . now ( )
1376+ } ;
1377+ scheduleVmZebraRefresh ( 420 ) ;
1378+ }
1379+
11121380let createFoldersInFlight = false ;
11131381let createFoldersQueued = false ;
11141382
@@ -1283,12 +1551,16 @@ const createFolders = async () => {
12831551
12841552 // Assing the folder done to the global object
12851553 globalFolders = foldersDone ;
1554+ ensureVmNativeDetailInteractionHooks ( ) ;
1555+ ensureVmNativeDetailRowObserver ( ) ;
1556+ adoptVmNativeDetailRows ( Array . from ( document . querySelectorAll ( `tbody#kvm_list > ${ VM_NATIVE_DETAIL_ROW_SELECTOR } ` ) ) ) ;
12861557 refreshVmFolderQuickActionStates ( ) ;
12871558 applyVmFocusedFolderState ( ) ;
12881559 syncVmRuntimeExpandedStore ( ) ;
12891560 persistVmExpandedStateFromGlobal ( ) ;
12901561 renderRuntimeHealthBadge ( globalFolders , folderTypePrefs ) ;
12911562 scheduleVmRuntimeWidthReflow ( 'create-folders' , 0 ) ;
1563+ applyVmZebra ( ) ;
12921564
12931565 folderDebugMode = false ;
12941566 markVmFatalBannerStep ( 'VM folders rendered' ) ;
@@ -1565,7 +1837,19 @@ const createFolder = (folder, id, position, order, vmInfo, foldersDone, matchCac
15651837 let $vmTR = $ ( '#kvm_list > tr.sortable' ) . filter ( function ( ) {
15661838 return $ ( this ) . find ( 'td.vm-name span.outer span.inner a' ) . first ( ) . text ( ) . trim ( ) === container ;
15671839 } ) . first ( ) ;
1568- $ ( `tr.folder-id-${ id } div.folder-storage` ) . append ( $vmTR . addClass ( `folder-${ id } -element` ) . addClass ( `folder-element` ) . removeClass ( 'sortable' ) ) ;
1840+ const vmRowNode = $vmTR . get ( 0 ) ;
1841+ const detailRows = collectExistingVmDetailRowsForVmRow ( vmRowNode ) ;
1842+ const storage = $ ( `tr.folder-id-${ id } div.folder-storage` ) . get ( 0 ) ;
1843+ if ( vmRowNode && storage instanceof Element ) {
1844+ applyVmFolderElementOwnership ( vmRowNode , id ) ;
1845+ vmRowNode . classList . remove ( 'sortable' ) ;
1846+ storage . appendChild ( vmRowNode ) ;
1847+ detailRows . forEach ( ( detailRow ) => {
1848+ applyVmFolderElementOwnership ( detailRow , id ) ;
1849+ detailRow . dataset . fvplusVmDetailAdopted = '1' ;
1850+ storage . appendChild ( detailRow ) ;
1851+ } ) ;
1852+ }
15691853
15701854 if ( folderDebugMode ) {
15711855 vmDebugLog ( `${ newFolder [ container ] . id } (${ offsetIndex } , ${ index } ) => ${ id } ` ) ;
@@ -1770,6 +2054,7 @@ const dropDownButton = (id, persistState = true) => {
17702054 persistVmExpandedStateFromGlobal ( ) ;
17712055 }
17722056 scheduleVmRuntimeWidthReflow ( 'folder-expand-toggle' , 32 ) ;
2057+ scheduleVmZebraRefresh ( ) ;
17732058 folderEvents . dispatchEvent ( new CustomEvent ( 'vm-post-folder-expansion' , { detail : { id } } ) ) ;
17742059} ;
17752060
0 commit comments