finalized update feature
This commit is contained in:
@@ -1870,7 +1870,7 @@ def copy_smtp_settings():
|
|||||||
def update_stack():
|
def update_stack():
|
||||||
try:
|
try:
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if not data or 'stack_id' not in data or 'StackFileContent' not in data:
|
if not data or 'stack_id' not in data:
|
||||||
return jsonify({'error': 'Missing required fields'}), 400
|
return jsonify({'error': 'Missing required fields'}), 400
|
||||||
|
|
||||||
# Get Portainer settings
|
# Get Portainer settings
|
||||||
@@ -1924,7 +1924,7 @@ def update_stack():
|
|||||||
current_app.logger.info(f"Using endpoint ID: {endpoint_id}")
|
current_app.logger.info(f"Using endpoint ID: {endpoint_id}")
|
||||||
current_app.logger.info(f"Using timeout: {stack_timeout} seconds")
|
current_app.logger.info(f"Using timeout: {stack_timeout} seconds")
|
||||||
|
|
||||||
# First, verify the stack exists
|
# First, verify the stack exists and get its current configuration
|
||||||
stack_url = f"{portainer_settings['url'].rstrip('/')}/api/stacks/{data['stack_id']}"
|
stack_url = f"{portainer_settings['url'].rstrip('/')}/api/stacks/{data['stack_id']}"
|
||||||
stack_response = requests.get(
|
stack_response = requests.get(
|
||||||
stack_url,
|
stack_url,
|
||||||
@@ -1942,16 +1942,84 @@ def update_stack():
|
|||||||
stack_info = stack_response.json()
|
stack_info = stack_response.json()
|
||||||
current_app.logger.info(f"Found existing stack: {stack_info['Name']} (ID: {stack_info['Id']})")
|
current_app.logger.info(f"Found existing stack: {stack_info['Name']} (ID: {stack_info['Id']})")
|
||||||
|
|
||||||
|
# Get the current stack file content from Portainer
|
||||||
|
stack_file_url = f"{portainer_settings['url'].rstrip('/')}/api/stacks/{data['stack_id']}/file"
|
||||||
|
stack_file_response = requests.get(
|
||||||
|
stack_file_url,
|
||||||
|
headers={
|
||||||
|
'X-API-Key': portainer_settings['api_key'],
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
params={'endpointId': endpoint_id},
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if not stack_file_response.ok:
|
||||||
|
current_app.logger.error(f"Failed to get stack file: {stack_file_response.text}")
|
||||||
|
return jsonify({'error': f'Failed to get existing stack file: {stack_file_response.text}'}), 500
|
||||||
|
|
||||||
|
stack_file_data = stack_file_response.json()
|
||||||
|
current_stack_file_content = stack_file_data.get('StackFileContent')
|
||||||
|
|
||||||
|
if not current_stack_file_content:
|
||||||
|
current_app.logger.error("No StackFileContent found in existing stack")
|
||||||
|
return jsonify({'error': 'No existing stack file content found'}), 500
|
||||||
|
|
||||||
|
current_app.logger.info("Retrieved existing stack file content")
|
||||||
|
|
||||||
|
# Get existing environment variables from the stack
|
||||||
|
existing_env_vars = stack_file_data.get('Env', [])
|
||||||
|
current_app.logger.info(f"Retrieved {len(existing_env_vars)} existing environment variables")
|
||||||
|
|
||||||
|
# Create a dictionary of existing environment variables for easy lookup
|
||||||
|
existing_env_dict = {env['name']: env['value'] for env in existing_env_vars}
|
||||||
|
current_app.logger.info(f"Existing environment variables: {list(existing_env_dict.keys())}")
|
||||||
|
|
||||||
|
# Get new environment variables from the request
|
||||||
|
new_env_vars = data.get('Env', [])
|
||||||
|
current_app.logger.info(f"New environment variables to update: {[env['name'] for env in new_env_vars]}")
|
||||||
|
|
||||||
|
# Merge existing and new environment variables
|
||||||
|
# Start with existing variables
|
||||||
|
merged_env_vars = existing_env_vars.copy()
|
||||||
|
|
||||||
|
# Update with new variables (this will overwrite existing ones with the same name)
|
||||||
|
for new_env in new_env_vars:
|
||||||
|
# Find if this environment variable already exists
|
||||||
|
existing_index = None
|
||||||
|
for i, existing_env in enumerate(merged_env_vars):
|
||||||
|
if existing_env['name'] == new_env['name']:
|
||||||
|
existing_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if existing_index is not None:
|
||||||
|
# Update existing variable
|
||||||
|
merged_env_vars[existing_index]['value'] = new_env['value']
|
||||||
|
current_app.logger.info(f"Updated environment variable: {new_env['name']} = {new_env['value']}")
|
||||||
|
else:
|
||||||
|
# Add new variable
|
||||||
|
merged_env_vars.append(new_env)
|
||||||
|
current_app.logger.info(f"Added new environment variable: {new_env['name']} = {new_env['value']}")
|
||||||
|
|
||||||
|
current_app.logger.info(f"Final merged environment variables: {[env['name'] for env in merged_env_vars]}")
|
||||||
|
|
||||||
# Update the stack using Portainer's update API
|
# Update the stack using Portainer's update API
|
||||||
update_url = f"{portainer_settings['url'].rstrip('/')}/api/stacks/{data['stack_id']}/update"
|
update_url = f"{portainer_settings['url'].rstrip('/')}/api/stacks/{data['stack_id']}"
|
||||||
current_app.logger.info(f"Making update request to: {update_url}")
|
current_app.logger.info(f"Making update request to: {update_url}")
|
||||||
|
|
||||||
# Prepare the request body for stack update
|
# Prepare the request body for stack update
|
||||||
request_body = {
|
request_body = {
|
||||||
'StackFileContent': data['StackFileContent'],
|
'StackFileContent': current_stack_file_content, # Use existing stack file content
|
||||||
'Env': data.get('Env', [])
|
'Env': merged_env_vars # Use merged environment variables
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# If new StackFileContent is provided, use it instead
|
||||||
|
if 'StackFileContent' in data:
|
||||||
|
request_body['StackFileContent'] = data['StackFileContent']
|
||||||
|
current_app.logger.info("Using provided StackFileContent for update")
|
||||||
|
else:
|
||||||
|
current_app.logger.info("Using existing StackFileContent for update")
|
||||||
|
|
||||||
# Add endpointId as a query parameter
|
# Add endpointId as a query parameter
|
||||||
params = {'endpointId': endpoint_id}
|
params = {'endpointId': endpoint_id}
|
||||||
|
|
||||||
|
|||||||
@@ -1409,7 +1409,7 @@ async function startUpdate(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the existing stack instead of creating a new one
|
// Update the existing stack instead of creating a new one
|
||||||
const stackResult = await updateStack(dockerComposeResult.content, instanceData.instance.portainer_stack_id, port);
|
const stackResult = await updateStack(dockerComposeResult.content, instanceData.instance.portainer_stack_id, port, instanceData.instance);
|
||||||
if (!stackResult.success) {
|
if (!stackResult.success) {
|
||||||
throw new Error(`Failed to update stack: ${stackResult.error}`);
|
throw new Error(`Failed to update stack: ${stackResult.error}`);
|
||||||
}
|
}
|
||||||
@@ -2446,8 +2446,12 @@ async function checkInstanceHealth(instanceUrl) {
|
|||||||
|
|
||||||
const data = await statusResponse.json();
|
const data = await statusResponse.json();
|
||||||
|
|
||||||
// Update the health check step
|
// Update the health check step - check if we're in update mode or launch mode
|
||||||
const healthStepElement = document.querySelectorAll('.step-item')[10]; // Adjust index based on your steps
|
const isUpdate = window.isUpdate;
|
||||||
|
const healthStepIndex = isUpdate ? 4 : 10; // Different indices for update vs launch
|
||||||
|
const healthStepElement = document.querySelectorAll('.step-item')[healthStepIndex];
|
||||||
|
|
||||||
|
if (healthStepElement) {
|
||||||
healthStepElement.classList.remove('active');
|
healthStepElement.classList.remove('active');
|
||||||
healthStepElement.classList.add('completed');
|
healthStepElement.classList.add('completed');
|
||||||
const statusText = healthStepElement.querySelector('.step-status');
|
const statusText = healthStepElement.querySelector('.step-status');
|
||||||
@@ -2456,7 +2460,7 @@ async function checkInstanceHealth(instanceUrl) {
|
|||||||
const elapsedTime = Math.round((Date.now() - startTime) / 1000);
|
const elapsedTime = Math.round((Date.now() - startTime) / 1000);
|
||||||
statusText.textContent = `Instance is healthy (Attempt ${currentAttempt}/${maxRetries}, ${elapsedTime}s elapsed)`;
|
statusText.textContent = `Instance is healthy (Attempt ${currentAttempt}/${maxRetries}, ${elapsedTime}s elapsed)`;
|
||||||
|
|
||||||
// Update progress bar to 100%
|
// Update progress bar to 100% if it exists
|
||||||
const progressBar = document.getElementById('healthProgress');
|
const progressBar = document.getElementById('healthProgress');
|
||||||
const progressText = document.getElementById('healthProgressText');
|
const progressText = document.getElementById('healthProgressText');
|
||||||
if (progressBar && progressText) {
|
if (progressBar && progressText) {
|
||||||
@@ -2488,11 +2492,28 @@ async function checkInstanceHealth(instanceUrl) {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error('Instance is not healthy');
|
throw new Error('Instance is not healthy');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If step element doesn't exist, just log the status
|
||||||
|
console.log(`Health check - Instance status: ${data.status}`);
|
||||||
|
if (data.status === 'active') {
|
||||||
|
const elapsedTime = Math.round((Date.now() - startTime) / 1000);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: data,
|
||||||
|
attempts: currentAttempt,
|
||||||
|
elapsedTime: elapsedTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Health check attempt ${currentAttempt} failed:`, error);
|
console.error(`Health check attempt ${currentAttempt} failed:`, error);
|
||||||
|
|
||||||
// Update status to show current attempt and elapsed time
|
// Update status to show current attempt and elapsed time - check if step element exists
|
||||||
const healthStepElement = document.querySelectorAll('.step-item')[10];
|
const isUpdate = window.isUpdate;
|
||||||
|
const healthStepIndex = isUpdate ? 4 : 10;
|
||||||
|
const healthStepElement = document.querySelectorAll('.step-item')[healthStepIndex];
|
||||||
|
|
||||||
|
if (healthStepElement) {
|
||||||
const statusText = healthStepElement.querySelector('.step-status');
|
const statusText = healthStepElement.querySelector('.step-status');
|
||||||
const elapsedTime = Math.round((Date.now() - startTime) / 1000);
|
const elapsedTime = Math.round((Date.now() - startTime) / 1000);
|
||||||
statusText.textContent = `Health check failed (Attempt ${currentAttempt}/${maxRetries}, ${elapsedTime}s elapsed): ${error.message}`;
|
statusText.textContent = `Health check failed (Attempt ${currentAttempt}/${maxRetries}, ${elapsedTime}s elapsed): ${error.message}`;
|
||||||
@@ -2506,10 +2527,14 @@ async function checkInstanceHealth(instanceUrl) {
|
|||||||
progressBar.textContent = `${Math.round(progressPercent)}%`;
|
progressBar.textContent = `${Math.round(progressPercent)}%`;
|
||||||
progressText.textContent = `Attempt ${currentAttempt}/${maxRetries} (${elapsedTime}s elapsed)`;
|
progressText.textContent = `Attempt ${currentAttempt}/${maxRetries} (${elapsedTime}s elapsed)`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (currentAttempt === maxRetries || (Date.now() - startTime > maxTotalTime)) {
|
if (currentAttempt === maxRetries || (Date.now() - startTime > maxTotalTime)) {
|
||||||
// Update progress bar to show failure
|
// Update progress bar to show failure if it exists
|
||||||
|
const progressBar = document.getElementById('healthProgress');
|
||||||
|
const progressText = document.getElementById('healthProgressText');
|
||||||
if (progressBar && progressText) {
|
if (progressBar && progressText) {
|
||||||
|
const elapsedTime = Math.round((Date.now() - startTime) / 1000);
|
||||||
progressBar.classList.remove('progress-bar-animated');
|
progressBar.classList.remove('progress-bar-animated');
|
||||||
progressBar.classList.add('bg-danger');
|
progressBar.classList.add('bg-danger');
|
||||||
progressText.textContent = `Health check failed after ${currentAttempt} attempts (${elapsedTime}s)`;
|
progressText.textContent = `Health check failed after ${currentAttempt} attempts (${elapsedTime}s)`;
|
||||||
@@ -2517,7 +2542,7 @@ async function checkInstanceHealth(instanceUrl) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Health check failed after ${currentAttempt} attempts (${elapsedTime}s): ${error.message}`
|
error: `Health check failed after ${currentAttempt} attempts (${Math.round((Date.now() - startTime) / 1000)}s): ${error.message}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2525,7 +2550,8 @@ async function checkInstanceHealth(instanceUrl) {
|
|||||||
await new Promise(resolve => setTimeout(resolve, baseDelay));
|
await new Promise(resolve => setTimeout(resolve, baseDelay));
|
||||||
currentAttempt++;
|
currentAttempt++;
|
||||||
|
|
||||||
// Update progress bar in real-time
|
// Update progress bar in real-time if it exists
|
||||||
|
const elapsedTime = Math.round((Date.now() - startTime) / 1000);
|
||||||
updateHealthProgress(currentAttempt, maxRetries, elapsedTime);
|
updateHealthProgress(currentAttempt, maxRetries, elapsedTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2543,127 +2569,6 @@ function updateHealthProgress(currentAttempt, maxRetries, elapsedTime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function authenticateInstance(instanceUrl, instanceId) {
|
|
||||||
try {
|
|
||||||
// First check if instance is already authenticated
|
|
||||||
const instancesResponse = await fetch('/instances');
|
|
||||||
const text = await instancesResponse.text();
|
|
||||||
const parser = new DOMParser();
|
|
||||||
const doc = parser.parseFromString(text, 'text/html');
|
|
||||||
|
|
||||||
// Find the instance with matching URL
|
|
||||||
const instanceRow = Array.from(doc.querySelectorAll('table tbody tr')).find(row => {
|
|
||||||
const urlCell = row.querySelector('td:nth-child(7) a'); // URL is in the 7th column
|
|
||||||
return urlCell && urlCell.textContent.trim() === instanceUrl;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!instanceRow) {
|
|
||||||
throw new Error('Instance not found in database');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the instance ID from the status badge's data attribute
|
|
||||||
const statusBadge = instanceRow.querySelector('[data-instance-id]');
|
|
||||||
if (!statusBadge) {
|
|
||||||
throw new Error('Could not find instance ID');
|
|
||||||
}
|
|
||||||
|
|
||||||
const dbInstanceId = statusBadge.dataset.instanceId;
|
|
||||||
|
|
||||||
// Check if already authenticated
|
|
||||||
const authStatusResponse = await fetch(`/instances/${dbInstanceId}/auth-status`);
|
|
||||||
if (!authStatusResponse.ok) {
|
|
||||||
throw new Error('Failed to check authentication status');
|
|
||||||
}
|
|
||||||
|
|
||||||
const authStatus = await authStatusResponse.json();
|
|
||||||
if (authStatus.authenticated) {
|
|
||||||
console.log('Instance is already authenticated');
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: 'Instance is already authenticated',
|
|
||||||
alreadyAuthenticated: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Attempting login to:', `${instanceUrl}/api/admin/login`);
|
|
||||||
|
|
||||||
// First login to get token
|
|
||||||
const loginResponse = await fetch(`${instanceUrl}/api/admin/login`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: 'administrator@docupulse.com',
|
|
||||||
password: 'changeme'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!loginResponse.ok) {
|
|
||||||
const errorText = await loginResponse.text();
|
|
||||||
throw new Error(`Login failed: ${errorText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginData = await loginResponse.json();
|
|
||||||
if (loginData.status !== 'success' || !loginData.token) {
|
|
||||||
throw new Error('Login failed: Invalid response from server');
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = loginData.token;
|
|
||||||
|
|
||||||
// Then create management API key
|
|
||||||
const keyResponse = await fetch(`${instanceUrl}/api/admin/management-api-key`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Authorization': `Bearer ${token}`
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: `Connection from ${window.location.hostname}`
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!keyResponse.ok) {
|
|
||||||
const errorText = await keyResponse.text();
|
|
||||||
throw new Error(`Failed to create API key: ${errorText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyData = await keyResponse.json();
|
|
||||||
if (!keyData.api_key) {
|
|
||||||
throw new Error('No API key received from server');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the token to our database
|
|
||||||
const saveResponse = await fetch(`/instances/${dbInstanceId}/save-token`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ token: keyData.api_key })
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!saveResponse.ok) {
|
|
||||||
const errorText = await saveResponse.text();
|
|
||||||
throw new Error(`Failed to save token: ${errorText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: 'Successfully authenticated instance',
|
|
||||||
alreadyAuthenticated: false
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Authentication error:', error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error.message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function applyCompanyInformation(instanceUrl, company) {
|
async function applyCompanyInformation(instanceUrl, company) {
|
||||||
try {
|
try {
|
||||||
console.log('Applying company information to:', instanceUrl);
|
console.log('Applying company information to:', instanceUrl);
|
||||||
@@ -3303,30 +3208,16 @@ function generateStackName(port) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add new function to update existing stack
|
// Add new function to update existing stack
|
||||||
async function updateStack(dockerComposeContent, stackId, port) {
|
async function updateStack(dockerComposeContent, stackId, port, instanceData = null) {
|
||||||
try {
|
try {
|
||||||
console.log('Updating existing stack:', stackId);
|
console.log('Updating existing stack:', stackId);
|
||||||
console.log('Port:', port);
|
console.log('Port:', port);
|
||||||
console.log('Modified docker-compose content length:', dockerComposeContent.length);
|
console.log('Modified docker-compose content length:', dockerComposeContent.length);
|
||||||
|
|
||||||
const response = await fetch('/api/admin/update-stack', {
|
// For updates, we only need to update version-related environment variables
|
||||||
method: 'POST',
|
// All other environment variables (pricing tiers, quotas, etc.) should be preserved
|
||||||
headers: {
|
// We also preserve the existing docker-compose configuration
|
||||||
'Content-Type': 'application/json',
|
const envVars = [
|
||||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
stack_id: stackId,
|
|
||||||
StackFileContent: dockerComposeContent,
|
|
||||||
Env: [
|
|
||||||
{
|
|
||||||
name: 'PORT',
|
|
||||||
value: port.toString()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ISMASTER',
|
|
||||||
value: 'false'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'APP_VERSION',
|
name: 'APP_VERSION',
|
||||||
value: window.currentDeploymentVersion || 'unknown'
|
value: window.currentDeploymentVersion || 'unknown'
|
||||||
@@ -3343,7 +3234,21 @@ async function updateStack(dockerComposeContent, stackId, port) {
|
|||||||
name: 'DEPLOYED_AT',
|
name: 'DEPLOYED_AT',
|
||||||
value: new Date().toISOString()
|
value: new Date().toISOString()
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
|
console.log('Updating stack with version environment variables:', envVars);
|
||||||
|
console.log('Preserving existing docker-compose configuration');
|
||||||
|
|
||||||
|
const response = await fetch('/api/admin/update-stack', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
stack_id: stackId,
|
||||||
|
// Don't send StackFileContent during updates - preserve existing configuration
|
||||||
|
Env: envVars
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3460,3 +3365,124 @@ async function updateStack(dockerComposeContent, stackId, port) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function authenticateInstance(instanceUrl, instanceId) {
|
||||||
|
try {
|
||||||
|
// First check if instance is already authenticated
|
||||||
|
const instancesResponse = await fetch('/instances');
|
||||||
|
const text = await instancesResponse.text();
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(text, 'text/html');
|
||||||
|
|
||||||
|
// Find the instance with matching URL
|
||||||
|
const instanceRow = Array.from(doc.querySelectorAll('table tbody tr')).find(row => {
|
||||||
|
const urlCell = row.querySelector('td:nth-child(7) a'); // URL is in the 7th column
|
||||||
|
return urlCell && urlCell.textContent.trim() === instanceUrl;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!instanceRow) {
|
||||||
|
throw new Error('Instance not found in database');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the instance ID from the status badge's data attribute
|
||||||
|
const statusBadge = instanceRow.querySelector('[data-instance-id]');
|
||||||
|
if (!statusBadge) {
|
||||||
|
throw new Error('Could not find instance ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbInstanceId = statusBadge.dataset.instanceId;
|
||||||
|
|
||||||
|
// Check if already authenticated
|
||||||
|
const authStatusResponse = await fetch(`/instances/${dbInstanceId}/auth-status`);
|
||||||
|
if (!authStatusResponse.ok) {
|
||||||
|
throw new Error('Failed to check authentication status');
|
||||||
|
}
|
||||||
|
|
||||||
|
const authStatus = await authStatusResponse.json();
|
||||||
|
if (authStatus.authenticated) {
|
||||||
|
console.log('Instance is already authenticated');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Instance is already authenticated',
|
||||||
|
alreadyAuthenticated: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Attempting login to:', `${instanceUrl}/api/admin/login`);
|
||||||
|
|
||||||
|
// First login to get token
|
||||||
|
const loginResponse = await fetch(`${instanceUrl}/api/admin/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: 'administrator@docupulse.com',
|
||||||
|
password: 'changeme'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!loginResponse.ok) {
|
||||||
|
const errorText = await loginResponse.text();
|
||||||
|
throw new Error(`Login failed: ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginData = await loginResponse.json();
|
||||||
|
if (loginData.status !== 'success' || !loginData.token) {
|
||||||
|
throw new Error('Login failed: Invalid response from server');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = loginData.token;
|
||||||
|
|
||||||
|
// Then create management API key
|
||||||
|
const keyResponse = await fetch(`${instanceUrl}/api/admin/management-api-key`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: `Connection from ${window.location.hostname}`
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!keyResponse.ok) {
|
||||||
|
const errorText = await keyResponse.text();
|
||||||
|
throw new Error(`Failed to create API key: ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyData = await keyResponse.json();
|
||||||
|
if (!keyData.api_key) {
|
||||||
|
throw new Error('No API key received from server');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the token to our database
|
||||||
|
const saveResponse = await fetch(`/instances/${dbInstanceId}/save-token`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ token: keyData.api_key })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!saveResponse.ok) {
|
||||||
|
const errorText = await saveResponse.text();
|
||||||
|
throw new Error(`Failed to save token: ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Successfully authenticated instance',
|
||||||
|
alreadyAuthenticated: false
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Authentication error:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user