fix launch issue
This commit is contained in:
@@ -7,6 +7,7 @@ Create Date: 2024-03-19 13:00:00.000000
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -17,7 +18,13 @@ depends_on = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade():
|
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():
|
def downgrade():
|
||||||
|
|||||||
@@ -841,4 +841,43 @@ def deploy_stack():
|
|||||||
return jsonify({'error': 'Request timed out while deploying stack. The operation may still be in progress.'}), 504
|
return jsonify({'error': 'Request timed out while deploying stack. The operation may still be in progress.'}), 504
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(f"Error deploying stack: {str(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
|
return jsonify({'error': str(e)}), 500
|
||||||
@@ -99,6 +99,18 @@ function initializeSteps() {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
stepsContainer.appendChild(stackDeployStep);
|
stepsContainer.appendChild(stackDeployStep);
|
||||||
|
|
||||||
|
// Add Save Instance Data step
|
||||||
|
const saveDataStep = document.createElement('div');
|
||||||
|
saveDataStep.className = 'step-item';
|
||||||
|
saveDataStep.innerHTML = `
|
||||||
|
<div class="step-icon"><i class="fas fa-save"></i></div>
|
||||||
|
<div class="step-content">
|
||||||
|
<h5>Saving Instance Data</h5>
|
||||||
|
<p class="step-status">Storing instance information...</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
stepsContainer.appendChild(saveDataStep);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startLaunch(data) {
|
async function startLaunch(data) {
|
||||||
@@ -250,7 +262,10 @@ async function startLaunch(data) {
|
|||||||
const stackDeployStep = document.querySelectorAll('.step-item')[6];
|
const stackDeployStep = document.querySelectorAll('.step-item')[6];
|
||||||
stackDeployStep.classList.remove('active');
|
stackDeployStep.classList.remove('active');
|
||||||
stackDeployStep.classList.add('completed');
|
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
|
// Add stack details
|
||||||
const stackDetails = document.createElement('div');
|
const stackDetails = document.createElement('div');
|
||||||
@@ -279,7 +294,9 @@ async function startLaunch(data) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-success">Deployed</span>
|
<span class="badge bg-${stackResult.data.status === 'existing' ? 'info' : 'success'}">
|
||||||
|
${stackResult.data.status === 'existing' ? 'Existing' : 'Deployed'}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -290,7 +307,70 @@ async function startLaunch(data) {
|
|||||||
`;
|
`;
|
||||||
stackDeployStep.querySelector('.step-content').appendChild(stackDetails);
|
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 = `
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title mb-3">Instance Information</h6>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Property</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Internal Port</td>
|
||||||
|
<td>${data.port}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Domains</td>
|
||||||
|
<td><a href="https://${data.webAddresses.join(', ')}" target="_blank">${data.webAddresses.join(', ')}</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
saveDataStep.querySelector('.step-content').appendChild(instanceDetails);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Launch failed:', error);
|
||||||
|
await updateStep(8, 'Saving Instance Data', `Error: ${error.message}`);
|
||||||
showError(error.message);
|
showError(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1020,4 +1100,82 @@ async function deployStack(dockerComposeContent, stackName, port) {
|
|||||||
error: error.message
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="stepsContainer" class="launch-steps-container">
|
<div id="stepsContainer" class="launch-steps-container">
|
||||||
<!-- Your custom steps will be added here -->
|
<!-- Steps will be added here by JavaScript -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center mt-4">
|
<div class="text-center mt-4">
|
||||||
@@ -68,6 +68,9 @@
|
|||||||
url: '{{ portainer_settings.url if portainer_settings else "" }}',
|
url: '{{ portainer_settings.url if portainer_settings else "" }}',
|
||||||
api_key: '{{ portainer_settings.api_key if portainer_settings else "" }}'
|
api_key: '{{ portainer_settings.api_key if portainer_settings else "" }}'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Pass CSRF token to JavaScript
|
||||||
|
window.csrfToken = '{{ csrf_token }}';
|
||||||
</script>
|
</script>
|
||||||
<script src="{{ url_for('static', filename='js/launch_progress.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/launch_progress.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user