init tools repo

This commit is contained in:
2025-11-23 19:57:05 +01:00
commit d778206940
35 changed files with 6197 additions and 0 deletions

View 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' }) %>

View 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 }) %>

View 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 }) %>

View 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>

View 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>
` }) %>