steps for launching
This commit is contained in:
Binary file not shown.
@@ -756,6 +756,46 @@ def list_gitea_repos():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'message': f'Failed to list repositories: {str(e)}'}), 400
|
return jsonify({'message': f'Failed to list repositories: {str(e)}'}), 400
|
||||||
|
|
||||||
|
@admin_api.route('/list-gitea-branches', methods=['POST'])
|
||||||
|
@csrf.exempt
|
||||||
|
def list_gitea_branches():
|
||||||
|
"""List branches from a Gitea repository"""
|
||||||
|
data = request.get_json()
|
||||||
|
if not data or 'url' not in data or 'token' not in data or 'repo' not in data:
|
||||||
|
return jsonify({'message': 'Missing required fields'}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try different authentication methods
|
||||||
|
headers = {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
# First try token in Authorization header
|
||||||
|
headers['Authorization'] = f'token {data["token"]}'
|
||||||
|
|
||||||
|
# Get repository branches
|
||||||
|
response = requests.get(
|
||||||
|
f'{data["url"]}/api/v1/repos/{data["repo"]}/branches',
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
|
||||||
|
# If that fails, try token as query parameter
|
||||||
|
if response.status_code != 200:
|
||||||
|
response = requests.get(
|
||||||
|
f'{data["url"]}/api/v1/repos/{data["repo"]}/branches?token={data["token"]}',
|
||||||
|
headers={'Accept': 'application/json'}
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return jsonify({
|
||||||
|
'branches': response.json()
|
||||||
|
}), 200
|
||||||
|
else:
|
||||||
|
return jsonify({'message': f'Failed to list branches: {response.json().get("message", "Unknown error")}'}), 400
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'message': f'Failed to list branches: {str(e)}'}), 400
|
||||||
|
|
||||||
@admin_api.route('/list-gitlab-repos', methods=['POST'])
|
@admin_api.route('/list-gitlab-repos', methods=['POST'])
|
||||||
@csrf.exempt
|
@csrf.exempt
|
||||||
def list_gitlab_repos():
|
def list_gitlab_repos():
|
||||||
|
|||||||
@@ -379,11 +379,13 @@ def init_routes(main_bp):
|
|||||||
# Get connection settings
|
# Get connection settings
|
||||||
portainer_settings = KeyValueSettings.get_value('portainer_settings')
|
portainer_settings = KeyValueSettings.get_value('portainer_settings')
|
||||||
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
||||||
|
git_settings = KeyValueSettings.get_value('git_settings')
|
||||||
|
|
||||||
return render_template('main/instances.html',
|
return render_template('main/instances.html',
|
||||||
instances=instances,
|
instances=instances,
|
||||||
portainer_settings=portainer_settings,
|
portainer_settings=portainer_settings,
|
||||||
nginx_settings=nginx_settings)
|
nginx_settings=nginx_settings,
|
||||||
|
git_settings=git_settings)
|
||||||
|
|
||||||
@main_bp.route('/instances/add', methods=['POST'])
|
@main_bp.route('/instances/add', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>PORT</th>
|
||||||
<th>Company</th>
|
<th>Company</th>
|
||||||
<th>Rooms</th>
|
<th>Rooms</th>
|
||||||
<th>Conversations</th>
|
<th>Conversations</th>
|
||||||
@@ -156,18 +156,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="step-item" data-step="2">
|
<div class="step-item" data-step="2">
|
||||||
<div class="step-circle">2</div>
|
<div class="step-circle">2</div>
|
||||||
<div class="step-label">Configuration</div>
|
<div class="step-label">Repository</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step-item" data-step="3">
|
<div class="step-item" data-step="3">
|
||||||
<div class="step-circle">3</div>
|
<div class="step-circle">3</div>
|
||||||
<div class="step-label">Resources</div>
|
<div class="step-label">Company</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step-item" data-step="4">
|
<div class="step-item" data-step="4">
|
||||||
<div class="step-circle">4</div>
|
<div class="step-circle">4</div>
|
||||||
<div class="step-label">Review</div>
|
<div class="step-label">Variables</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step-item" data-step="5">
|
<div class="step-item" data-step="5">
|
||||||
<div class="step-circle">5</div>
|
<div class="step-circle">5</div>
|
||||||
|
<div class="step-label">White Label</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-item" data-step="6">
|
||||||
|
<div class="step-circle">6</div>
|
||||||
<div class="step-label">Launch</div>
|
<div class="step-label">Launch</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,9 +180,6 @@
|
|||||||
<div class="step-content">
|
<div class="step-content">
|
||||||
<!-- Step 1 -->
|
<!-- Step 1 -->
|
||||||
<div class="step-pane active" id="step1">
|
<div class="step-pane active" id="step1">
|
||||||
<h4>Connection Verification</h4>
|
|
||||||
<p class="text-muted mb-4">Let's verify that your connections are properly configured before proceeding.</p>
|
|
||||||
|
|
||||||
<!-- Portainer Connection -->
|
<!-- Portainer Connection -->
|
||||||
<div class="connection-check mb-4">
|
<div class="connection-check mb-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
@@ -223,24 +224,149 @@
|
|||||||
|
|
||||||
<!-- Step 2 -->
|
<!-- Step 2 -->
|
||||||
<div class="step-pane" id="step2">
|
<div class="step-pane" id="step2">
|
||||||
<h4>Configuration</h4>
|
<div class="step-content" id="step2">
|
||||||
<p class="text-muted">Configure your instance settings.</p>
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Repository</label>
|
||||||
|
<select class="form-select" id="giteaRepoSelect">
|
||||||
|
<option value="">Loading repositories...</option>
|
||||||
|
</select>
|
||||||
|
<div class="form-text">Select the repository containing your application code</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Branch</label>
|
||||||
|
<select class="form-select" id="giteaBranchSelect" disabled>
|
||||||
|
<option value="">Select a repository first</option>
|
||||||
|
</select>
|
||||||
|
<div class="form-text">Select the branch to deploy</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 3 -->
|
<!-- Step 3 -->
|
||||||
<div class="step-pane" id="step3">
|
<div class="step-pane" id="step3">
|
||||||
<h4>Resources</h4>
|
<div class="step-content">
|
||||||
<p class="text-muted">Set up your instance resources.</p>
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Company Name <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="companyName" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Industry</label>
|
||||||
|
<input type="text" class="form-control" id="industry">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Street Address <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="streetAddress" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">City <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="city" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 mb-3">
|
||||||
|
<label class="form-label">State</label>
|
||||||
|
<input type="text" class="form-control" id="state">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 mb-3">
|
||||||
|
<label class="form-label">ZIP Code <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="zipCode" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Country <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="country" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Website</label>
|
||||||
|
<input type="url" class="form-control" id="website">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Email <span class="text-danger">*</span></label>
|
||||||
|
<input type="email" class="form-control" id="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Phone <span class="text-danger">*</span></label>
|
||||||
|
<input type="tel" class="form-control" id="phone" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Company Description</label>
|
||||||
|
<textarea class="form-control" id="companyDescription" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 4 -->
|
<!-- Step 4 -->
|
||||||
<div class="step-pane" id="step4">
|
<div class="step-pane" id="step4">
|
||||||
<h4>Review</h4>
|
<h5 class="mb-4">Port Configuration</h5>
|
||||||
<p class="text-muted">Review your instance configuration.</p>
|
<div class="mb-3">
|
||||||
|
<label for="port" class="form-label">Port Number <span class="text-danger">*</span></label>
|
||||||
|
<input type="number" class="form-control" id="port" required min="1024" max="65535">
|
||||||
|
<div class="form-text">Ports below 1024 are reserved for system use. Suggested port: <span id="suggestedPort">3000</span></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 5 -->
|
<!-- Step 5: White Label -->
|
||||||
<div class="step-pane" id="step5">
|
<div class="step-pane" id="step5">
|
||||||
|
<div class="step-content">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title mb-4">White Label Configuration</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Primary Color</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="color" class="form-control form-control-color" id="primaryColor" value="#16767B" title="Choose primary color">
|
||||||
|
<input type="text" class="form-control" id="primaryColorRGB" value="22,118,123" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">Default: RGB(22,118,123)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Secondary Color</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="color" class="form-control form-control-color" id="secondaryColor" value="#741B5F" title="Choose secondary color">
|
||||||
|
<input type="text" class="form-control" id="secondaryColorRGB" value="116,27,95" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">Default: RGB(116,27,95)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<h6>Preview</h6>
|
||||||
|
<div id="previewContainer">
|
||||||
|
<div class="preview-box p-3 rounded mb-2" id="primaryPreview" style="background-color: #16767B; color: white;">
|
||||||
|
Primary Color Sample
|
||||||
|
</div>
|
||||||
|
<div class="preview-box p-3 rounded" id="secondaryPreview" style="background-color: #741B5F; color: white;">
|
||||||
|
Secondary Color Sample
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 6 -->
|
||||||
|
<div class="step-pane" id="step6">
|
||||||
<h4>Launch</h4>
|
<h4>Launch</h4>
|
||||||
<p class="text-muted">Ready to launch your instance!</p>
|
<p class="text-muted">Ready to launch your instance!</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -448,6 +574,9 @@ let authModal;
|
|||||||
let launchStepsModal;
|
let launchStepsModal;
|
||||||
let currentStep = 1;
|
let currentStep = 1;
|
||||||
|
|
||||||
|
// Update the total number of steps
|
||||||
|
const totalSteps = 6;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
addInstanceModal = new bootstrap.Modal(document.getElementById('addInstanceModal'));
|
addInstanceModal = new bootstrap.Modal(document.getElementById('addInstanceModal'));
|
||||||
editInstanceModal = new bootstrap.Modal(document.getElementById('editInstanceModal'));
|
editInstanceModal = new bootstrap.Modal(document.getElementById('editInstanceModal'));
|
||||||
@@ -484,6 +613,47 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Set up periodic status checks (every 30 seconds)
|
// Set up periodic status checks (every 30 seconds)
|
||||||
setInterval(checkAllInstanceStatuses, 30000);
|
setInterval(checkAllInstanceStatuses, 30000);
|
||||||
|
|
||||||
|
// Update color picker functionality
|
||||||
|
const primaryColor = document.getElementById('primaryColor');
|
||||||
|
const secondaryColor = document.getElementById('secondaryColor');
|
||||||
|
const primaryColorRGB = document.getElementById('primaryColorRGB');
|
||||||
|
const secondaryColorRGB = document.getElementById('secondaryColorRGB');
|
||||||
|
const primaryPreview = document.getElementById('primaryPreview');
|
||||||
|
const secondaryPreview = document.getElementById('secondaryPreview');
|
||||||
|
|
||||||
|
function updateColorPreview() {
|
||||||
|
// Update preview boxes directly instead of using CSS variables
|
||||||
|
primaryPreview.style.backgroundColor = primaryColor.value;
|
||||||
|
secondaryPreview.style.backgroundColor = secondaryColor.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToRgb(hex) {
|
||||||
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
return result ?
|
||||||
|
`${parseInt(result[1], 16)},${parseInt(result[2], 16)},${parseInt(result[3], 16)}` :
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToHex(r, g, b) {
|
||||||
|
return '#' + [r, g, b].map(x => {
|
||||||
|
const hex = x.toString(16);
|
||||||
|
return hex.length === 1 ? '0' + hex : hex;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
primaryColor.addEventListener('input', function() {
|
||||||
|
primaryColorRGB.value = hexToRgb(this.value);
|
||||||
|
updateColorPreview();
|
||||||
|
});
|
||||||
|
|
||||||
|
secondaryColor.addEventListener('input', function() {
|
||||||
|
secondaryColorRGB.value = hexToRgb(this.value);
|
||||||
|
updateColorPreview();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize color preview
|
||||||
|
updateColorPreview();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Function to check status of all instances
|
// Function to check status of all instances
|
||||||
@@ -1105,51 +1275,48 @@ function nextStepLaunchInstance() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateStepDisplay() {
|
function updateStepDisplay() {
|
||||||
// Update step indicators
|
// Hide all steps
|
||||||
document.querySelectorAll('.step-item').forEach((item, index) => {
|
document.querySelectorAll('.step-pane').forEach(pane => {
|
||||||
const stepNum = index + 1;
|
pane.classList.remove('active');
|
||||||
item.classList.remove('active', 'completed');
|
|
||||||
if (stepNum === currentStep) {
|
|
||||||
item.classList.add('active');
|
|
||||||
} else if (stepNum < currentStep) {
|
|
||||||
item.classList.add('completed');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update step content
|
// Show current step
|
||||||
document.querySelectorAll('.step-pane').forEach((pane, index) => {
|
document.getElementById(`step${currentStep}`).classList.add('active');
|
||||||
pane.classList.remove('active');
|
|
||||||
if (index + 1 === currentStep) {
|
// Update step indicators
|
||||||
pane.classList.add('active');
|
document.querySelectorAll('.step-item').forEach(item => {
|
||||||
}
|
const step = parseInt(item.getAttribute('data-step'));
|
||||||
|
item.classList.toggle('active', step === currentStep);
|
||||||
|
item.classList.toggle('completed', step < currentStep);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update buttons
|
// Update buttons
|
||||||
const prevButton = document.querySelector('#launchStepsModal .btn-secondary');
|
const prevButton = document.querySelector('.modal-footer .btn-secondary');
|
||||||
const nextButton = document.querySelector('#launchStepsModal .btn-primary');
|
const nextButton = document.querySelector('.modal-footer .btn-primary');
|
||||||
|
|
||||||
prevButton.style.display = currentStep === 1 ? 'none' : 'inline-block';
|
prevButton.style.display = currentStep === 1 ? 'none' : 'inline-block';
|
||||||
nextButton.innerHTML = currentStep === 5 ? 'Launch <i class="fas fa-rocket ms-1"></i>' : 'Next <i class="fas fa-arrow-right ms-1"></i>';
|
nextButton.innerHTML = currentStep === totalSteps ? 'Launch Instance' : 'Next <i class="fas fa-arrow-right ms-1"></i>';
|
||||||
|
|
||||||
|
// If we're on step 4, get the next available port
|
||||||
|
if (currentStep === 4) {
|
||||||
|
getNextAvailablePort();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextStep() {
|
function nextStep() {
|
||||||
if (currentStep < 5) {
|
if (currentStep === 2 && !validateStep2()) {
|
||||||
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
if (currentStep === 3 && !validateStep3()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentStep < totalSteps) {
|
||||||
currentStep++;
|
currentStep++;
|
||||||
updateStepDisplay();
|
updateStepDisplay();
|
||||||
} else {
|
} else {
|
||||||
// Handle launch
|
// Handle final step (launch instance)
|
||||||
console.log('Launching instance...');
|
launchInstance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1232,5 +1399,226 @@ async function verifyConnections() {
|
|||||||
nginxDetails.innerHTML = `<small class="text-danger">${error.message || 'Error checking NGINX connection'}</small>`;
|
nginxDetails.innerHTML = `<small class="text-danger">${error.message || 'Error checking NGINX connection'}</small>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Repository Selection Functions
|
||||||
|
async function loadRepositories() {
|
||||||
|
const repoSelect = document.getElementById('giteaRepoSelect');
|
||||||
|
const branchSelect = document.getElementById('giteaBranchSelect');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const gitSettings = JSON.parse('{{ git_settings|tojson|safe }}');
|
||||||
|
if (!gitSettings || !gitSettings.url || !gitSettings.token) {
|
||||||
|
throw new Error('No Git settings found. Please configure Git in the settings page.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load repositories using the correct endpoint
|
||||||
|
const repoResponse = await fetch('/api/admin/list-gitea-repos', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: gitSettings.url,
|
||||||
|
token: gitSettings.token
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!repoResponse.ok) {
|
||||||
|
throw new Error('Failed to load repositories');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await repoResponse.json();
|
||||||
|
|
||||||
|
if (data.repositories) {
|
||||||
|
repoSelect.innerHTML = '<option value="">Select a repository</option>' +
|
||||||
|
data.repositories.map(repo =>
|
||||||
|
`<option value="${repo.full_name}" ${repo.full_name === gitSettings.repo ? 'selected' : ''}>${repo.full_name}</option>`
|
||||||
|
).join('');
|
||||||
|
repoSelect.disabled = false;
|
||||||
|
|
||||||
|
// If we have a saved repository, load its branches
|
||||||
|
if (gitSettings.repo) {
|
||||||
|
loadBranches(gitSettings.repo);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repoSelect.innerHTML = '<option value="">No repositories found</option>';
|
||||||
|
repoSelect.disabled = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading repositories:', error);
|
||||||
|
repoSelect.innerHTML = `<option value="">Error: ${error.message}</option>`;
|
||||||
|
repoSelect.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadBranches(repoId) {
|
||||||
|
const branchSelect = document.getElementById('giteaBranchSelect');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const gitSettings = JSON.parse('{{ git_settings|tojson|safe }}');
|
||||||
|
if (!gitSettings || !gitSettings.url || !gitSettings.token) {
|
||||||
|
throw new Error('No Git settings found. Please configure Git in the settings page.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/admin/list-gitea-branches', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: gitSettings.url,
|
||||||
|
token: gitSettings.token,
|
||||||
|
repo: repoId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to load branches');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.branches) {
|
||||||
|
branchSelect.innerHTML = '<option value="">Select a branch</option>' +
|
||||||
|
data.branches.map(branch =>
|
||||||
|
`<option value="${branch.name}" ${branch.name === 'master' ? 'selected' : ''}>${branch.name}</option>`
|
||||||
|
).join('');
|
||||||
|
branchSelect.disabled = false;
|
||||||
|
} else {
|
||||||
|
branchSelect.innerHTML = '<option value="">No branches found</option>';
|
||||||
|
branchSelect.disabled = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading branches:', error);
|
||||||
|
branchSelect.innerHTML = `<option value="">Error: ${error.message}</option>`;
|
||||||
|
branchSelect.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event Listeners for Repository Selection
|
||||||
|
document.getElementById('giteaRepoSelect').addEventListener('change', function() {
|
||||||
|
const repoId = this.value;
|
||||||
|
if (repoId) {
|
||||||
|
loadBranches(repoId);
|
||||||
|
} else {
|
||||||
|
document.getElementById('giteaBranchSelect').innerHTML = '<option value="">Select a repository first</option>';
|
||||||
|
document.getElementById('giteaBranchSelect').disabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load repositories when step 2 is shown
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const step2Content = document.getElementById('step2');
|
||||||
|
const observer = new MutationObserver(function(mutations) {
|
||||||
|
mutations.forEach(function(mutation) {
|
||||||
|
if (mutation.target.classList.contains('active')) {
|
||||||
|
loadRepositories();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(step2Content, { attributes: true, attributeFilter: ['class'] });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to get the next available port
|
||||||
|
async function getNextAvailablePort() {
|
||||||
|
try {
|
||||||
|
// Get all existing instances
|
||||||
|
const response = await fetch('/instances');
|
||||||
|
const text = await response.text();
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(text, 'text/html');
|
||||||
|
|
||||||
|
// Get all port numbers from the table
|
||||||
|
const portCells = doc.querySelectorAll('table tbody tr td:first-child');
|
||||||
|
const ports = Array.from(portCells)
|
||||||
|
.map(cell => {
|
||||||
|
const portText = cell.textContent.trim();
|
||||||
|
// Only parse if it's a number
|
||||||
|
return /^\d+$/.test(portText) ? parseInt(portText) : null;
|
||||||
|
})
|
||||||
|
.filter(port => port !== null)
|
||||||
|
.sort((a, b) => a - b); // Sort numerically
|
||||||
|
|
||||||
|
console.log('Found existing ports:', ports);
|
||||||
|
|
||||||
|
// Find the next available port
|
||||||
|
let nextPort = 10339; // Start from the next port after your highest
|
||||||
|
while (ports.includes(nextPort)) {
|
||||||
|
nextPort++;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Next available port:', nextPort);
|
||||||
|
|
||||||
|
// Set the suggested port
|
||||||
|
document.getElementById('port').value = nextPort;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting next available port:', error);
|
||||||
|
// Default to 10339 if there's an error
|
||||||
|
document.getElementById('port').value = 10339;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to validate step 2
|
||||||
|
function validateStep2() {
|
||||||
|
const repositorySelect = document.getElementById('giteaRepoSelect');
|
||||||
|
const branchSelect = document.getElementById('giteaBranchSelect');
|
||||||
|
|
||||||
|
if (!repositorySelect.value) {
|
||||||
|
alert('Please select a repository');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!branchSelect.value) {
|
||||||
|
alert('Please select a branch');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to validate step 3
|
||||||
|
function validateStep3() {
|
||||||
|
const requiredFields = [
|
||||||
|
{ id: 'companyName', name: 'Company Name' },
|
||||||
|
{ id: 'streetAddress', name: 'Street Address' },
|
||||||
|
{ id: 'city', name: 'City' },
|
||||||
|
{ id: 'zipCode', name: 'ZIP Code' },
|
||||||
|
{ id: 'country', name: 'Country' },
|
||||||
|
{ id: 'email', name: 'Email' },
|
||||||
|
{ id: 'phone', name: 'Phone' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const missingFields = requiredFields.filter(field => {
|
||||||
|
const element = document.getElementById(field.id);
|
||||||
|
return !element.value.trim();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (missingFields.length > 0) {
|
||||||
|
const fieldNames = missingFields.map(field => field.name).join(', ');
|
||||||
|
alert(`Please fill in all required fields: ${fieldNames}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate email format
|
||||||
|
const email = document.getElementById('email').value;
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!emailRegex.test(email)) {
|
||||||
|
alert('Please enter a valid email address');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate phone format (basic validation)
|
||||||
|
const phone = document.getElementById('phone').value;
|
||||||
|
const phoneRegex = /^[\d\s\-\+\(\)]{10,}$/;
|
||||||
|
if (!phoneRegex.test(phone)) {
|
||||||
|
alert('Please enter a valid phone number');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -17,18 +17,18 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form id="portainerForm" onsubmit="savePortainerConnection(event)">
|
<form id="portainerForm" onsubmit="savePortainerConnection(event)">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="portainerUrl" class="form-label">Portainer URL</label>
|
<label for="portainerUrl" class="form-label">Portainer Server URL</label>
|
||||||
<input type="url" class="form-control" id="portainerUrl" name="portainerUrl"
|
<input type="url" class="form-control" id="portainerUrl" name="portainerUrl"
|
||||||
placeholder="https://portainer.example.com" required
|
placeholder="https://portainer.example.com" required
|
||||||
value="{{ portainer_settings.url if portainer_settings and portainer_settings.url else '' }}">
|
value="{{ portainer_settings.url if portainer_settings and portainer_settings.url else '' }}">
|
||||||
<div class="form-text">The URL of your Portainer instance</div>
|
<div class="form-text">The URL of your Portainer server</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="portainerApiKey" class="form-label">API Key</label>
|
<label for="portainerApiKey" class="form-label">API Key</label>
|
||||||
<input type="password" class="form-control" id="portainerApiKey" name="portainerApiKey"
|
<input type="password" class="form-control" id="portainerApiKey" name="portainerApiKey"
|
||||||
placeholder="Enter your Portainer API key" required
|
placeholder="Enter your Portainer API key" required
|
||||||
value="{{ portainer_settings.api_key if portainer_settings and portainer_settings.api_key else '' }}">
|
value="{{ portainer_settings.api_key if portainer_settings and portainer_settings.api_key else '' }}">
|
||||||
<div class="form-text">You can generate this in Portainer under Settings > API Keys</div>
|
<div class="form-text">You can generate this in your Portainer user settings</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0">
|
<h5 class="mb-0">
|
||||||
<i class="fas fa-network-wired me-2"></i>NGINX Proxy Manager Connection
|
<i class="fas fa-globe me-2"></i>NGINX Connection
|
||||||
</h5>
|
</h5>
|
||||||
<button class="btn btn-sm btn-outline-primary" onclick="testNginxConnection()">
|
<button class="btn btn-sm btn-outline-primary" onclick="testNginxConnection()">
|
||||||
<i class="fas fa-plug me-1"></i>Test Connection
|
<i class="fas fa-plug me-1"></i>Test Connection
|
||||||
@@ -82,64 +82,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- GitLab Connection Card -->
|
|
||||||
<div class="col-md-6 mb-4">
|
|
||||||
<div class="card h-100">
|
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
|
||||||
<h5 class="mb-0">
|
|
||||||
<i class="fab fa-gitlab me-2"></i>GitLab Connection
|
|
||||||
</h5>
|
|
||||||
<button class="btn btn-sm btn-outline-primary" onclick="testGitConnection('gitlab')">
|
|
||||||
<i class="fas fa-plug me-1"></i>Test Connection
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<form id="gitlabForm" onsubmit="saveGitConnection(event, 'gitlab')">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="gitlabUrl" class="form-label">GitLab Server URL</label>
|
|
||||||
<input type="url" class="form-control" id="gitlabUrl" name="gitlabUrl"
|
|
||||||
placeholder="https://gitlab.com" required
|
|
||||||
value="{{ git_settings.url if git_settings and git_settings.provider == 'gitlab' and git_settings.url else '' }}">
|
|
||||||
<div class="form-text">The URL of your GitLab server (use https://gitlab.com for GitLab.com)</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="gitlabUsername" class="form-label">Username</label>
|
|
||||||
<input type="text" class="form-control" id="gitlabUsername" name="gitlabUsername"
|
|
||||||
placeholder="Enter your GitLab username" required
|
|
||||||
value="{{ git_settings.username if git_settings and git_settings.provider == 'gitlab' and git_settings.username else '' }}">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="gitlabToken" class="form-label">Personal Access Token</label>
|
|
||||||
<input type="password" class="form-control" id="gitlabToken" name="gitlabToken"
|
|
||||||
placeholder="Enter your GitLab personal access token" required
|
|
||||||
value="{{ git_settings.token if git_settings and git_settings.provider == 'gitlab' and git_settings.token else '' }}">
|
|
||||||
<div class="form-text">You can generate this in your GitLab user settings > Access Tokens</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="gitlabRepo" class="form-label">Repository</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<select class="form-select" id="gitlabRepo" name="gitlabRepo" required>
|
|
||||||
<option value="">Select a repository</option>
|
|
||||||
{% if git_settings and git_settings.provider == 'gitlab' and git_settings.repo %}
|
|
||||||
<option value="{{ git_settings.repo }}" selected>{{ git_settings.repo }}</option>
|
|
||||||
{% endif %}
|
|
||||||
</select>
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="loadGitlabRepos()">
|
|
||||||
<i class="fas fa-sync-alt"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="form-text">Select the repository to connect to</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<button type="submit" class="btn btn-primary">
|
|
||||||
<i class="fas fa-save me-1"></i>Save GitLab Settings
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Gitea Connection Card -->
|
<!-- Gitea Connection Card -->
|
||||||
<div class="col-md-6 mb-4">
|
<div class="col-md-6 mb-4">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
@@ -349,59 +291,11 @@ async function loadGiteaRepos() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load GitLab Repositories
|
|
||||||
async function loadGitlabRepos() {
|
|
||||||
const url = document.getElementById('gitlabUrl').value;
|
|
||||||
const token = document.getElementById('gitlabToken').value;
|
|
||||||
const repoSelect = document.getElementById('gitlabRepo');
|
|
||||||
const currentRepo = repoSelect.value; // Store current selection
|
|
||||||
|
|
||||||
if (!url || !token) {
|
|
||||||
showError('Please fill in the server URL and access token');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/admin/list-gitlab-repos', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRF-Token': getCsrfToken()
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ url, token })
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
repoSelect.innerHTML = '<option value="">Select a repository</option>';
|
|
||||||
|
|
||||||
data.repositories.forEach(repo => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = repo.path_with_namespace;
|
|
||||||
option.textContent = repo.path_with_namespace;
|
|
||||||
if (repo.path_with_namespace === currentRepo) { // Restore selection
|
|
||||||
option.selected = true;
|
|
||||||
}
|
|
||||||
repoSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error(data.message || 'Failed to load repositories');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load repositories on page load if settings exist
|
// Load repositories on page load if settings exist
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const gitSettings = JSON.parse('{{ git_settings|tojson|safe }}');
|
const gitSettings = JSON.parse('{{ git_settings|tojson|safe }}');
|
||||||
if (gitSettings) {
|
if (gitSettings && gitSettings.provider === 'gitea' && gitSettings.url && gitSettings.token) {
|
||||||
if (gitSettings.provider === 'gitea' && gitSettings.url && gitSettings.token) {
|
|
||||||
loadGiteaRepos();
|
loadGiteaRepos();
|
||||||
} else if (gitSettings.provider === 'gitlab' && gitSettings.url && gitSettings.token) {
|
|
||||||
loadGitlabRepos();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -414,8 +308,6 @@ async function testGitConnection(provider) {
|
|||||||
saveModal.show();
|
saveModal.show();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let data = {};
|
|
||||||
if (provider === 'gitea') {
|
|
||||||
const url = document.getElementById('giteaUrl').value;
|
const url = document.getElementById('giteaUrl').value;
|
||||||
const username = document.getElementById('giteaUsername').value;
|
const username = document.getElementById('giteaUsername').value;
|
||||||
const token = document.getElementById('giteaToken').value;
|
const token = document.getElementById('giteaToken').value;
|
||||||
@@ -424,28 +316,12 @@ async function testGitConnection(provider) {
|
|||||||
throw new Error('Please fill in all required fields');
|
throw new Error('Please fill in all required fields');
|
||||||
}
|
}
|
||||||
|
|
||||||
data = {
|
const data = {
|
||||||
provider: 'gitea',
|
provider: 'gitea',
|
||||||
url: url,
|
url: url,
|
||||||
username: username,
|
username: username,
|
||||||
token: token
|
token: token
|
||||||
};
|
};
|
||||||
} else if (provider === 'gitlab') {
|
|
||||||
const url = document.getElementById('gitlabUrl').value;
|
|
||||||
const username = document.getElementById('gitlabUsername').value;
|
|
||||||
const token = document.getElementById('gitlabToken').value;
|
|
||||||
|
|
||||||
if (!url || !username || !token) {
|
|
||||||
throw new Error('Please fill in all required fields');
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
provider: 'gitlab',
|
|
||||||
url: url,
|
|
||||||
username: username,
|
|
||||||
token: token
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch('/settings/test-git-connection', {
|
const response = await fetch('/settings/test-git-connection', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -664,8 +540,6 @@ async function saveGitConnection(event, provider) {
|
|||||||
messageElement.className = '';
|
messageElement.className = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let data = {};
|
|
||||||
if (provider === 'gitea') {
|
|
||||||
const url = document.getElementById('giteaUrl').value;
|
const url = document.getElementById('giteaUrl').value;
|
||||||
const username = document.getElementById('giteaUsername').value;
|
const username = document.getElementById('giteaUsername').value;
|
||||||
const token = document.getElementById('giteaToken').value;
|
const token = document.getElementById('giteaToken').value;
|
||||||
@@ -677,7 +551,7 @@ async function saveGitConnection(event, provider) {
|
|||||||
throw new Error('Please fill in all required fields');
|
throw new Error('Please fill in all required fields');
|
||||||
}
|
}
|
||||||
|
|
||||||
data = {
|
const data = {
|
||||||
provider: 'gitea',
|
provider: 'gitea',
|
||||||
url: url,
|
url: url,
|
||||||
username: username,
|
username: username,
|
||||||
@@ -686,24 +560,6 @@ async function saveGitConnection(event, provider) {
|
|||||||
password: password,
|
password: password,
|
||||||
otp: otp
|
otp: otp
|
||||||
};
|
};
|
||||||
} else if (provider === 'gitlab') {
|
|
||||||
const url = document.getElementById('gitlabUrl').value;
|
|
||||||
const username = document.getElementById('gitlabUsername').value;
|
|
||||||
const token = document.getElementById('gitlabToken').value;
|
|
||||||
const repo = document.getElementById('gitlabRepo').value;
|
|
||||||
|
|
||||||
if (!url || !username || !token || !repo) {
|
|
||||||
throw new Error('Please fill in all required fields');
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
provider: 'gitlab',
|
|
||||||
url: url,
|
|
||||||
username: username,
|
|
||||||
token: token,
|
|
||||||
repo: repo
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch('/settings/save-git-connection', {
|
const response = await fetch('/settings/save-git-connection', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
Reference in New Issue
Block a user