208 lines
8.7 KiB
Plaintext
208 lines
8.7 KiB
Plaintext
<%- include('layout', { body: `
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2><i class="fas fa-tachometer-alt"></i> My DevBenches</h2>
|
|
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#createDevbenchModal">
|
|
<i class="fas fa-plus"></i> Create DevBench
|
|
</button>
|
|
</div>
|
|
|
|
<div class="row">
|
|
${devbenches.map(db => `
|
|
<div class="col-md-6 col-lg-4 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0">${db.name}</h6>
|
|
<span id="status-${db.id}" class="badge bg-${db.status === 'active' ? 'success' : db.status === 'creating' ? 'warning' : 'danger'}">
|
|
${db.status}
|
|
</span>
|
|
</div>
|
|
<div class="card-body">
|
|
${db.actual_name ? `
|
|
<p><strong>VM Name:</strong> ${db.actual_name}</p>
|
|
|
|
${db.ssh_info || db.vnc_info ? `
|
|
<div class="connection-info">
|
|
<h6><i class="fas fa-plug"></i> Connection Info:</h6>
|
|
${db.ssh_info ? `
|
|
<div class="mb-3">
|
|
<strong><i class="fas fa-terminal"></i> SSH Port:</strong><br>
|
|
<code class="fs-5">${db.ssh_info}</code>
|
|
<button class="btn btn-sm btn-outline-primary ms-2" onclick="copyToClipboard('${db.ssh_info}')">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
` : ''}
|
|
${db.vnc_info ? `
|
|
<div class="mb-3">
|
|
<strong><i class="fas fa-desktop"></i> VNC Port:</strong><br>
|
|
<code class="fs-5">${db.vnc_info}</code>
|
|
<button class="btn btn-sm btn-outline-primary ms-2" onclick="copyToClipboard('${db.vnc_info}')">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
` : ''}
|
|
<div class="alert alert-info mt-3">
|
|
<i class="fas fa-info-circle"></i> Need help connecting?
|
|
<a href="/help" class="alert-link">View setup guide</a>
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
` : `
|
|
<p class="text-muted">DevBench is being created...</p>
|
|
<div class="log-output" id="log-${db.id}"></div>
|
|
`}
|
|
|
|
<p class="text-muted mt-2">
|
|
<small>Created: ${new Date(db.created_at).toLocaleString()}</small>
|
|
</p>
|
|
</div>
|
|
<div class="card-footer">
|
|
<div class="btn-group w-100" role="group">
|
|
${db.status !== 'creating' ? `
|
|
<button class="btn btn-sm btn-primary" onclick="activateDevbench(${db.id})"
|
|
${db.status === 'active' ? 'disabled' : ''}>
|
|
<i class="fas fa-play"></i> Activate
|
|
</button>
|
|
<button class="btn btn-sm btn-info" onclick="checkStatus(${db.id})">
|
|
<i class="fas fa-sync"></i> Check Status
|
|
</button>
|
|
` : ''}
|
|
<button class="btn btn-sm btn-danger" onclick="deleteDevbench(${db.id})">
|
|
<i class="fas fa-trash"></i> Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('')}
|
|
|
|
${devbenches.length === 0 ? `
|
|
<div class="col-12">
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-server fa-3x text-muted mb-3"></i>
|
|
<h4 class="text-muted">No DevBenches Yet</h4>
|
|
<p class="text-muted">Create your first DevBench to get started!</p>
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
|
|
<!-- Create DevBench Modal -->
|
|
<div class="modal fade" id="createDevbenchModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Create New DevBench</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<form id="createDevbenchForm">
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="devbenchName" class="form-label">DevBench Name</label>
|
|
<input type="text" class="form-control" id="devbenchName" name="name" required
|
|
pattern="[a-zA-Z0-9_-]+" title="Only letters, numbers, hyphens, and underscores allowed">
|
|
<div class="form-text">
|
|
Only letters, numbers, hyphens (-), and underscores (_) are allowed.<br>
|
|
Full name will be: <strong>${username}_[your-name]</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-success">Create DevBench</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.getElementById('createDevbenchForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
|
|
try {
|
|
const response = await fetch('/create-devbench', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(Object.fromEntries(formData))
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
location.reload();
|
|
} else {
|
|
alert(result.error);
|
|
}
|
|
} catch (error) {
|
|
alert('Error creating DevBench');
|
|
}
|
|
});
|
|
|
|
async function deleteDevbench(devbenchId) {
|
|
if (confirm('Are you sure you want to delete this DevBench?')) {
|
|
try {
|
|
const response = await fetch(\`/delete-devbench/\${devbenchId}\`, { method: 'POST' });
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
location.reload();
|
|
} else {
|
|
alert(result.error);
|
|
}
|
|
} catch (error) {
|
|
alert('Error deleting DevBench');
|
|
}
|
|
}
|
|
}
|
|
|
|
async function activateDevbench(devbenchId) {
|
|
try {
|
|
const response = await fetch(\`/activate-devbench/\${devbenchId}\`, { method: 'POST' });
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
alert('DevBench activation started');
|
|
} else {
|
|
alert(result.error);
|
|
}
|
|
} catch (error) {
|
|
alert('Error activating DevBench');
|
|
}
|
|
}
|
|
|
|
async function checkStatus(devbenchId) {
|
|
try {
|
|
const response = await fetch(\`/check-status/\${devbenchId}\`);
|
|
const result = await response.json();
|
|
|
|
const statusElement = document.getElementById(\`status-\${devbenchId}\`);
|
|
if (statusElement) {
|
|
statusElement.className = \`badge bg-\${result.status === 'active' ? 'success' : 'danger'}\`;
|
|
statusElement.textContent = result.status;
|
|
}
|
|
} catch (error) {
|
|
alert('Error checking status');
|
|
}
|
|
}
|
|
|
|
function copyToClipboard(text) {
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
// Show a temporary success message
|
|
const toast = document.createElement('div');
|
|
toast.className = 'alert alert-success position-fixed';
|
|
toast.style.cssText = 'top: 20px; right: 20px; z-index: 9999; opacity: 0.9;';
|
|
toast.innerHTML = '<i class="fas fa-check"></i> Copied to clipboard!';
|
|
document.body.appendChild(toast);
|
|
|
|
setTimeout(() => {
|
|
document.body.removeChild(toast);
|
|
}, 2000);
|
|
}).catch(err => {
|
|
console.error('Failed to copy: ', err);
|
|
alert('Failed to copy to clipboard');
|
|
});
|
|
}
|
|
</script>
|
|
`, username }) %> |