step one of launch instance
This commit is contained in:
Binary file not shown.
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 %}
|
||||||
Reference in New Issue
Block a user