From b672b249a6339ef18c3828b5de6a19d59201c234 Mon Sep 17 00:00:00 2001 From: waffles Date: Thu, 26 Jun 2025 12:44:28 -0700 Subject: [PATCH] first commit --- .dockerignore | 75 +++++++++++ .gitignore | 165 +++++++++++++++++++++++ Dockerfile | 42 ++++++ README.md | 307 ++++++++++++++++++++++++++++++++++++++++++ app.py | 136 +++++++++++++++++++ config.py | 17 +++ docker-compose.yml | 49 +++++++ requirements.txt | 7 + static/css/style.css | 205 ++++++++++++++++++++++++++++ static/js/main.js | 205 ++++++++++++++++++++++++++++ templates/404.html | 33 +++++ templates/500.html | 28 ++++ templates/base.html | 75 +++++++++++ templates/finger.html | 115 ++++++++++++++++ templates/index.html | 17 +++ 15 files changed, 1476 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 app.py create mode 100644 config.py create mode 100644 docker-compose.yml create mode 100644 requirements.txt create mode 100644 static/css/style.css create mode 100644 static/js/main.js create mode 100644 templates/404.html create mode 100644 templates/500.html create mode 100644 templates/base.html create mode 100644 templates/finger.html create mode 100644 templates/index.html diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..033da8b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,75 @@ +# Git +.git +.gitignore + +# Python +__pycache__ +*.pyc +*.pyo +*.pyd +.Python +env +pip-log.txt +pip-delete-this-directory.txt +.tox +.coverage +.coverage.* +.pytest_cache +nosetests.xml +coverage.xml +*.cover +*.log +.cache +.mypy_cache + +# Virtual environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Documentation +README.md +*.md + +# Docker +Dockerfile +.dockerignore + +# Temporary files +*.tmp +*.temp +temp/ +tmp/ + +# Logs +*.log +logs/ + +# Database files (if any) +*.db +*.sqlite +*.sqlite3 + +# Backup files +*.bak +*.backup diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c3c0b96 --- /dev/null +++ b/.gitignore @@ -0,0 +1,165 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.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 + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# IDE specific files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Flask specific +*.db +*.sqlite +*.sqlite3 +app.db + +# Logs +*.log +logs/ + +# Temporary files +*.tmp +*.temp +temp/ +tmp/ + +# Backup files +*.bak +*.backup diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7acd49d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +# Use Python 3.11 slim image as base +FROM python:3.11-slim + +# Set working directory in container +WORKDIR /app + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + FLASK_APP=app.py \ + FLASK_ENV=production + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + gcc \ + finger \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements first to leverage Docker cache +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY . . + +# Create non-root user for security +RUN adduser --disabled-password --gecos '' appuser && \ + chown -R appuser:appuser /app +USER appuser + +# Expose port +EXPOSE 5000 + +# Health check +HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:5000/ || exit 1 + +# Run the application +CMD ["python", "app.py"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a637cf --- /dev/null +++ b/README.md @@ -0,0 +1,307 @@ +# Finger Web Flask Application + +A simple, modern Flask web application demonstrating basic web development concepts with clean code structure, responsive design, and best practices. + +## 🚀 Features + +- **Multiple Routes**: Home, About, Contact pages with clean navigation +- **Contact Form**: Functional contact form with validation and flash messages +- **JSON API**: RESTful API endpoints for data exchange +- **Responsive Design**: Mobile-first design using Bootstrap 5 +- **Error Handling**: Custom 404 and 500 error pages +- **Modern UI**: Clean, professional interface with animations +- **Form Validation**: Client-side and server-side validation +- **Configuration Management**: Environment-based configuration + +## 📁 Project Structure + +``` +finger-web/ +├── app.py # Main Flask application +├── config.py # Configuration settings +├── requirements.txt # Python dependencies +├── README.md # Project documentation +├── .gitignore # Git ignore file +├── Dockerfile # Docker container configuration +├── .dockerignore # Docker ignore file +├── docker-compose.yml # Docker Compose configuration +├── templates/ # HTML templates +│ ├── base.html # Base template with navigation +│ ├── index.html # Home page +│ ├── about.html # About page +│ ├── contact.html # Contact form +│ ├── 404.html # 404 error page +│ └── 500.html # 500 error page +└── static/ # Static assets + ├── css/ + │ └── style.css # Custom styles + └── js/ + └── main.js # JavaScript functionality +``` + +## đŸ› ī¸ Technologies Used + +### Backend +- **Python 3.x** - Programming language +- **Flask 2.3.3** - Web framework +- **Jinja2** - Template engine +- **Werkzeug** - WSGI toolkit + +### Frontend +- **HTML5** - Markup language +- **CSS3** - Styling with custom animations +- **JavaScript (ES6+)** - Interactive functionality +- **Bootstrap 5.3** - CSS framework for responsive design + +## 📋 Prerequisites + +- Python 3.7 or higher +- pip (Python package installer) +- Virtual environment (recommended) + +## 🚀 Installation & Setup + +### 1. Clone or Download the Project + +```bash +# If using git +git clone +cd finger-web + +# Or download and extract the project files +``` + +### 2. Create Virtual Environment (Recommended) + +```bash +# Create virtual environment +python -m venv venv + +# Activate virtual environment +# On Windows: +venv\Scripts\activate +# On macOS/Linux: +source venv/bin/activate +``` + +### 3. Install Dependencies + +```bash +pip install -r requirements.txt +``` + +### 4. Run the Application + +```bash +python app.py +``` + +The application will start on `http://localhost:5000` + +## 🌐 Available Routes + +| Route | Method | Description | +|-------|--------|-------------| +| `/` | GET | Home page | +| `/about` | GET | About page | +| `/contact` | GET, POST | Contact form | +| `/api/hello` | GET | Simple JSON API endpoint | +| `/api/info` | GET | Application information API | + +## 🔧 Configuration + +The application uses environment variables for configuration. You can set these in your environment or create a `.env` file: + +```bash +# Flask Configuration +FLASK_DEBUG=True +SECRET_KEY=your-secret-key-here + +# Database (for future use) +DATABASE_URL=sqlite:///app.db + +# Mail Configuration (for future use) +MAIL_SERVER=smtp.gmail.com +MAIL_PORT=587 +MAIL_USE_TLS=True +MAIL_USERNAME=your-email@gmail.com +MAIL_PASSWORD=your-app-password +``` + +## 📱 API Endpoints + +### GET /api/hello +Returns a simple greeting message. + +**Response:** +```json +{ + "message": "Hello from Flask API!", + "status": "success", + "version": "1.0" +} +``` + +### GET /api/info +Returns application information and available routes. + +**Response:** +```json +{ + "app_name": "Finger Web Flask App", + "routes": ["/", "/about", "/contact", "/api/hello", "/api/info"], + "framework": "Flask" +} +``` + +## 🎨 Customization + +### Styling +- Edit `static/css/style.css` to customize the appearance +- The app uses Bootstrap 5 classes for responsive design +- Custom CSS variables and animations are included + +### JavaScript +- Modify `static/js/main.js` for additional functionality +- Includes form validation, animations, and keyboard shortcuts +- API helper functions are available + +### Templates +- All HTML templates extend `templates/base.html` +- Use Jinja2 template syntax for dynamic content +- Bootstrap components are readily available + +## 🔍 Features in Detail + +### Contact Form +- Client-side validation with real-time feedback +- Server-side validation and sanitization +- Flash messages for user feedback +- Form submission with loading states + +### Responsive Design +- Mobile-first approach +- Bootstrap grid system +- Custom breakpoints and animations +- Touch-friendly interface + +### Error Handling +- Custom 404 and 500 error pages +- Graceful error handling in routes +- User-friendly error messages + +### JavaScript Features +- Form validation and enhancement +- Smooth scrolling navigation +- Card animations on scroll +- Keyboard shortcuts (Alt+H, Alt+A, Alt+C) +- API interaction helpers + +## 🚀 Deployment + +### Development +```bash +python app.py +``` + +### Docker Deployment + +#### Option 1: Using Docker directly +```bash +# Build the Docker image +docker build -t finger-web . + +# Run the container +docker run -d -p 5000:5000 --name finger-web-app finger-web +``` + +#### Option 2: Using Docker Compose (Recommended) +```bash +# Build and start the application +docker-compose up -d + +# View logs +docker-compose logs -f + +# Stop the application +docker-compose down +``` + +#### Docker Commands +```bash +# Build image +docker build -t finger-web . + +# Run container with environment variables +docker run -d \ + -p 5000:5000 \ + -e FLASK_ENV=production \ + -e SECRET_KEY=your-secret-key \ + --name finger-web-app \ + finger-web + +# View container logs +docker logs finger-web-app + +# Stop and remove container +docker stop finger-web-app +docker rm finger-web-app +``` + +### Production +For production deployment, consider using: +- **Docker** for containerization (included) +- **Docker Compose** for orchestration (included) +- **Gunicorn** as WSGI server +- **Nginx** as reverse proxy +- **Heroku**, **DigitalOcean**, or **AWS** for hosting + +Example with Gunicorn: +```bash +pip install gunicorn +gunicorn -w 4 -b 0.0.0.0:5000 app:app +``` + +## 🔒 Security Considerations + +- Change the `SECRET_KEY` in production +- Use environment variables for sensitive data +- Enable HTTPS in production +- Implement rate limiting for forms +- Validate and sanitize all user inputs + +## 🤝 Contributing + +1. Fork the project +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Submit a pull request + +## 📝 License + +This project is open source and available under the [MIT License](LICENSE). + +## 📞 Support + +If you encounter any issues or have questions: +1. Check the existing documentation +2. Review the code comments +3. Test in a clean virtual environment +4. Create an issue with detailed information + +## đŸŽ¯ Future Enhancements + +- Database integration with SQLAlchemy +- User authentication and sessions +- Email functionality for contact form +- Admin dashboard +- API rate limiting +- Unit tests +- CI/CD pipeline +- Kubernetes deployment manifests +- Monitoring and logging integration + +--- + +**Built with â¤ī¸ using Flask and Bootstrap** diff --git a/app.py b/app.py new file mode 100644 index 0000000..67f6410 --- /dev/null +++ b/app.py @@ -0,0 +1,136 @@ +from flask import Flask, render_template, request, jsonify, redirect, url_for, flash +import os +import subprocess +import shlex +from config import Config + +app = Flask(__name__) +app.config.from_object(Config) + +@app.route('/') +def index(): + """Home page route""" + return render_template('index.html', title='Home') + +@app.route('/finger', methods=['GET', 'POST']) +def finger(): + """Finger command route""" + result = None + error = None + username = request.args.get('user', '') or request.form.get('username', '') + + if request.method == 'POST' or username: + try: + # Sanitize username input + if username: + # Allow alphanumeric characters, dots, hyphens, underscores, and @ symbol for email-style usernames + if not all(c.isalnum() or c in '.-_@' for c in username): + error = "Invalid username format. Only alphanumeric characters, dots, hyphens, underscores, and @ symbol are allowed." + else: + # Execute finger command with specific user + cmd = ['finger', username] + else: + # Execute finger command without user (show all logged in users) + cmd = ['finger'] + + if not error: + # Execute the command safely + process = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=10 + ) + + if process.returncode == 0: + result = process.stdout + if not result.strip(): + result = "No information available." + else: + error = process.stderr or "Finger command failed." + + except subprocess.TimeoutExpired: + error = "Command timed out. Please try again." + except FileNotFoundError: + error = "Finger command not available on this system." + except Exception as e: + error = f"An error occurred: {str(e)}" + + return render_template('finger.html', title='Finger', result=result, error=error, username=username) + +@app.route('/finger/') +def finger_direct(username): + """Direct finger command route with username in URL""" + result = None + error = None + + try: + # Sanitize username input + if username: + # Allow alphanumeric characters, dots, hyphens, underscores, and @ symbol for email-style usernames + if not all(c.isalnum() or c in '.-_@' for c in username): + error = "Invalid username format. Only alphanumeric characters, dots, hyphens, underscores, and @ symbol are allowed." + else: + # Execute finger command with specific user + cmd = ['finger', username] + + # Execute the command safely + process = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=10 + ) + + if process.returncode == 0: + result = process.stdout + if not result.strip(): + result = "No information available." + else: + error = process.stderr or "Finger command failed." + + except subprocess.TimeoutExpired: + error = "Command timed out. Please try again." + except FileNotFoundError: + error = "Finger command not available on this system." + except Exception as e: + error = f"An error occurred: {str(e)}" + + return render_template('finger.html', title=f'Finger - {username}', result=result, error=error, username=username) + +@app.route('/api/hello') +def api_hello(): + """Simple JSON API endpoint""" + return jsonify({ + 'message': 'Hello from Flask API!', + 'status': 'success', + 'version': '1.0' + }) + +@app.route('/api/info') +def api_info(): + """API info endpoint""" + return jsonify({ + 'app_name': 'Finger Web Flask App', + 'routes': [ + '/', + '/finger', + '/finger/', + '/api/hello', + '/api/info' + ], + 'framework': 'Flask' + }) + +@app.errorhandler(404) +def not_found_error(error): + """Handle 404 errors""" + return render_template('404.html', title='Page Not Found'), 404 + +@app.errorhandler(500) +def internal_error(error): + """Handle 500 errors""" + return render_template('500.html', title='Server Error'), 500 + +if __name__ == '__main__': + app.run(debug=True, host='0.0.0.0', port=5000) diff --git a/config.py b/config.py new file mode 100644 index 0000000..067ceb8 --- /dev/null +++ b/config.py @@ -0,0 +1,17 @@ +import os + +class Config: + """Flask configuration class""" + SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production' + DEBUG = os.environ.get('FLASK_DEBUG', 'True').lower() == 'true' + + # Database configuration (for future use) + SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///app.db' + SQLALCHEMY_TRACK_MODIFICATIONS = False + + # Mail configuration (for future use) + MAIL_SERVER = os.environ.get('MAIL_SERVER') + MAIL_PORT = int(os.environ.get('MAIL_PORT') or 587) + MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1'] + MAIL_USERNAME = os.environ.get('MAIL_USERNAME') + MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..93de61b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,49 @@ +version: '3.8' + +services: + finger-web: + build: . + container_name: finger-web-app + ports: + - "5000:5000" + environment: + - FLASK_ENV=production + - FLASK_DEBUG=False + - SECRET_KEY=your-production-secret-key-here + volumes: + # Mount logs directory for persistence (optional) + - ./logs:/app/logs + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5000/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - finger-web-network + + # Optional: Add nginx reverse proxy for production + # nginx: + # image: nginx:alpine + # container_name: finger-web-nginx + # ports: + # - "80:80" + # - "443:443" + # volumes: + # - ./nginx.conf:/etc/nginx/nginx.conf:ro + # - ./ssl:/etc/nginx/ssl:ro + # depends_on: + # - finger-web + # restart: unless-stopped + # networks: + # - finger-web-network + +networks: + finger-web-network: + driver: bridge + +# Optional: Add volumes for data persistence +# volumes: +# finger-web-data: +# driver: local diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..241479f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +Flask==2.3.3 +Werkzeug==2.3.7 +Jinja2==3.1.2 +MarkupSafe==2.1.3 +itsdangerous==2.1.2 +click==8.1.7 +blinker==1.6.3 diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..82c14e3 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,205 @@ +/* Custom styles for Finger Web Flask App */ + +/* Global styles */ +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; +} + +/* Navigation enhancements */ +.navbar-brand { + font-weight: bold; + font-size: 1.5rem; +} + +.navbar-nav .nav-link { + font-weight: 500; + transition: color 0.3s ease; +} + +.navbar-nav .nav-link:hover { + color: #fff !important; + text-decoration: underline; +} + +/* Card enhancements */ +.card { + border: none; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); +} + +/* Jumbotron styling */ +.jumbotron { + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + border: 1px solid #dee2e6; +} + +/* Button enhancements */ +.btn { + border-radius: 6px; + font-weight: 500; + transition: all 0.3s ease; +} + +.btn-primary { + background: linear-gradient(45deg, #007bff, #0056b3); + border: none; +} + +.btn-primary:hover { + background: linear-gradient(45deg, #0056b3, #004085); + transform: translateY(-1px); +} + +.btn-outline-primary:hover { + transform: translateY(-1px); +} + +/* Form enhancements */ +.form-control { + border-radius: 6px; + border: 2px solid #e9ecef; + transition: border-color 0.3s ease, box-shadow 0.3s ease; +} + +.form-control:focus { + border-color: #007bff; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +/* Alert enhancements */ +.alert { + border-radius: 8px; + border: none; +} + +.alert-success { + background: linear-gradient(45deg, #d4edda, #c3e6cb); + color: #155724; +} + +.alert-danger { + background: linear-gradient(45deg, #f8d7da, #f5c6cb); + color: #721c24; +} + +/* Footer styling */ +footer { + margin-top: auto; + border-top: 1px solid #e9ecef; +} + +/* Code block styling */ +pre { + font-size: 0.9rem; + line-height: 1.4; +} + +pre code { + color: #495057; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .jumbotron { + padding: 2rem 1rem; + } + + .display-4 { + font-size: 2rem; + } + + .card-body { + padding: 1rem; + } +} + +/* Animation for page load */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +main { + animation: fadeIn 0.6s ease-out; +} + +/* Custom utility classes */ +.text-gradient { + background: linear-gradient(45deg, #007bff, #6610f2); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.shadow-custom { + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); +} + +/* Loading animation for forms */ +.btn.loading { + position: relative; + color: transparent; +} + +.btn.loading::after { + content: ""; + position: absolute; + width: 16px; + height: 16px; + top: 50%; + left: 50%; + margin-left: -8px; + margin-top: -8px; + border: 2px solid #ffffff; + border-radius: 50%; + border-top-color: transparent; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* Finger command specific styles */ +.finger-output pre { + font-family: 'Courier New', Consolas, monospace; + font-size: 0.85rem; + line-height: 1.4; + white-space: pre-wrap; + word-wrap: break-word; + max-height: 500px; + overflow-y: auto; + color: white; +} + +.finger-output pre::-webkit-scrollbar { + width: 8px; +} + +.finger-output pre::-webkit-scrollbar-track { + background: #2d3748; +} + +.finger-output pre::-webkit-scrollbar-thumb { + background: #4a5568; + border-radius: 4px; +} + +.finger-output pre::-webkit-scrollbar-thumb:hover { + background: #718096; +} diff --git a/static/js/main.js b/static/js/main.js new file mode 100644 index 0000000..4b5b9da --- /dev/null +++ b/static/js/main.js @@ -0,0 +1,205 @@ +// Main JavaScript file for Finger Web Flask App + +document.addEventListener('DOMContentLoaded', function() { + // Initialize all functionality when DOM is loaded + initializeFormValidation(); + initializeNavigation(); + initializeAnimations(); + initializeTooltips(); +}); + +// Form validation and enhancement +function initializeFormValidation() { + const forms = document.querySelectorAll('form'); + + forms.forEach(form => { + form.addEventListener('submit', function(event) { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } else { + // Add loading state to submit button + const submitBtn = form.querySelector('button[type="submit"]'); + if (submitBtn) { + submitBtn.classList.add('loading'); + submitBtn.disabled = true; + } + } + + form.classList.add('was-validated'); + }); + + // Real-time validation feedback + const inputs = form.querySelectorAll('input, textarea'); + inputs.forEach(input => { + input.addEventListener('blur', function() { + if (this.checkValidity()) { + this.classList.remove('is-invalid'); + this.classList.add('is-valid'); + } else { + this.classList.remove('is-valid'); + this.classList.add('is-invalid'); + } + }); + }); + }); +} + +// Navigation enhancements +function initializeNavigation() { + // Highlight current page in navigation + const currentPath = window.location.pathname; + const navLinks = document.querySelectorAll('.navbar-nav .nav-link'); + + navLinks.forEach(link => { + if (link.getAttribute('href') === currentPath) { + link.classList.add('active'); + } + }); + + // Smooth scrolling for anchor links + const anchorLinks = document.querySelectorAll('a[href^="#"]'); + anchorLinks.forEach(link => { + link.addEventListener('click', function(e) { + e.preventDefault(); + const target = document.querySelector(this.getAttribute('href')); + if (target) { + target.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }); + }); +} + +// Animation and visual enhancements +function initializeAnimations() { + // Fade in cards on scroll + const observerOptions = { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px' + }; + + const observer = new IntersectionObserver(function(entries) { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.style.opacity = '1'; + entry.target.style.transform = 'translateY(0)'; + } + }); + }, observerOptions); + + // Observe all cards + const cards = document.querySelectorAll('.card'); + cards.forEach(card => { + card.style.opacity = '0'; + card.style.transform = 'translateY(20px)'; + card.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; + observer.observe(card); + }); +} + +// Initialize Bootstrap tooltips +function initializeTooltips() { + // Enable tooltips if Bootstrap is available + if (typeof bootstrap !== 'undefined') { + const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); + tooltipTriggerList.map(function(tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); + } +} + +// Utility functions +function showNotification(message, type = 'info') { + // Create a notification element + const notification = document.createElement('div'); + notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`; + notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;'; + notification.innerHTML = ` + ${message} + + `; + + document.body.appendChild(notification); + + // Auto-remove after 5 seconds + setTimeout(() => { + if (notification.parentNode) { + notification.remove(); + } + }, 5000); +} + +// API interaction helpers +async function fetchAPI(endpoint) { + try { + const response = await fetch(endpoint); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error('API fetch error:', error); + showNotification('Failed to fetch data from API', 'danger'); + return null; + } +} + +// Form submission with AJAX (optional enhancement) +function submitFormAjax(form, successCallback) { + const formData = new FormData(form); + + fetch(form.action, { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showNotification(data.message, 'success'); + if (successCallback) successCallback(data); + } else { + showNotification(data.message || 'An error occurred', 'danger'); + } + }) + .catch(error => { + console.error('Form submission error:', error); + showNotification('Failed to submit form', 'danger'); + }) + .finally(() => { + // Remove loading state + const submitBtn = form.querySelector('button[type="submit"]'); + if (submitBtn) { + submitBtn.classList.remove('loading'); + submitBtn.disabled = false; + } + }); +} + +// Keyboard shortcuts +document.addEventListener('keydown', function(e) { + // Alt + H for Home + if (e.altKey && e.key === 'h') { + e.preventDefault(); + window.location.href = '/'; + } + + // Alt + A for About + if (e.altKey && e.key === 'a') { + e.preventDefault(); + window.location.href = '/about'; + } + + // Alt + F for Finger + if (e.altKey && e.key === 'f') { + e.preventDefault(); + window.location.href = '/finger'; + } +}); + +// Console welcome message +console.log('%c🚀 Finger Web Flask App', 'color: #007bff; font-size: 16px; font-weight: bold;'); +console.log('%cWelcome to the developer console!', 'color: #6c757d;'); +console.log('%cKeyboard shortcuts: Alt+H (Home), Alt+A (About), Alt+F (Finger)', 'color: #6c757d;'); diff --git a/templates/404.html b/templates/404.html new file mode 100644 index 0000000..ffd454c --- /dev/null +++ b/templates/404.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+
+

404

+

Page Not Found

+

+ Oops! The page you're looking for doesn't exist. It might have been moved, deleted, or you entered the wrong URL. +

+
+ +
+
+ +
+
Popular Pages:
+
    +
  • + Home +
  • +
  • â€ĸ
  • +
  • +
+
+
+
+{% endblock %} diff --git a/templates/500.html b/templates/500.html new file mode 100644 index 0000000..817cd00 --- /dev/null +++ b/templates/500.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+
+

500

+

Internal Server Error

+

+ Something went wrong on our end. We're working to fix the issue. Please try again later. +

+
+ +
+
+ +
+ +
+
+
+{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..27c6866 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,75 @@ + + + + + + {% if title %}{{ title }} - {% endif %}Finger Web App + + + + + + + + + + + + + + + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for category, message in messages %} + + {% endfor %} +
+ {% endif %} + {% endwith %} + + +
+ {% block content %}{% endblock %} +
+ + +
+
+

© 2025 Finger Web App. Built with Flask and Bootstrap.

+
+
+ + + + + + + + diff --git a/templates/finger.html b/templates/finger.html new file mode 100644 index 0000000..26e46ce --- /dev/null +++ b/templates/finger.html @@ -0,0 +1,115 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

🔍 Finger Command

+ +
+
+
+
+
User Lookup
+
+
+ + +
+ Leave empty to show all logged-in users, or enter a username or email address. +
+
+ + + +
+
+
+ +
+
+
â„šī¸ About Finger
+

+ The finger command displays information about users on the system, including: +

+
    +
  • Login name and real name
  • +
  • Terminal and login time
  • +
  • Idle time
  • +
  • Home directory and shell
  • +
  • Plan and project files (if available)
  • +
+
+
+
+ +
+
+
+
+ {% if username %} + Results for "{{ username }}" + {% else %} + System Users + {% endif %} +
+ {% if result or error %} + + {{ moment().format('YYYY-MM-DD HH:mm:ss') if moment else '' }} + + {% endif %} +
+
+ {% if error %} + + {% elif result %} + +
+
{{ result }}
+
+ {% else %} +
+ +

Enter a username and click "Run Finger" to see user information,
+ or leave username empty to see all logged-in users.

+
+ {% endif %} +
+
+
+
+ + +
+
+ + +{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..9998813 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+

Welcome to Finger Web!

+

A simple web interface to the unix finger command.

+
+

Finger is a utility from 1971 written to discover user information on remote unix systems. While it's use has fallen off, you can sometimes still find active finger servers where users provide information about themselves.

See waffles@yttrx.com as an example :)

+ +

This site was written by waffles but inspired by Ben Brown's happy net box project.

+
+
+
+ +{% endblock %}