fixing iscsi mapping for library
This commit is contained in:
@@ -176,6 +176,10 @@ func (a *App) routes() {
|
|||||||
func(w http.ResponseWriter, r *http.Request) { a.handleListVTLMediaChangers(w, r) },
|
func(w http.ResponseWriter, r *http.Request) { a.handleListVTLMediaChangers(w, r) },
|
||||||
nil, nil, nil, nil,
|
nil, nil, nil, nil,
|
||||||
))
|
))
|
||||||
|
a.mux.HandleFunc("/api/v1/vtl/devices/iscsi", methodHandler(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) { a.handleListVTLDevicesForISCSI(w, r) },
|
||||||
|
nil, nil, nil, nil,
|
||||||
|
))
|
||||||
a.mux.HandleFunc("/api/v1/vtl/changer/status", methodHandler(
|
a.mux.HandleFunc("/api/v1/vtl/changer/status", methodHandler(
|
||||||
func(w http.ResponseWriter, r *http.Request) { a.handleGetVTLMediaChangerStatus(w, r) },
|
func(w http.ResponseWriter, r *http.Request) { a.handleGetVTLMediaChangerStatus(w, r) },
|
||||||
nil, nil, nil, nil,
|
nil, nil, nil, nil,
|
||||||
|
|||||||
@@ -285,3 +285,42 @@ func (a *App) handleEjectTape(w http.ResponseWriter, r *http.Request) {
|
|||||||
"drive_id": fmt.Sprintf("%d", req.DriveID),
|
"drive_id": fmt.Sprintf("%d", req.DriveID),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleListVTLDevicesForISCSI returns all tape devices (drives and medium changers) for iSCSI passthrough
|
||||||
|
func (a *App) handleListVTLDevicesForISCSI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
devices := []map[string]interface{}{}
|
||||||
|
|
||||||
|
// Get drives
|
||||||
|
drives, err := a.vtlService.ListDrives()
|
||||||
|
if err == nil {
|
||||||
|
for _, drive := range drives {
|
||||||
|
devices = append(devices, map[string]interface{}{
|
||||||
|
"type": "drive",
|
||||||
|
"device": drive.Device,
|
||||||
|
"id": drive.ID,
|
||||||
|
"library_id": drive.LibraryID,
|
||||||
|
"vendor": drive.Vendor,
|
||||||
|
"product": drive.Product,
|
||||||
|
"description": fmt.Sprintf("Tape Drive %d (Library %d) - %s %s", drive.ID, drive.LibraryID, drive.Vendor, drive.Product),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get medium changers
|
||||||
|
changers, err := a.vtlService.ListMediaChangers()
|
||||||
|
if err == nil {
|
||||||
|
for _, changer := range changers {
|
||||||
|
devices = append(devices, map[string]interface{}{
|
||||||
|
"type": "changer",
|
||||||
|
"device": changer.Device,
|
||||||
|
"id": changer.ID,
|
||||||
|
"library_id": changer.LibraryID,
|
||||||
|
"slots": changer.Slots,
|
||||||
|
"drives": changer.Drives,
|
||||||
|
"description": fmt.Sprintf("Media Changer (Library %d) - %d slots, %d drives", changer.LibraryID, changer.Slots, changer.Drives),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(w, http.StatusOK, devices)
|
||||||
|
}
|
||||||
|
|||||||
@@ -85,13 +85,17 @@
|
|||||||
|
|
||||||
<!-- Add LUN Modal -->
|
<!-- Add LUN Modal -->
|
||||||
<div id="add-lun-modal" class="hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
<div id="add-lun-modal" class="hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||||
<div class="bg-slate-800 rounded-lg border border-slate-700 p-6 max-w-md w-full mx-4">
|
<div class="bg-slate-800 rounded-lg border border-slate-700 p-6 max-w-md w-full mx-4 max-h-[90vh] overflow-y-auto">
|
||||||
<h3 class="text-xl font-semibold text-white mb-4">Add LUN to Target</h3>
|
<h3 class="text-xl font-semibold text-white mb-4">Add LUN to Target</h3>
|
||||||
<form id="add-lun-form" onsubmit="addLUN(event)" class="space-y-4">
|
<form id="add-lun-form" onsubmit="addLUN(event)" class="space-y-4">
|
||||||
<input type="hidden" name="target_id" id="lun-target-id">
|
<input type="hidden" name="target_id" id="lun-target-id">
|
||||||
|
<input type="hidden" name="target_type" id="lun-target-type" value="disk">
|
||||||
|
|
||||||
|
<!-- Disk Mode: Storage Volume -->
|
||||||
|
<div id="lun-disk-mode" class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-1">Select Storage Volume</label>
|
<label class="block text-sm font-medium text-slate-300 mb-1">Select Storage Volume</label>
|
||||||
<select name="zvol" id="lun-zvol-select" required class="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-600">
|
<select name="zvol" id="lun-zvol-select" class="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-600">
|
||||||
<option value="">Loading volumes...</option>
|
<option value="">Loading volumes...</option>
|
||||||
</select>
|
</select>
|
||||||
<p class="text-xs text-slate-400 mt-1">Or enter manually below</p>
|
<p class="text-xs text-slate-400 mt-1">Or enter manually below</p>
|
||||||
@@ -100,6 +104,24 @@
|
|||||||
<label class="block text-sm font-medium text-slate-300 mb-1">Or Enter Volume Name Manually</label>
|
<label class="block text-sm font-medium text-slate-300 mb-1">Or Enter Volume Name Manually</label>
|
||||||
<input type="text" name="zvol-manual" id="lun-zvol-manual" placeholder="pool/zvol" class="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-600">
|
<input type="text" name="zvol-manual" id="lun-zvol-manual" placeholder="pool/zvol" class="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-600">
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tape Mode: Tape Devices -->
|
||||||
|
<div id="lun-tape-mode" class="space-y-4 hidden">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-slate-300 mb-1">Select Tape Device</label>
|
||||||
|
<select name="device" id="lun-device-select" class="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-600">
|
||||||
|
<option value="">Loading devices...</option>
|
||||||
|
</select>
|
||||||
|
<p class="text-xs text-slate-400 mt-1">Physical medium changer or tape drive device</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-slate-300 mb-1">Or Enter Device Path Manually</label>
|
||||||
|
<input type="text" name="device-manual" id="lun-device-manual" placeholder="/dev/sg0 or /dev/st0" class="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-600">
|
||||||
|
<p class="text-xs text-slate-400 mt-1">Medium changer: /dev/sg* | Tape drive: /dev/st* or /dev/nst*</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-2 justify-end">
|
<div class="flex gap-2 justify-end">
|
||||||
<button type="button" onclick="closeModal('add-lun-modal')" class="px-4 py-2 bg-slate-700 hover:bg-slate-600 text-white rounded text-sm">
|
<button type="button" onclick="closeModal('add-lun-modal')" class="px-4 py-2 bg-slate-700 hover:bg-slate-600 text-white rounded text-sm">
|
||||||
Cancel
|
Cancel
|
||||||
@@ -212,10 +234,10 @@ async function loadISCSITargets(type = 'disk') {
|
|||||||
<div class="flex items-center justify-between text-sm">
|
<div class="flex items-center justify-between text-sm">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-slate-400">LUN ${lun.id}:</span>
|
<span class="text-slate-400">LUN ${lun.id}:</span>
|
||||||
<span class="text-slate-300 font-mono">${lun.zvol}</span>
|
<span class="text-slate-300 font-mono">${lun.zvol || lun.device || 'N/A'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-slate-300">${formatBytes(lun.size)}</span>
|
${lun.size > 0 ? `<span class="text-slate-300">${formatBytes(lun.size)}</span>` : ''}
|
||||||
<button onclick="removeLUN('${target.id}', ${lun.id})" class="ml-2 px-2 py-1 bg-red-600 hover:bg-red-700 text-white rounded text-xs" title="Remove LUN">
|
<button onclick="removeLUN('${target.id}', ${lun.id})" class="ml-2 px-2 py-1 bg-red-600 hover:bg-red-700 text-white rounded text-xs" title="Remove LUN">
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
@@ -281,12 +303,38 @@ function showCreateISCSIModal(type = 'disk') {
|
|||||||
document.getElementById('create-iscsi-modal').classList.remove('hidden');
|
document.getElementById('create-iscsi-modal').classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showAddLUNModal(targetId) {
|
async function showAddLUNModal(targetId, targetType) {
|
||||||
document.getElementById('lun-target-id').value = targetId;
|
document.getElementById('lun-target-id').value = targetId;
|
||||||
document.getElementById('add-lun-modal').classList.remove('hidden');
|
|
||||||
|
|
||||||
// Load available storage volumes
|
// Use provided target type, or fallback to current tab
|
||||||
|
const type = targetType || (currentTab === 'tape' ? 'tape' : 'disk');
|
||||||
|
document.getElementById('lun-target-type').value = type;
|
||||||
|
|
||||||
|
// Show/hide appropriate sections
|
||||||
|
const diskMode = document.getElementById('lun-disk-mode');
|
||||||
|
const tapeMode = document.getElementById('lun-tape-mode');
|
||||||
|
const zvolSelect = document.getElementById('lun-zvol-select');
|
||||||
|
const deviceSelect = document.getElementById('lun-device-select');
|
||||||
|
|
||||||
|
if (targetType === 'tape') {
|
||||||
|
diskMode.classList.add('hidden');
|
||||||
|
tapeMode.classList.remove('hidden');
|
||||||
|
zvolSelect.removeAttribute('required');
|
||||||
|
deviceSelect.setAttribute('required', 'required');
|
||||||
|
|
||||||
|
// Load tape devices
|
||||||
|
await loadTapeDevicesForLUN();
|
||||||
|
} else {
|
||||||
|
diskMode.classList.remove('hidden');
|
||||||
|
tapeMode.classList.add('hidden');
|
||||||
|
zvolSelect.setAttribute('required', 'required');
|
||||||
|
deviceSelect.removeAttribute('required');
|
||||||
|
|
||||||
|
// Load storage volumes
|
||||||
await loadZVOLsForLUN();
|
await loadZVOLsForLUN();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('add-lun-modal').classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadZVOLsForLUN() {
|
async function loadZVOLsForLUN() {
|
||||||
@@ -339,6 +387,56 @@ async function loadZVOLsForLUN() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadTapeDevicesForLUN() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/v1/vtl/devices/iscsi', { headers: getAuthHeaders() });
|
||||||
|
const selectEl = document.getElementById('lun-device-select');
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
selectEl.innerHTML = '<option value="">Error loading devices</option>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const devices = await res.json();
|
||||||
|
|
||||||
|
if (!Array.isArray(devices)) {
|
||||||
|
selectEl.innerHTML = '<option value="">No devices available</option>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devices.length === 0) {
|
||||||
|
selectEl.innerHTML = '<option value="">No tape devices found. Make sure mhvtl is running.</option>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear and populate dropdown
|
||||||
|
selectEl.innerHTML = '<option value="">Select a device...</option>';
|
||||||
|
devices.forEach(device => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = device.device;
|
||||||
|
option.textContent = device.description || `${device.type}: ${device.device}`;
|
||||||
|
selectEl.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update manual input when dropdown changes
|
||||||
|
selectEl.addEventListener('change', function() {
|
||||||
|
if (this.value) {
|
||||||
|
document.getElementById('lun-device-manual').value = this.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update dropdown when manual input changes
|
||||||
|
document.getElementById('lun-device-manual').addEventListener('input', function() {
|
||||||
|
if (this.value && !selectEl.value) {
|
||||||
|
// Allow manual entry even if not in dropdown
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error loading tape devices:', err);
|
||||||
|
document.getElementById('lun-device-select').innerHTML = '<option value="">Error loading devices</option>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function showConnectionInstructions(targetId) {
|
async function showConnectionInstructions(targetId) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/v1/iscsi/targets/${targetId}/connection`, { headers: getAuthHeaders() });
|
const res = await fetch(`/api/v1/iscsi/targets/${targetId}/connection`, { headers: getAuthHeaders() });
|
||||||
@@ -419,15 +517,27 @@ async function addLUN(e) {
|
|||||||
let requestBody = {};
|
let requestBody = {};
|
||||||
|
|
||||||
if (targetType === 'tape') {
|
if (targetType === 'tape') {
|
||||||
// Tape mode: use device
|
// Tape mode: use device (medium changer or tape drive)
|
||||||
const device = document.getElementById('lun-device-input').value.trim();
|
const deviceSelect = document.getElementById('lun-device-select').value;
|
||||||
|
const deviceManual = document.getElementById('lun-device-manual').value.trim();
|
||||||
|
const device = deviceSelect || deviceManual;
|
||||||
|
|
||||||
if (!device) {
|
if (!device) {
|
||||||
alert('Please enter a tape device path');
|
alert('Please select or enter a tape device path (e.g., /dev/sg0 for medium changer or /dev/st0 for tape drive)');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine backstore type based on device
|
||||||
|
let backstore = 'pscsi'; // Default for tape devices
|
||||||
|
if (device.startsWith('/dev/sg')) {
|
||||||
|
backstore = 'pscsi'; // Medium changer
|
||||||
|
} else if (device.startsWith('/dev/st') || device.startsWith('/dev/nst')) {
|
||||||
|
backstore = 'pscsi'; // Tape drive
|
||||||
|
}
|
||||||
|
|
||||||
requestBody = {
|
requestBody = {
|
||||||
device: device,
|
device: device,
|
||||||
backstore: 'pscsi'
|
backstore: backstore
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Disk mode: use ZVOL
|
// Disk mode: use ZVOL
|
||||||
@@ -456,7 +566,8 @@ async function addLUN(e) {
|
|||||||
e.target.reset();
|
e.target.reset();
|
||||||
document.getElementById('lun-zvol-select').innerHTML = '<option value="">Loading volumes...</option>';
|
document.getElementById('lun-zvol-select').innerHTML = '<option value="">Loading volumes...</option>';
|
||||||
document.getElementById('lun-zvol-manual').value = '';
|
document.getElementById('lun-zvol-manual').value = '';
|
||||||
document.getElementById('lun-device-input').value = '';
|
document.getElementById('lun-device-select').innerHTML = '<option value="">Loading devices...</option>';
|
||||||
|
document.getElementById('lun-device-manual').value = '';
|
||||||
// Reload targets for the current tab
|
// Reload targets for the current tab
|
||||||
loadISCSITargets(targetType);
|
loadISCSITargets(targetType);
|
||||||
alert('LUN added successfully');
|
alert('LUN added successfully');
|
||||||
|
|||||||
Reference in New Issue
Block a user