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
+
+
+
+ `;
+ 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