diff --git a/NGINX_swagger.json b/NGINX_swagger.json deleted file mode 100644 index 4a502b4..0000000 --- a/NGINX_swagger.json +++ /dev/null @@ -1,274 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "Nginx Proxy Manager API", - "version": "2.x.x" - }, - "servers": [ - { - "url": "http://127.0.0.1:81/api" - } - ], - "components": { - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } - } - }, - "paths": { - "/": { - "get": { - "$ref": "./paths/get.json" - } - }, - "/audit-log": { - "get": { - "$ref": "./paths/audit-log/get.json" - } - }, - "/nginx/access-lists": { - "get": { - "$ref": "./paths/nginx/access-lists/get.json" - }, - "post": { - "$ref": "./paths/nginx/access-lists/post.json" - } - }, - "/nginx/access-lists/{listID}": { - "get": { - "$ref": "./paths/nginx/access-lists/listID/get.json" - }, - "put": { - "$ref": "./paths/nginx/access-lists/listID/put.json" - }, - "delete": { - "$ref": "./paths/nginx/access-lists/listID/delete.json" - } - }, - "/nginx/certificates": { - "get": { - "$ref": "./paths/nginx/certificates/get.json" - }, - "post": { - "$ref": "./paths/nginx/certificates/post.json" - } - }, - "/nginx/certificates/validate": { - "post": { - "$ref": "./paths/nginx/certificates/validate/post.json" - } - }, - "/nginx/certificates/test-http": { - "get": { - "$ref": "./paths/nginx/certificates/test-http/get.json" - } - }, - "/nginx/certificates/{certID}": { - "get": { - "$ref": "./paths/nginx/certificates/certID/get.json" - }, - "delete": { - "$ref": "./paths/nginx/certificates/certID/delete.json" - } - }, - "/nginx/certificates/{certID}/download": { - "get": { - "$ref": "./paths/nginx/certificates/certID/download/get.json" - } - }, - "/nginx/certificates/{certID}/renew": { - "post": { - "$ref": "./paths/nginx/certificates/certID/renew/post.json" - } - }, - "/nginx/certificates/{certID}/upload": { - "post": { - "$ref": "./paths/nginx/certificates/certID/upload/post.json" - } - }, - "/nginx/proxy-hosts": { - "get": { - "$ref": "./paths/nginx/proxy-hosts/get.json" - }, - "post": { - "$ref": "./paths/nginx/proxy-hosts/post.json" - } - }, - "/nginx/proxy-hosts/{hostID}": { - "get": { - "$ref": "./paths/nginx/proxy-hosts/hostID/get.json" - }, - "put": { - "$ref": "./paths/nginx/proxy-hosts/hostID/put.json" - }, - "delete": { - "$ref": "./paths/nginx/proxy-hosts/hostID/delete.json" - } - }, - "/nginx/proxy-hosts/{hostID}/enable": { - "post": { - "$ref": "./paths/nginx/proxy-hosts/hostID/enable/post.json" - } - }, - "/nginx/proxy-hosts/{hostID}/disable": { - "post": { - "$ref": "./paths/nginx/proxy-hosts/hostID/disable/post.json" - } - }, - "/nginx/redirection-hosts": { - "get": { - "$ref": "./paths/nginx/redirection-hosts/get.json" - }, - "post": { - "$ref": "./paths/nginx/redirection-hosts/post.json" - } - }, - "/nginx/redirection-hosts/{hostID}": { - "get": { - "$ref": "./paths/nginx/redirection-hosts/hostID/get.json" - }, - "put": { - "$ref": "./paths/nginx/redirection-hosts/hostID/put.json" - }, - "delete": { - "$ref": "./paths/nginx/redirection-hosts/hostID/delete.json" - } - }, - "/nginx/redirection-hosts/{hostID}/enable": { - "post": { - "$ref": "./paths/nginx/redirection-hosts/hostID/enable/post.json" - } - }, - "/nginx/redirection-hosts/{hostID}/disable": { - "post": { - "$ref": "./paths/nginx/redirection-hosts/hostID/disable/post.json" - } - }, - "/nginx/dead-hosts": { - "get": { - "$ref": "./paths/nginx/dead-hosts/get.json" - }, - "post": { - "$ref": "./paths/nginx/dead-hosts/post.json" - } - }, - "/nginx/dead-hosts/{hostID}": { - "get": { - "$ref": "./paths/nginx/dead-hosts/hostID/get.json" - }, - "put": { - "$ref": "./paths/nginx/dead-hosts/hostID/put.json" - }, - "delete": { - "$ref": "./paths/nginx/dead-hosts/hostID/delete.json" - } - }, - "/nginx/dead-hosts/{hostID}/enable": { - "post": { - "$ref": "./paths/nginx/dead-hosts/hostID/enable/post.json" - } - }, - "/nginx/dead-hosts/{hostID}/disable": { - "post": { - "$ref": "./paths/nginx/dead-hosts/hostID/disable/post.json" - } - }, - "/nginx/streams": { - "get": { - "$ref": "./paths/nginx/streams/get.json" - }, - "post": { - "$ref": "./paths/nginx/streams/post.json" - } - }, - "/nginx/streams/{streamID}": { - "get": { - "$ref": "./paths/nginx/streams/streamID/get.json" - }, - "put": { - "$ref": "./paths/nginx/streams/streamID/put.json" - }, - "delete": { - "$ref": "./paths/nginx/streams/streamID/delete.json" - } - }, - "/nginx/streams/{streamID}/enable": { - "post": { - "$ref": "./paths/nginx/streams/streamID/enable/post.json" - } - }, - "/nginx/streams/{streamID}/disable": { - "post": { - "$ref": "./paths/nginx/streams/streamID/disable/post.json" - } - }, - "/reports/hosts": { - "get": { - "$ref": "./paths/reports/hosts/get.json" - } - }, - "/schema": { - "get": { - "$ref": "./paths/schema/get.json" - } - }, - "/settings": { - "get": { - "$ref": "./paths/settings/get.json" - } - }, - "/settings/{settingID}": { - "get": { - "$ref": "./paths/settings/settingID/get.json" - }, - "put": { - "$ref": "./paths/settings/settingID/put.json" - } - }, - "/tokens": { - "get": { - "$ref": "./paths/tokens/get.json" - }, - "post": { - "$ref": "./paths/tokens/post.json" - } - }, - "/users": { - "get": { - "$ref": "./paths/users/get.json" - }, - "post": { - "$ref": "./paths/users/post.json" - } - }, - "/users/{userID}": { - "get": { - "$ref": "./paths/users/userID/get.json" - }, - "put": { - "$ref": "./paths/users/userID/put.json" - }, - "delete": { - "$ref": "./paths/users/userID/delete.json" - } - }, - "/users/{userID}/auth": { - "put": { - "$ref": "./paths/users/userID/auth/put.json" - } - }, - "/users/{userID}/permissions": { - "put": { - "$ref": "./paths/users/userID/permissions/put.json" - } - }, - "/users/{userID}/login": { - "post": { - "$ref": "./paths/users/userID/login/post.json" - } - } - } -} diff --git a/PRICING_CONFIGURATION.md b/PRICING_CONFIGURATION.md deleted file mode 100644 index 2ff76a5..0000000 --- a/PRICING_CONFIGURATION.md +++ /dev/null @@ -1,246 +0,0 @@ -# Pricing Configuration Feature - -This document describes the new configurable pricing feature that allows MASTER instances to manage pricing plans through the admin interface. - -## Overview - -The pricing configuration feature allows administrators on MASTER instances to: -- Create, edit, and delete pricing plans -- Configure plan features, prices, and settings -- Set resource quotas for rooms, conversations, storage, and users -- Mark plans as "Most Popular" or "Custom" -- Control plan visibility and ordering -- Update pricing without code changes - -## Features - -### Pricing Plan Management -- **Plan Name**: Display name for the pricing plan -- **Description**: Optional description shown below the plan name -- **Pricing**: Monthly and annual prices (annual prices are typically 20% lower) -- **Features**: Dynamic list of features with checkmarks -- **Button Configuration**: Customizable button text and URL -- **Plan Types**: Regular plans with prices or custom plans -- **Popular Plans**: Mark one plan as "Most Popular" with special styling -- **Active/Inactive**: Toggle plan visibility -- **Ordering**: Control the display order of plans - -### Resource Quotas -- **Room Quota**: Maximum number of rooms allowed (0 = unlimited) -- **Conversation Quota**: Maximum number of conversations allowed (0 = unlimited) -- **Storage Quota**: Maximum storage in gigabytes (0 = unlimited) -- **Manager Quota**: Maximum number of manager users allowed (0 = unlimited) -- **Admin Quota**: Maximum number of admin users allowed (0 = unlimited) - -### Admin Interface -- **Pricing Tab**: New tab in admin settings (MASTER instances only) -- **Add/Edit Modals**: User-friendly forms for plan management -- **Real-time Updates**: Changes are reflected immediately -- **Feature Management**: Add/remove features dynamically -- **Quota Configuration**: Set resource limits for each plan -- **Status Toggles**: Quick switches for plan properties - -## Setup Instructions - -### 1. Database Migration -Run the migrations to create the pricing_plans table and add quota fields: - -```bash -# Apply the migrations -alembic upgrade head -``` - -### 2. Initialize Default Plans (Optional) -Run the initialization script to create default pricing plans: - -```bash -# Set MASTER environment variable -export MASTER=true - -# Run the initialization script -python init_pricing_plans.py -``` - -This will create four default plans with quotas: -- **Starter**: 5 rooms, 10 conversations, 10GB storage, 10 managers, 1 admin -- **Professional**: 25 rooms, 50 conversations, 100GB storage, 50 managers, 3 admins -- **Enterprise**: 100 rooms, 200 conversations, 500GB storage, 200 managers, 10 admins -- **Custom**: Unlimited everything - -### 3. Access Admin Interface -1. Log in as an admin user on a MASTER instance -2. Go to Settings -3. Click on the "Pricing" tab -4. Configure your pricing plans - -## Usage - -### Creating a New Plan -1. Click "Add New Plan" in the pricing tab -2. Fill in the plan details: - - **Name**: Plan display name - - **Description**: Optional description - - **Monthly Price**: Price per month - - **Annual Price**: Price per month when billed annually - - **Quotas**: Set resource limits (0 = unlimited) - - **Features**: Add features using the "Add Feature" button - - **Button Text**: Text for the call-to-action button - - **Button URL**: URL the button should link to - - **Options**: Check "Most Popular", "Custom Plan", or "Active" as needed -3. Click "Create Plan" - -### Editing a Plan -1. Click the "Edit" button on any plan card -2. Modify the plan details in the modal -3. Click "Update Plan" - -### Managing Plan Status -- **Active/Inactive**: Use the toggle switch in the plan header -- **Most Popular**: Check the "Most Popular" checkbox (only one plan can be popular) -- **Custom Plan**: Check "Custom Plan" for plans without fixed pricing - -### Deleting a Plan -1. Click the "Delete" button on a plan card -2. Confirm the deletion in the modal - -## Technical Details - -### Database Schema -The `pricing_plans` table includes: -- `id`: Primary key -- `name`: Plan name (required) -- `description`: Optional description -- `monthly_price`: Monthly price (float) -- `annual_price`: Annual price (float) -- `features`: JSON array of feature strings -- `button_text`: Button display text -- `button_url`: Button link URL -- `is_popular`: Boolean for "Most Popular" styling -- `is_custom`: Boolean for custom plans -- `is_active`: Boolean for plan visibility -- `order_index`: Integer for display ordering -- `room_quota`: Maximum rooms (0 = unlimited) -- `conversation_quota`: Maximum conversations (0 = unlimited) -- `storage_quota_gb`: Maximum storage in GB (0 = unlimited) -- `manager_quota`: Maximum managers (0 = unlimited) -- `admin_quota`: Maximum admins (0 = unlimited) -- `created_by`: Foreign key to user who created the plan -- `created_at`/`updated_at`: Timestamps - -### API Endpoints -- `POST /api/admin/pricing-plans` - Create new plan -- `GET /api/admin/pricing-plans/` - Get plan details -- `PUT /api/admin/pricing-plans/` - Update plan -- `DELETE /api/admin/pricing-plans/` - Delete plan -- `PATCH /api/admin/pricing-plans//status` - Update plan status - -### Template Integration -The pricing page automatically uses configured plans: -- Falls back to hardcoded plans if no plans are configured -- Supports dynamic feature lists -- Handles custom plans without pricing -- Shows/hides billing toggle based on plan types -- Displays quota information in plan cards - -### Quota Enforcement -The PricingPlan model includes utility methods for quota checking: -- `check_quota(quota_type, current_count)`: Returns True if quota allows the operation -- `get_quota_remaining(quota_type, current_count)`: Returns remaining quota -- `format_quota_display(quota_type)`: Formats quota for display -- `get_storage_quota_bytes()`: Converts GB to bytes for storage calculations - -Example usage in your application: -```python -# Check if user can create a new room -plan = PricingPlan.query.get(user_plan_id) -current_rooms = Room.query.filter_by(created_by=user.id).count() -if plan.check_quota('room_quota', current_rooms): - # Allow room creation - pass -else: - # Show upgrade message - pass -``` - -## Security - -- Only admin users can access pricing configuration -- Only MASTER instances can configure pricing -- All API endpoints require authentication and admin privileges -- CSRF protection is enabled for all forms - -## Customization - -### Styling -The pricing plans use the existing CSS variables: -- `--primary-color`: Main brand color -- `--secondary-color`: Secondary brand color -- `--shadow-color`: Card shadows - -### Button URLs -Configure button URLs to point to: -- Contact forms -- Payment processors -- Sales pages -- Custom landing pages - -### Features -Features can include: -- Storage limits -- User limits -- Feature availability -- Support levels -- Integration options - -### Quota Integration -To integrate quotas into your application: - -1. **User Plan Assignment**: Associate users with pricing plans -2. **Quota Checking**: Use the `check_quota()` method before operations -3. **Upgrade Prompts**: Show upgrade messages when quotas are exceeded -4. **Usage Tracking**: Track current usage for quota calculations - -## Troubleshooting - -### Common Issues - -1. **Pricing tab not visible** - - Ensure you're on a MASTER instance (`MASTER=true`) - - Ensure you're logged in as an admin user - -2. **Plans not showing on pricing page** - - Check that plans are marked as "Active" - - Verify the database migration was applied - - Check for JavaScript errors in browser console - -3. **Features not saving** - - Ensure at least one feature is provided - - Check that feature text is not empty - -4. **Quota fields not working** - - Verify the quota migration was applied - - Check that quota values are integers - - Ensure quota fields are included in form submissions - -5. **API errors** - - Verify CSRF token is included in requests - - Check that all required fields are provided - - Ensure proper JSON formatting for features - -### Debugging -- Check browser console for JavaScript errors -- Review server logs for API errors -- Verify database connectivity -- Test with default plans first - -## Future Enhancements - -Potential future improvements: -- Plan categories/tiers -- Regional pricing -- Currency support -- Promotional pricing -- Plan comparison features -- Analytics and usage tracking -- Automatic quota enforcement middleware -- Usage dashboard for quota monitoring \ No newline at end of file diff --git a/clear_files_and_db.py b/clear_files_and_db.py deleted file mode 100644 index e74435a..0000000 --- a/clear_files_and_db.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import shutil -from app import create_app -from models import db, RoomFile, Room, RoomMemberPermission -from sqlalchemy import text - -app = create_app() - -def clear_all_data(): - with app.app_context(): - # Delete records in the correct order to handle foreign key constraints - # 1. Delete all RoomFile records from the database - RoomFile.query.delete() - print("All RoomFile records deleted.") - - # 2. Delete all RoomMemberPermission records - RoomMemberPermission.query.delete() - print("All RoomMemberPermission records deleted.") - - # 3. Delete all room_members associations - db.session.execute(text('DELETE FROM room_members')) - print("All room_members associations deleted.") - - # 4. Delete all Room records - Room.query.delete() - print("All Room records deleted.") - - # Commit the database changes - db.session.commit() - print("Database cleanup completed.") - -def clear_filesystem(): - # 1. Clear the data/rooms directory - data_root = os.path.join(os.path.dirname(__file__), 'data', 'rooms') - if os.path.exists(data_root): - for item in os.listdir(data_root): - item_path = os.path.join(data_root, item) - if os.path.isfile(item_path): - os.remove(item_path) - elif os.path.isdir(item_path): - shutil.rmtree(item_path) - print("Cleared data/rooms directory") - - # 2. Clear the uploads directory except for profile_pics - uploads_dir = os.path.join(os.path.dirname(__file__), 'uploads') - if os.path.exists(uploads_dir): - for item in os.listdir(uploads_dir): - if item != 'profile_pics': - item_path = os.path.join(uploads_dir, item) - if os.path.isfile(item_path): - os.remove(item_path) - elif os.path.isdir(item_path): - shutil.rmtree(item_path) - print("Cleared uploads directory") - -if __name__ == '__main__': - clear_all_data() - clear_filesystem() - print("Cleanup completed successfully!") \ No newline at end of file diff --git a/clear_specific_files.py b/clear_specific_files.py deleted file mode 100644 index 56b7b5b..0000000 --- a/clear_specific_files.py +++ /dev/null @@ -1,27 +0,0 @@ -from app import create_app, db -from app.models import RoomFile, Room -import os - -app = create_app() -with app.app_context(): - # Get the Test room - room = Room.query.filter_by(name='Test').first() - if not room: - print("Test room not found") - exit(1) - - # Delete from database - files = ['Screenshot_2025-03-19_100338.png', 'Screenshot_2025-03-19_100419.png'] - deleted = RoomFile.query.filter_by(room_id=room.id, name__in=files).delete() - db.session.commit() - print(f"Deleted {deleted} records from database") - - # Delete from filesystem - room_path = os.path.join('data', 'rooms', str(room.id)) - for file in files: - file_path = os.path.join(room_path, file) - if os.path.exists(file_path): - os.remove(file_path) - print(f"Deleted file: {file_path}") - else: - print(f"File not found: {file_path}") \ No newline at end of file diff --git a/create_notifs_table.py b/create_notifs_table.py deleted file mode 100644 index b708e06..0000000 --- a/create_notifs_table.py +++ /dev/null @@ -1,11 +0,0 @@ -from app import app, db -from models import Notif - -def create_notifs_table(): - with app.app_context(): - # Create the table - Notif.__table__.create(db.engine) - print("Notifications table created successfully!") - -if __name__ == '__main__': - create_notifs_table() \ No newline at end of file