BAMS initial project structure

This commit is contained in:
2025-12-23 18:34:39 +00:00
parent e1df870f98
commit 861e0f65c3
24 changed files with 2495 additions and 0 deletions

354
cockpit/bams.js Normal file
View File

@@ -0,0 +1,354 @@
(function() {
"use strict";
const API_BASE = "http://localhost:8080/api/v1";
let currentTab = "dashboard";
// Initialize
$(document).ready(function() {
setupTabs();
loadDashboard();
setupEventHandlers();
});
function setupTabs() {
$("a[data-tab]").on("click", function(e) {
e.preventDefault();
const tab = $(this).data("tab");
switchTab(tab);
});
}
function switchTab(tab) {
$(".tab-content").hide();
$(".nav li").removeClass("active");
$(`#${tab}`).show();
$(`a[data-tab="${tab}"]`).parent().addClass("active");
currentTab = tab;
// Load tab-specific data
switch(tab) {
case "dashboard":
loadDashboard();
break;
case "storage":
loadRepositories();
break;
case "tape":
loadTapeLibrary();
break;
case "iscsi":
loadiSCSITargets();
break;
case "bacula":
loadBaculaStatus();
break;
case "logs":
loadLogs();
break;
}
}
function apiCall(endpoint, method, data) {
return new Promise((resolve, reject) => {
const options = {
url: `${API_BASE}${endpoint}`,
method: method || "GET",
contentType: "application/json",
success: resolve,
error: function(xhr, status, error) {
reject(new Error(error || xhr.responseText));
}
};
if (data) {
options.data = JSON.stringify(data);
}
$.ajax(options);
});
}
function loadDashboard() {
apiCall("/dashboard")
.then(data => {
// Update disk stats
const disk = data.disk || {};
$("#disk-stats").html(`
<p><strong>Repositories:</strong> ${disk.repositories || 0}</p>
<p><strong>Total:</strong> ${formatBytes(disk.total_capacity || 0)}</p>
<p><strong>Used:</strong> ${formatBytes(disk.used_capacity || 0)}</p>
`);
// Update tape stats
const tape = data.tape || {};
$("#tape-stats").html(`
<p><strong>Status:</strong> ${tape.library_status || "unknown"}</p>
<p><strong>Active Drives:</strong> ${tape.drives_active || 0}</p>
<p><strong>Total Slots:</strong> ${tape.total_slots || 0}</p>
`);
// Update iSCSI stats
const iscsi = data.iscsi || {};
$("#iscsi-stats").html(`
<p><strong>Targets:</strong> ${iscsi.targets || 0}</p>
<p><strong>Sessions:</strong> ${iscsi.sessions || 0}</p>
`);
// Update Bacula stats
const bacula = data.bacula || {};
$("#bacula-stats").html(`
<p><strong>Status:</strong> ${bacula.status || "unknown"}</p>
`);
// Update alerts
const alerts = data.alerts || [];
if (alerts.length > 0) {
let alertsHtml = "<ul>";
alerts.forEach(alert => {
alertsHtml += `<li class="alert alert-${alert.level || 'warning'}">${alert.message}</li>`;
});
alertsHtml += "</ul>";
$("#alerts").html(alertsHtml);
} else {
$("#alerts").html("<p>No alerts</p>");
}
})
.catch(err => {
console.error("Failed to load dashboard:", err);
$("#disk-stats, #tape-stats, #iscsi-stats, #bacula-stats").html("<p>Error loading data</p>");
});
}
function loadRepositories() {
apiCall("/disk/repositories")
.then(repos => {
let html = "";
if (repos.length === 0) {
html = "<tr><td colspan='6'>No repositories</td></tr>";
} else {
repos.forEach(repo => {
html += `
<tr>
<td>${repo.name}</td>
<td>${repo.type}</td>
<td>${repo.size}</td>
<td>${repo.used || "N/A"}</td>
<td><span class="label label-${repo.status === 'active' ? 'success' : 'default'}">${repo.status}</span></td>
<td>
<button class="btn btn-sm btn-danger" onclick="deleteRepository('${repo.id}')">Delete</button>
</td>
</tr>
`;
});
}
$("#repositories-table").html(html);
})
.catch(err => {
console.error("Failed to load repositories:", err);
$("#repositories-table").html("<tr><td colspan='6'>Error loading repositories</td></tr>");
});
}
function loadTapeLibrary() {
Promise.all([
apiCall("/tape/library"),
apiCall("/tape/drives"),
apiCall("/tape/slots")
]).then(([library, drives, slots]) => {
// Library status
$("#library-status").html(`
<p><strong>Status:</strong> ${library.status}</p>
<p><strong>Model:</strong> ${library.model || "N/A"}</p>
<p><strong>Total Slots:</strong> ${library.total_slots}</p>
<p><strong>Active Drives:</strong> ${library.active_drives}</p>
`);
// Drives
let drivesHtml = "";
if (drives.length === 0) {
drivesHtml = "<p>No drives detected</p>";
} else {
drives.forEach(drive => {
drivesHtml += `
<div class="drive-item">
<strong>${drive.id}</strong>: ${drive.status}
${drive.loaded_tape ? ` (Tape: ${drive.barcode || drive.loaded_tape})` : ""}
</div>
`;
});
}
$("#drives-list").html(drivesHtml);
// Slots
let slotsHtml = "<table class='table'><thead><tr><th>Slot</th><th>Barcode</th><th>Status</th></tr></thead><tbody>";
if (slots.length === 0) {
slotsHtml += "<tr><td colspan='3'>No slots</td></tr>";
} else {
slots.forEach(slot => {
slotsHtml += `
<tr>
<td>${slot.number}</td>
<td>${slot.barcode || "N/A"}</td>
<td>${slot.status}</td>
</tr>
`;
});
}
slotsHtml += "</tbody></table>";
$("#slots-list").html(slotsHtml);
}).catch(err => {
console.error("Failed to load tape library:", err);
});
}
function loadiSCSITargets() {
Promise.all([
apiCall("/iscsi/targets"),
apiCall("/iscsi/sessions")
]).then(([targets, sessions]) => {
// Targets
let targetsHtml = "";
if (targets.length === 0) {
targetsHtml = "<tr><td colspan='5'>No targets</td></tr>";
} else {
targets.forEach(target => {
targetsHtml += `
<tr>
<td>${target.iqn}</td>
<td>${target.portals.join(", ") || "N/A"}</td>
<td>${target.initiators.join(", ") || "N/A"}</td>
<td><span class="label label-${target.status === 'active' ? 'success' : 'default'}">${target.status}</span></td>
<td>
<button class="btn btn-sm btn-primary" onclick="applyTarget('${target.id}')">Apply</button>
<button class="btn btn-sm btn-danger" onclick="deleteTarget('${target.id}')">Delete</button>
</td>
</tr>
`;
});
}
$("#targets-table").html(targetsHtml);
// Sessions
let sessionsHtml = "";
if (sessions.length === 0) {
sessionsHtml = "<tr><td colspan='4'>No active sessions</td></tr>";
} else {
sessions.forEach(session => {
sessionsHtml += `
<tr>
<td>${session.target_iqn}</td>
<td>${session.initiator_iqn}</td>
<td>${session.ip}</td>
<td>${session.state}</td>
</tr>
`;
});
}
$("#sessions-table").html(sessionsHtml);
}).catch(err => {
console.error("Failed to load iSCSI targets:", err);
});
}
function loadBaculaStatus() {
apiCall("/bacula/status")
.then(status => {
$("#bacula-status").html(`
<p><strong>Status:</strong> <span class="label label-${status.status === 'running' ? 'success' : 'danger'}">${status.status}</span></p>
${status.version ? `<p><strong>Version:</strong> ${status.version}</p>` : ""}
${status.pid ? `<p><strong>PID:</strong> ${status.pid}</p>` : ""}
`);
})
.catch(err => {
console.error("Failed to load Bacula status:", err);
});
}
function loadLogs() {
const service = $("#log-service").val();
apiCall(`/logs/${service}?lines=100`)
.then(logs => {
let logsHtml = "";
logs.forEach(entry => {
logsHtml += `[${entry.timestamp}] ${entry.level}: ${entry.message}\n`;
});
$("#logs-content").text(logsHtml);
})
.catch(err => {
console.error("Failed to load logs:", err);
$("#logs-content").text("Error loading logs: " + err.message);
});
}
function setupEventHandlers() {
$("#inventory-btn").on("click", function() {
apiCall("/tape/inventory", "POST")
.then(() => {
alert("Inventory started");
loadTapeLibrary();
})
.catch(err => alert("Failed to start inventory: " + err.message));
});
$("#bacula-inventory-btn").on("click", function() {
apiCall("/bacula/inventory", "POST")
.then(() => alert("Inventory started"))
.catch(err => alert("Failed to start inventory: " + err.message));
});
$("#bacula-restart-btn").on("click", function() {
if (confirm("Restart Bacula Storage Daemon?")) {
apiCall("/bacula/restart", "POST")
.then(() => {
alert("Restart initiated");
setTimeout(loadBaculaStatus, 2000);
})
.catch(err => alert("Failed to restart: " + err.message));
}
});
$("#refresh-logs-btn").on("click", loadLogs);
$("#log-service").on("change", loadLogs);
$("#download-bundle-btn").on("click", function() {
window.location.href = `${API_BASE}/diagnostics/bundle`;
});
}
function formatBytes(bytes) {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB", "TB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
}
// Global functions for inline handlers
window.deleteRepository = function(id) {
if (confirm("Delete this repository?")) {
apiCall(`/disk/repositories/${id}`, "DELETE")
.then(() => {
alert("Repository deleted");
loadRepositories();
})
.catch(err => alert("Failed to delete: " + err.message));
}
};
window.applyTarget = function(id) {
apiCall(`/iscsi/targets/${id}/apply`, "POST")
.then(() => alert("Target configuration applied"))
.catch(err => alert("Failed to apply: " + err.message));
};
window.deleteTarget = function(id) {
if (confirm("Delete this iSCSI target?")) {
apiCall(`/iscsi/targets/${id}`, "DELETE")
.then(() => {
alert("Target deleted");
loadiSCSITargets();
})
.catch(err => alert("Failed to delete: " + err.message));
}
};
})();