From a78f3c07864b9361627e15a3d000717260d21acd Mon Sep 17 00:00:00 2001 From: Kobe Date: Thu, 5 Jun 2025 11:40:52 +0200 Subject: [PATCH] added usage limit visuals and DB --- __pycache__/models.cpython-313.pyc | Bin 30144 -> 32854 bytes .../versions/add_docupulse_settings_table.py | 36 ++++++ models.py | 40 ++++++ routes/__pycache__/admin.cpython-313.pyc | Bin 10730 -> 11403 bytes routes/__pycache__/main.cpython-313.pyc | Bin 78726 -> 75991 bytes routes/admin.py | 14 ++- routes/main.py | 117 ++++++++---------- templates/components/usage_limits.html | 67 ++++++++++ templates/dashboard/dashboard.html | 4 + 9 files changed, 212 insertions(+), 66 deletions(-) create mode 100644 migrations/versions/add_docupulse_settings_table.py create mode 100644 templates/components/usage_limits.html diff --git a/__pycache__/models.cpython-313.pyc b/__pycache__/models.cpython-313.pyc index ca58e881759f4f853175b0c5091e973dc6eec0f3..a56e18f0ff5ac86cb8b82b468cdd14fd2d5ea8cd 100644 GIT binary patch delta 2781 zcmZuzeQZ{mTJIDnD=N@U1=dN>c@v_r4O+FTv`zEF1G{f@>h=#pLnSn+#Jsnd9Chk=S>a7H4Z4z<`$Y) zXi(2_v`XZvIM1R|&a)U^*35w(Z7wmxx3!aks`xrnG@wG~95lGx3BB$>Eja@(*VT|S ziHmjD2zf6Num6DXqXeb~f-qHO(|dFty_<*e!X;W1A--*y-jkzp&sT2%ue;b}aBHY# zN502c%E9;Da|Uu-kE;n1(b6gjuPS_vs(YQ4r)J=@Lv>h|k ze#AUn8pSx`+vlZ`$cUs^QS?W`-wIO67Y#+il43$iibkk!Fi78F9`rH;iDytzL6;%% z9s|YkKdCJq3`QTNa}J%hGMHG%ePSy*;*PtIG{>7$wz-prKU<1FDlW~GmS?KwX37`b z&C!Z^lWq3|&TIkaiUk!6{Gruq&G;SkHOlfY12DIX?qD&pWfb8w^$^SuT*6DiM* zbHkcmwOW`n2i77xRIar1y#gdxF%v7CTxl~Fpbj69H`!rgnL${Q-Qvz-G%!6f=oCS= z*mB6qHjZ0CVE^i>qSeGXF~xb8~yELddXIFDwLj^nnn^TT=Ay7tA7td{=2iNETE%j9}yrna%ETaXx^ zwen~e8|SikXTDv!jtvygmR2%w8R?%;zJc&D1I6r*Q5p54g z^74)bsh>If2FHVn_=L->DyS4>y&mISR<+3Jvlz%l3`FOT#KKV}ckN@vN{y)g6{@uZ zv9Mp!NU;$*jAR^1{Xl3q8l?0vH;yC8?;G~fuVm=b(JSciE;^W^k*IGto2zYR@I=^( zd4|kJEU}|vUBMo;F%%1SKultt!#c{ya^)XR?gl%pYOD7zt+9v6-ZY0)W* z&Yu;WEk09xw(LyVFS^g~e0yigwKgTLON-;1WpVTG+S2Vi(mbW-N~_W(Pst@uWh!0i%0{`eF;iBZE~}NxYBOJRX67|y<}A##Z%eoD zlG}GBef_ES{u5>C+7_9;l`WS&S1a0*yF;14a5^w12gas2ZVkVdpYGwfAX&>NU8~Z~ zJu-V;J;|MYSDk&#U?abY&vZVU?i`Ri2a=(oROir%)#>G}GJC6AKU^+Xb=oUA$5FN^M^wlGs*KjQkK>(w>KHyzz{&LkJC zf~W0;aIrbB<}NSj#Aywuw@#rnB{JzQpRW^CZW<-JLR~%4)UcnBWa6TGHL{Squq4f;=*D2+qBaXv^* zubEW!H&h$kTkGuJK0s2>F1;sp4%s$d37Dy@d^%>!(Ti)Pp1Ufo?sR@;ngnnza*5p82gmMCPo(seQExwXO&bAFa zX-#x|vzm}?K(|Lt{b)xq!+hK;;s;%~$Fc)igjV*EWN7K@gbUr1{0>}2gx;P_WP9Sx zo`XafLQ910(A2x03?;tT`x0*-WQ3v*N1~wtDf?f-E?BVZ<2gPwU&)qZKk9<2_eP&% zcWypL;3se;AVQ=e{7=53-1-W|%`8UzAxZ(|T!hGmSlniR>2n!H?te&;-I;s$k!PUKg(4*vX!72J3 z@&(M3I=t%Dpq8*&aB9)0_2EEA_59SM8N;EPn;I+5L%VSuwY!%wNinP%i3DQ9!6tf+ iY1zw3|4#f~Y9ZG7M0k}yZa8YV$1(BVGX(NtKmRX;(j`9t delta 1102 zcmYL{VQf=X6vywm?KXI0-9(rjFbj;x3b>MNfEo?k*lIU~v6iu!;IQqwwME1>Ujk9^O;w>a** zT6mKRt!fKJ)!RBgSVlLQ{Or#@14;ry#V& z@XA7f3H)(eu6ULlglpE)Z{uH0OE8{t;`Qyre3oId(h%7d#~UG{b$nJOxVi55u#&KIHcp zHnyLDN9anGFCvj3Oj}E*!x?0FFZ497>G+I4WH{Dw-Zt}q$-!4E?1daGG2sEjuU{Jm zj=8;M@<^wbgG-LRUIDNU{|*}PGWLYZ!G~{!0>yRYA-Jrik7F+G$LGUCP>0`#!{Cw6 zbR7e&g+v4ojz$`J3t*<%Da*Ule9>mBYUU@BMyy-kDIC=97W_Q=hs#Tz7p&ZEGc~op z$LLQ*LL*#8(xnY5XIVfWvP!mO_sp4i%76bG*iA62jVqam_ov4c`0rO z2+Q2wZ#eA0{=_5b#NOml7{`B;wOWFNgdNhGasW>N-mP-VMDGNLAl4fl3zXsvwGjsV zWYuoueSYZ0w!UcTelqI>HC6^i2LMOgOL3;JO-oZw2ufdiDL`5saAsc~2SaB2>j4hq z)%`y}7T-P4!`~s}Q_GK#i9SjUQZ!G!n6R2K1h_o4Ov}<1!9f4PpYkbkSK{hS224Da x*y4C%u`=P^8AFetSSzJ3%uAQ)o)}QIK(mzlKc=FI!j{(6)Ov(TN diff --git a/migrations/versions/add_docupulse_settings_table.py b/migrations/versions/add_docupulse_settings_table.py new file mode 100644 index 0000000..98fdf7a --- /dev/null +++ b/migrations/versions/add_docupulse_settings_table.py @@ -0,0 +1,36 @@ +"""add docupulse settings table + +Revision ID: add_docupulse_settings +Revises: add_notifs_table +Create Date: 2024-03-19 10:00:00.000000 + +""" +from alembic import op +import sqlalchemy as sa +from datetime import datetime + +# revision identifiers, used by Alembic. +revision = 'add_docupulse_settings' +down_revision = 'add_notifs_table' +branch_labels = None +depends_on = None + +def upgrade(): + # Create docupulse_settings table + op.create_table('docupulse_settings', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('max_rooms', sa.Integer(), nullable=False, server_default='10'), + sa.Column('max_conversations', sa.Integer(), nullable=False, server_default='10'), + sa.Column('max_storage', sa.Integer(), nullable=False, server_default='10737418240'), # 10GB in bytes + sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')), + sa.PrimaryKeyConstraint('id') + ) + + # Insert default settings + op.execute(""" + INSERT INTO docupulse_settings (id, max_rooms, max_conversations, max_storage, updated_at) + VALUES (1, 10, 10, 10737418240, CURRENT_TIMESTAMP) + """) + +def downgrade(): + op.drop_table('docupulse_settings') \ No newline at end of file diff --git a/models.py b/models.py index 576a659..40383c9 100644 --- a/models.py +++ b/models.py @@ -165,6 +165,46 @@ class SiteSettings(db.Model): db.session.commit() return settings +class DocuPulseSettings(db.Model): + __tablename__ = 'docupulse_settings' + id = db.Column(db.Integer, primary_key=True) + max_rooms = db.Column(db.Integer, default=10) + max_conversations = db.Column(db.Integer, default=10) + max_storage = db.Column(db.Integer, default=10737418240) # 10GB in bytes + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + @classmethod + def get_settings(cls): + settings = cls.query.first() + if not settings: + settings = cls( + max_rooms=10, + max_conversations=10, + max_storage=10737418240 # 10GB in bytes + ) + db.session.add(settings) + db.session.commit() + return settings + + @classmethod + def get_usage_stats(cls): + settings = cls.get_settings() + total_rooms = Room.query.count() + total_conversations = Conversation.query.count() + total_storage = db.session.query(db.func.sum(RoomFile.size)).filter(RoomFile.deleted == False).scalar() or 0 + + return { + 'max_rooms': settings.max_rooms, + 'max_conversations': settings.max_conversations, + 'max_storage': settings.max_storage, + 'current_rooms': total_rooms, + 'current_conversations': total_conversations, + 'current_storage': total_storage, + 'rooms_percentage': (total_rooms / settings.max_rooms) * 100 if settings.max_rooms > 0 else 0, + 'conversations_percentage': (total_conversations / settings.max_conversations) * 100 if settings.max_conversations > 0 else 0, + 'storage_percentage': (total_storage / settings.max_storage) * 100 if settings.max_storage > 0 else 0 + } + class KeyValueSettings(db.Model): id = db.Column(db.Integer, primary_key=True) key = db.Column(db.String(100), unique=True, nullable=False) diff --git a/routes/__pycache__/admin.cpython-313.pyc b/routes/__pycache__/admin.cpython-313.pyc index b5da3be96417d4a734640a8a0efcd3b14202482a..3304fd8105c4b44285467e31411d15c88924d941 100644 GIT binary patch delta 1660 zcmb`HO>7%Q6vt=0UVl5@d^XrgYzN1wp^1|wX;W$ft{_0tme6fLX)IPwJa#Pn#ap{T zCF;Nhq^eS*5fV43KtK*im2yYQkpog7LaCNOREY~cr3ylb6Z6)IE9Jt4k@mO$*?F`3 z=09)tp?Yhf<-qH85z)TRBv-yawbs&2r`HD056`&49U>}Gt+O8Rh`T)No$-P8#<~gswYlW=Q&HtskN$67s|`ExmrPIDyvrWrJTNTn<@^a z2eau#^dW-$TjzD!$s?`_+Q+ZCUZx)Yg=@V%E(Q&Ewp_?C&?@Qb3XE~hU8I*besEK- z(u#B25bcN#{#*CG`kat|P=V=n3*dJQdxjNQm1W=rMs*{eK==`FAg&++0_$>PiW9^- zv{Jgr8VQ=I1~F=0AZ9nCAcYUi1vJME?)CdGyp97A#MS=@w`X-t&);Ao$t6?3YdB5i zd4Hnbg_DC|!zY~xE5d;oL_CFf8et}J3e^x|7~w>mM3~v3moS1DMVv;A2^dn2RSjFZ zQemZxA*Gje7{{?N5pOo6O1T1)*biZ>oUgK?4(Nm-6<7(L5h~UN3I1xpKR6{i@EqcK z0mC7dlohq0!z)K!+(Vsz5(tjGgkk3Qapf2WGx+GSoy*vr70}TK{vgod4~5{O(1!0i zgM9Wq4Lz%-s|+d|-kx`zJ;w_|#7@J4;8j@UcL$>N0ihvkzGAt+(j}JBfGw9Hql0O7 z_y1}3HT+l(VOEZvkOcx~uzwEm76Sc)^8zs^Bv6|k0;;BwP{eK-VOnH*g1Z|!YH*Q% z7VV?@#&^-*>Bx<)c)F60r!&QTDPGglIW~kY>1b%PiEM}@u7iidT6&qk9qz05H;;FT zEXqfr^JD^#lV}kVCX23yCd5wC{e+b0<4Bb*dXGlu>Cvo_rc$lr7TppQ|EdyA(#YXG zOS9xzDM+$)8j~->u{|`m#eRHS+~PBaPZX|ZuFtFjy$+3Bb#u$n`}eN9 z4bU({hH_0*@g)qZAan$#x=s!RNu%r?iDEfZE3hfJDW2h`=sN=Y5~XyHME1z|9+~=? zO#MQfzmnbuj@CPapH6N&BC9fgGxp)xeQT7)_va|F*gpz<5ZLk#+>^g~XEm@z&)%oL WM-~T-A4tTaASNGKMHk)|um2ZI(pp;p delta 1092 zcmb`G%TH556vk(0OW(Cno^7Ew3MI9G5hXqfXiP+;#0Nzq+N3FT3N>wSJ+}~H!ymwk zjB9r;>OxJraq0iy?ncv%8(kQbtqbS74aB%{<6Zpj`DV_Ud*++D`>WqFfg``)!_>2{ zr#5dT4*~<+b1;?~*I0&S>}fA~)!UKwE&9l(u`EB$GM!ofbO%Zpqd+#W9Fx2|bnr@Z z1W&|g&Czu%WG~ko-5d5qP!gZvcKMcvo1b{l6^vkoC@=&J%U|wo-XjgqG#`=;&jR<# zpPoi&QcarPqFyct%GL7qO`4K#ycNFGjQY4g7({Is&<%v-#NfNVyDI$373LcqgzuQn zf+&l+C{Q1=`hfvp5O@gO2f_*sN4vyLx?agu^A*u<&}wxUSt|+&rBS|44Thjhs>16{^WKE1-KQTbA#`Gk1DX9HUN*^Z<6i1;l{_a2BwdNFo{s zCIB}u23Xxq%GpT&P1Cj^>SkxYRuk2NY0Iw}bO9-vf@!PiHM#^DgLIVYqGAxXZQ9DB zN>@~r=%bW;83~WiB5w}3h6I=DzgQ{@gBFe@4$PqIO3GFwmYl*kwuv;lt<)dhtwAm- z7&s((J{pQ>nnr0Q%v0NfO2wTV9IEH*g7juK`rIA;`}kGvJUvoep~v!YG`=^YVj8uU zSTBowRTOe0)^#cvWLZ1#zpTY~R)Hd5-4iEctBj_hF9S~j7)VzY5^hQ%woH76=)_4X zLskLHBg+(=rShZwP+ESE4|8hz$G-56L`VjbH`lFe;8kg+D`#EYT#kH@4$`-jXG>U6 z2S}m8juctjU)M}UFKm^?9PO$x{5r;Kg+m+Xyv0UZ>|%>0KeD9!kbL3!VUP23w4>&K E0}^=Q)Bpeg diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index 5c83e0abf68042c21d6598b78023457789368669..c7affbdf68401f5a5480a32f6502556b7a78b0f7 100644 GIT binary patch delta 12100 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 diff --git a/routes/admin.py b/routes/admin.py index 2ef39f2..7f34e49 100644 --- a/routes/admin.py +++ b/routes/admin.py @@ -1,6 +1,6 @@ from flask import Blueprint, jsonify from flask_login import login_required, current_user -from models import db, Room, RoomFile, User +from models import db, Room, RoomFile, User, DocuPulseSettings import os from datetime import datetime @@ -241,4 +241,16 @@ def cleanup_orphaned_records(): }) except Exception as e: db.session.rollback() + return jsonify({'error': str(e)}), 500 + +@admin.route('/api/admin/usage-stats', methods=['GET']) +@login_required +def get_usage_stats(): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + try: + stats = DocuPulseSettings.get_usage_stats() + return jsonify(stats) + except Exception as e: return jsonify({'error': str(e)}), 500 \ No newline at end of file diff --git a/routes/main.py b/routes/main.py index fc69fbf..cb7d5d1 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 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 +from models import User, db, Room, RoomFile, RoomMemberPermission, SiteSettings, Event, Conversation, Message, MessageAttachment, Notif, EmailTemplate, Mail, KeyValueSettings, DocuPulseSettings from routes.auth import require_password_change import os from werkzeug.utils import secure_filename @@ -79,6 +79,9 @@ def init_routes(main_bp): ) ).order_by(Event.timestamp.desc()).limit(7).all() + # Get usage stats + usage_stats = DocuPulseSettings.get_usage_stats() + # Room count and size logic if current_user.is_admin: logger.info("Loading admin dashboard...") @@ -201,26 +204,16 @@ def init_routes(main_bp): RoomFile.uploaded_at.desc() ).limit(10).all() - logger.info(f"Recent activity query results (non-admin): {len(recent_activity)}") - if len(recent_activity) == 0: - # Debug query to see what files exist - all_files = RoomFile.query.filter( - RoomFile.room_id.in_(room_ids), - RoomFile.deleted == False - ).all() - logger.info(f"Total non-deleted files in accessible rooms: {len(all_files)}") - for file in all_files[:5]: # Log first 5 files for debugging - logger.info(f"File: {file.name}, Uploaded: {file.uploaded_at}, Type: {file.type}") - # Format the activity data formatted_activity = [] - user_perms = {p.room_id: p for p in RoomMemberPermission.query.filter( - RoomMemberPermission.room_id.in_(room_ids), - RoomMemberPermission.user_id==current_user.id - ).all()} - for file, room, user in recent_activity: - perm = user_perms.get(room.id) + # Check if user has download permission + permission = RoomMemberPermission.query.filter_by( + room_id=room.id, + user_id=current_user.id + ).first() + can_download = permission and permission.can_download if permission else False + activity = { 'name': file.name, 'type': file.type, @@ -229,11 +222,12 @@ def init_routes(main_bp): 'uploaded_at': file.uploaded_at, 'is_starred': current_user in file.starred_by, 'is_deleted': file.deleted, - 'can_download': perm.can_download if perm else False + 'can_download': can_download } formatted_activity.append(activity) formatted_activities = formatted_activity - # Get storage usage by file type for accessible rooms including trash + + # Get storage usage by file type for accessible rooms storage_by_type = db.session.query( case( (RoomFile.name.like('%.%'), @@ -249,16 +243,13 @@ def init_routes(main_bp): # Get trash and starred stats for user's accessible rooms trash_count = RoomFile.query.filter(RoomFile.room_id.in_(room_ids), RoomFile.deleted==True).count() - starred_count = RoomFile.query.filter( - RoomFile.room_id.in_(room_ids), - RoomFile.starred_by.contains(current_user) - ).count() - # Get oldest trash date and total trash size for user's rooms + starred_count = RoomFile.query.filter(RoomFile.room_id.in_(room_ids), RoomFile.starred_by.contains(current_user)).count() + # Get oldest trash date and total trash size for accessible rooms oldest_trash = RoomFile.query.filter(RoomFile.room_id.in_(room_ids), RoomFile.deleted==True).order_by(RoomFile.deleted_at.asc()).first() oldest_trash_date = oldest_trash.deleted_at.strftime('%Y-%m-%d') if oldest_trash and oldest_trash.deleted_at else None trash_size = db.session.query(db.func.sum(RoomFile.size)).filter(RoomFile.room_id.in_(room_ids), RoomFile.deleted==True).scalar() or 0 - # Get files that will be deleted in next 7 days for user's rooms + # Get files that will be deleted in next 7 days for accessible rooms seven_days_from_now = datetime.utcnow() + timedelta(days=7) thirty_days_ago = datetime.utcnow() - timedelta(days=30) pending_deletion = RoomFile.query.filter( @@ -268,7 +259,7 @@ def init_routes(main_bp): RoomFile.deleted_at > thirty_days_ago - timedelta(days=7) ).count() - # Get trash file type breakdown for user's rooms + # Get trash file type breakdown for accessible rooms trash_by_type = db.session.query( case( (RoomFile.name.like('%.%'), @@ -280,45 +271,41 @@ def init_routes(main_bp): RoomFile.room_id.in_(room_ids), RoomFile.deleted==True ).group_by('extension').all() - - # Get conversation statistics - if current_user.is_admin: - conversation_count = Conversation.query.count() - message_count = Message.query.count() - attachment_count = MessageAttachment.query.count() - conversation_total_size = db.session.query(db.func.sum(MessageAttachment.size)).scalar() or 0 - recent_conversations = Conversation.query.order_by(Conversation.created_at.desc()).limit(5).all() - else: - # For regular users, only show their conversations - conversation_count = Conversation.query.filter(Conversation.members.any(id=current_user.id)).count() - message_count = Message.query.join(Conversation).filter(Conversation.members.any(id=current_user.id)).count() - attachment_count = MessageAttachment.query.join(Message).join(Conversation).filter(Conversation.members.any(id=current_user.id)).count() - conversation_total_size = db.session.query(db.func.sum(MessageAttachment.size)).join(Message).join(Conversation).filter(Conversation.members.any(id=current_user.id)).scalar() or 0 - recent_conversations = Conversation.query.filter(Conversation.members.any(id=current_user.id)).order_by(Conversation.created_at.desc()).limit(5).all() - return render_template('dashboard/dashboard.html', - recent_contacts=recent_contacts, - active_count=active_count, - inactive_count=inactive_count, - room_count=room_count, - file_count=file_count, - folder_count=folder_count, - total_size=total_size, # Room storage size - storage_by_type=storage_by_type, - trash_count=trash_count, - starred_count=starred_count, - oldest_trash_date=oldest_trash_date, - trash_size=trash_size, - pending_deletion=pending_deletion, - trash_by_type=trash_by_type, - recent_events=recent_events, - is_admin=current_user.is_admin, - conversation_count=conversation_count, - message_count=message_count, - attachment_count=attachment_count, - conversation_total_size=conversation_total_size, # Conversation storage size - recent_conversations=recent_conversations, - recent_notifications=recent_notifications) + # Get conversation stats + conversation_count = Conversation.query.count() + message_count = Message.query.count() + attachment_count = MessageAttachment.query.count() + conversation_total_size = db.session.query(func.sum(MessageAttachment.size)).scalar() or 0 + recent_conversations = Conversation.query.order_by(Conversation.created_at.desc()).limit(5).all() + + return render_template('dashboard/dashboard.html', + room_count=room_count, + file_count=file_count, + folder_count=folder_count, + total_size=total_size, + storage_by_type=storage_by_type, + recent_activities=formatted_activities, + trash_count=trash_count, + pending_deletion=pending_deletion, + oldest_trash_date=oldest_trash_date, + trash_size=trash_size, + trash_by_type=trash_by_type, + starred_count=starred_count, + recent_events=recent_events, + recent_contacts=recent_contacts, + active_count=active_count, + inactive_count=inactive_count, + recent_notifications=recent_notifications, + unread_notifications=get_unread_count(current_user.id), + conversation_count=conversation_count, + message_count=message_count, + attachment_count=attachment_count, + conversation_total_size=conversation_total_size, + recent_conversations=recent_conversations, + usage_stats=usage_stats, + is_admin=current_user.is_admin + ) UPLOAD_FOLDER = os.path.join(os.getcwd(), 'uploads', 'profile_pics') if not os.path.exists(UPLOAD_FOLDER): diff --git a/templates/components/usage_limits.html b/templates/components/usage_limits.html new file mode 100644 index 0000000..4c890db --- /dev/null +++ b/templates/components/usage_limits.html @@ -0,0 +1,67 @@ +{% from 'common/macros.html' import format_size %} + +{% macro usage_limits(usage_stats) %} +
+
+
+
+
Usage Limits
+
+ + +
+
+
+ + Rooms: +
+
{{ usage_stats.current_rooms }} / {{ usage_stats.max_rooms }}
+
+
+
+
+
+ + +
+
+
+ + Conversations: +
+
{{ usage_stats.current_conversations }} / {{ usage_stats.max_conversations }}
+
+
+
+
+
+ + +
+
+
+ + Storage: +
+
{{ format_size(usage_stats.current_storage) }} / {{ format_size(usage_stats.max_storage) }}
+
+
+
+
+
+
+
+
+{% endmacro %} \ No newline at end of file diff --git a/templates/dashboard/dashboard.html b/templates/dashboard/dashboard.html index 1f644f6..6d767cb 100644 --- a/templates/dashboard/dashboard.html +++ b/templates/dashboard/dashboard.html @@ -26,6 +26,7 @@ {% from 'components/recent_activity.html' import recent_activity %} {% from 'components/conversation_storage.html' import conversation_storage %} {% from 'components/notification_overview.html' import notification_overview %} +{% from 'components/usage_limits.html' import usage_limits %}