From 42ca79236f505d77bc4d7e6a49526767350187b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:23:05 +0000 Subject: [PATCH 1/2] Initial plan From 84fd54327f18d4951c71c60008363c7cbf1a1f57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:32:45 +0000 Subject: [PATCH 2/2] Complete Flask web frontend for Conference User Creation Co-authored-by: tsimiz <60962498+tsimiz@users.noreply.github.com> --- .gitignore | 55 +++++- WEB_FRONTEND_README.md | 206 +++++++++++++++++++++ app.py | 195 ++++++++++++++++++++ requirements.txt | 7 + static/css/style.css | 193 ++++++++++++++++++++ static/js/script.js | 329 ++++++++++++++++++++++++++++++++++ templates/about.html | 181 +++++++++++++++++++ templates/base.html | 67 +++++++ templates/command_result.html | 182 +++++++++++++++++++ templates/create_users.html | 162 +++++++++++++++++ templates/index.html | 115 ++++++++++++ templates/remove_users.html | 161 +++++++++++++++++ 12 files changed, 1852 insertions(+), 1 deletion(-) create mode 100644 WEB_FRONTEND_README.md create mode 100644 app.py create mode 100644 requirements.txt create mode 100644 static/css/style.css create mode 100644 static/js/script.js create mode 100644 templates/about.html create mode 100644 templates/base.html create mode 100644 templates/command_result.html create mode 100644 templates/create_users.html create mode 100644 templates/index.html create mode 100644 templates/remove_users.html diff --git a/.gitignore b/.gitignore index 81e6d90..875fc34 100644 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,57 @@ TestResults/ secrets.json appsettings.local.json *.secret -*.key \ No newline at end of file +*.key + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Virtual environments +venv/ +ENV/ +env/ +.venv/ +.ENV/ +.env/ + +# Flask +instance/ +.webassets-cache + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db \ No newline at end of file diff --git a/WEB_FRONTEND_README.md b/WEB_FRONTEND_README.md new file mode 100644 index 0000000..5119007 --- /dev/null +++ b/WEB_FRONTEND_README.md @@ -0,0 +1,206 @@ +# Conference User Creation - Web Frontend + +A locally running Python Flask web frontend for the Conference User Creation PowerShell scripts. This web interface provides a user-friendly way to generate PowerShell commands for creating and managing conference workshop user accounts in Azure/Entra ID environments. + +## Features + +- **User-Friendly Forms**: Web-based forms for all PowerShell script parameters +- **Real-Time Validation**: Form validation with immediate feedback +- **Command Generation**: Generates exact PowerShell commands with proper syntax +- **Copy to Clipboard**: One-click copying of generated commands +- **Responsive Design**: Works on desktop and mobile devices +- **Safety Features**: Built-in warnings and confirmation steps for removal operations +- **Professional UI**: Bootstrap-based interface with modern styling + +## Quick Start + +### Prerequisites + +- Python 3.7 or higher +- pip (Python package installer) + +### Installation + +1. **Navigate to the repository directory:** + ```bash + cd confUserCreation + ``` + +2. **Install Python dependencies:** + ```bash + pip install -r requirements.txt + ``` + +### Running the Web Frontend + +1. **Start the Flask application:** + ```bash + python app.py + ``` + +2. **Open your web browser and navigate to:** + ``` + http://localhost:5000 + ``` + +3. **Use the web interface to:** + - Create conference users by filling out the creation form + - Remove conference users by filling out the removal form + - Copy the generated PowerShell commands + - Run the commands in your PowerShell environment + +## Web Interface Overview + +### Home Page +- Overview of available operations +- Links to create and remove user forms +- Information about prerequisites and how it works + +### Create Users Form +- **Basic Configuration**: Conference name, user count, domain, password settings +- **Azure Resource Groups**: Options for creating individual resource groups +- **Advanced Options**: Excel output path, dry run mode +- **Validation**: Real-time form validation with helpful error messages + +### Remove Users Form +- **Basic Configuration**: Conference name and domain +- **Removal Options**: Choose what to remove (users, groups, resource groups) +- **Execution Options**: Dry run and force mode settings +- **Safety Features**: Multiple confirmation checkboxes and warnings + +### Command Result Page +- **Generated Command**: Formatted PowerShell command ready to copy +- **Copy Button**: One-click copying to clipboard +- **Next Steps**: Clear instructions on how to use the command +- **Context Information**: Shows what the command will do + +### About Page +- Detailed information about features and prerequisites +- PowerShell module installation instructions +- Required Azure permissions +- Security considerations and limitations + +## Form Parameters + +### User Creation Parameters + +| Parameter | Description | Required | Default | +|-----------|-------------|----------|---------| +| Conference Name | Used as username prefix | Yes | - | +| User Count | Number of users to create (1-1000) | No | 10 | +| Domain | Domain for user principal names | No | Auto-detected | +| Password | Initial password for users | No | Auto-generated | +| Force Password Change | Require password change on first login | No | True | +| Create Resource Groups | Create individual Azure resource groups | No | False | +| Subscription ID | Azure subscription for resource groups | No | Current context | +| Location | Azure location for resource groups | No | Interactive selection | +| Excel Output Path | Directory for Excel export | No | Current directory | +| Dry Run | Preview mode without creating resources | No | False | + +### User Removal Parameters + +| Parameter | Description | Required | Default | +|-----------|-------------|----------|---------| +| Conference Name | Conference to identify users for removal | Yes | - | +| Domain | Domain for user principal names | No | Auto-detected | +| Remove Groups | Remove associated Entra ID groups | No | True | +| Remove Resource Groups | Remove associated Azure resource groups | No | False | +| Force | Skip confirmation prompts | No | False | +| Dry Run | Preview mode without removing resources | No | False | + +## Security Features + +- **Input Validation**: All form inputs are validated for format and security +- **XSS Protection**: Templates use automatic escaping +- **CSRF Protection**: Flask secret key for session security +- **Safe Defaults**: Secure default values for all options +- **Warning Messages**: Clear warnings for destructive operations + +## Development + +### File Structure +``` +├── app.py # Main Flask application +├── requirements.txt # Python dependencies +├── templates/ # HTML templates +│ ├── base.html # Base template with navigation +│ ├── index.html # Home page +│ ├── create_users.html # User creation form +│ ├── remove_users.html # User removal form +│ ├── command_result.html # Generated command display +│ └── about.html # About page +└── static/ # Static assets + ├── css/ + │ └── style.css # Custom styling + └── js/ + └── script.js # JavaScript functionality +``` + +### Customization + +The web frontend can be customized by modifying: + +- **Styling**: Edit `static/css/style.css` for visual changes +- **Functionality**: Modify `static/js/script.js` for client-side behavior +- **Templates**: Update HTML templates in `templates/` directory +- **Validation**: Adjust validation rules in `app.py` + +### Development Mode + +Run the application in development mode with debug enabled: + +```bash +python app.py +``` + +The application will: +- Run on `http://localhost:5000` +- Enable debug mode with auto-reload +- Show detailed error messages +- Allow access from any IP address (`0.0.0.0`) + +## Troubleshooting + +### Common Issues + +1. **Port Already in Use** + ``` + Error: Address already in use + ``` + Solution: Change the port in `app.py` or kill the process using port 5000 + +2. **Module Not Found** + ``` + ModuleNotFoundError: No module named 'flask' + ``` + Solution: Install requirements with `pip install -r requirements.txt` + +3. **Permission Denied** + ``` + PermissionError: [Errno 13] Permission denied + ``` + Solution: Run with appropriate permissions or use a different port + +### Browser Compatibility + +The web frontend is tested and compatible with: +- Chrome 90+ +- Firefox 88+ +- Safari 14+ +- Edge 90+ + +For best experience, use a modern browser with JavaScript enabled. + +## Support + +For issues related to the web frontend: +1. Check the console output for error messages +2. Verify all dependencies are installed correctly +3. Ensure you're using a supported Python version +4. Check the GitHub repository for known issues + +For issues related to the PowerShell scripts themselves, refer to the main README.md file. + +## License + +This web frontend is part of the Conference User Creation project and is licensed under the MIT License. See the LICENSE file for details. \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..949e8f3 --- /dev/null +++ b/app.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +""" +Flask Web Frontend for Conference User Creation Script + +This web application provides a user-friendly interface for generating +PowerShell commands to create and remove conference users in Azure/Entra ID. +""" + +from flask import Flask, render_template, request, redirect, url_for, flash +import re +import os + +app = Flask(__name__) +app.secret_key = 'conference-user-creation-secret-key' + +# Configuration +VALID_AZURE_LOCATIONS = [ + 'East US', 'East US 2', 'West US', 'West US 2', 'West US 3', + 'Central US', 'North Central US', 'South Central US', 'West Central US', + 'Canada Central', 'Canada East', + 'Brazil South', + 'North Europe', 'West Europe', 'UK South', 'UK West', + 'France Central', 'Germany West Central', 'Switzerland North', + 'Norway East', 'Sweden Central', + 'Australia East', 'Australia Southeast', 'Australia Central', + 'Japan East', 'Japan West', 'Korea Central', 'Korea South', + 'Southeast Asia', 'East Asia', + 'Central India', 'South India', 'West India', + 'UAE North', 'South Africa North' +] + +@app.route('/') +def index(): + """Home page with options to create or remove users.""" + return render_template('index.html') + +@app.route('/create', methods=['GET', 'POST']) +def create_users(): + """Form for creating conference users.""" + if request.method == 'POST': + # Get form data + conference_name = request.form.get('conference_name', '').strip() + user_count = request.form.get('user_count', '10') + domain = request.form.get('domain', '').strip() + password = request.form.get('password', '').strip() + force_password_change = request.form.get('force_password_change') == 'true' + create_resource_groups = request.form.get('create_resource_groups') == 'true' + subscription_id = request.form.get('subscription_id', '').strip() + location = request.form.get('location', '').strip() + dry_run = request.form.get('dry_run') == 'true' + excel_output_path = request.form.get('excel_output_path', '').strip() + + # Validate required fields + errors = [] + if not conference_name: + errors.append('Conference Name is required') + elif not re.match(r'^[a-zA-Z0-9_-]+$', conference_name): + errors.append('Conference Name must contain only letters, numbers, hyphens, and underscores') + + try: + user_count_int = int(user_count) + if user_count_int < 1 or user_count_int > 1000: + errors.append('User Count must be between 1 and 1000') + except ValueError: + errors.append('User Count must be a valid number') + + if domain and not re.match(r'^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', domain): + errors.append('Domain must be a valid domain name (e.g., company.com)') + + if subscription_id and not re.match(r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$', subscription_id): + errors.append('Subscription ID must be a valid GUID format') + + if location and location not in VALID_AZURE_LOCATIONS: + errors.append('Please select a valid Azure location') + + if errors: + for error in errors: + flash(error, 'error') + return render_template('create_users.html', + locations=VALID_AZURE_LOCATIONS, + form_data=request.form) + + # Generate PowerShell command + powershell_command = generate_create_command( + conference_name, user_count, domain, password, force_password_change, + create_resource_groups, subscription_id, location, dry_run, excel_output_path + ) + + return render_template('command_result.html', + command=powershell_command, + operation='create', + conference_name=conference_name, + user_count=user_count) + + return render_template('create_users.html', locations=VALID_AZURE_LOCATIONS) + +@app.route('/remove', methods=['GET', 'POST']) +def remove_users(): + """Form for removing conference users.""" + if request.method == 'POST': + # Get form data + conference_name = request.form.get('conference_name', '').strip() + domain = request.form.get('domain', '').strip() + remove_groups = request.form.get('remove_groups') == 'true' + remove_resource_groups = request.form.get('remove_resource_groups') == 'true' + force = request.form.get('force') == 'true' + dry_run = request.form.get('dry_run') == 'true' + + # Validate required fields + errors = [] + if not conference_name: + errors.append('Conference Name is required') + elif not re.match(r'^[a-zA-Z0-9_-]+$', conference_name): + errors.append('Conference Name must contain only letters, numbers, hyphens, and underscores') + + if domain and not re.match(r'^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', domain): + errors.append('Domain must be a valid domain name (e.g., company.com)') + + if errors: + for error in errors: + flash(error, 'error') + return render_template('remove_users.html', form_data=request.form) + + # Generate PowerShell command + powershell_command = generate_remove_command( + conference_name, domain, remove_groups, remove_resource_groups, force, dry_run + ) + + return render_template('command_result.html', + command=powershell_command, + operation='remove', + conference_name=conference_name) + + return render_template('remove_users.html') + +def generate_create_command(conference_name, user_count, domain, password, force_password_change, + create_resource_groups, subscription_id, location, dry_run, excel_output_path): + """Generate PowerShell command for creating users.""" + cmd = f".\\New-ConferenceUsers.ps1 -ConferenceName '{conference_name}' -UserCount {user_count}" + + if domain: + cmd += f" -Domain '{domain}'" + + if password: + cmd += f" -Password '{password}'" + + if not force_password_change: + cmd += " -ForcePasswordChange $false" + + if create_resource_groups: + cmd += " -CreateResourceGroups $true" + + if subscription_id: + cmd += f" -SubscriptionId '{subscription_id}'" + + if location: + cmd += f" -Location '{location}'" + + if excel_output_path: + cmd += f" -ExcelOutputPath '{excel_output_path}'" + + if dry_run: + cmd += " -DryRun" + + return cmd + +def generate_remove_command(conference_name, domain, remove_groups, remove_resource_groups, force, dry_run): + """Generate PowerShell command for removing users.""" + cmd = f".\\Remove-ConferenceUsers.ps1 -ConferenceName '{conference_name}'" + + if domain: + cmd += f" -Domain '{domain}'" + + if not remove_groups: + cmd += " -RemoveGroups $false" + + if remove_resource_groups: + cmd += " -RemoveResourceGroups $true" + + if force: + cmd += " -Force" + + if dry_run: + cmd += " -DryRun" + + return cmd + +@app.route('/about') +def about(): + """About page with information about the tool.""" + return render_template('about.html') + +if __name__ == '__main__': + # Run the Flask development server + app.run(debug=True, host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a94a28d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +Flask==3.0.0 +Werkzeug==3.0.1 +Jinja2==3.1.2 +MarkupSafe==2.1.3 +itsdangerous==2.1.2 +click==8.1.7 +blinker==1.7.0 \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..1220c10 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,193 @@ +/* Custom CSS for Conference User Creation Web Frontend */ + +/* Main styling */ +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #f8f9fa; +} + +/* Navigation */ +.navbar-brand { + font-weight: 600; +} + +/* Cards and forms */ +.card { + border: none; + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + transition: box-shadow 0.15s ease-in-out; +} + +.card:hover { + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); +} + +.card-header { + background-color: #f8f9fa; + border-bottom: 1px solid #dee2e6; + font-weight: 600; +} + +/* Form elements */ +.form-label { + font-weight: 500; + color: #495057; +} + +.form-control, .form-select { + border-radius: 0.375rem; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +.form-control:focus, .form-select:focus { + border-color: #86b7fe; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} + +/* Buttons */ +.btn { + font-weight: 500; + border-radius: 0.375rem; + transition: all 0.15s ease-in-out; +} + +.btn-success:hover { + background-color: #157347; + border-color: #146c43; +} + +.btn-danger:hover { + background-color: #bb2d3b; + border-color: #b02a37; +} + +/* Command display */ +#commandText { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 0.9rem; + line-height: 1.4; + background-color: #1e1e1e !important; + border-radius: 0.5rem; + max-height: 300px; + overflow-y: auto; +} + +/* Alert improvements */ +.alert { + border: none; + border-radius: 0.5rem; +} + +.alert-warning { + background-color: #fff3cd; + color: #664d03; +} + +.alert-info { + background-color: #d1ecf1; + color: #055160; +} + +.alert-danger { + background-color: #f8d7da; + color: #721c24; +} + +/* Jumbotron styling */ +.jumbotron { + background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); + border-radius: 1rem; +} + +/* Icon styling */ +.bi { + vertical-align: text-bottom; +} + +/* Feature cards on home page */ +.card h5 .bi { + margin-right: 0.5rem; +} + +/* Form validation */ +.is-invalid { + border-color: #dc3545; +} + +.invalid-feedback { + display: block; +} + +/* Responsive improvements */ +@media (max-width: 768px) { + .jumbotron { + padding: 2rem 1rem !important; + } + + .jumbotron h1 { + font-size: 2rem; + } + + .jumbotron .fs-4 { + font-size: 1.25rem !important; + } + + #commandText { + font-size: 0.8rem; + } +} + +/* Loading state */ +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* Custom checkbox styling */ +.form-check-input:checked { + background-color: #007bff; + border-color: #007bff; +} + +/* Pre-formatted text improvements */ +pre { + font-size: 0.875rem; + line-height: 1.4; +} + +/* Table improvements */ +.table { + margin-bottom: 0; +} + +.table th { + border-top: none; + font-weight: 600; + background-color: #f8f9fa; +} + +/* Footer */ +footer { + margin-top: auto; +} + +/* Utility classes */ +.text-decoration-none:hover { + text-decoration: underline !important; +} + +/* Animation for button state changes */ +.btn-success, .btn-primary, .btn-danger { + transition: all 0.2s ease-in-out; +} + +/* Copy button animation */ +@keyframes buttonSuccess { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); } + 100% { transform: scale(1); } +} + +.btn.copied { + animation: buttonSuccess 0.3s ease-in-out; +} \ No newline at end of file diff --git a/static/js/script.js b/static/js/script.js new file mode 100644 index 0000000..23c9531 --- /dev/null +++ b/static/js/script.js @@ -0,0 +1,329 @@ +// JavaScript functionality for Conference User Creation Web Frontend + +document.addEventListener('DOMContentLoaded', function() { + // Initialize tooltips (if Bootstrap tooltips are used) + var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); + var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); + + // Form validation enhancement + enhanceFormValidation(); + + // Auto-hide alerts + autoHideAlerts(); + + // Add smooth scrolling + addSmoothScrolling(); +}); + +/** + * Enhance form validation with real-time feedback + */ +function enhanceFormValidation() { + const forms = document.querySelectorAll('form'); + + forms.forEach(function(form) { + // Conference name validation + const conferenceNameInput = form.querySelector('#conference_name'); + if (conferenceNameInput) { + conferenceNameInput.addEventListener('input', function() { + validateConferenceName(this); + }); + } + + // User count validation + const userCountInput = form.querySelector('#user_count'); + if (userCountInput) { + userCountInput.addEventListener('input', function() { + validateUserCount(this); + }); + } + + // Domain validation + const domainInput = form.querySelector('#domain'); + if (domainInput) { + domainInput.addEventListener('input', function() { + validateDomain(this); + }); + } + + // Subscription ID validation + const subscriptionIdInput = form.querySelector('#subscription_id'); + if (subscriptionIdInput) { + subscriptionIdInput.addEventListener('input', function() { + validateSubscriptionId(this); + }); + } + }); +} + +/** + * Validate conference name format + */ +function validateConferenceName(input) { + const value = input.value.trim(); + const pattern = /^[a-zA-Z0-9_-]+$/; + + if (value && !pattern.test(value)) { + setInputError(input, 'Conference name must contain only letters, numbers, hyphens, and underscores'); + } else { + clearInputError(input); + } +} + +/** + * Validate user count range + */ +function validateUserCount(input) { + const value = parseInt(input.value); + + if (value && (value < 1 || value > 1000)) { + setInputError(input, 'User count must be between 1 and 1000'); + } else { + clearInputError(input); + } +} + +/** + * Validate domain format + */ +function validateDomain(input) { + const value = input.value.trim(); + const pattern = /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + + if (value && !pattern.test(value)) { + setInputError(input, 'Please enter a valid domain name (e.g., company.com)'); + } else { + clearInputError(input); + } +} + +/** + * Validate subscription ID format + */ +function validateSubscriptionId(input) { + const value = input.value.trim(); + const pattern = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; + + if (value && !pattern.test(value)) { + setInputError(input, 'Please enter a valid GUID format (e.g., 12345678-1234-1234-1234-123456789012)'); + } else { + clearInputError(input); + } +} + +/** + * Set input error state + */ +function setInputError(input, message) { + input.classList.add('is-invalid'); + + // Remove existing error message + const existingError = input.parentNode.querySelector('.invalid-feedback'); + if (existingError) { + existingError.remove(); + } + + // Add new error message + const errorDiv = document.createElement('div'); + errorDiv.className = 'invalid-feedback'; + errorDiv.textContent = message; + input.parentNode.appendChild(errorDiv); +} + +/** + * Clear input error state + */ +function clearInputError(input) { + input.classList.remove('is-invalid'); + + const errorDiv = input.parentNode.querySelector('.invalid-feedback'); + if (errorDiv) { + errorDiv.remove(); + } +} + +/** + * Auto-hide alerts after a delay + */ +function autoHideAlerts() { + const alerts = document.querySelectorAll('.alert:not(.alert-warning):not(.alert-danger)'); + + alerts.forEach(function(alert) { + // Don't auto-hide error or warning alerts + if (!alert.classList.contains('alert-danger') && !alert.classList.contains('alert-warning')) { + setTimeout(function() { + if (alert && alert.parentNode) { + alert.style.transition = 'opacity 0.5s ease-out'; + alert.style.opacity = '0'; + setTimeout(function() { + if (alert && alert.parentNode) { + alert.remove(); + } + }, 500); + } + }, 5000); + } + }); +} + +/** + * Add smooth scrolling for anchor links + */ +function addSmoothScrolling() { + const links = document.querySelectorAll('a[href^="#"]'); + + links.forEach(function(link) { + link.addEventListener('click', function(e) { + const target = document.querySelector(this.getAttribute('href')); + if (target) { + e.preventDefault(); + target.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }); + }); +} + +/** + * Toggle resource group options visibility + */ +function toggleResourceGroupOptions() { + const checkbox = document.getElementById('create_resource_groups'); + const options = document.getElementById('resourceGroupOptions'); + + if (checkbox && options) { + options.style.display = checkbox.checked ? 'block' : 'none'; + + // Add animation + if (checkbox.checked) { + options.style.opacity = '0'; + options.style.transform = 'translateY(-10px)'; + setTimeout(function() { + options.style.transition = 'all 0.3s ease-in-out'; + options.style.opacity = '1'; + options.style.transform = 'translateY(0)'; + }, 10); + } + } +} + +/** + * Copy command to clipboard with enhanced feedback + */ +function copyCommand() { + const commandText = document.getElementById('commandText'); + if (!commandText) return; + + const text = commandText.textContent || commandText.innerText; + + // Use modern clipboard API if available + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(text).then(function() { + showCopySuccess(); + }).catch(function(err) { + fallbackCopyTextToClipboard(text); + }); + } else { + fallbackCopyTextToClipboard(text); + } +} + +/** + * Fallback copy method for older browsers + */ +function fallbackCopyTextToClipboard(text) { + const textArea = document.createElement("textarea"); + textArea.value = text; + textArea.style.position = "fixed"; + textArea.style.left = "-999999px"; + textArea.style.top = "-999999px"; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand('copy'); + showCopySuccess(); + } catch (err) { + showCopyError(); + } + + document.body.removeChild(textArea); +} + +/** + * Show copy success feedback + */ +function showCopySuccess() { + const button = event.target.closest('button'); + if (!button) return; + + const originalHTML = button.innerHTML; + const originalClasses = button.className; + + button.innerHTML = ' Copied!'; + button.className = button.className.replace('btn-outline-primary', 'btn-success'); + button.classList.add('copied'); + + setTimeout(function() { + button.innerHTML = originalHTML; + button.className = originalClasses; + button.classList.remove('copied'); + }, 2000); +} + +/** + * Show copy error feedback + */ +function showCopyError() { + // Create a temporary alert + const alertDiv = document.createElement('div'); + alertDiv.className = 'alert alert-warning alert-dismissible fade show mt-3'; + alertDiv.innerHTML = ` + Copy failed! Please select and copy the command manually. + + `; + + const commandCard = document.querySelector('#commandText').closest('.card'); + commandCard.parentNode.insertBefore(alertDiv, commandCard); + + // Auto-hide after 5 seconds + setTimeout(function() { + if (alertDiv && alertDiv.parentNode) { + alertDiv.remove(); + } + }, 5000); +} + +/** + * Form submission enhancement + */ +function enhanceFormSubmission() { + const forms = document.querySelectorAll('form'); + + forms.forEach(function(form) { + form.addEventListener('submit', function(e) { + const submitButton = form.querySelector('button[type="submit"]'); + if (submitButton) { + const originalText = submitButton.innerHTML; + submitButton.innerHTML = ' Generating...'; + submitButton.disabled = true; + + // Re-enable if form validation fails + setTimeout(function() { + if (form.checkValidity && !form.checkValidity()) { + submitButton.innerHTML = originalText; + submitButton.disabled = false; + } + }, 100); + } + }); + }); +} + +// Initialize form submission enhancement when DOM is loaded +document.addEventListener('DOMContentLoaded', enhanceFormSubmission); \ No newline at end of file diff --git a/templates/about.html b/templates/about.html new file mode 100644 index 0000000..b365f66 --- /dev/null +++ b/templates/about.html @@ -0,0 +1,181 @@ +{% extends "base.html" %} + +{% block title %}About - Conference User Creation{% endblock %} + +{% block content %} +
+ This web frontend provides a user-friendly interface for generating PowerShell commands + to create and manage conference workshop user accounts in Azure/Entra ID environments. +
+ ++ The underlying PowerShell scripts automate the process of setting up multiple user accounts, + groups, and resources for conferences, workshops, and training events, making it easy to + provision standardized environments for participants. +
++ The web interface acts as a command generator - it doesn't directly modify your + Azure environment but creates the exact PowerShell commands you need. +
+Install the required Microsoft Graph PowerShell modules:
+Install-Module Microsoft.Graph.Authentication -Force +Install-Module Microsoft.Graph.Users -Force +Install-Module Microsoft.Graph.Groups -Force +Install-Module Microsoft.Graph.Identity.DirectoryManagement -Force+ +
Install-Module Az.Accounts -Force +Install-Module Az.Resources -Force+ +
Install-Module ImportExcel -Force+ +
The user running the script must have sufficient permissions in the Azure tenant:
+| Permission | +Purpose | +
|---|---|
User.ReadWrite.All |
+ To create and manage users | +
Directory.Read.All |
+ To read tenant information | +
Group.ReadWrite.All |
+ To create and manage groups | +
| Contributor or Owner | +For Azure resource group creation | +
+ This tool is part of the Conference User Creation project. The PowerShell scripts and this web frontend + are open source and available on GitHub. +
++ Repository: + + tsimiz/confUserCreation + +
++ For issues, feature requests, or contributions, please visit the GitHub repository. +
+Command to create {{ user_count }} users for "{{ conference_name }}"
+Command to remove users for "{{ conference_name }}"
+{{ command }}
+ Fill in the parameters to generate a PowerShell command for creating conference users.
+ + +Web frontend for Azure/Entra ID conference user management PowerShell scripts
++ Generate PowerShell commands to create and manage conference workshop user accounts in Azure/Entra ID environments. +
++ Generate commands to create multiple user accounts for conferences, workshops, and training events. + Supports user creation, Entra ID groups, and Azure resource groups. +
++ Generate commands to clean up conference resources when no longer needed. + Supports user removal, group cleanup, and resource group deletion. +
+Enter your conference details and configuration parameters
+Get the complete PowerShell command with all your parameters
+Copy and execute the command in your PowerShell environment
++ Before running the generated commands, ensure you have the required PowerShell modules installed and appropriate Azure permissions. + Learn more about prerequisites. +
+Fill in the parameters to generate a PowerShell command for removing conference users and resources.
+ +