step one of launch instance

This commit is contained in:
2025-06-12 15:07:01 +02:00
parent 5c2c514825
commit 83c94acbac
4 changed files with 392 additions and 12 deletions

View File

@@ -572,11 +572,33 @@ def test_nginx_connection():
return jsonify({'error': 'Missing required fields'}), 400 return jsonify({'error': 'Missing required fields'}), 400
try: try:
# Test NGINX Proxy Manager connection # First, get the JWT token
token_response = requests.post(
f"{url.rstrip('/')}/api/tokens",
json={
'identity': username,
'secret': password
},
headers={'Content-Type': 'application/json'},
timeout=5
)
if token_response.status_code != 200:
return jsonify({'error': 'Failed to authenticate with NGINX Proxy Manager'}), 400
token_data = token_response.json()
token = token_data.get('token')
if not token:
return jsonify({'error': 'No token received from NGINX Proxy Manager'}), 400
# Now test the connection using the token
response = requests.get( response = requests.get(
f"{url.rstrip('/')}/api/nginx/proxy-hosts", f"{url.rstrip('/')}/api/nginx/proxy-hosts",
auth=(username, password), headers={
headers={'Accept': 'application/json'}, 'Authorization': f'Bearer {token}',
'Accept': 'application/json'
},
timeout=5 timeout=5
) )

View File

@@ -376,7 +376,14 @@ def init_routes(main_bp):
db.session.commit() db.session.commit()
return render_template('main/instances.html', instances=instances) # Get connection settings
portainer_settings = KeyValueSettings.get_value('portainer_settings')
nginx_settings = KeyValueSettings.get_value('nginx_settings')
return render_template('main/instances.html',
instances=instances,
portainer_settings=portainer_settings,
nginx_settings=nginx_settings)
@main_bp.route('/instances/add', methods=['POST']) @main_bp.route('/instances/add', methods=['POST'])
@login_required @login_required

View File

@@ -122,24 +122,230 @@
<div class="modal-body"> <div class="modal-body">
<form id="addInstanceForm"> <form id="addInstanceForm">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="mb-3"> <div class="text-center mb-4">
<label for="name" class="form-label">Instance Name</label> <i class="fas fa-rocket fa-3x mb-3" style="color: var(--primary-color);"></i>
<input type="text" class="form-control" id="name" name="name" required> <h4>Ready to Launch?</h4>
</div> <p class="text-muted">We'll guide you through the process of launching a new DocuPulse instance.</p>
<div class="mb-3">
<label for="main_url" class="form-label">Main URL</label>
<input type="url" class="form-control" id="main_url" name="main_url" required>
</div> </div>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="submitAddInstance()">Launch Instance</button> <button type="button" class="btn btn-primary" onclick="nextStepLaunchInstance()">
Next <i class="fas fa-arrow-right ms-1"></i>
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Launch Steps Modal -->
<div class="modal fade" id="launchStepsModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Launch New Instance</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<!-- Steps Navigation -->
<div class="d-flex justify-content-between mb-4">
<div class="step-item active" data-step="1">
<div class="step-circle">1</div>
<div class="step-label">Connections</div>
</div>
<div class="step-item" data-step="2">
<div class="step-circle">2</div>
<div class="step-label">Configuration</div>
</div>
<div class="step-item" data-step="3">
<div class="step-circle">3</div>
<div class="step-label">Resources</div>
</div>
<div class="step-item" data-step="4">
<div class="step-circle">4</div>
<div class="step-label">Review</div>
</div>
<div class="step-item" data-step="5">
<div class="step-circle">5</div>
<div class="step-label">Launch</div>
</div>
</div>
<!-- Step Content -->
<div class="step-content">
<!-- Step 1 -->
<div class="step-pane active" id="step1">
<h4>Connection Verification</h4>
<p class="text-muted mb-4">Let's verify that your connections are properly configured before proceeding.</p>
<!-- Portainer Connection -->
<div class="connection-check mb-4">
<div class="d-flex align-items-center mb-2">
<i class="fas fa-server me-2"></i>
<h5 class="mb-0">Portainer Connection</h5>
<div class="ms-auto">
<span class="connection-status" id="portainerStatus">
<i class="fas fa-spinner fa-spin"></i> Checking...
</span>
</div>
</div>
<div class="connection-details" id="portainerDetails">
<small class="text-muted">Verifying connection to Portainer...</small>
</div>
</div>
<!-- NGINX Connection -->
<div class="connection-check">
<div class="d-flex align-items-center mb-2">
<i class="fas fa-network-wired me-2"></i>
<h5 class="mb-0">NGINX Proxy Manager Connection</h5>
<div class="ms-auto">
<span class="connection-status" id="nginxStatus">
<i class="fas fa-spinner fa-spin"></i> Checking...
</span>
</div>
</div>
<div class="connection-details" id="nginxDetails">
<small class="text-muted">Verifying connection to NGINX Proxy Manager...</small>
</div>
</div>
<!-- Retry Button -->
<div class="text-center mt-4">
<button class="btn btn-sm"
onclick="retryConnection('nginx')"
style="background-color: var(--primary-color); color: white;">
<i class="fas fa-sync-alt me-1"></i> Retry
</button>
</div>
</div>
<!-- Step 2 -->
<div class="step-pane" id="step2">
<h4>Configuration</h4>
<p class="text-muted">Configure your instance settings.</p>
</div>
<!-- Step 3 -->
<div class="step-pane" id="step3">
<h4>Resources</h4>
<p class="text-muted">Set up your instance resources.</p>
</div>
<!-- Step 4 -->
<div class="step-pane" id="step4">
<h4>Review</h4>
<p class="text-muted">Review your instance configuration.</p>
</div>
<!-- Step 5 -->
<div class="step-pane" id="step5">
<h4>Launch</h4>
<p class="text-muted">Ready to launch your instance!</p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="previousStep()">
<i class="fas fa-arrow-left me-1"></i> Previous
</button>
<button type="button" class="btn btn-primary" onclick="nextStep()">
Next <i class="fas fa-arrow-right ms-1"></i>
</button>
</div>
</div>
</div>
</div>
<style>
.step-item {
text-align: center;
position: relative;
flex: 1;
}
.step-item:not(:last-child)::after {
content: '';
position: absolute;
top: 20px;
right: -50%;
width: 100%;
height: 2px;
background-color: #e9ecef;
z-index: 1;
}
.step-circle {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #e9ecef;
color: #6c757d;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 8px;
position: relative;
z-index: 2;
}
.step-label {
font-size: 0.875rem;
color: #6c757d;
}
.step-item.active .step-circle {
background-color: var(--primary-color);
color: white;
}
.step-item.active .step-label {
color: var(--primary-color);
font-weight: 500;
}
.step-item.completed .step-circle {
background-color: var(--primary-color);
color: white;
}
.step-item.completed:not(:last-child)::after {
background-color: var(--primary-color);
}
.step-pane {
display: none;
}
.step-pane.active {
display: block;
}
.connection-check {
background-color: #f8f9fa;
border-radius: 8px;
padding: 1rem;
}
.connection-status {
font-size: 0.875rem;
}
.connection-status.success {
color: #198754;
}
.connection-status.error {
color: #dc3545;
}
.connection-details {
font-size: 0.875rem;
margin-top: 0.5rem;
}
</style>
<!-- Edit Instance Modal --> <!-- Edit Instance Modal -->
<div class="modal fade" id="editInstanceModal" tabindex="-1"> <div class="modal fade" id="editInstanceModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
@@ -239,12 +445,15 @@ let addInstanceModal;
let editInstanceModal; let editInstanceModal;
let addExistingInstanceModal; let addExistingInstanceModal;
let authModal; let authModal;
let launchStepsModal;
let currentStep = 1;
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
addInstanceModal = new bootstrap.Modal(document.getElementById('addInstanceModal')); addInstanceModal = new bootstrap.Modal(document.getElementById('addInstanceModal'));
editInstanceModal = new bootstrap.Modal(document.getElementById('editInstanceModal')); editInstanceModal = new bootstrap.Modal(document.getElementById('editInstanceModal'));
addExistingInstanceModal = new bootstrap.Modal(document.getElementById('addExistingInstanceModal')); addExistingInstanceModal = new bootstrap.Modal(document.getElementById('addExistingInstanceModal'));
authModal = new bootstrap.Modal(document.getElementById('authModal')); authModal = new bootstrap.Modal(document.getElementById('authModal'));
launchStepsModal = new bootstrap.Modal(document.getElementById('launchStepsModal'));
// Initialize tooltips // Initialize tooltips
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
@@ -881,5 +1090,147 @@ function copyConnectionToken(button) {
button.innerHTML = originalText; button.innerHTML = originalText;
}, 2000); }, 2000);
} }
function nextStepLaunchInstance() {
// Hide the initial modal
addInstanceModal.hide();
// Reset and show the steps modal
currentStep = 1;
updateStepDisplay();
launchStepsModal.show();
// Start verifying connections
verifyConnections();
}
function updateStepDisplay() {
// Update step indicators
document.querySelectorAll('.step-item').forEach((item, index) => {
const stepNum = index + 1;
item.classList.remove('active', 'completed');
if (stepNum === currentStep) {
item.classList.add('active');
} else if (stepNum < currentStep) {
item.classList.add('completed');
}
});
// Update step content
document.querySelectorAll('.step-pane').forEach((pane, index) => {
pane.classList.remove('active');
if (index + 1 === currentStep) {
pane.classList.add('active');
}
});
// Update buttons
const prevButton = document.querySelector('#launchStepsModal .btn-secondary');
const nextButton = document.querySelector('#launchStepsModal .btn-primary');
prevButton.style.display = currentStep === 1 ? 'none' : 'inline-block';
nextButton.innerHTML = currentStep === 5 ? 'Launch <i class="fas fa-rocket ms-1"></i>' : 'Next <i class="fas fa-arrow-right ms-1"></i>';
}
function nextStep() {
if (currentStep < 5) {
// If we're on step 1, verify connections before proceeding
if (currentStep === 1) {
const portainerStatus = document.getElementById('portainerStatus');
const nginxStatus = document.getElementById('nginxStatus');
if (!portainerStatus.innerHTML.includes('Connected') || !nginxStatus.innerHTML.includes('Connected')) {
alert('Please ensure all connections are working before proceeding.');
return;
}
}
currentStep++;
updateStepDisplay();
} else {
// Handle launch
console.log('Launching instance...');
}
}
function previousStep() {
if (currentStep > 1) {
currentStep--;
updateStepDisplay();
}
}
async function verifyConnections() {
// Verify Portainer connection
try {
const portainerResponse = await fetch('/api/admin/test-portainer-connection', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('input[name="csrf_token"]').value
},
body: JSON.stringify({
url: '{{ portainer_settings.url if portainer_settings else "" }}',
api_key: '{{ portainer_settings.api_key if portainer_settings else "" }}'
})
});
const portainerStatus = document.getElementById('portainerStatus');
const portainerDetails = document.getElementById('portainerDetails');
if (portainerResponse.ok) {
portainerStatus.innerHTML = '<i class="fas fa-check-circle"></i> Connected';
portainerStatus.className = 'connection-status success';
portainerDetails.innerHTML = '<small class="text-success">Successfully connected to Portainer</small>';
} else {
const error = await portainerResponse.json();
portainerStatus.innerHTML = '<i class="fas fa-times-circle"></i> Failed';
portainerStatus.className = 'connection-status error';
portainerDetails.innerHTML = `<small class="text-danger">${error.error || 'Failed to connect to Portainer'}</small>`;
}
} catch (error) {
const portainerStatus = document.getElementById('portainerStatus');
const portainerDetails = document.getElementById('portainerDetails');
portainerStatus.innerHTML = '<i class="fas fa-times-circle"></i> Error';
portainerStatus.className = 'connection-status error';
portainerDetails.innerHTML = `<small class="text-danger">${error.message || 'Error checking Portainer connection'}</small>`;
}
// Verify NGINX connection
try {
const nginxResponse = await fetch('/api/admin/test-nginx-connection', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('input[name="csrf_token"]').value
},
body: JSON.stringify({
url: '{{ nginx_settings.url if nginx_settings else "" }}',
username: '{{ nginx_settings.username if nginx_settings else "" }}',
password: '{{ nginx_settings.password if nginx_settings else "" }}'
})
});
const nginxStatus = document.getElementById('nginxStatus');
const nginxDetails = document.getElementById('nginxDetails');
if (nginxResponse.ok) {
nginxStatus.innerHTML = '<i class="fas fa-check-circle"></i> Connected';
nginxStatus.className = 'connection-status success';
nginxDetails.innerHTML = '<small class="text-success">Successfully connected to NGINX Proxy Manager</small>';
} else {
const error = await nginxResponse.json();
nginxStatus.innerHTML = '<i class="fas fa-times-circle"></i> Failed';
nginxStatus.className = 'connection-status error';
nginxDetails.innerHTML = `<small class="text-danger">${error.error || 'Failed to connect to NGINX Proxy Manager'}</small>`;
}
} catch (error) {
const nginxStatus = document.getElementById('nginxStatus');
const nginxDetails = document.getElementById('nginxDetails');
nginxStatus.innerHTML = '<i class="fas fa-times-circle"></i> Error';
nginxStatus.className = 'connection-status error';
nginxDetails.innerHTML = `<small class="text-danger">${error.message || 'Error checking NGINX connection'}</small>`;
}
}
</script> </script>
{% endblock %} {% endblock %}