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
|
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
|
||||||
|
|||||||
79
dist/adastra-vtl-installer/web-ui/api.php
vendored
79
dist/adastra-vtl-installer/web-ui/api.php
vendored
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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>
|
</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>
|
||||||
|
|||||||
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>
|
</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');
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
Reference in New Issue
Block a user