diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index 4236a75..0244db8 100644 Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ diff --git a/__pycache__/models.cpython-313.pyc b/__pycache__/models.cpython-313.pyc index 4d2cb8b..961c070 100644 Binary files a/__pycache__/models.cpython-313.pyc and b/__pycache__/models.cpython-313.pyc differ diff --git a/migrations/versions/add_instances_table.py b/migrations/versions/add_instances_table.py new file mode 100644 index 0000000..74928fb --- /dev/null +++ b/migrations/versions/add_instances_table.py @@ -0,0 +1,39 @@ +"""add instances table + +Revision ID: add_instances_table +Revises: +Create Date: 2024-03-19 10:00:00.000000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'add_instances_table' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table('instances', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('company', sa.String(length=100), nullable=False), + sa.Column('rooms_count', sa.Integer(), nullable=False, server_default='0'), + sa.Column('conversations_count', sa.Integer(), nullable=False, server_default='0'), + sa.Column('data_size', sa.Float(), nullable=False, server_default='0.0'), + sa.Column('payment_plan', sa.String(length=20), nullable=False, server_default='Basic'), + sa.Column('main_url', sa.String(length=255), nullable=False), + sa.Column('status', sa.String(length=20), nullable=False, server_default='inactive'), + sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')), + sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name'), + sa.UniqueConstraint('main_url') + ) + + +def downgrade(): + op.drop_table('instances') \ No newline at end of file diff --git a/models.py b/models.py index 895e7b2..2ded884 100644 --- a/models.py +++ b/models.py @@ -493,4 +493,22 @@ class ManagementAPIKey(db.Model): creator = db.relationship('User', backref=db.backref('created_api_keys', cascade='all, delete-orphan')) def __repr__(self): - return f'' \ No newline at end of file + return f'' + +class Instance(db.Model): + __tablename__ = 'instances' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + company = db.Column(db.String(100), nullable=False) + rooms_count = db.Column(db.Integer, default=0) + conversations_count = db.Column(db.Integer, default=0) + data_size = db.Column(db.Float, default=0) # in GB + payment_plan = db.Column(db.String(50), nullable=False) + main_url = db.Column(db.String(255), nullable=False) + status = db.Column(db.String(20), default='inactive') # active or inactive + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + def __repr__(self): + return f'' \ No newline at end of file diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index 321d4b7..4f74aa3 100644 Binary files a/routes/__pycache__/main.cpython-313.pyc and b/routes/__pycache__/main.cpython-313.pyc differ diff --git a/routes/main.py b/routes/main.py index a1792dd..7ab7210 100644 --- a/routes/main.py +++ b/routes/main.py @@ -1,6 +1,6 @@ from flask import render_template, Blueprint, redirect, url_for, request, flash, Response, jsonify, session, current_app from flask_login import current_user, login_required -from models import User, db, Room, RoomFile, RoomMemberPermission, SiteSettings, Event, Conversation, Message, MessageAttachment, Notif, EmailTemplate, Mail, KeyValueSettings, DocuPulseSettings, PasswordSetupToken +from models import User, db, Room, RoomFile, RoomMemberPermission, SiteSettings, Event, Conversation, Message, MessageAttachment, Notif, EmailTemplate, Mail, KeyValueSettings, DocuPulseSettings, PasswordSetupToken, Instance from routes.auth import require_password_change import os from werkzeug.utils import secure_filename @@ -339,7 +339,97 @@ def init_routes(main_bp): if not os.environ.get('MASTER', 'false').lower() == 'true': flash('This page is only available in master instances.', 'error') return redirect(url_for('main.dashboard')) - return render_template('main/instances.html') + + instances = Instance.query.all() + return render_template('main/instances.html', instances=instances) + + @main_bp.route('/instances/add', methods=['POST']) + @login_required + @require_password_change + def add_instance(): + if not os.environ.get('MASTER', 'false').lower() == 'true': + return jsonify({'error': 'Unauthorized'}), 403 + + data = request.get_json() + try: + instance = Instance( + name=data['name'], + company=data['company'], + payment_plan=data['payment_plan'], + main_url=data['main_url'], + status='inactive' # New instances start as inactive + ) + db.session.add(instance) + db.session.commit() + return jsonify({ + 'message': 'Instance added successfully', + 'instance': { + 'id': instance.id, + 'name': instance.name, + 'company': instance.company, + 'rooms_count': instance.rooms_count, + 'conversations_count': instance.conversations_count, + 'data_size': instance.data_size, + 'payment_plan': instance.payment_plan, + 'main_url': instance.main_url, + 'status': instance.status + } + }) + except Exception as e: + db.session.rollback() + return jsonify({'error': str(e)}), 400 + + @main_bp.route('/instances/', methods=['PUT']) + @login_required + @require_password_change + def update_instance(instance_id): + if not os.environ.get('MASTER', 'false').lower() == 'true': + return jsonify({'error': 'Unauthorized'}), 403 + + instance = Instance.query.get_or_404(instance_id) + data = request.get_json() + + try: + instance.name = data.get('name', instance.name) + instance.company = data.get('company', instance.company) + instance.payment_plan = data.get('payment_plan', instance.payment_plan) + instance.main_url = data.get('main_url', instance.main_url) + instance.status = data.get('status', instance.status) + + db.session.commit() + return jsonify({ + 'message': 'Instance updated successfully', + 'instance': { + 'id': instance.id, + 'name': instance.name, + 'company': instance.company, + 'rooms_count': instance.rooms_count, + 'conversations_count': instance.conversations_count, + 'data_size': instance.data_size, + 'payment_plan': instance.payment_plan, + 'main_url': instance.main_url, + 'status': instance.status + } + }) + except Exception as e: + db.session.rollback() + return jsonify({'error': str(e)}), 400 + + @main_bp.route('/instances/', methods=['DELETE']) + @login_required + @require_password_change + def delete_instance(instance_id): + if not os.environ.get('MASTER', 'false').lower() == 'true': + return jsonify({'error': 'Unauthorized'}), 403 + + instance = Instance.query.get_or_404(instance_id) + try: + db.session.delete(instance) + db.session.commit() + return jsonify({'message': 'Instance deleted successfully'}) + except Exception as e: + db.session.rollback() + return jsonify({'error': str(e)}), 400 UPLOAD_FOLDER = '/app/uploads/profile_pics' if not os.path.exists(UPLOAD_FOLDER): diff --git a/templates/components/header.html b/templates/components/header.html index 83c5717..6b9293a 100644 --- a/templates/components/header.html +++ b/templates/components/header.html @@ -1,4 +1,4 @@ -{% macro header(title, description="", button_text="", button_url="", icon="fa-folder", button_class="", button_icon="fa-plus", button_style="") %} +{% macro header(title, description="", button_text="", button_url="", icon="fa-folder", button_class="", button_icon="fa-plus", button_style="", buttons=None) %}
@@ -11,8 +11,31 @@

{{ description }}

{% endif %}
- {% if button_text and button_url %} -
+
+ {% if buttons %} + {% for button in buttons %} + {% if button.url == "#" %} + + {% else %} + + {% endif %} + {% endfor %} + {% elif button_text and button_url %} {% if button_url == "#" %}
- {% endif %} + {% endif %} +
diff --git a/templates/main/instances.html b/templates/main/instances.html index f10ab10..ba34f97 100644 --- a/templates/main/instances.html +++ b/templates/main/instances.html @@ -7,14 +7,344 @@ {{ header( title="Instances", description="Manage your DocuPulse instances", - button_text="", - button_url="", - icon="fa-server" + icon="fa-server", + buttons=[ + { + 'text': 'Launch New Instance', + 'url': '#', + 'icon': 'fa-rocket', + 'class': 'btn-primary', + 'onclick': 'showAddInstanceModal()' + }, + { + 'text': 'Add Existing Instance', + 'url': '#', + 'icon': 'fa-link', + 'class': 'btn-primary', + 'onclick': 'showAddExistingInstanceModal()' + } + ] ) }} -
-
-

Instance management will be available soon.

+
+
+
+
+
+
+ + + + + + + + + + + + + + + + {% for instance in instances %} + + + + + + + + + + + + {% endfor %} + +
NameCompanyRoomsConversationsDataPayment PlanMain URLStatusActions
{{ instance.name }}{{ instance.company }}{{ instance.rooms_count }}{{ instance.conversations_count }}{{ "%.1f"|format(instance.data_size) }} GB{{ instance.payment_plan }}{{ instance.main_url }} + + {{ instance.status|title }} + + +
+ + +
+
+
+
+
+
+ + + + + + + + + + +{% endblock %} + +{% block extra_js %} + {% endblock %} \ No newline at end of file diff --git a/templates/settings/settings.html b/templates/settings/settings.html index d0931d5..754ebc7 100644 --- a/templates/settings/settings.html +++ b/templates/settings/settings.html @@ -35,6 +35,7 @@ Theme Colors + {% if not is_master %} + {% endif %} + {% if not is_master %} + + {% endif %}
+ {% if not is_master %}
{{ company_info_tab(site_settings, form, csrf_token) }} @@ -88,12 +94,14 @@
{{ email_templates_tab(email_templates, csrf_token) }}
+ {% endif %}
{{ mails_tab(mails, csrf_token, users, total_pages, current_page) }}
+ {% if not is_master %}
{{ security_tab() }} @@ -103,6 +111,7 @@
{{ events_tab(events, csrf_token, users, total_pages, current_page) }}
+ {% endif %}