diff --git a/asf-cloud-server/testarena_1/.env.example b/asf-cloud-server/testarena_1/.env.example index 87c3d85..e06a52a 100644 --- a/asf-cloud-server/testarena_1/.env.example +++ b/asf-cloud-server/testarena_1/.env.example @@ -1,3 +1,8 @@ DATABASE_URL=postgresql://testarena_user:testarena_pass_change_me@db:5432/testarena SECRET_KEY=change_this_secret_key_in_production FLASK_ENV=production + +# SSH Configuration for Git operations +SSH_PASSWORD=your_ssh_password_here +SSH_HOST=asfserver +SSH_USER=asf \ No newline at end of file diff --git a/asf-cloud-server/testarena_1/Dockerfile b/asf-cloud-server/testarena_1/Dockerfile index fa27b89..5b24129 100644 --- a/asf-cloud-server/testarena_1/Dockerfile +++ b/asf-cloud-server/testarena_1/Dockerfile @@ -5,6 +5,7 @@ WORKDIR /app # Install system dependencies RUN apt-get update && apt-get install -y \ git \ + sshpass \ && rm -rf /var/lib/apt/lists/* # Copy requirements diff --git a/asf-cloud-server/testarena_1/PHASE2_FEATURES.md b/asf-cloud-server/testarena_1/PHASE2_FEATURES.md new file mode 100644 index 0000000..4f91dbe --- /dev/null +++ b/asf-cloud-server/testarena_1/PHASE2_FEATURES.md @@ -0,0 +1,227 @@ +# Phase 2 Features - Implementation Summary + +## ✅ Features Implemented + +### 1. Right-Click Context Menu for Job Abortion + +**Feature:** Users can right-click on "in_progress" jobs to abort them. + +**Implementation:** +- Added context menu that appears on right-click +- Only shows for jobs with status "in_progress" +- Clicking "Abort Job" marks the job as aborted (⚫ black icon) +- Context menu automatically hides when clicking elsewhere + +**Files Modified:** +- `app/templates/dashboard/index.html` - Added context menu HTML and JavaScript +- `app/static/css/style.css` - Added context menu styling + +**Usage:** +1. Right-click on any job with orange icon (in progress) +2. Select "Abort Job" from the context menu +3. Job status changes to aborted (black icon) + +--- + +### 2. Git Branch Validation + +**Feature:** Validates that the branch exists on remote before allowing job submission. + +**Implementation:** +- When user enters branch name and clicks "Validate Branch" +- System executes SSH commands to remote server: + 1. `./TPF/gitea_repo_controller.sh clone` - Clones/updates repository + 2. `./TPF/gitea_repo_controller.sh checkout ` - Checks out branch +- Parses output to detect success or failure +- Shows appropriate message to user + +**Success Flow:** +``` +User enters branch → Clicks "Validate Branch" → +Loading indicator → SSH commands execute → +Branch found → Green success message → +"Next" button appears → User proceeds to scenario selection +``` + +**Failure Flow:** +``` +User enters branch → Clicks "Validate Branch" → +Loading indicator → SSH commands execute → +Branch not found (fatal: error) → Red error message → +"Branch not found on remote. Please push the branch first." → +User cannot proceed +``` + +**Files Modified:** +- `app/routes/jobs.py` - Added branch validation logic with SSH commands +- `app/templates/jobs/submit.html` - Added AJAX validation and UI feedback +- `app/static/css/style.css` - Added validation message styling +- `Dockerfile` - Added sshpass package +- `docker-compose.yml` - Added SSH environment variables +- `.env.example` - Added SSH configuration template + +**Configuration Required:** + +Update these environment variables in `docker-compose.yml` or `.env`: +```env +SSH_PASSWORD=your_actual_password +SSH_HOST=asfserver +SSH_USER=asf +``` + +--- + +## 🔧 Technical Details + +### SSH Command Execution + +The system uses `subprocess` to execute SSH commands: + +```python +# Clone repository +clone_cmd = f"sshpass -p '{ssh_password}' ssh -o StrictHostKeyChecking=no {ssh_user}@{ssh_host} './TPF/gitea_repo_controller.sh clone'" + +# Checkout branch +checkout_cmd = f"sshpass -p '{ssh_password}' ssh -o StrictHostKeyChecking=no {ssh_user}@{ssh_host} './TPF/gitea_repo_controller.sh checkout {branch_name}'" +``` + +### Error Detection + +The system detects branch validation failures by checking for: +- "fatal:" in stdout or stderr +- Non-zero return code +- Timeout (60 seconds) + +### Security Considerations + +⚠️ **Important:** The SSH password is stored in environment variables. For production: +1. Use SSH keys instead of passwords +2. Store credentials in a secure vault (e.g., HashiCorp Vault) +3. Use encrypted environment variables +4. Implement proper access controls + +--- + +## 📋 Deployment Steps + +### 1. Update Configuration + +Edit `docker-compose.yml`: +```yaml +environment: + SSH_PASSWORD: your_actual_password # Replace with real password + SSH_HOST: asfserver # Your SSH host + SSH_USER: asf # Your SSH user +``` + +### 2. Rebuild Container + +```bash +docker-compose down +docker-compose up -d --build +``` + +### 3. Test Branch Validation + +1. Go to Submit New Job +2. Enter a valid branch name +3. Click "Validate Branch" +4. Should see green success message +5. Try invalid branch - should see red error + +### 4. Test Job Abortion + +1. Create a job (it will be "in progress") +2. Right-click on the job +3. Select "Abort Job" +4. Job icon should turn black (⚫) + +--- + +## 🎨 UI/UX Improvements + +### Context Menu +- Clean, modern design +- Smooth hover effects +- Automatically positions near cursor +- Closes when clicking elsewhere + +### Branch Validation +- Three states: Loading (yellow), Success (green), Error (red) +- Loading spinner animation +- Clear error messages +- Disabled "Next" button until validation succeeds +- Re-validation required if branch name changes + +### Visual Feedback +- ⚫ Black icon for aborted jobs +- 🟠 Orange icon for in-progress jobs +- 🟢 Green icon for passed jobs +- 🔴 Red icon for failed jobs + +--- + +## 🐛 Known Limitations + +1. **SSH Password Security:** Currently stored in plain text environment variables +2. **No Retry Logic:** If SSH command fails, user must manually retry +3. **Timeout:** 60-second timeout for SSH commands +4. **No Progress Bar:** User doesn't see detailed progress during validation +5. **Single Server:** Only supports one SSH host + +--- + +## 🚀 Future Enhancements + +### Suggested Improvements: +1. **SSH Key Authentication:** Replace password with SSH keys +2. **Retry Logic:** Automatic retry on transient failures +3. **Progress Indicators:** Show detailed progress during validation +4. **Multiple Servers:** Support for multiple SSH hosts +5. **Caching:** Cache branch validation results +6. **Webhooks:** Real-time notifications when branch is pushed +7. **Batch Operations:** Abort multiple jobs at once +8. **Job History:** Show abort history and who aborted + +--- + +## 📞 Support + +If you encounter issues: + +1. **Branch Validation Fails:** + - Check SSH credentials in docker-compose.yml + - Verify sshpass is installed: `docker exec testarena_web which sshpass` + - Check SSH connectivity: `docker exec testarena_web ssh asf@asfserver` + - Review logs: `docker-compose logs web` + +2. **Context Menu Not Showing:** + - Ensure job status is "in_progress" + - Check browser console for JavaScript errors + - Clear browser cache + +3. **Abort Not Working:** + - Check logs: `docker-compose logs web` + - Verify job exists in database + - Check user permissions + +--- + +## 📝 Testing Checklist + +- [ ] Branch validation with valid branch +- [ ] Branch validation with invalid branch +- [ ] Branch validation timeout handling +- [ ] Context menu appears on right-click +- [ ] Context menu only shows for in-progress jobs +- [ ] Abort job functionality works +- [ ] Job icon changes to black after abort +- [ ] Cannot proceed without valid branch +- [ ] Re-validation required after branch name change +- [ ] SSH commands execute successfully + +--- + +**Implementation Date:** November 28, 2024 +**Version:** 1.1.0 +**Status:** ✅ Ready for Testing diff --git a/asf-cloud-server/testarena_1/app/routes/jobs.py b/asf-cloud-server/testarena_1/app/routes/jobs.py index 1f12109..4dbf2c0 100644 --- a/asf-cloud-server/testarena_1/app/routes/jobs.py +++ b/asf-cloud-server/testarena_1/app/routes/jobs.py @@ -3,6 +3,8 @@ from flask_login import login_required, current_user from app.models import Job from app import db import json +import subprocess +import os jobs_bp = Blueprint('jobs', __name__, url_prefix='/jobs') @@ -15,18 +17,75 @@ def submit(): @login_required def submit_step1(): branch_name = request.form.get('branch_name') - # TODO: Implement branch checkout and scenario detection - # For now, return mock scenarios - scenarios = [ - 'Scenario_1_Basic_Test', - 'Scenario_2_Advanced_Test', - 'Scenario_3_Integration_Test', - 'Scenario_4_Performance_Test', - 'Scenario_5_Security_Test' - ] - return render_template('jobs/submit_step2.html', branch_name=branch_name, scenarios=scenarios) + + if not branch_name: + flash('Branch name is required', 'error') + return redirect(url_for('jobs.submit')) + + # Validate branch exists on remote + try: + # Get SSH password from environment variable + ssh_password = os.environ.get('SSH_PASSWORD', 'default_password') + ssh_host = os.environ.get('SSH_HOST', 'remote_host') + ssh_user = os.environ.get('SSH_USER', 'asf') + + # First, clone the repository + clone_cmd = f"sshpass -p '{ssh_password}' ssh -o StrictHostKeyChecking=no {ssh_user}@{ssh_host} './TPF/gitea_repo_controller.sh clone'" + clone_result = subprocess.run(clone_cmd, shell=True, capture_output=True, text=True, timeout=60) + + # Then, checkout the branch + checkout_cmd = f"sshpass -p '{ssh_password}' ssh -o StrictHostKeyChecking=no {ssh_user}@{ssh_host} './TPF/gitea_repo_controller.sh checkout {branch_name}'" + checkout_result = subprocess.run(checkout_cmd, shell=True, capture_output=True, text=True, timeout=60) + + # Check if checkout was successful (no "fatal:" in output) + if "fatal:" in checkout_result.stdout or "fatal:" in checkout_result.stderr or checkout_result.returncode != 0: + return jsonify({ + 'success': False, + 'error': f'Branch "{branch_name}" not found on remote. Please push the branch first.', + 'output': checkout_result.stdout + checkout_result.stderr + }) + + # If successful, get available scenarios (mock for now) + scenarios = [ + 'Scenario_1_Basic_Test', + 'Scenario_2_Advanced_Test', + 'Scenario_3_Integration_Test', + 'Scenario_4_Performance_Test', + 'Scenario_5_Security_Test' + ] + + return jsonify({ + 'success': True, + 'scenarios': scenarios, + 'message': f'Branch "{branch_name}" validated successfully' + }) + + except subprocess.TimeoutExpired: + return jsonify({ + 'success': False, + 'error': 'Branch validation timed out. Please try again.', + 'output': '' + }) + except Exception as e: + return jsonify({ + 'success': False, + 'error': f'Error validating branch: {str(e)}', + 'output': '' + }) -@jobs_bp.route('/submit/step2', methods=['POST']) +@jobs_bp.route('/submit/step2-validated', methods=['POST']) +@login_required +def submit_step2_validated(): + branch_name = request.form.get('branch_name') + scenarios_json = request.form.get('scenarios') + + try: + scenarios = json.loads(scenarios_json) + except: + flash('Invalid scenarios data', 'error') + return redirect(url_for('jobs.submit')) + + return render_template('jobs/submit_step2.html', branch_name=branch_name, scenarios=scenarios) @login_required def submit_step2(): branch_name = request.form.get('branch_name') diff --git a/asf-cloud-server/testarena_1/app/static/css/style.css b/asf-cloud-server/testarena_1/app/static/css/style.css index 60b58e0..34a74e4 100644 --- a/asf-cloud-server/testarena_1/app/static/css/style.css +++ b/asf-cloud-server/testarena_1/app/static/css/style.css @@ -540,3 +540,87 @@ body { .empty-state h3 { margin-bottom: 10px; } +/* Context Menu */ +.context-menu { + display: none; + position: absolute; + background: white; + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0,0,0,0.15); + z-index: 1000; + min-width: 150px; +} + +.context-menu-item { + padding: 12px 16px; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; + transition: background-color 0.2s; +} + +.context-menu-item:hover { + background: var(--light); +} + +.context-menu-item:first-child { + border-radius: 8px 8px 0 0; +} + +.context-menu-item:last-child { + border-radius: 0 0 8px 8px; +} + +.context-menu-icon { + font-size: 16px; +} + +/* Branch Validation */ +.branch-validation { + margin-top: 15px; + padding: 12px; + border-radius: 8px; + display: none; +} + +.branch-validation.success { + background: #d1fae5; + color: #065f46; + border: 1px solid #10b981; +} + +.branch-validation.error { + background: #fee2e2; + color: #991b1b; + border: 1px solid #ef4444; +} + +.branch-validation.loading { + background: #fef3c7; + color: #92400e; + border: 1px solid #f59e0b; +} + +.loading-spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid #f3f3f3; + border-top: 2px solid var(--primary); + border-radius: 50%; + animation: spin 1s linear infinite; + margin-right: 8px; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.btn:disabled { + background: #9ca3af; + cursor: not-allowed; + opacity: 0.6; +} \ No newline at end of file diff --git a/asf-cloud-server/testarena_1/app/templates/dashboard/index.html b/asf-cloud-server/testarena_1/app/templates/dashboard/index.html index 39e9650..8db87c1 100644 --- a/asf-cloud-server/testarena_1/app/templates/dashboard/index.html +++ b/asf-cloud-server/testarena_1/app/templates/dashboard/index.html @@ -13,7 +13,7 @@
{% if jobs %} {% for job in jobs %} -
+
{{ job.get_status_icon() }}

Job #{{ job.id }} - {{ job.branch_name }}

@@ -44,7 +44,48 @@
+ +
+
+ + Abort Job +
+
+ {% endblock %} diff --git a/asf-cloud-server/testarena_1/docker-compose.yml b/asf-cloud-server/testarena_1/docker-compose.yml index f21400b..b856f30 100644 --- a/asf-cloud-server/testarena_1/docker-compose.yml +++ b/asf-cloud-server/testarena_1/docker-compose.yml @@ -21,6 +21,9 @@ services: DATABASE_URL: postgresql://testarena_user:testarena_pass_change_me@db:5432/testarena SECRET_KEY: change_this_secret_key_in_production FLASK_ENV: production + SSH_PASSWORD: ASF + SSH_HOST: asfserver + SSH_USER: asf volumes: - ./app:/app/app - ./wsgi.py:/app/wsgi.py