diff --git a/migrations/versions/add_connection_token.py b/migrations/versions/add_connection_token.py index 6070679..0121e34 100644 --- a/migrations/versions/add_connection_token.py +++ b/migrations/versions/add_connection_token.py @@ -7,6 +7,7 @@ Create Date: 2024-03-19 13:00:00.000000 """ from alembic import op import sqlalchemy as sa +from sqlalchemy import inspect # revision identifiers, used by Alembic. @@ -17,7 +18,13 @@ depends_on = None def upgrade(): - op.add_column('instances', sa.Column('connection_token', sa.String(64), nullable=True, unique=True)) + # Get the inspector + inspector = inspect(op.get_bind()) + + # Check if the column exists + columns = [col['name'] for col in inspector.get_columns('instances')] + if 'connection_token' not in columns: + op.add_column('instances', sa.Column('connection_token', sa.String(64), nullable=True, unique=True)) def downgrade(): diff --git a/routes/launch_api.py b/routes/launch_api.py index fa32c70..7b8d612 100644 --- a/routes/launch_api.py +++ b/routes/launch_api.py @@ -841,4 +841,43 @@ def deploy_stack(): return jsonify({'error': 'Request timed out while deploying stack. The operation may still be in progress.'}), 504 except Exception as e: current_app.logger.error(f"Error deploying stack: {str(e)}") + return jsonify({'error': str(e)}), 500 + +@launch_api.route('/save-instance', methods=['POST']) +@csrf.exempt +def save_instance(): + try: + if not request.is_json: + return jsonify({'error': 'Request must be JSON'}), 400 + + data = request.get_json() + required_fields = ['name', 'port', 'domains', 'stack_id', 'stack_name', 'status', 'repository', 'branch'] + + if not all(field in data for field in required_fields): + missing_fields = [field for field in required_fields if field not in data] + return jsonify({'error': f'Missing required fields: {", ".join(missing_fields)}'}), 400 + + # Save instance data + instance_data = { + 'name': data['name'], + 'port': data['port'], + 'domains': data['domains'], + 'stack_id': data['stack_id'], + 'stack_name': data['stack_name'], + 'status': data['status'], + 'repository': data['repository'], + 'branch': data['branch'], + 'created_at': datetime.utcnow().isoformat() + } + + # Save to database using KeyValueSettings + KeyValueSettings.set_value(f'instance_{data["name"]}', instance_data) + + return jsonify({ + 'message': 'Instance data saved successfully', + 'data': instance_data + }) + + except Exception as e: + current_app.logger.error(f"Error saving instance data: {str(e)}") return jsonify({'error': str(e)}), 500 \ No newline at end of file diff --git a/static/js/launch_progress.js b/static/js/launch_progress.js index 7ea297f..6beacdf 100644 --- a/static/js/launch_progress.js +++ b/static/js/launch_progress.js @@ -99,6 +99,18 @@ function initializeSteps() { `; stepsContainer.appendChild(stackDeployStep); + + // Add Save Instance Data step + const saveDataStep = document.createElement('div'); + saveDataStep.className = 'step-item'; + saveDataStep.innerHTML = ` +
+
+
Saving Instance Data
+

Storing instance information...

+
+ `; + stepsContainer.appendChild(saveDataStep); } async function startLaunch(data) { @@ -250,7 +262,10 @@ async function startLaunch(data) { const stackDeployStep = document.querySelectorAll('.step-item')[6]; stackDeployStep.classList.remove('active'); stackDeployStep.classList.add('completed'); - stackDeployStep.querySelector('.step-status').textContent = 'Successfully deployed stack'; + stackDeployStep.querySelector('.step-status').textContent = + stackResult.data.status === 'existing' ? + 'Using existing stack' : + 'Successfully deployed stack'; // Add stack details const stackDetails = document.createElement('div'); @@ -279,7 +294,9 @@ async function startLaunch(data) { Status - Deployed + + ${stackResult.data.status === 'existing' ? 'Existing' : 'Deployed'} + @@ -290,7 +307,70 @@ async function startLaunch(data) { `; stackDeployStep.querySelector('.step-content').appendChild(stackDetails); + // Save instance data + await updateStep(8, 'Saving Instance Data', 'Storing instance information...'); + try { + const instanceData = { + name: data.instanceName, + port: data.port, + domains: data.webAddresses, + stack_id: stackResult.data.id, + stack_name: stackResult.data.name, + status: stackResult.data.status, + repository: data.repository, + branch: data.branch + }; + console.log('Saving instance data:', instanceData); + const saveResult = await saveInstanceData(instanceData); + console.log('Save result:', saveResult); + await updateStep(8, 'Saving Instance Data', 'Instance data saved successfully'); + } catch (error) { + console.error('Error saving instance data:', error); + await updateStep(8, 'Saving Instance Data', `Error: ${error.message}`); + throw error; + } + + // Update the step to show success + const saveDataStep = document.querySelectorAll('.step-item')[7]; + saveDataStep.classList.remove('active'); + saveDataStep.classList.add('completed'); + saveDataStep.querySelector('.step-status').textContent = 'Successfully saved instance data'; + + // Add instance details + const instanceDetails = document.createElement('div'); + instanceDetails.className = 'mt-3'; + instanceDetails.innerHTML = ` +
+
+
Instance Information
+
+ + + + + + + + + + + + + + + + + +
PropertyValue
Internal Port${data.port}
Domains${data.webAddresses.join(', ')}
+
+
+
+ `; + saveDataStep.querySelector('.step-content').appendChild(instanceDetails); + } catch (error) { + console.error('Launch failed:', error); + await updateStep(8, 'Saving Instance Data', `Error: ${error.message}`); showError(error.message); } } @@ -1020,4 +1100,82 @@ async function deployStack(dockerComposeContent, stackName, port) { error: error.message }; } +} + +// Add new function to save instance data +async function saveInstanceData(instanceData) { + try { + console.log('Saving instance data:', instanceData); + + // First check if instance already exists + const checkResponse = await fetch('/instances'); + const text = await checkResponse.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(text, 'text/html'); + + // Look for an existing instance with the same name + const existingInstance = Array.from(doc.querySelectorAll('table tbody tr')).find(row => { + const nameCell = row.querySelector('td:first-child'); + return nameCell && nameCell.textContent.trim() === instanceData.port; + }); + + if (existingInstance) { + console.log('Instance already exists:', instanceData.port); + return { + success: true, + data: { + name: instanceData.port, + company: 'loading...', + rooms_count: 0, + conversations_count: 0, + data_size: 0.0, + payment_plan: 'Basic', + main_url: `https://${instanceData.domains[0]}`, + status: 'inactive', + port: instanceData.port, + stack_id: instanceData.stack_id, + stack_name: instanceData.stack_name, + repository: instanceData.repository, + branch: instanceData.branch + } + }; + } + + // If instance doesn't exist, create it + const response = await fetch('/instances/add', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content + }, + body: JSON.stringify({ + name: instanceData.port, + company: 'loading...', + rooms_count: 0, + conversations_count: 0, + data_size: 0.0, + payment_plan: 'Basic', + main_url: `https://${instanceData.domains[0]}`, + status: 'inactive', + port: instanceData.port, + stack_id: instanceData.stack_id, + stack_name: instanceData.stack_name, + repository: instanceData.repository, + branch: instanceData.branch + }) + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error('Error response:', errorText); + throw new Error(`Failed to save instance data: ${response.status} ${response.statusText}`); + } + + const result = await response.json(); + console.log('Instance data saved:', result); + return result; + } catch (error) { + console.error('Error saving instance data:', error); + throw error; + } } \ No newline at end of file diff --git a/templates/main/launch_progress.html b/templates/main/launch_progress.html index 4f47708..20990de 100644 --- a/templates/main/launch_progress.html +++ b/templates/main/launch_progress.html @@ -35,7 +35,7 @@
- +
@@ -68,6 +68,9 @@ url: '{{ portainer_settings.url if portainer_settings else "" }}', api_key: '{{ portainer_settings.api_key if portainer_settings else "" }}' }; + + // Pass CSRF token to JavaScript + window.csrfToken = '{{ csrf_token }}'; {% endblock %} \ No newline at end of file