feat: Visualize active iSCSI sessions

This commit is contained in:
2025-12-10 14:48:16 +00:00
parent 4f28dfbc11
commit 8c9d1cc8d4
8 changed files with 249 additions and 27 deletions

Binary file not shown.

View File

@@ -1,4 +1,4 @@
Adastra VTL Installer Adastra VTL Installer
Version: 1.0.0 Version: 1.0.0
Build Date: 2025-12-10 14:40:16 Build Date: 2025-12-10 14:48:16
Build Host: vtl-dev Build Host: vtl-dev

View File

@@ -177,7 +177,8 @@ switch ($action) {
function listTargets() { function listTargets() {
$output = []; $output = [];
$returnCode = 0; $returnCode = 0;
exec('sudo tgtadm --lld iscsi --mode target --op show 2>&1', $output, $returnCode); // Use absolute path
exec('sudo /usr/sbin/tgtadm --lld iscsi --mode target --op show 2>&1', $output, $returnCode);
if ($returnCode !== 0) { if ($returnCode !== 0) {
echo json_encode([ echo json_encode([
@@ -189,34 +190,86 @@ function listTargets() {
$targets = []; $targets = [];
$currentTarget = null; $currentTarget = null;
$inACLSection = false; $currentSession = null;
$section = ''; // 'acl', 'nexus', 'account', etc.
foreach ($output as $line) { foreach ($output as $line) {
// Check for new Target start
if (preg_match('/^Target (\d+): (.+)$/', $line, $matches)) { if (preg_match('/^Target (\d+): (.+)$/', $line, $matches)) {
// Save previous target and session
if ($currentTarget) { if ($currentTarget) {
if ($currentSession) {
$currentTarget['sessions'][] = $currentSession;
$currentSession = null;
}
$targets[] = $currentTarget; $targets[] = $currentTarget;
} }
$currentTarget = [ $currentTarget = [
'tid' => intval($matches[1]), 'tid' => intval($matches[1]),
'name' => trim($matches[2]), 'name' => trim($matches[2]),
'luns' => 0, 'luns' => 0,
'acls' => 0 'acls' => 0,
'sessions' => []
]; ];
$inACLSection = false; $section = '';
} elseif ($currentTarget && preg_match('/^\s+LUN: (\d+)/', $line)) { } elseif ($currentTarget) {
$currentTarget['luns']++; // Check for LUNs (also simple check for count)
$inACLSection = false; if (preg_match('/^\s+LUN: (\d+)/', $line)) {
} elseif ($currentTarget && preg_match('/^\s+ACL information:/', $line)) { $currentTarget['luns']++;
$inACLSection = true; // If we were parsing a session, save it as LUN info usually means we left nexus section or are in LUN section
} elseif ($currentTarget && $inACLSection && preg_match('/^\s+(.+)$/', $line, $matches)) { if ($currentSession) {
$acl = trim($matches[1]); $currentTarget['sessions'][] = $currentSession;
if (!empty($acl) && !preg_match('/^(Account|I_T nexus|LUN|System)/', $acl)) { $currentSession = null;
$currentTarget['acls']++; }
}
// Section Headers
elseif (preg_match('/^\s+ACL information:/', $line)) {
$section = 'acl';
if ($currentSession) { $currentTarget['sessions'][] = $currentSession; $currentSession = null; }
}
elseif (preg_match('/^\s+I_T nexus information:/', $line)) {
$section = 'nexus';
}
elseif (preg_match('/^\s+Account information:/', $line)) {
$section = 'account'; // Ignore or handle if needed
if ($currentSession) { $currentTarget['sessions'][] = $currentSession; $currentSession = null; }
}
// ACL Section Content
elseif ($section === 'acl' && preg_match('/^\s+(.+)$/', $line, $matches)) {
$content = trim($matches[1]);
// Filter out headers if regex matches them by accident (though section logic prevents this usually)
if (!empty($content) && !preg_match('/^(Account|I_T nexus|LUN|System)/', $content)) {
$currentTarget['acls']++;
}
}
// I_T Nexus Section Content (Sessions)
elseif ($section === 'nexus') {
if (preg_match('/^\s+I_T nexus: (\d+)/', $line, $matches)) {
// New session found
if ($currentSession) {
$currentTarget['sessions'][] = $currentSession;
}
$currentSession = [
'nexus_id' => $matches[1],
'initiator' => 'Unknown',
'ip' => 'Unknown'
];
} elseif ($currentSession && preg_match('/^\s+Initiator: (.+)$/', $line, $matches)) {
$currentSession['initiator'] = trim($matches[1]);
} elseif ($currentSession && preg_match('/^\s+IP Address: (.+)$/', $line, $matches)) {
$currentSession['ip'] = trim($matches[1]);
}
} }
} }
} }
// Save last items
if ($currentTarget) { if ($currentTarget) {
if ($currentSession) {
$currentTarget['sessions'][] = $currentSession;
}
$targets[] = $currentTarget; $targets[] = $currentTarget;
} }

View File

@@ -404,6 +404,32 @@
</div> </div>
</div> </div>
<div class="card" style="margin-top: 1rem;">
<div class="card-header">
<h3>👥 Active Client Sessions</h3>
<button class="btn btn-primary btn-small" onclick="loadTargets()">
<span>🔄</span> Refresh
</button>
</div>
<div class="card-body">
<table class="tape-table">
<thead>
<tr>
<th>Target</th>
<th>Initiator Name (Client)</th>
<th>IP Address</th>
</tr>
</thead>
<tbody id="iscsi-sessions-body">
<!-- JS populates this -->
</tbody>
</table>
<div id="no-sessions-msg" class="alert alert-info" style="display: none; margin-top: 0.5rem;">
No active sessions found.
</div>
</div>
</div>
<div class="card" style="margin-top: 1rem;"> <div class="card" style="margin-top: 1rem;">
<div class="card-header"> <div class="card-header">
<h3> Create New Target</h3> <h3> Create New Target</h3>

View File

@@ -1029,6 +1029,38 @@ function loadTargets() {
</td> </td>
</tr> </tr>
`).join(''); `).join('');
// Update Active Sessions Table
const sessionTbody = document.getElementById('iscsi-sessions-body');
const noSessionsMsg = document.getElementById('no-sessions-msg');
let hasSessions = false;
if (sessionTbody) {
let sessionsHtml = '';
data.targets.forEach(target => {
if (target.sessions && target.sessions.length > 0) {
target.sessions.forEach(session => {
hasSessions = true;
sessionsHtml += `
<tr>
<td>
<strong>TID ${target.tid}</strong><br>
<small style="font-size:0.75rem; color:#888;">${target.name}</small>
</td>
<td><code>${session.initiator}</code></td>
<td><span class="badge badge-success">${session.ip}</span></td>
</tr>
`;
});
}
});
sessionTbody.innerHTML = sessionsHtml;
if (noSessionsMsg) {
noSessionsMsg.style.display = hasSessions ? 'none' : 'block';
}
}
} }
} else { } else {
showNotification(data.error, 'error'); showNotification(data.error, 'error');

View File

@@ -177,7 +177,8 @@ switch ($action) {
function listTargets() { function listTargets() {
$output = []; $output = [];
$returnCode = 0; $returnCode = 0;
exec('sudo tgtadm --lld iscsi --mode target --op show 2>&1', $output, $returnCode); // Use absolute path
exec('sudo /usr/sbin/tgtadm --lld iscsi --mode target --op show 2>&1', $output, $returnCode);
if ($returnCode !== 0) { if ($returnCode !== 0) {
echo json_encode([ echo json_encode([
@@ -189,34 +190,86 @@ function listTargets() {
$targets = []; $targets = [];
$currentTarget = null; $currentTarget = null;
$inACLSection = false; $currentSession = null;
$section = ''; // 'acl', 'nexus', 'account', etc.
foreach ($output as $line) { foreach ($output as $line) {
// Check for new Target start
if (preg_match('/^Target (\d+): (.+)$/', $line, $matches)) { if (preg_match('/^Target (\d+): (.+)$/', $line, $matches)) {
// Save previous target and session
if ($currentTarget) { if ($currentTarget) {
if ($currentSession) {
$currentTarget['sessions'][] = $currentSession;
$currentSession = null;
}
$targets[] = $currentTarget; $targets[] = $currentTarget;
} }
$currentTarget = [ $currentTarget = [
'tid' => intval($matches[1]), 'tid' => intval($matches[1]),
'name' => trim($matches[2]), 'name' => trim($matches[2]),
'luns' => 0, 'luns' => 0,
'acls' => 0 'acls' => 0,
'sessions' => []
]; ];
$inACLSection = false; $section = '';
} elseif ($currentTarget && preg_match('/^\s+LUN: (\d+)/', $line)) { } elseif ($currentTarget) {
$currentTarget['luns']++; // Check for LUNs (also simple check for count)
$inACLSection = false; if (preg_match('/^\s+LUN: (\d+)/', $line)) {
} elseif ($currentTarget && preg_match('/^\s+ACL information:/', $line)) { $currentTarget['luns']++;
$inACLSection = true; // If we were parsing a session, save it as LUN info usually means we left nexus section or are in LUN section
} elseif ($currentTarget && $inACLSection && preg_match('/^\s+(.+)$/', $line, $matches)) { if ($currentSession) {
$acl = trim($matches[1]); $currentTarget['sessions'][] = $currentSession;
if (!empty($acl) && !preg_match('/^(Account|I_T nexus|LUN|System)/', $acl)) { $currentSession = null;
$currentTarget['acls']++; }
}
// Section Headers
elseif (preg_match('/^\s+ACL information:/', $line)) {
$section = 'acl';
if ($currentSession) { $currentTarget['sessions'][] = $currentSession; $currentSession = null; }
}
elseif (preg_match('/^\s+I_T nexus information:/', $line)) {
$section = 'nexus';
}
elseif (preg_match('/^\s+Account information:/', $line)) {
$section = 'account'; // Ignore or handle if needed
if ($currentSession) { $currentTarget['sessions'][] = $currentSession; $currentSession = null; }
}
// ACL Section Content
elseif ($section === 'acl' && preg_match('/^\s+(.+)$/', $line, $matches)) {
$content = trim($matches[1]);
// Filter out headers if regex matches them by accident (though section logic prevents this usually)
if (!empty($content) && !preg_match('/^(Account|I_T nexus|LUN|System)/', $content)) {
$currentTarget['acls']++;
}
}
// I_T Nexus Section Content (Sessions)
elseif ($section === 'nexus') {
if (preg_match('/^\s+I_T nexus: (\d+)/', $line, $matches)) {
// New session found
if ($currentSession) {
$currentTarget['sessions'][] = $currentSession;
}
$currentSession = [
'nexus_id' => $matches[1],
'initiator' => 'Unknown',
'ip' => 'Unknown'
];
} elseif ($currentSession && preg_match('/^\s+Initiator: (.+)$/', $line, $matches)) {
$currentSession['initiator'] = trim($matches[1]);
} elseif ($currentSession && preg_match('/^\s+IP Address: (.+)$/', $line, $matches)) {
$currentSession['ip'] = trim($matches[1]);
}
} }
} }
} }
// Save last items
if ($currentTarget) { if ($currentTarget) {
if ($currentSession) {
$currentTarget['sessions'][] = $currentSession;
}
$targets[] = $currentTarget; $targets[] = $currentTarget;
} }

View File

@@ -404,6 +404,32 @@
</div> </div>
</div> </div>
<div class="card" style="margin-top: 1rem;">
<div class="card-header">
<h3>👥 Active Client Sessions</h3>
<button class="btn btn-primary btn-small" onclick="loadTargets()">
<span>🔄</span> Refresh
</button>
</div>
<div class="card-body">
<table class="tape-table">
<thead>
<tr>
<th>Target</th>
<th>Initiator Name (Client)</th>
<th>IP Address</th>
</tr>
</thead>
<tbody id="iscsi-sessions-body">
<!-- JS populates this -->
</tbody>
</table>
<div id="no-sessions-msg" class="alert alert-info" style="display: none; margin-top: 0.5rem;">
No active sessions found.
</div>
</div>
</div>
<div class="card" style="margin-top: 1rem;"> <div class="card" style="margin-top: 1rem;">
<div class="card-header"> <div class="card-header">
<h3> Create New Target</h3> <h3> Create New Target</h3>

View File

@@ -1029,6 +1029,38 @@ function loadTargets() {
</td> </td>
</tr> </tr>
`).join(''); `).join('');
// Update Active Sessions Table
const sessionTbody = document.getElementById('iscsi-sessions-body');
const noSessionsMsg = document.getElementById('no-sessions-msg');
let hasSessions = false;
if (sessionTbody) {
let sessionsHtml = '';
data.targets.forEach(target => {
if (target.sessions && target.sessions.length > 0) {
target.sessions.forEach(session => {
hasSessions = true;
sessionsHtml += `
<tr>
<td>
<strong>TID ${target.tid}</strong><br>
<small style="font-size:0.75rem; color:#888;">${target.name}</small>
</td>
<td><code>${session.initiator}</code></td>
<td><span class="badge badge-success">${session.ip}</span></td>
</tr>
`;
});
}
});
sessionTbody.innerHTML = sessionsHtml;
if (noSessionsMsg) {
noSessionsMsg.style.display = hasSessions ? 'none' : 'block';
}
}
} }
} else { } else {
showNotification(data.error, 'error'); showNotification(data.error, 'error');