feat: Add complete iSCSI target management to Web UI- Add iSCSI tab with full target management- Implement create/delete targets with auto-generated IQN- Add LUN (backing store) management- Implement initiator ACL management (bind/unbind)- Add real-time target listing with LUN/ACL counts- Add comprehensive iSCSI management guide- Update sudoers to allow tgtadm commands- Add tape management features (create/list/delete/bulk delete)- Add service status monitoring- Security: Input validation, path security, sudo restrictions- Tested: Full CRUD operations working- Package size: 29KB, production ready

This commit is contained in:
2025-12-09 15:06:23 +00:00
parent fc2fb763f5
commit 8b6fad85a2
43 changed files with 9179 additions and 5 deletions

458
web-ui/index.html Normal file
View File

@@ -0,0 +1,458 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>mhvtl Configuration Manager - Adastra VTL</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav class="navbar">
<div class="container">
<div class="nav-brand">
<h1>🎞️ Adastra VTL</h1>
<span class="subtitle">Virtual Tape Library Configuration</span>
</div>
<div class="nav-links">
<a href="#library" class="nav-link active">Library</a>
<a href="#drives" class="nav-link">Drives</a>
<a href="#tapes" class="nav-link">Tapes</a>
<a href="#manage-tapes" class="nav-link">Manage Tapes</a>
<a href="#iscsi" class="nav-link">iSCSI</a>
<a href="#export" class="nav-link">Export</a>
</div>
</div>
</nav>
<main class="container">
<section id="library" class="section active">
<div class="section-header">
<h2>📚 Library Configuration</h2>
<p>Configure your virtual tape library settings</p>
</div>
<div class="card">
<div class="card-header">
<h3>Library Settings</h3>
</div>
<div class="card-body">
<div class="form-grid">
<div class="form-group">
<label for="lib-id">Library ID</label>
<input type="number" id="lib-id" value="10" min="0" max="99">
</div>
<div class="form-group">
<label for="lib-channel">Channel</label>
<input type="number" id="lib-channel" value="0" min="0" max="15">
</div>
<div class="form-group">
<label for="lib-target">Target</label>
<input type="number" id="lib-target" value="0" min="0" max="15">
</div>
<div class="form-group">
<label for="lib-lun">LUN</label>
<input type="number" id="lib-lun" value="0" min="0" max="7">
</div>
<div class="form-group">
<label for="lib-vendor">Vendor</label>
<input type="text" id="lib-vendor" value="STK" maxlength="8">
</div>
<div class="form-group">
<label for="lib-product">Product</label>
<input type="text" id="lib-product" value="L700" maxlength="16">
</div>
<div class="form-group">
<label for="lib-serial">Serial Number</label>
<input type="text" id="lib-serial" value="XYZZY_A" maxlength="10">
</div>
<div class="form-group">
<label for="lib-naa">NAA</label>
<input type="text" id="lib-naa" value="10:22:33:44:ab:cd:ef:00" pattern="[0-9a-f:]+">
</div>
<div class="form-group">
<label for="lib-home">Home Directory</label>
<input type="text" id="lib-home" value="/opt/mhvtl">
</div>
<div class="form-group">
<label for="lib-backoff">Backoff (ms)</label>
<input type="number" id="lib-backoff" value="400" min="0" max="10000">
</div>
</div>
</div>
</div>
</section>
<section id="drives" class="section">
<div class="section-header">
<h2>💾 Drive Configuration</h2>
<p>Manage virtual tape drives</p>
</div>
<div class="drives-container" id="drives-container">
</div>
<button class="btn btn-primary" onclick="addDrive()">
<span></span> Add Drive
</button>
</section>
<section id="tapes" class="section">
<div class="section-header">
<h2>📼 Tape Configuration</h2>
<p>Configure virtual tape media</p>
</div>
<div class="card">
<div class="card-header">
<h3>Tape Generation Settings</h3>
</div>
<div class="card-body">
<div class="form-grid">
<div class="form-group">
<label for="tape-library">Library ID</label>
<input type="number" id="tape-library" value="10" min="0" max="99">
</div>
<div class="form-group">
<label for="tape-barcode-prefix">Barcode Prefix</label>
<input type="text" id="tape-barcode-prefix" value="CLN" maxlength="6">
</div>
<div class="form-group">
<label for="tape-start-num">Starting Number</label>
<input type="number" id="tape-start-num" value="100" min="1" max="9999">
</div>
<div class="form-group">
<label for="tape-size">Tape Size (MB)</label>
<input type="number" id="tape-size" value="2500000" min="1000" max="10000000">
</div>
<div class="form-group">
<label for="tape-media-type">Media Type</label>
<select id="tape-media-type">
<option value="data">Data</option>
<option value="clean">Cleaning</option>
<option value="WORM">WORM</option>
</select>
</div>
<div class="form-group">
<label for="tape-density">Density</label>
<select id="tape-density">
<option value="LTO5">LTO-5</option>
<option value="LTO6" selected>LTO-6</option>
<option value="LTO7">LTO-7</option>
<option value="LTO8">LTO-8</option>
<option value="LTO9">LTO-9</option>
</select>
</div>
<div class="form-group">
<label for="tape-count">Number of Tapes</label>
<input type="number" id="tape-count" value="20" min="1" max="1000">
</div>
</div>
<div class="alert alert-info">
<strong> Info:</strong> Generate mktape commands for creating virtual tapes. Run these commands on the server after installation.
</div>
</div>
</div>
</section>
<section id="manage-tapes" class="section">
<div class="section-header">
<h2>🗂️ Manage Virtual Tapes</h2>
<p>Complete CRUD management for virtual tape files</p>
</div>
<div class="card">
<div class="card-header">
<h3> Create New Tapes</h3>
</div>
<div class="card-body">
<div class="form-grid">
<div class="form-group">
<label for="create-library">Library Number</label>
<input type="number" id="create-library" value="10" min="1">
</div>
<div class="form-group">
<label for="create-barcode-prefix">Barcode Prefix</label>
<input type="text" id="create-barcode-prefix" value="CLN" maxlength="6">
</div>
<div class="form-group">
<label for="create-start-num">Starting Number</label>
<input type="number" id="create-start-num" value="100" min="0">
</div>
<div class="form-group">
<label for="create-count">Number of Tapes</label>
<input type="number" id="create-count" value="1" min="1" max="100">
</div>
<div class="form-group">
<label for="create-size">Tape Size (MB)</label>
<input type="number" id="create-size" value="2500000" min="1000">
<small>Default: 2.5TB = 2,500,000 MB</small>
</div>
<div class="form-group">
<label for="create-media-type">Media Type</label>
<select id="create-media-type">
<option value="data">Data</option>
<option value="clean">Cleaning</option>
<option value="WORM">WORM</option>
</select>
</div>
<div class="form-group">
<label for="create-density">Density</label>
<select id="create-density">
<option value="LTO5">LTO-5</option>
<option value="LTO6" selected>LTO-6</option>
<option value="LTO7">LTO-7</option>
<option value="LTO8">LTO-8</option>
<option value="LTO9">LTO-9</option>
</select>
</div>
</div>
<div style="margin-top: 1rem;">
<button class="btn btn-success" onclick="createTapes()">
<span></span> Create Tapes
</button>
<div id="create-result" class="alert" style="display: none; margin-top: 1rem;"></div>
</div>
</div>
</div>
<div class="card" style="margin-top: 1rem;">
<div class="card-header">
<h3>📋 Tape Files</h3>
<button class="btn btn-primary" onclick="loadTapeList()">
<span>🔄</span> Refresh List
</button>
</div>
<div class="card-body">
<div id="tape-list-loading" style="display: none; text-align: center; padding: 2rem;">
<strong></strong> Loading tape files...
</div>
<div id="tape-list-error" class="alert alert-danger" style="display: none;"></div>
<div id="tape-list-empty" class="alert alert-info" style="display: none;">
<strong></strong> No tape files found. Create tapes using the commands from the "Tapes" section.
</div>
<div id="tape-list-container" style="display: none;">
<div style="margin-bottom: 1rem;">
<input type="text" id="tape-search" placeholder="🔍 Search tapes..."
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;"
onkeyup="filterTapes()">
</div>
<table class="tape-table" id="tape-table">
<thead>
<tr>
<th>Barcode</th>
<th>Size</th>
<th>Modified</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="tape-list-body">
</tbody>
</table>
<div style="margin-top: 1rem; padding: 1rem; background: #f8f9fa; border-radius: 4px;">
<strong>Total Tapes:</strong> <span id="tape-count-display">0</span>
</div>
</div>
</div>
</div>
<div class="card" style="margin-top: 1rem;">
<div class="card-header">
<h3>Bulk Actions</h3>
</div>
<div class="card-body">
<p>Delete multiple tapes at once. Use with caution!</p>
<div class="form-group">
<label for="bulk-delete-pattern">Delete Pattern (e.g., CLN*)</label>
<input type="text" id="bulk-delete-pattern" placeholder="CLN*">
</div>
<button class="btn btn-danger" onclick="bulkDeleteTapes()">
<span>🗑️</span> Bulk Delete
</button>
<div id="bulk-delete-result" class="alert" style="display: none; margin-top: 1rem;"></div>
</div>
</div>
</section>
<section id="iscsi" class="section">
<div class="section-header">
<h2>🔌 iSCSI Target Management</h2>
<p>Manage iSCSI targets, initiators, and LUNs</p>
</div>
<div class="card">
<div class="card-header">
<h3>🎯 iSCSI Targets</h3>
<button class="btn btn-primary" onclick="loadTargets()">
<span>🔄</span> Refresh
</button>
</div>
<div class="card-body">
<div id="target-list-empty" class="alert alert-info">
<strong></strong> No targets configured. Create a target below.
</div>
<div id="target-list-container" style="display: none;">
<table class="tape-table" id="target-table">
<thead>
<tr>
<th>TID</th>
<th>Target Name (IQN)</th>
<th>LUNs</th>
<th>ACLs</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="target-list-body">
</tbody>
</table>
</div>
</div>
</div>
<div class="card" style="margin-top: 1rem;">
<div class="card-header">
<h3> Create New Target</h3>
</div>
<div class="card-body">
<div class="form-grid">
<div class="form-group">
<label for="target-tid">Target ID (TID)</label>
<input type="number" id="target-tid" value="1" min="1">
<small>Unique target identifier</small>
</div>
<div class="form-group">
<label for="target-name">Target Name</label>
<input type="text" id="target-name" placeholder="vtl.drive0">
<small>Will be: iqn.2024-01.com.vtl-linux:<name></small>
</div>
</div>
<button class="btn btn-success" onclick="createTarget()">
<span></span> Create Target
</button>
<div id="create-target-result" class="alert" style="display: none; margin-top: 1rem;"></div>
</div>
</div>
<div class="card" style="margin-top: 1rem;">
<div class="card-header">
<h3>💾 Add LUN (Backing Store)</h3>
</div>
<div class="card-body">
<div class="form-grid">
<div class="form-group">
<label for="lun-tid">Target ID</label>
<input type="number" id="lun-tid" value="1" min="1">
</div>
<div class="form-group">
<label for="lun-number">LUN Number</label>
<input type="number" id="lun-number" value="1" min="0">
</div>
<div class="form-group">
<label for="lun-device">Device Path</label>
<input type="text" id="lun-device" placeholder="/dev/sg1">
<small>SCSI generic device (e.g., /dev/sg1, /dev/sg2)</small>
</div>
</div>
<button class="btn btn-success" onclick="addLun()">
<span></span> Add LUN
</button>
<div id="add-lun-result" class="alert" style="display: none; margin-top: 1rem;"></div>
</div>
</div>
<div class="card" style="margin-top: 1rem;">
<div class="card-header">
<h3>🔐 Manage Initiator ACLs</h3>
</div>
<div class="card-body">
<div class="form-grid">
<div class="form-group">
<label for="acl-tid">Target ID</label>
<input type="number" id="acl-tid" value="1" min="1">
</div>
<div class="form-group">
<label for="acl-address">Initiator Address</label>
<input type="text" id="acl-address" placeholder="192.168.1.100 or ALL">
<small>IP address or "ALL" for any initiator</small>
</div>
</div>
<div style="margin-top: 1rem;">
<button class="btn btn-success" onclick="bindInitiator()">
<span></span> Allow Initiator
</button>
<button class="btn btn-danger" onclick="unbindInitiator()">
<span>🚫</span> Block Initiator
</button>
</div>
<div id="acl-result" class="alert" style="display: none; margin-top: 1rem;"></div>
</div>
</div>
</section>
<section id="export" class="section">
<div class="section-header">
<h2>📤 Export Configuration</h2>
<p>Generate and download configuration files</p>
</div>
<div class="card">
<div class="card-header">
<h3>Configuration Preview</h3>
</div>
<div class="card-body">
<pre id="config-preview" class="config-preview"></pre>
</div>
</div>
<div class="button-group">
<button class="btn btn-primary" onclick="generateConfig()">
<span>🔄</span> Generate Config
</button>
<button class="btn btn-success" onclick="applyConfig()">
<span>💾</span> Apply to Server
</button>
<button class="btn btn-success" onclick="downloadConfig()">
<span>⬇️</span> Download device.conf
</button>
<button class="btn btn-secondary" onclick="copyConfig()">
<span>📋</span> Copy to Clipboard
</button>
</div>
<div id="apply-result" class="alert" style="display: none; margin-top: 1rem;"></div>
<div class="card" style="margin-top: 1rem;">
<div class="card-header">
<h3>Service Management</h3>
</div>
<div class="card-body">
<p>After applying configuration, restart the mhvtl service to apply changes.</p>
<button class="btn btn-warning" onclick="restartService()">
<span>🔄</span> Restart mhvtl Service
</button>
<div id="restart-result" class="alert" style="display: none; margin-top: 1rem;"></div>
</div>
</div>
<div class="card">
<div class="card-header">
<h3>Installation Command</h3>
</div>
<div class="card-body">
<pre id="install-command" class="config-preview"></pre>
<button class="btn btn-secondary" onclick="copyInstallCommand()">
<span>📋</span> Copy Command
</button>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<p>© Adastra Visi Teknologi • <a href="http://adastra.id">adastra.id</a> • mhvtl Configuration Manager</p>
</div>
</footer>
<script src="script.js"></script>
</body>
</html>