This commit is contained in:
2025-06-24 11:25:10 +02:00
parent 6412d9f01a
commit 996adb4bce
15 changed files with 1695 additions and 4 deletions

View File

@@ -3,6 +3,7 @@ from flask_login import login_required, current_user
from models import db, Room, RoomFile, User, DocuPulseSettings, HelpArticle
import os
from datetime import datetime
import json
admin = Blueprint('admin', __name__)
@@ -438,4 +439,240 @@ def get_help_article(article_id):
'order_index': article.order_index
}
return jsonify({'article': article_data})
return jsonify({'article': article_data})
@admin.route('/api/admin/pricing-plans', methods=['POST'])
@login_required
def create_pricing_plan():
if not current_user.is_admin:
return jsonify({'error': 'Unauthorized'}), 403
# Check if this is a MASTER instance
is_master = os.environ.get('MASTER', 'false').lower() == 'true'
if not is_master:
return jsonify({'error': 'Pricing configuration is only available on MASTER instances'}), 403
try:
from models import PricingPlan
# Get form data
name = request.form.get('name')
description = request.form.get('description')
monthly_price = float(request.form.get('monthly_price'))
annual_price = float(request.form.get('annual_price'))
features = json.loads(request.form.get('features', '[]'))
button_text = request.form.get('button_text', 'Get Started')
button_url = request.form.get('button_url', '#')
is_popular = request.form.get('is_popular') == 'true'
is_custom = request.form.get('is_custom') == 'true'
is_active = request.form.get('is_active') == 'true'
# Get quota fields
room_quota = int(request.form.get('room_quota', 0))
conversation_quota = int(request.form.get('conversation_quota', 0))
storage_quota_gb = int(request.form.get('storage_quota_gb', 0))
manager_quota = int(request.form.get('manager_quota', 0))
admin_quota = int(request.form.get('admin_quota', 0))
# Validate required fields
if not name or not features:
return jsonify({'error': 'Name and features are required'}), 400
# Get the highest order index
max_order = db.session.query(db.func.max(PricingPlan.order_index)).scalar() or 0
# Create new pricing plan
plan = PricingPlan(
name=name,
description=description,
monthly_price=monthly_price,
annual_price=annual_price,
features=features,
button_text=button_text,
button_url=button_url,
is_popular=is_popular,
is_custom=is_custom,
is_active=is_active,
room_quota=room_quota,
conversation_quota=conversation_quota,
storage_quota_gb=storage_quota_gb,
manager_quota=manager_quota,
admin_quota=admin_quota,
order_index=max_order + 1,
created_by=current_user.id
)
db.session.add(plan)
db.session.commit()
return jsonify({'success': True, 'message': 'Pricing plan created successfully'})
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500
@admin.route('/api/admin/pricing-plans/<int:plan_id>', methods=['GET'])
@login_required
def get_pricing_plan(plan_id):
if not current_user.is_admin:
return jsonify({'error': 'Unauthorized'}), 403
try:
from models import PricingPlan
plan = PricingPlan.query.get(plan_id)
if not plan:
return jsonify({'error': 'Pricing plan not found'}), 404
return jsonify({
'success': True,
'plan': {
'id': plan.id,
'name': plan.name,
'description': plan.description,
'monthly_price': plan.monthly_price,
'annual_price': plan.annual_price,
'features': plan.features,
'button_text': plan.button_text,
'button_url': plan.button_url,
'is_popular': plan.is_popular,
'is_custom': plan.is_custom,
'is_active': plan.is_active,
'order_index': plan.order_index,
'room_quota': plan.room_quota,
'conversation_quota': plan.conversation_quota,
'storage_quota_gb': plan.storage_quota_gb,
'manager_quota': plan.manager_quota,
'admin_quota': plan.admin_quota
}
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@admin.route('/api/admin/pricing-plans/<int:plan_id>', methods=['PUT'])
@login_required
def update_pricing_plan(plan_id):
if not current_user.is_admin:
return jsonify({'error': 'Unauthorized'}), 403
# Check if this is a MASTER instance
is_master = os.environ.get('MASTER', 'false').lower() == 'true'
if not is_master:
return jsonify({'error': 'Pricing configuration is only available on MASTER instances'}), 403
try:
from models import PricingPlan
plan = PricingPlan.query.get(plan_id)
if not plan:
return jsonify({'error': 'Pricing plan not found'}), 404
# Get form data
name = request.form.get('name')
description = request.form.get('description')
monthly_price = float(request.form.get('monthly_price'))
annual_price = float(request.form.get('annual_price'))
features = json.loads(request.form.get('features', '[]'))
button_text = request.form.get('button_text', 'Get Started')
button_url = request.form.get('button_url', '#')
is_popular = request.form.get('is_popular') == 'true'
is_custom = request.form.get('is_custom') == 'true'
is_active = request.form.get('is_active') == 'true'
# Get quota fields
room_quota = int(request.form.get('room_quota', 0))
conversation_quota = int(request.form.get('conversation_quota', 0))
storage_quota_gb = int(request.form.get('storage_quota_gb', 0))
manager_quota = int(request.form.get('manager_quota', 0))
admin_quota = int(request.form.get('admin_quota', 0))
# Validate required fields
if not name or not features:
return jsonify({'error': 'Name and features are required'}), 400
# Update plan
plan.name = name
plan.description = description
plan.monthly_price = monthly_price
plan.annual_price = annual_price
plan.features = features
plan.button_text = button_text
plan.button_url = button_url
plan.is_popular = is_popular
plan.is_custom = is_custom
plan.is_active = is_active
plan.room_quota = room_quota
plan.conversation_quota = conversation_quota
plan.storage_quota_gb = storage_quota_gb
plan.manager_quota = manager_quota
plan.admin_quota = admin_quota
db.session.commit()
return jsonify({'success': True, 'message': 'Pricing plan updated successfully'})
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500
@admin.route('/api/admin/pricing-plans/<int:plan_id>', methods=['DELETE'])
@login_required
def delete_pricing_plan(plan_id):
if not current_user.is_admin:
return jsonify({'error': 'Unauthorized'}), 403
# Check if this is a MASTER instance
is_master = os.environ.get('MASTER', 'false').lower() == 'true'
if not is_master:
return jsonify({'error': 'Pricing configuration is only available on MASTER instances'}), 403
try:
from models import PricingPlan
plan = PricingPlan.query.get(plan_id)
if not plan:
return jsonify({'error': 'Pricing plan not found'}), 404
db.session.delete(plan)
db.session.commit()
return jsonify({'success': True, 'message': 'Pricing plan deleted successfully'})
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500
@admin.route('/api/admin/pricing-plans/<int:plan_id>/status', methods=['PATCH'])
@login_required
def update_pricing_plan_status(plan_id):
if not current_user.is_admin:
return jsonify({'error': 'Unauthorized'}), 403
# Check if this is a MASTER instance
is_master = os.environ.get('MASTER', 'false').lower() == 'true'
if not is_master:
return jsonify({'error': 'Pricing configuration is only available on MASTER instances'}), 403
try:
from models import PricingPlan
plan = PricingPlan.query.get(plan_id)
if not plan:
return jsonify({'error': 'Pricing plan not found'}), 404
data = request.get_json()
field = data.get('field')
value = data.get('value')
if field not in ['is_active', 'is_popular', 'is_custom']:
return jsonify({'error': 'Invalid field'}), 400
setattr(plan, field, value)
db.session.commit()
return jsonify({'success': True, 'message': 'Plan status updated successfully'})
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500

View File

@@ -1122,7 +1122,7 @@ def init_routes(main_bp):
active_tab = request.args.get('tab', 'colors')
# Validate tab parameter
valid_tabs = ['colors', 'general', 'email_templates', 'mails', 'security', 'events', 'debugging', 'smtp', 'connections']
valid_tabs = ['colors', 'general', 'email_templates', 'mails', 'security', 'events', 'debugging', 'smtp', 'connections', 'pricing']
if active_tab not in valid_tabs:
active_tab = 'colors'
@@ -1180,6 +1180,12 @@ def init_routes(main_bp):
current_page = mails.page
users = User.query.order_by(User.username).all()
# Get pricing plans for the pricing tab (only for MASTER instances)
pricing_plans = []
if active_tab == 'pricing':
from models import PricingPlan
pricing_plans = PricingPlan.query.order_by(PricingPlan.order_index).all()
if request.method == 'GET':
company_form.company_name.data = site_settings.company_name
company_form.company_website.data = site_settings.company_website
@@ -1210,6 +1216,7 @@ def init_routes(main_bp):
nginx_settings=nginx_settings,
git_settings=git_settings,
cloudflare_settings=cloudflare_settings,
pricing_plans=pricing_plans,
csrf_token=generate_csrf())
@main_bp.route('/settings/update-smtp', methods=['POST'])

View File

@@ -1,5 +1,5 @@
from flask import Blueprint, render_template, redirect, url_for, request
from models import SiteSettings, HelpArticle
from models import SiteSettings, HelpArticle, PricingPlan
import os
def init_public_routes(public_bp):
@@ -8,6 +8,11 @@ def init_public_routes(public_bp):
site_settings = SiteSettings.query.first()
return dict(site_settings=site_settings)
@public_bp.context_processor
def inject_pricing_plans():
"""Make PricingPlan model available in templates"""
return dict(PricingPlan=PricingPlan)
@public_bp.route('/features')
def features():
"""Features page"""