feat: Visualize active iSCSI sessions
This commit is contained in:
BIN
dist/adastra-vtl-installer-1.0.0.tar.gz
vendored
BIN
dist/adastra-vtl-installer-1.0.0.tar.gz
vendored
Binary file not shown.
2
dist/adastra-vtl-installer/VERSION
vendored
2
dist/adastra-vtl-installer/VERSION
vendored
@@ -1,4 +1,4 @@
|
||||
Adastra VTL Installer
|
||||
Version: 1.0.0
|
||||
Build Date: 2025-12-10 14:40:16
|
||||
Build Date: 2025-12-10 14:48:16
|
||||
Build Host: vtl-dev
|
||||
|
||||
75
dist/adastra-vtl-installer/web-ui/api.php
vendored
75
dist/adastra-vtl-installer/web-ui/api.php
vendored
@@ -177,7 +177,8 @@ switch ($action) {
|
||||
function listTargets() {
|
||||
$output = [];
|
||||
$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) {
|
||||
echo json_encode([
|
||||
@@ -189,34 +190,86 @@ function listTargets() {
|
||||
|
||||
$targets = [];
|
||||
$currentTarget = null;
|
||||
$inACLSection = false;
|
||||
$currentSession = null;
|
||||
$section = ''; // 'acl', 'nexus', 'account', etc.
|
||||
|
||||
foreach ($output as $line) {
|
||||
// Check for new Target start
|
||||
if (preg_match('/^Target (\d+): (.+)$/', $line, $matches)) {
|
||||
// Save previous target and session
|
||||
if ($currentTarget) {
|
||||
if ($currentSession) {
|
||||
$currentTarget['sessions'][] = $currentSession;
|
||||
$currentSession = null;
|
||||
}
|
||||
$targets[] = $currentTarget;
|
||||
}
|
||||
$currentTarget = [
|
||||
'tid' => intval($matches[1]),
|
||||
'name' => trim($matches[2]),
|
||||
'luns' => 0,
|
||||
'acls' => 0
|
||||
'acls' => 0,
|
||||
'sessions' => []
|
||||
];
|
||||
$inACLSection = false;
|
||||
} elseif ($currentTarget && preg_match('/^\s+LUN: (\d+)/', $line)) {
|
||||
$section = '';
|
||||
} elseif ($currentTarget) {
|
||||
// Check for LUNs (also simple check for count)
|
||||
if (preg_match('/^\s+LUN: (\d+)/', $line)) {
|
||||
$currentTarget['luns']++;
|
||||
$inACLSection = false;
|
||||
} elseif ($currentTarget && preg_match('/^\s+ACL information:/', $line)) {
|
||||
$inACLSection = true;
|
||||
} elseif ($currentTarget && $inACLSection && preg_match('/^\s+(.+)$/', $line, $matches)) {
|
||||
$acl = trim($matches[1]);
|
||||
if (!empty($acl) && !preg_match('/^(Account|I_T nexus|LUN|System)/', $acl)) {
|
||||
// If we were parsing a session, save it as LUN info usually means we left nexus section or are in LUN section
|
||||
if ($currentSession) {
|
||||
$currentTarget['sessions'][] = $currentSession;
|
||||
$currentSession = null;
|
||||
}
|
||||
}
|
||||
// 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 ($currentSession) {
|
||||
$currentTarget['sessions'][] = $currentSession;
|
||||
}
|
||||
$targets[] = $currentTarget;
|
||||
}
|
||||
|
||||
|
||||
26
dist/adastra-vtl-installer/web-ui/index.html
vendored
26
dist/adastra-vtl-installer/web-ui/index.html
vendored
@@ -404,6 +404,32 @@
|
||||
</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-header">
|
||||
<h3>➕ Create New Target</h3>
|
||||
|
||||
32
dist/adastra-vtl-installer/web-ui/script.js
vendored
32
dist/adastra-vtl-installer/web-ui/script.js
vendored
@@ -1029,6 +1029,38 @@ function loadTargets() {
|
||||
</td>
|
||||
</tr>
|
||||
`).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 {
|
||||
showNotification(data.error, 'error');
|
||||
|
||||
@@ -177,7 +177,8 @@ switch ($action) {
|
||||
function listTargets() {
|
||||
$output = [];
|
||||
$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) {
|
||||
echo json_encode([
|
||||
@@ -189,34 +190,86 @@ function listTargets() {
|
||||
|
||||
$targets = [];
|
||||
$currentTarget = null;
|
||||
$inACLSection = false;
|
||||
$currentSession = null;
|
||||
$section = ''; // 'acl', 'nexus', 'account', etc.
|
||||
|
||||
foreach ($output as $line) {
|
||||
// Check for new Target start
|
||||
if (preg_match('/^Target (\d+): (.+)$/', $line, $matches)) {
|
||||
// Save previous target and session
|
||||
if ($currentTarget) {
|
||||
if ($currentSession) {
|
||||
$currentTarget['sessions'][] = $currentSession;
|
||||
$currentSession = null;
|
||||
}
|
||||
$targets[] = $currentTarget;
|
||||
}
|
||||
$currentTarget = [
|
||||
'tid' => intval($matches[1]),
|
||||
'name' => trim($matches[2]),
|
||||
'luns' => 0,
|
||||
'acls' => 0
|
||||
'acls' => 0,
|
||||
'sessions' => []
|
||||
];
|
||||
$inACLSection = false;
|
||||
} elseif ($currentTarget && preg_match('/^\s+LUN: (\d+)/', $line)) {
|
||||
$section = '';
|
||||
} elseif ($currentTarget) {
|
||||
// Check for LUNs (also simple check for count)
|
||||
if (preg_match('/^\s+LUN: (\d+)/', $line)) {
|
||||
$currentTarget['luns']++;
|
||||
$inACLSection = false;
|
||||
} elseif ($currentTarget && preg_match('/^\s+ACL information:/', $line)) {
|
||||
$inACLSection = true;
|
||||
} elseif ($currentTarget && $inACLSection && preg_match('/^\s+(.+)$/', $line, $matches)) {
|
||||
$acl = trim($matches[1]);
|
||||
if (!empty($acl) && !preg_match('/^(Account|I_T nexus|LUN|System)/', $acl)) {
|
||||
// If we were parsing a session, save it as LUN info usually means we left nexus section or are in LUN section
|
||||
if ($currentSession) {
|
||||
$currentTarget['sessions'][] = $currentSession;
|
||||
$currentSession = null;
|
||||
}
|
||||
}
|
||||
// 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 ($currentSession) {
|
||||
$currentTarget['sessions'][] = $currentSession;
|
||||
}
|
||||
$targets[] = $currentTarget;
|
||||
}
|
||||
|
||||
|
||||
@@ -404,6 +404,32 @@
|
||||
</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-header">
|
||||
<h3>➕ Create New Target</h3>
|
||||
|
||||
@@ -1029,6 +1029,38 @@ function loadTargets() {
|
||||
</td>
|
||||
</tr>
|
||||
`).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 {
|
||||
showNotification(data.error, 'error');
|
||||
|
||||
Reference in New Issue
Block a user