feat: Add complete Docker deployment with web-based setup wizard

Major additions:
- Web-based setup wizard (setup.php, setup_wizard.php, setup-wizard.js)
- Production Docker configuration (docker-compose.prod.yml, .env.production)
- Database initialization SQL files (deploy/init_settings.sql)
- Template builder system with drag-and-drop UI
- Advanced features (OAuth, CDN, enhanced analytics, monetization)
- Comprehensive documentation (deployment guides, quick start, feature docs)
- Design system with accessibility and responsive layout
- Deployment automation scripts (deploy.ps1, generate-secrets.ps1)

Setup wizard allows customization of:
- Platform name and branding
- Domain configuration
- Membership tiers and pricing
- Admin credentials
- Feature toggles

Database includes 270+ tables for complete video streaming platform with
advanced features for analytics, moderation, template building, and monetization.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
SamiAhmed7777
2025-10-26 01:42:31 -07:00
parent 0b7e2d0a5b
commit d22b3e1c0d
90 changed files with 22329 additions and 268 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,423 @@
/**
* EasyStream Setup Wizard - Frontend Logic
* Handles wizard navigation, form validation, and AJAX submissions
*/
let currentStep = 1;
const totalSteps = 9;
let setupData = {};
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
updateProgressBar();
attachEventListeners();
loadSavedData();
});
function attachEventListeners() {
// Color picker updates
document.getElementById('primaryColor').addEventListener('input', function(e) {
document.getElementById('primaryColorHex').textContent = e.target.value;
});
document.getElementById('secondaryColor').addEventListener('input', function(e) {
document.getElementById('secondaryColorHex').textContent = e.target.value;
});
// Save form data on input
const inputs = document.querySelectorAll('input, select, textarea');
inputs.forEach(input => {
input.addEventListener('change', saveFormData);
});
}
function changeStep(direction) {
const nextStep = currentStep + direction;
// Validate before moving forward
if (direction > 0) {
if (!validateCurrentStep()) {
return;
}
}
// Special handling for certain steps
if (currentStep === 7 && direction > 0) {
// Review step - show summary and install
showReviewSummary();
startInstallation();
}
if (nextStep >= 1 && nextStep <= totalSteps) {
// Hide current step
document.getElementById(`step${currentStep}`).classList.remove('active');
// Show next step
currentStep = nextStep;
document.getElementById(`step${currentStep}`).classList.add('active');
// Update progress bar
updateProgressBar();
// Update button visibility
updateButtons();
// Scroll to top
document.querySelector('.wizard-body').scrollTop = 0;
}
}
function updateProgressBar() {
const progress = ((currentStep - 1) / (totalSteps - 1)) * 100;
document.getElementById('progressBar').style.width = progress + '%';
}
function updateButtons() {
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
// Show/hide previous button
prevBtn.style.display = currentStep > 1 && currentStep < 8 ? 'block' : 'none';
// Update next button text and visibility
if (currentStep === 1) {
nextBtn.textContent = 'Get Started →';
} else if (currentStep === 7) {
nextBtn.textContent = 'Install Now →';
} else if (currentStep === 8 || currentStep === 9) {
nextBtn.style.display = 'none';
} else {
nextBtn.textContent = 'Next →';
}
}
function validateCurrentStep() {
clearAlert();
switch (currentStep) {
case 1:
// Welcome page, no validation needed
return true;
case 2:
// Platform configuration
const platformName = document.getElementById('platformName').value.trim();
const domainName = document.getElementById('domainName').value.trim();
const contactEmail = document.getElementById('contactEmail').value.trim();
if (!platformName) {
showAlert('error', 'Please enter a platform name');
return false;
}
if (!domainName) {
showAlert('error', 'Please enter a domain name');
return false;
}
if (!contactEmail || !isValidEmail(contactEmail)) {
showAlert('error', 'Please enter a valid contact email');
return false;
}
return true;
case 3:
// Branding, no strict validation
return true;
case 4:
// Membership tiers
const tier1Name = document.getElementById('tier1Name').value.trim();
const tier2Name = document.getElementById('tier2Name').value.trim();
const tier3Name = document.getElementById('tier3Name').value.trim();
if (!tier1Name || !tier2Name || !tier3Name) {
showAlert('error', 'Please enter names for all membership tiers');
return false;
}
return true;
case 5:
// Admin account
const username = document.getElementById('adminUsername').value.trim();
const email = document.getElementById('adminEmail').value.trim();
const password = document.getElementById('adminPassword').value;
const passwordConfirm = document.getElementById('adminPasswordConfirm').value;
if (!username || username.length < 4) {
showAlert('error', 'Username must be at least 4 characters');
return false;
}
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
showAlert('error', 'Username can only contain letters, numbers, and underscores');
return false;
}
if (!email || !isValidEmail(email)) {
showAlert('error', 'Please enter a valid admin email');
return false;
}
if (!password || password.length < 8) {
showAlert('error', 'Password must be at least 8 characters long');
return false;
}
if (!/[A-Z]/.test(password) || !/[a-z]/.test(password) || !/[0-9]/.test(password)) {
showAlert('error', 'Password must contain uppercase, lowercase, and numbers');
return false;
}
if (password !== passwordConfirm) {
showAlert('error', 'Passwords do not match');
return false;
}
return true;
case 6:
// Features, no strict validation
return true;
default:
return true;
}
}
function isValidEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
function saveFormData() {
// Save to localStorage for recovery
const formData = collectFormData();
localStorage.setItem('easystreamSetup', JSON.stringify(formData));
}
function loadSavedData() {
// Load from localStorage if available
const saved = localStorage.getItem('easystreamSetup');
if (saved) {
try {
const data = JSON.parse(saved);
// Populate form fields
Object.keys(data).forEach(key => {
const element = document.getElementById(key);
if (element) {
if (element.type === 'checkbox') {
element.checked = data[key];
} else {
element.value = data[key];
}
}
});
} catch (e) {
console.error('Failed to load saved data:', e);
}
}
}
function collectFormData() {
const formData = {};
// Platform config
formData.platformName = document.getElementById('platformName').value;
formData.platformTagline = document.getElementById('platformTagline').value;
formData.domainName = document.getElementById('domainName').value;
formData.contactEmail = document.getElementById('contactEmail').value;
formData.timezone = document.getElementById('timezone').value;
// Branding
formData.primaryColor = document.getElementById('primaryColor').value;
formData.secondaryColor = document.getElementById('secondaryColor').value;
formData.defaultTheme = document.getElementById('defaultTheme').value;
formData.enableTheming = document.getElementById('enableTheming').checked;
// Membership tiers
formData.tier1Name = document.getElementById('tier1Name').value;
formData.tier1Upload = document.getElementById('tier1Upload').value;
formData.tier1Storage = document.getElementById('tier1Storage').value;
formData.tier2Name = document.getElementById('tier2Name').value;
formData.tier2Upload = document.getElementById('tier2Upload').value;
formData.tier2Storage = document.getElementById('tier2Storage').value;
formData.tier2Price = document.getElementById('tier2Price').value;
formData.tier3Name = document.getElementById('tier3Name').value;
formData.tier3Upload = document.getElementById('tier3Upload').value;
formData.tier3Storage = document.getElementById('tier3Storage').value;
formData.tier3Price = document.getElementById('tier3Price').value;
// Admin account
formData.adminUsername = document.getElementById('adminUsername').value;
formData.adminEmail = document.getElementById('adminEmail').value;
formData.adminPassword = document.getElementById('adminPassword').value;
formData.adminPasswordConfirm = document.getElementById('adminPasswordConfirm').value;
formData.adminDisplayName = document.getElementById('adminDisplayName').value;
// Features
formData.enableRegistration = document.getElementById('enableRegistration').checked;
formData.enableEmailVerification = document.getElementById('enableEmailVerification').checked;
formData.enableLiveStreaming = document.getElementById('enableLiveStreaming').checked;
formData.enableComments = document.getElementById('enableComments').checked;
formData.enableDownloads = document.getElementById('enableDownloads').checked;
formData.enableMonetization = document.getElementById('enableMonetization').checked;
formData.enableTemplateBuilder = document.getElementById('enableTemplateBuilder').checked;
formData.enableAnalytics = document.getElementById('enableAnalytics').checked;
return formData;
}
function showReviewSummary() {
const data = collectFormData();
const summary = document.getElementById('reviewSummary');
summary.innerHTML = `
<div style="margin-bottom: 24px;">
<h3 style="color: #667eea; margin-bottom: 12px;">Platform Configuration</h3>
<p><strong>Name:</strong> ${data.platformName}</p>
<p><strong>Domain:</strong> ${data.domainName}</p>
<p><strong>Email:</strong> ${data.contactEmail}</p>
<p><strong>Timezone:</strong> ${data.timezone}</p>
</div>
<div style="margin-bottom: 24px;">
<h3 style="color: #667eea; margin-bottom: 12px;">Membership Tiers</h3>
<p><strong>${data.tier1Name}:</strong> ${data.tier1Upload}MB upload, ${data.tier1Storage}GB storage</p>
<p><strong>${data.tier2Name}:</strong> ${data.tier2Upload}MB upload, ${data.tier2Storage}GB storage ($${data.tier2Price}/month)</p>
<p><strong>${data.tier3Name}:</strong> ${data.tier3Upload}MB upload, ${data.tier3Storage}GB storage ($${data.tier3Price}/month)</p>
</div>
<div style="margin-bottom: 24px;">
<h3 style="color: #667eea; margin-bottom: 12px;">Admin Account</h3>
<p><strong>Username:</strong> ${data.adminUsername}</p>
<p><strong>Email:</strong> ${data.adminEmail}</p>
<p><strong>Display Name:</strong> ${data.adminDisplayName || 'Administrator'}</p>
</div>
<div>
<h3 style="color: #667eea; margin-bottom: 12px;">Enabled Features</h3>
<p>${data.enableRegistration ? '✓' : '✗'} User Registration</p>
<p>${data.enableLiveStreaming ? '✓' : '✗'} Live Streaming</p>
<p>${data.enableComments ? '✓' : '✗'} Video Comments</p>
<p>${data.enableMonetization ? '✓' : '✗'} Monetization</p>
<p>${data.enableTemplateBuilder ? '✓' : '✗'} Template Builder</p>
<p>${data.enableAnalytics ? '✓' : '✗'} Analytics</p>
</div>
`;
}
async function startInstallation() {
// Move to installation step
document.getElementById('step7').classList.remove('active');
currentStep = 8;
document.getElementById('step8').classList.add('active');
updateProgressBar();
updateButtons();
const data = collectFormData();
setupData = data;
try {
// Step 1: Save configuration
await installStep('Saving configuration...', async () => {
const response = await fetch('setup.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ action: 'save_configuration', ...data })
});
return await response.json();
});
// Step 2: Create admin user
await installStep('Creating admin account...', async () => {
const response = await fetch('setup.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ action: 'create_admin', ...data })
});
return await response.json();
});
// Step 3: Finalize setup
await installStep('Finalizing setup...', async () => {
const response = await fetch('setup.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ action: 'finalize', ...data })
});
return await response.json();
});
// Success! Move to final step
document.getElementById('step8').classList.remove('active');
currentStep = 9;
document.getElementById('step9').classList.add('active');
updateProgressBar();
// Populate final details
document.getElementById('finalPlatformName').textContent = data.platformName;
document.getElementById('finalDomainName').textContent = data.domainName;
document.getElementById('finalAdminUsername').textContent = data.adminUsername;
// Clear saved data
localStorage.removeItem('easystreamSetup');
} catch (error) {
document.getElementById('installStatus').textContent = 'Installation Failed';
document.getElementById('installStep').innerHTML = `
<div style="color: #e53e3e; padding: 20px; background: #fed7d7; border-radius: 8px;">
<strong>Error:</strong> ${error.message}
<br><br>
<button onclick="location.reload()" class="btn btn-primary">Try Again</button>
</div>
`;
}
}
async function installStep(message, action) {
document.getElementById('installStep').textContent = message;
// Add progress item
const progress = document.getElementById('installProgress');
const item = document.createElement('div');
item.style.cssText = 'padding: 12px; background: #f7fafc; border-radius: 6px; margin-bottom: 8px; display: flex; align-items: center;';
item.innerHTML = `<span class="spinner" style="width: 16px; height: 16px; margin-right: 12px; border-width: 2px;"></span><span>${message}</span>`;
progress.appendChild(item);
try {
const result = await action();
if (!result.success) {
throw new Error(result.error || 'Unknown error occurred');
}
// Update to success
item.innerHTML = `<span style="color: #48bb78; font-size: 20px; margin-right: 12px;">✓</span><span>${message} <strong style="color: #48bb78;">Done!</strong></span>`;
return result;
} catch (error) {
item.innerHTML = `<span style="color: #e53e3e; font-size: 20px; margin-right: 12px;">✗</span><span>${message} <strong style="color: #e53e3e;">Failed!</strong></span>`;
throw error;
}
}
function showAlert(type, message) {
const alert = document.getElementById('alert');
alert.className = `alert alert-${type} show`;
alert.textContent = message;
// Auto-hide after 5 seconds
setTimeout(() => {
alert.classList.remove('show');
}, 5000);
}
function clearAlert() {
const alert = document.getElementById('alert');
alert.classList.remove('show');
}