init tools repo
This commit is contained in:
212
asf-cloud-server/TBM_devbench/views/admin.ejs
Normal file
212
asf-cloud-server/TBM_devbench/views/admin.ejs
Normal file
@@ -0,0 +1,212 @@
|
||||
<%- include('layout', { body: `
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="fas fa-cogs"></i> Admin Dashboard</h2>
|
||||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#addUserModal">
|
||||
<i class="fas fa-user-plus"></i> Add User
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Users Section -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-users"></i> Users Management</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>DevBenches</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${users.map(user => `
|
||||
<tr>
|
||||
<td>${user.username}</td>
|
||||
<td>${user.email}</td>
|
||||
<td><span class="badge bg-info">${user.devbench_count}</span></td>
|
||||
<td>${new Date(user.created_at).toLocaleDateString()}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-warning" onclick="resetPassword(${user.id})">
|
||||
<i class="fas fa-key"></i> Reset Password
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteUser(${user.id})">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DevBenches Section -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5><i class="fas fa-server"></i> All DevBenches</h5>
|
||||
<small class="text-light"><i class="fas fa-info-circle"></i> Default SSH/VNC Password: <strong>ASF</strong></small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>DevBench Name</th>
|
||||
<th>Actual Name</th>
|
||||
<th>Status</th>
|
||||
<th>SSH Port</th>
|
||||
<th>VNC Port</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${devbenches.map(db => `
|
||||
<tr>
|
||||
<td>${db.username}</td>
|
||||
<td>${db.name}</td>
|
||||
<td>${db.actual_name || 'N/A'}</td>
|
||||
<td>
|
||||
<span id="status-${db.id}" class="badge bg-${db.status === 'active' ? 'success' : db.status === 'creating' ? 'warning' : 'danger'}">
|
||||
${db.status}
|
||||
</span>
|
||||
</td>
|
||||
<td>${db.ssh_info || 'N/A'}</td>
|
||||
<td>${db.vnc_info || 'N/A'}</td>
|
||||
<td>${new Date(db.created_at).toLocaleDateString()}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-danger" onclick="adminDeleteDevbench(${db.id})">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add User Modal -->
|
||||
<div class="modal fade" id="addUserModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add New User</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form id="addUserForm">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="newUsername" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="newUsername" name="username" required
|
||||
pattern="[a-zA-Z]+" title="Username must contain only letters">
|
||||
<div class="form-text">Username must contain only letters (no spaces, numbers, or special characters)</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="newEmail" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" id="newEmail" name="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="newPassword" class="form-label">Initial Password</label>
|
||||
<input type="password" class="form-control" id="newPassword" name="password" required>
|
||||
</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">Add User</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('addUserForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
|
||||
try {
|
||||
const response = await fetch('/admin/add-user', {
|
||||
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 adding user');
|
||||
}
|
||||
});
|
||||
|
||||
async function deleteUser(userId) {
|
||||
if (confirm('Are you sure you want to delete this user and all their DevBenches?')) {
|
||||
try {
|
||||
const response = await fetch(\`/admin/delete-user/\${userId}\`, { method: 'POST' });
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error deleting user');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function resetPassword(userId) {
|
||||
const newPassword = prompt('Enter new password:');
|
||||
if (newPassword) {
|
||||
try {
|
||||
const response = await fetch(\`/admin/reset-password/\${userId}\`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ newPassword })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
alert('Password reset successfully');
|
||||
} else {
|
||||
alert(result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error resetting password');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function adminDeleteDevbench(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');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
`, username: typeof username !== 'undefined' ? username : 'admin' }) %>
|
||||
208
asf-cloud-server/TBM_devbench/views/dashboard.ejs
Normal file
208
asf-cloud-server/TBM_devbench/views/dashboard.ejs
Normal file
@@ -0,0 +1,208 @@
|
||||
<%- 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 }) %>
|
||||
125
asf-cloud-server/TBM_devbench/views/help.ejs
Normal file
125
asf-cloud-server/TBM_devbench/views/help.ejs
Normal file
@@ -0,0 +1,125 @@
|
||||
<%- include('layout', { body: `
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="fas fa-question-circle"></i> Help - How to Use DevBench Manager</h2>
|
||||
<a href="/dashboard" class="btn btn-primary">
|
||||
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5><i class="fas fa-info-circle"></i> SSH Configuration Tool</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="lead">Follow these steps to configure SSH access to your VM:</p>
|
||||
|
||||
<ol class="help-steps">
|
||||
<li>
|
||||
<strong>Download the VM SSH Config Tool</strong>
|
||||
<p>Download the configuration tool from here:</p>
|
||||
<a href="/downloads/db_vm_ssh_config_manager.exe" class="btn btn-success mb-2" download>
|
||||
<i class="fas fa-download"></i> Download SSH Config Manager
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Open the Tool</strong>
|
||||
<p>Run the <code>db_vm_ssh_config_manager.exe</code> application.</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Add Friendly Name</strong>
|
||||
<p>In the "Host" field, enter a friendly name for your VM (e.g., <code>vm-test1</code>).</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Add SSH Port</strong>
|
||||
<p>Enter the SSH port number shown in your DevBench connection info (e.g., <code>6004</code>).</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Add Username</strong>
|
||||
<p>Enter the username: <code>asf_user</code></p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Keep Jump Proxy</strong>
|
||||
<p>Keep the jump proxy as: <code>asf-jump</code></p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Press Add VM</strong>
|
||||
<p>Click the "Add VM" button to save the configuration.</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Select VM Host and Show Command</strong>
|
||||
<p>In the "Select VM Host" dropdown, choose your VM name and click "Show Commands".</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Access Your VM</strong>
|
||||
<p>You will now see the SSH command to access your VM. Copy and use it in your terminal.</p>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5><i class="fas fa-terminal"></i> Connection Information</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6>SSH Connection:</h6>
|
||||
<p>After configuring the SSH tool, you can connect using:</p>
|
||||
<pre class="bg-light p-3 rounded"><code>ssh [your-vm-name]</code></pre>
|
||||
|
||||
<h6 class="mt-4">VNC Connection:</h6>
|
||||
<p>For graphical access, use your VNC client with the port shown in your DevBench info.</p>
|
||||
<p><strong>Default Password:</strong> <code>ASF</code></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h5><i class="fas fa-exclamation-triangle"></i> Important Notes</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul>
|
||||
<li>The default password for both SSH and VNC is: <strong>ASF</strong></li>
|
||||
<li>Make sure to keep your SSH config tool updated with the correct ports</li>
|
||||
<li>Each DevBench has unique SSH and VNC ports</li>
|
||||
<li>Contact your administrator if you encounter any issues</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.help-steps {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.help-steps li {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.help-steps strong {
|
||||
color: #0d6efd;
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.help-steps p {
|
||||
margin-left: 1rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.help-steps code {
|
||||
background-color: #f8f9fa;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
color: #d63384;
|
||||
}
|
||||
</style>
|
||||
`, username }) %>
|
||||
134
asf-cloud-server/TBM_devbench/views/layout.ejs
Normal file
134
asf-cloud-server/TBM_devbench/views/layout.ejs
Normal file
@@ -0,0 +1,134 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DevBench Manager</title>
|
||||
<link rel="icon" type="image/png" href="/images/tbm-icon.png">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="/images/tbm-icon.png" alt="TBM" class="tbm-icon me-2" style="height: 32px; width: 32px;">
|
||||
<img src="/images/nabd-logo-white.svg" alt="NABD Solutions" class="company-logo">
|
||||
DevBench Manager
|
||||
</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<% if (typeof username !== 'undefined') { %>
|
||||
<a class="nav-link" href="/help" title="Help">
|
||||
<i class="fas fa-question-circle"></i> Help
|
||||
</a>
|
||||
<span class="navbar-text me-3">Welcome, <%= username %></span>
|
||||
<a class="nav-link" href="/logout">
|
||||
<i class="fas fa-sign-out-alt"></i> Logout
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
<%- body %>
|
||||
</div>
|
||||
|
||||
<!-- Theme Toggle Button -->
|
||||
<button class="theme-toggle" id="themeToggle" title="Toggle Dark/Light Theme">
|
||||
<i class="fas fa-moon" id="themeIcon"></i>
|
||||
</button>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// Theme Toggle Functionality
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
const themeIcon = document.getElementById('themeIcon');
|
||||
const body = document.body;
|
||||
|
||||
// Load saved theme preference
|
||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-theme');
|
||||
themeIcon.classList.remove('fa-moon');
|
||||
themeIcon.classList.add('fa-sun');
|
||||
}
|
||||
|
||||
// Toggle theme
|
||||
themeToggle.addEventListener('click', () => {
|
||||
body.classList.toggle('dark-theme');
|
||||
|
||||
if (body.classList.contains('dark-theme')) {
|
||||
themeIcon.classList.remove('fa-moon');
|
||||
themeIcon.classList.add('fa-sun');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
themeIcon.classList.remove('fa-sun');
|
||||
themeIcon.classList.add('fa-moon');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
// WebSocket connection for real-time updates
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const ws = new WebSocket(`${protocol}//${window.location.host}`);
|
||||
|
||||
ws.onopen = function() {
|
||||
console.log('WebSocket connected');
|
||||
// Register this connection with user ID if available
|
||||
<% if (typeof username !== 'undefined') { %>
|
||||
// Get user ID from session (we'll need to pass this from server)
|
||||
fetch('/api/user-info')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.userId) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'register',
|
||||
userId: data.userId
|
||||
}));
|
||||
console.log('Registered WebSocket for user:', data.userId);
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error getting user info:', error));
|
||||
<% } %>
|
||||
};
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
const message = JSON.parse(event.data);
|
||||
console.log('WebSocket message received:', message);
|
||||
handleWebSocketMessage(message);
|
||||
};
|
||||
|
||||
ws.onerror = function(error) {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
|
||||
ws.onclose = function() {
|
||||
console.log('WebSocket disconnected');
|
||||
};
|
||||
|
||||
function handleWebSocketMessage(message) {
|
||||
if (message.type === 'script_output') {
|
||||
const logElement = document.getElementById(`log-${message.devbenchId}`);
|
||||
if (logElement) {
|
||||
logElement.textContent += message.data;
|
||||
logElement.scrollTop = logElement.scrollHeight;
|
||||
}
|
||||
} else if (message.type === 'script_complete') {
|
||||
console.log('Script completed with exit code:', message.exitCode);
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 3000);
|
||||
} else if (message.type === 'status_update') {
|
||||
const statusElement = document.getElementById(`status-${message.devbenchId}`);
|
||||
if (statusElement) {
|
||||
statusElement.className = `badge bg-${message.status === 'active' ? 'success' : 'danger'}`;
|
||||
statusElement.textContent = message.status;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
31
asf-cloud-server/TBM_devbench/views/login.ejs
Normal file
31
asf-cloud-server/TBM_devbench/views/login.ejs
Normal file
@@ -0,0 +1,31 @@
|
||||
<%- include('layout', { body: `
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card shadow login-card">
|
||||
<div class="card-header bg-primary text-white text-center">
|
||||
<img src="/images/nabd-logo.svg" alt="NABD Solutions" style="height: 40px; margin-bottom: 10px;">
|
||||
<h4><i class="fas fa-sign-in-alt"></i> Login</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
${error ? `<div class="alert alert-danger">${error}</div>` : ''}
|
||||
<form method="POST" action="/login">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="fas fa-sign-in-alt"></i> Login
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer text-center text-muted">
|
||||
<small>DevBench Manager v1.0</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
` }) %>
|
||||
Reference in New Issue
Block a user