diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc
index b18bbe3..41d000d 100644
Binary files a/routes/__pycache__/main.cpython-313.pyc and b/routes/__pycache__/main.cpython-313.pyc differ
diff --git a/routes/admin_api.py b/routes/admin_api.py
index f044450..fad2521 100644
--- a/routes/admin_api.py
+++ b/routes/admin_api.py
@@ -572,11 +572,33 @@ def test_nginx_connection():
return jsonify({'error': 'Missing required fields'}), 400
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(
f"{url.rstrip('/')}/api/nginx/proxy-hosts",
- auth=(username, password),
- headers={'Accept': 'application/json'},
+ headers={
+ 'Authorization': f'Bearer {token}',
+ 'Accept': 'application/json'
+ },
timeout=5
)
diff --git a/routes/main.py b/routes/main.py
index f237b08..cae30ba 100644
--- a/routes/main.py
+++ b/routes/main.py
@@ -376,7 +376,14 @@ def init_routes(main_bp):
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'])
@login_required
diff --git a/templates/main/instances.html b/templates/main/instances.html
index dc68d1e..1bb01e4 100644
--- a/templates/main/instances.html
+++ b/templates/main/instances.html
@@ -122,24 +122,230 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Connection Verification
+
Let's verify that your connections are properly configured before proceeding.
+
+
+
+
+
+
Portainer Connection
+
+
+ Checking...
+
+
+
+
+ Verifying connection to Portainer...
+
+
+
+
+
+
+
+
NGINX Proxy Manager Connection
+
+
+ Checking...
+
+
+
+
+ Verifying connection to NGINX Proxy Manager...
+
+
+
+
+
+
+
+
+
+
+
+
Configuration
+
Configure your instance settings.
+
+
+
+
+
Resources
+
Set up your instance resources.
+
+
+
+
+
Review
+
Review your instance configuration.
+
+
+
+
+
Launch
+
Ready to launch your instance!
+
+
+
+
+
+
+
+
+
+
@@ -239,12 +445,15 @@ let addInstanceModal;
let editInstanceModal;
let addExistingInstanceModal;
let authModal;
+let launchStepsModal;
+let currentStep = 1;
document.addEventListener('DOMContentLoaded', function() {
addInstanceModal = new bootstrap.Modal(document.getElementById('addInstanceModal'));
editInstanceModal = new bootstrap.Modal(document.getElementById('editInstanceModal'));
addExistingInstanceModal = new bootstrap.Modal(document.getElementById('addExistingInstanceModal'));
authModal = new bootstrap.Modal(document.getElementById('authModal'));
+ launchStepsModal = new bootstrap.Modal(document.getElementById('launchStepsModal'));
// Initialize tooltips
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
@@ -881,5 +1090,147 @@ function copyConnectionToken(button) {
button.innerHTML = originalText;
}, 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 ' : 'Next ';
+}
+
+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 = ' Connected';
+ portainerStatus.className = 'connection-status success';
+ portainerDetails.innerHTML = 'Successfully connected to Portainer';
+ } else {
+ const error = await portainerResponse.json();
+ portainerStatus.innerHTML = ' Failed';
+ portainerStatus.className = 'connection-status error';
+ portainerDetails.innerHTML = `${error.error || 'Failed to connect to Portainer'}`;
+ }
+ } catch (error) {
+ const portainerStatus = document.getElementById('portainerStatus');
+ const portainerDetails = document.getElementById('portainerDetails');
+ portainerStatus.innerHTML = ' Error';
+ portainerStatus.className = 'connection-status error';
+ portainerDetails.innerHTML = `${error.message || 'Error checking Portainer connection'}`;
+ }
+
+ // 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 = ' Connected';
+ nginxStatus.className = 'connection-status success';
+ nginxDetails.innerHTML = 'Successfully connected to NGINX Proxy Manager';
+ } else {
+ const error = await nginxResponse.json();
+ nginxStatus.innerHTML = ' Failed';
+ nginxStatus.className = 'connection-status error';
+ nginxDetails.innerHTML = `${error.error || 'Failed to connect to NGINX Proxy Manager'}`;
+ }
+ } catch (error) {
+ const nginxStatus = document.getElementById('nginxStatus');
+ const nginxDetails = document.getElementById('nginxDetails');
+ nginxStatus.innerHTML = ' Error';
+ nginxStatus.className = 'connection-status error';
+ nginxDetails.innerHTML = `${error.message || 'Error checking NGINX connection'}`;
+ }
+}
{% endblock %}
\ No newline at end of file