@@ -223,24 +224,149 @@
-
Configuration
-
Configure your instance settings.
+
+
+
+
+
+
+
Select the repository containing your application code
+
+
+
+
+
Select the branch to deploy
+
+
+
+
-
Resources
-
Set up your instance resources.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
Review
-
Review your instance configuration.
+
Port Configuration
+
+
+
+
Ports below 1024 are reserved for system use. Suggested port: 3000
+
-
+
+
+
+
+
White Label Configuration
+
+
+
+
+
+
+
+
+
Default: RGB(22,118,123)
+
+
+
+
+
+
+
+
+
+
Default: RGB(116,27,95)
+
+
+
+
+
Preview
+
+
+ Primary Color Sample
+
+
+ Secondary Color Sample
+
+
+
+
+
+
+
+
+
+
Launch
Ready to launch your instance!
@@ -448,6 +574,9 @@ let authModal;
let launchStepsModal;
let currentStep = 1;
+// Update the total number of steps
+const totalSteps = 6;
+
document.addEventListener('DOMContentLoaded', function() {
addInstanceModal = new bootstrap.Modal(document.getElementById('addInstanceModal'));
editInstanceModal = new bootstrap.Modal(document.getElementById('editInstanceModal'));
@@ -484,6 +613,47 @@ document.addEventListener('DOMContentLoaded', function() {
// Set up periodic status checks (every 30 seconds)
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
@@ -1105,51 +1275,48 @@ function nextStepLaunchInstance() {
}
function updateStepDisplay() {
- // Update step indicators
- document.querySelectorAll('.step-item').forEach((item, index) => {
- const stepNum = index + 1;
- item.classList.remove('active', 'completed');
- if (stepNum === currentStep) {
- item.classList.add('active');
- } else if (stepNum < currentStep) {
- item.classList.add('completed');
- }
+ // Hide all steps
+ document.querySelectorAll('.step-pane').forEach(pane => {
+ pane.classList.remove('active');
});
- // Update step content
- document.querySelectorAll('.step-pane').forEach((pane, index) => {
- pane.classList.remove('active');
- if (index + 1 === currentStep) {
- pane.classList.add('active');
- }
+ // Show current step
+ document.getElementById(`step${currentStep}`).classList.add('active');
+
+ // Update step indicators
+ 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
- const prevButton = document.querySelector('#launchStepsModal .btn-secondary');
- const nextButton = document.querySelector('#launchStepsModal .btn-primary');
+ const prevButton = document.querySelector('.modal-footer .btn-secondary');
+ const nextButton = document.querySelector('.modal-footer .btn-primary');
prevButton.style.display = currentStep === 1 ? 'none' : 'inline-block';
- nextButton.innerHTML = currentStep === 5 ? 'Launch
' : 'Next
';
+ nextButton.innerHTML = currentStep === totalSteps ? 'Launch Instance' : 'Next
';
+
+ // If we're on step 4, get the next available port
+ if (currentStep === 4) {
+ getNextAvailablePort();
+ }
}
function nextStep() {
- if (currentStep < 5) {
- // 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;
- }
- }
-
+ if (currentStep === 2 && !validateStep2()) {
+ return;
+ }
+ if (currentStep === 3 && !validateStep3()) {
+ return;
+ }
+
+ if (currentStep < totalSteps) {
currentStep++;
updateStepDisplay();
} else {
- // Handle launch
- console.log('Launching instance...');
+ // Handle final step (launch instance)
+ launchInstance();
}
}
@@ -1232,5 +1399,226 @@ async function verifyConnections() {
nginxDetails.innerHTML = `
${error.message || 'Error checking NGINX connection'}`;
}
}
+
+// 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 = '
' +
+ data.repositories.map(repo =>
+ `
`
+ ).join('');
+ repoSelect.disabled = false;
+
+ // If we have a saved repository, load its branches
+ if (gitSettings.repo) {
+ loadBranches(gitSettings.repo);
+ }
+ } else {
+ repoSelect.innerHTML = '
';
+ repoSelect.disabled = true;
+ }
+ } catch (error) {
+ console.error('Error loading repositories:', error);
+ repoSelect.innerHTML = `
`;
+ 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 = '
' +
+ data.branches.map(branch =>
+ `
`
+ ).join('');
+ branchSelect.disabled = false;
+ } else {
+ branchSelect.innerHTML = '
';
+ branchSelect.disabled = true;
+ }
+ } catch (error) {
+ console.error('Error loading branches:', error);
+ branchSelect.innerHTML = `
`;
+ 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 = '
';
+ 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;
+}
{% endblock %}
\ No newline at end of file
diff --git a/templates/settings/tabs/connections.html b/templates/settings/tabs/connections.html
index 179afe3..2a27be6 100644
--- a/templates/settings/tabs/connections.html
+++ b/templates/settings/tabs/connections.html
@@ -17,18 +17,18 @@