From 2014c326b171b3a813da268dd4d6d5ea033cb2f8 Mon Sep 17 00:00:00 2001 From: Kobe Date: Mon, 9 Jun 2025 10:24:42 +0200 Subject: [PATCH] Active status check --- __pycache__/models.cpython-313.pyc | Bin 37092 -> 37330 bytes .../versions/4ee23cb29001_merge_heads.py | 24 +++++++ migrations/versions/add_instances_table.py | 62 ++++++++++++----- migrations/versions/add_status_details.py | 24 +++++++ models.py | 19 ++--- requirements.txt | 15 ++-- routes/__pycache__/main.cpython-313.pyc | Bin 83471 -> 85748 bytes routes/main.py | 51 ++++++++++++++ templates/main/instances.html | 65 +++++++++++++++++- 9 files changed, 227 insertions(+), 33 deletions(-) create mode 100644 migrations/versions/4ee23cb29001_merge_heads.py create mode 100644 migrations/versions/add_status_details.py diff --git a/__pycache__/models.cpython-313.pyc b/__pycache__/models.cpython-313.pyc index 961c0702dd59404b0a4514bc3c3d02b30eb00575..ce6d3ac90418b32ad6cc266eb28a556ae3c45caf 100644 GIT binary patch delta 701 zcmaE|km=H5Cce+Syj%=GaB#9)Mnd#PzUl4U>>xf6d^Ta8yrI2yvTk$QWCayrreL^kvOWXtPqN< zM6j?FLy=^#NRd>!sHWKDd0pz0`CGZ_nSd@Rk^vIw5T^in3P9E^R;R?`%;X{^5St@2 zFEP0!vn=(NpmS(YkgH!ve2Ay7YjB98Z-6FKku*pRUvX+tS!z*yN@`kSX--L%E`~}4 ze?NuL02jv)R|SL|JJguTAG>N9nI`9VYffes5u2=#BR5$gfOYc4?hv&gi18(fC8fnc z<4Y1VbBc?6K!PkKsTCzfen4i?rO6Z9WG9FB@G)9VPU|sXbJ<~fk=t={U6HhdkjQk+ zNt!d9=6lWb+8}y>@uHAZgZB+7nfaQtG#5B6_gd(+L-YjWMJcZfEaEo=gr;*&;+&y0 zUt^}miqIX-7X=(9zwZ%cKj3~**r&l~@?SpbdS3qi$gaoREnqNM%e1phEQL_fW8-l{qB_~PFFq&^Y(;8@f(ed&_mtxDu3 zE2s#w1aky)POjIGM{x2> zxmdwMlNV&lf)&Xpak2&T26I|6O+K&b&I+W9#3onU|Pcl3A8ov}5vy2*V^LkjYoeg_(*jO`g~$J6WukkB1v*P8-7w5%~t+2a^{n zicHQdG6)h9nXWlWbB5D=ubEyO7%vJLHF)2Ul9{hLOLKwKa<7G6I~XrYSzTZezabzr zopTcB45j%RGc{H?UKG%4@Vp@`I^A-T<&2>Dk+UKf6wQv^5PDJAdh&%{5joY1{1y!! zH+cE`BfBCe6iu(3RJlNCa_x$&i@cT%Zj&Ec2v1gMXPbPoj(4(xKI`N@7w*lmeU6Nb dr#8>;S6~trVASj=>aXam_{soe7MTM54FI9ImQerz diff --git a/migrations/versions/4ee23cb29001_merge_heads.py b/migrations/versions/4ee23cb29001_merge_heads.py new file mode 100644 index 0000000..2dbebb0 --- /dev/null +++ b/migrations/versions/4ee23cb29001_merge_heads.py @@ -0,0 +1,24 @@ +"""merge heads + +Revision ID: 4ee23cb29001 +Revises: 72ab6c4c6a5f, add_status_details +Create Date: 2025-06-09 10:04:48.708415 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4ee23cb29001' +down_revision = ('72ab6c4c6a5f', 'add_status_details') +branch_labels = None +depends_on = None + + +def upgrade(): + pass + + +def downgrade(): + pass diff --git a/migrations/versions/add_instances_table.py b/migrations/versions/add_instances_table.py index 74928fb..cd1ede7 100644 --- a/migrations/versions/add_instances_table.py +++ b/migrations/versions/add_instances_table.py @@ -7,6 +7,7 @@ Create Date: 2024-03-19 10:00:00.000000 """ from alembic import op import sqlalchemy as sa +from sqlalchemy import text # revision identifiers, used by Alembic. @@ -17,23 +18,52 @@ 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') - ) + conn = op.get_bind() + result = conn.execute(text(""" + SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_name = 'instances' + ); + """)) + exists = result.scalar() + if not exists: + op.create_table('instances', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=255), nullable=False), + sa.Column('company', sa.String(length=255), 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.String(length=50), nullable=False, server_default='0 MB'), + sa.Column('payment_plan', sa.String(length=50), nullable=False, server_default='free'), + 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')), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name'), + sa.UniqueConstraint('main_url') + ) + + # Create a trigger to automatically update the updated_at column + op.execute(""" + CREATE OR REPLACE FUNCTION update_updated_at_column() + RETURNS TRIGGER AS $$ + BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; + END; + $$ language 'plpgsql'; + """) + + op.execute(""" + CREATE TRIGGER update_instances_updated_at + BEFORE UPDATE ON instances + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + """) def downgrade(): + op.execute("DROP TRIGGER IF EXISTS update_instances_updated_at ON instances") + op.execute("DROP FUNCTION IF EXISTS update_updated_at_column()") op.drop_table('instances') \ No newline at end of file diff --git a/migrations/versions/add_status_details.py b/migrations/versions/add_status_details.py new file mode 100644 index 0000000..c073ac6 --- /dev/null +++ b/migrations/versions/add_status_details.py @@ -0,0 +1,24 @@ +"""add status_details column + +Revision ID: add_status_details +Revises: add_instances_table +Create Date: 2024-03-19 11:00:00.000000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'add_status_details' +down_revision = 'add_instances_table' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('instances', sa.Column('status_details', sa.Text(), nullable=True)) + + +def downgrade(): + op.drop_column('instances', 'status_details') \ No newline at end of file diff --git a/models.py b/models.py index 2ded884..0aac28c 100644 --- a/models.py +++ b/models.py @@ -499,16 +499,17 @@ class Instance(db.Model): __tablename__ = 'instances' id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(100), nullable=False) + name = db.Column(db.String(100), unique=True, 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) + rooms_count = db.Column(db.Integer, nullable=False, default=0) + conversations_count = db.Column(db.Integer, nullable=False, default=0) + data_size = db.Column(db.Float, nullable=False, default=0.0) + payment_plan = db.Column(db.String(20), nullable=False, default='Basic') + main_url = db.Column(db.String(255), unique=True, nullable=False) + status = db.Column(db.String(20), nullable=False, default='inactive') + status_details = db.Column(db.Text, nullable=True) + created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP')) + updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')) def __repr__(self): return f'' \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3ef9775..73f3b4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,16 @@ Flask>=2.0.0 Flask-SQLAlchemy>=3.0.0 Flask-Login>=0.6.0 -Flask-WTF>=1.0.0 +Flask-Mail==0.9.1 Flask-Migrate>=4.0.0 -SQLAlchemy>=1.4.0 -Werkzeug>=2.0.0 -WTForms==3.1.1 +Flask-WTF>=1.0.0 +email-validator==2.1.0.post1 python-dotenv>=0.19.0 -psycopg2-binary==2.9.9 -gunicorn==21.2.0 -email_validator==2.1.0.post1 +Werkzeug>=2.0.0 +SQLAlchemy>=1.4.0 alembic>=1.7.0 +psycopg2-binary==2.9.9 +requests>=2.31.0 +gunicorn==21.2.0 prometheus-client>=0.16.0 PyJWT>=2.8.0 \ No newline at end of file diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index 4f74aa36d2d7019964413f69e87c99603b017c53..adf63b143e404ccfaee7c42cdc6e99554f88e949 100644 GIT binary patch delta 11064 zcmb_i3w)H-mH+O%UztfDA(IEmkDWh7jH%2tmJmsj&@ z_?xnPZhZ}}@kr|?uf8jhR>{&0Q0f+!Dm{(4 zhv?(hEt<)2B}w9oeW}?JU(#yyru7(fQ*~A^Q;b`AAS#QQhF0fytdg|8Xnpa+Qlqq5 z!ga*6hPO;UbGFn`EcP2SnME8i=9(IdXE2{t>@|-U|88`fZ9Y4!>NsG^WGUjTIY&%4 zxy??WE7_Pzjk~EaE!j8*8)3c%vs*Z)SW4C!R30UXW>3$SHdz^a>A{mkrUS-}5zmQ4(;u((+c z)pM!-7}Up8J&)>_pq@{457nny-C~u;W*=hIS18t7OT~lptPYKPU$Ho7Ej8bu>O`d4 z3dAL=J7c0+uhI?cIZ5Q%N;8Ulq?2nXUa=l1B}u`CjVToefiiiN*~-NGK$$WMrCj9M zOEW4)p-dI)fii6r%5-tiUh17O3T0-tl-!Tx-*6TxKC?2(uWy#tC!CG6jM+)rFj=a^ zO^(uxIipZ+6nlYEJql&6coir$qfq7vM@nf%t!j4HXmh@}2`CFj8M9FAO)2#bosVI> zE!HF$lCl6LDO)niJWEIEUDzsl>%<2s-)A#Kx3h~C^nB(_VWujuX(dP0rsfE5>NGY% ztV^A3uEs`IE&{(9Cmz^4Rs1eB--KPlEZoUALI*NXl02&DjCFW-jc?Fd~6cOYy<*n!ZF(18#}XcaS-d9oX!sElce@Qy$% z2FnMUVxcXe*mk~7gqM{RMX={)ge}<75ovD*dHgXRklVP7?J@D}vJw-=VyC#UY%=gR zzCKgOyT#P{D60~W*H2;7d;03j#uPRUx<%+KNk;e`3%{55LhHj)p6UYG)v@ARx#?*v zx#>>Hr<<+|GjS*_&siunGN0aOsM3XXjk?64DJNOFI_Mm& zX_F_`7W9B6#5!e9q{R~mdqQEThMR((4ck3Uk#Gz(A8z%8VzQ?#7-)~Rd4l2QXgm@M z$M_;xw#!_uw)2~z27$zk#ue#|oqrkXU`{l-VpcnCS4^~(LB$dZt0k|FpTM5x=3p!k zYL~kT>(q6ch(R8Vb@FhqImu;Hq&etW8TVQhOGh9S_IL7j#l+oYecS8@fDlOFwGD1%F#$z zhFO{ev4FyYlVmg$5B_jE${ED1KRMh#D0Ycp`^LplkddUD!M@~Q!6e<%fFa%LJeym5 zDz~gJx9n8zjK16%$9(;{RlPZLo|)L2wO~*Enaupeod@nbRR0X?%bc^P?yM{Gl&h%E zRdh)1ca^*Y)NelYO%dO3I9oF1pC%ohbZmBiN%aY9f621%w!IwhI+I`YSl9QujuiLj zmtQjK-D3tMecG4-hm`Mqr(ofUx>Jj8>05M5@4{Pq^S`0duU@f8sqRy*Nqw$Kmn8%6 zm|c7(C-0HX->-yD>CQh%`V{A91A3r*Di?z&I~V8{WJ)g@(-1S$7wYvdmZmJINzuPJ zn_;EK2q->ZS7^Emq_1J(@ClDNl<_ezHq;pejs4afcexPpS4l)*{a}hdF>*1eS>`o zw3G4iUy6?P?l@SlHMxVbH&xL^WW^i|Zwc{8n3Fjh+ap_poLg`pF3n8{xB{<4xDVk3 zfZ`l9zCo+zsYpRK6;o5Bqazd}7wI){1qr`FLUGbyLt*fI{m>+1k^wf>O-4pt#j*NK z@z?cMTL=y4aiCunPXFqgFPo&S!aa5GW|tqc^k-M?S^iF*_sFEayy<)D_O5zo!qj8l zz6sTP>fUw~T{1(@D>lh$f6#WH?UW^}&yw|~CFcWIUT^-46V^UgeXpgSSNBByx%$?( zVF7*$;T?o`0Tf4LIM5kui||lau=%_IKKNQ}H-li&KzO_2hz7QI1j8|Zv^@~!Xyp7Q z0I!ky@ei@#90HL}KUM^ z0&Wn0Pb8X3TlsIXT#KMih^3DZE+Bl2aP9PpRr3WF9%*mi5NO)OUxSWbJ@3Q*xJCR8 z02yN&9eh#RJcE6+=l14DjMGO^6#q{g@_Pi0i($)((v3y&^GLpk@CN{I))xdq5}Leb zWr_{Fn?J(+Gb(0qGm-`nMqL95L#8)OElFk63~J+#*zqy~$!t*U$%BAt1<6iYAO#Fs zCAdCe-!z9E?pd3ys@$=uvEK<#_b+r7CeC!1vUi1L$42&{kawIf_ypzr7Jyf;*dSF6 zwfJM(qe0$)?WkF?W9Qnh+0k=Z5F7~PmQt|fM6d#QEj%5|U)?hedm7crfthK$USLba zA9rnKyTr!3$EV>$w2z7*8i=)VAs)FqFHV+?Ml>vryo^<1$c3d!g!>U@A)wn)EGT|B z&=KSXST)FjEx}ir&yGz*l zo=NvyG}`NMg25Y&XKx8>63h2yv1$?AyDfDo(uQ}qxVO?1g8W#{016l$CL=#E_j=~` zyKDMg^Lj1wc(qu#&zD_}91I%?-4!F<1rj%xvP=^dBjUAvuvE{7`~GCmjbY;b139yr zaLKPB+=^rL(`K~sHmt6u>hy|cz7ead0FE*~2G%*MgGDRE;=&=jIBEiQLMNn>%_#;R z3^YSZkr1s*Uj~w*$7Fd(+k7H+&qC04I0dV<2zwFGc6kfJZiIWq;%^tlX@w`Sz694o zJLXHVG{g(p$Wgn>%N!(CBg_X-EReVRA$<)hHaOKB_6f?wf#QfoVu5x)9Tb_@veAc9LScT(h@f)#399*kuJ{Ji?n&vfF z9nv(Pht*nuqk2^(ntY3qR0rVAQ6t|?*nmprE0`1?X4C%{E#ov857cl|hdSCQ0!pxJ~Ct3w9H zzl#$YD51zeV9UQY90YT@B?;f27g1&lmy%oYyY0s?jX!nYV{GzO$gdumsYel>#!?^-` z7~v}0E^`ADkM}Igz?UY@Q7th9I*-c_{5&@=8>^cE6kC(bA*Mw(1;gG-RZlw;wI$4L zQV)c<<>!;*a3Um*&QtX>32!=!!84`LxOzT!DizPDXzT>9kYiV<;GrTdGCE zegwMcWMheTDhEsVAdpeJac-Am@IK(Dc-1Gy{PR!RwjfVi5z3&q*TuJC86}1!M7=)v zF%prOsuEQBvDGP z@nYk18;c*po(B-<4)bj+J&f=jgapE7arHTGE}H14Dfrwl1$2b(Au^9bo0svhnEQNb z_D)=w4yhSSv`?y25<8zSDx+ws8IzV~>r^q{fI)*t6%NlE&o45!piPyt#3eH;?wS9> zUHTC&ggAyBliT@_6pB6uHl#^`T(4P@JYgt2_!&AP#k5_!411nQy-Xd#al+ zW}dv2Jt7`FIe%URT7khKqd00Hvs0|Jvt%|II4~37sE$7fOH5?nJ`S;#%gcx->|U217F0H06M#0TK(0q3INBqv5x?rjoB2<${iou>>B%T} z)|mj?DegEkaVhSI<_K_zdilnrQ@so2Vo9L#$73lEp#ZC@wc_I=_lVy<=Z^pHOkK~8 z7vTb=QL?2Gdv8|HI{7avNr;KDk^&FXqWe92p_(icfs$pi3^YYB4E%xZ`00D~rVD5s zJ`sI7LquM6_msT9)LMx0!1YBpr9xpL#o89!1`nBF^n=e2-7r*q_zkvPeDdL3_G>Zu zT(09J43Xhix*dpX&egGH;?Z+ukYB%Yt~0+0mGoR$cO)Y9a|xe zoSzI4?5*>9W>Fd7FN?DuWm2H!uZX{Ww3@9J4Hp)azK$~)IWEc1AoL^jBD@KpzK`DS z=|(=EyRZ^IF*rW1NPf*&@bN+W5IQ*XlZN%wpF8e#JCp5zbu!93jIDURDLJ8VC??)=ra9CJ;^3C$hQuW|%jkLk^LaW}eLRn`LgUEzs&Pk`n7@1X`2`eU z70OS0e+yK-gEvK*n-VchSQJ|*>JP-5o8cpatUmkb)qN$?K!LrvqsHz3$6alRBfGW8 zE^$XU3`(c8*!KtQf(aoUP)$9WW=7dO{xOcx_G}+iWd}?#e7k7363>}g8T<9|3ug8o z#^IS-;@uSX^)gbQc^HF3>f^{3cmzxZA(w@V_Ae8+Ia&S!Ey>aBo9aX$N#~_Qq2p#+U<~wA-B_p%&bPx?#!yCCH^#yRXcx-$}su1%dubwUzC`a$<~fJ zi+tcY^%f3y3Uf8{nSc{Mi7*jC(--CTnm?c$#J$+%z64~~#qkjqLT?kKD$0)N@<7{T zkb}WZoV@fG4*s>rUA258BrDtmpqSuGVze{H&DcU3{YQ{w@W`hOGb3J=c*@P@=q4DC zUv#tSc(4m{Sn9~TcUS7lhzCF7dEhB=O9(z~)4MyQznvR42Ag8|{?)vlYblU3h<7tT z=o3C>#Db7gIgu3?0GwyCe&mQ_o~qcB-C+Nf6sjbSfOdKTktB#v*7h`!+3W zO&s*FCFT&?>aIasbrrCW%<*=Vg$%X>ODB-0DlCl%mNfq;5fcIAtLm+z|(mPTW1nymsO!_}+!` zkxE@iO~)#QVKNpxH5N#5k*QxAABh)PV1kzD_IF_dLrHqvHB z_-#5ZHtbH5d!QtvAJbekcxd+=HrvEZK$=M@*m3xJSPYhu*k8>`;-_)lTr{%rs4)2d zj!nVl5SP6-z^7mQUylt@J_q|OKv;(GEP{e?1>sYK|3=W^G?Ni35T+wkBH*(%zZGFU zLI9x!A#@w!0|?(k_z}X-5S~Iffp89ivd;l5eTra5nbQ$S1qE30B9tPvADEGE01!Ls z^TqypLlIs)ppG9{Loq*x_y!Mf8Jza}B=ae7oEEXFX*VE|vMv;Jsmbun9PZE-UjeL4CccT^2* delta 9163 zcmb_h3wYGUwcpw7Bb&`r$b$sPk^o^z!ka({ARqxm1tCBclx36MgoRCZo&N>_iy_up zEsBp(_v#h353JNuf%qBQ`iM_@uTlh2^e221VZ?tBf%E(AF(cc%h%vpV@`PR$=@%rwvnZj~e#p3m9Mn``%HmV$3 zKWhfHer$AAF1}jj;|mcD!PumNkyJm<~^{TbMxh|hqDoibielwVe+&4fiN*%B6ZSVdp zVwNXU3&cRR&pOatH;6m39R+!VdsbaxF?3=w)fE@2iyb3#T}eno)Xk3a+!8|p)#ijj zrD{J>h9;p5Q(0-{c2}LD29Y*I)u$EYmL-{egt|GcJlCCMj*)6VQOc80DpZ!UJhw6l zWt3V-l+j5jW7N$=8JmPMPVIM=r;qQEt!_f0Do8JJPweSP*l<;$X=1m^dj4-I#L4AO zO2R!^ZAvfCtxiIjqMjg1O%lpf^$k&`8D`Z>+0)gujPl%blZ=_6HW4Le;R*82Qcq-* zk2x>NnAu5oGNj4mu3gYzQoGjmSHa9{#dvixbE7Ek2xesn+hp3fUZi$s7pc3m$BCh8 zU-o2s4LZeWb#iB(D#^LnhHp7ptSa zPrxS^s>8Ks#5DEVoLyp~`q|t`E?RH|Uje4L+Q$;b6$?^ZPvY8|0-Je>u9X0j(tz(Nlu{7P(wKIIl@msqFJdidCxS z{PJ@f!IuIkCvdyvm1wUAYyfNotOINYtOW!C&45O={d`y95^Cz_G=^leHxi-cy$zAT zxtLsb9_yi^;0r1*62+j#(E}o};#TOT_1DcUGyoC9}Uevu?B+QQwxgBA3|tug<>I zRF_(99%^zs)OigTi&}MWL&2&YLU#mONUtpoe$DOF%^^*<`&-rpWT-_?Y4S&OYcO=B zU&>UPMOpziKrsN;FRur%>Yk>Sh5+l5FcbL6HU)thrjjRsIz(&9CxT1OJ0N1Q8 z9H*x?djl<=RvFY&11+p>zuPL8W8^--UjX|thmCd^Y1Pz@)h;K@shde1T0OQV#w8CB zWd~U%8Jgy8@?%y*GskbCRow;hSqyy+@I2rJRTC%==(dJXb8{fVcABvID_}VUcoool z8QoF4zQG@k1W3)PG87EfdmGm3DOyCjQ*?{Z8}Z85X_PMf8g%P&kxtdVzy#ZJ=&@RT z6rR>Gd(ATI_#_&WFJiry07-Nq52$^i(lG}?ehu&jfqTGr_68F2Z}(9vS2*`V9p6dk z1R)#X1RMddp8F`_uOPeycw4oH`whSz zV%g{5JO%gy!1nqjT3-Q96Sz~Qi4I%-Fy}wPY2^iII`zVqr^vIfyYWhKjXHDVpd3uZ zTG1_GZ)CN+O;y}9*w}p^k7kQzfwC}$eftKqs?_?M1{cLe`HsA5_f2`VeA>go{{R66 z6)j|hLfujy3I!$Wx(oETneOp41ihN(@#v`@Pm8zN@A1gDRQlF&qnCimNjujaje(%w z!*YL(&VOKJhJT&ECF1GUr97sZx0X^UymjkH@o2~6TR*Zo=YqF82R(B8NHIpeeft1W zqrSR*efAvC5*ga>sG>~p*d3Il)ZshovWuYdM20DM4i{6E_fDFje4B&GC!44rY*ABBBp`QEC z+LD-xci0T)0hO&uw@AO&C(o$zdz$9rp!AeLOGL)hEz7_;01!X7SniZtdghUSw5|nQ zr{1`yw2fETj_+~U_ZhUtqZN~dL;DUVODczhGy+gbpr`s*JlPop>~=}@T666ooX zP{bSbunTCi3PVtT=vJc#QG)zjUA4{OoQy@8SfY+SnVZQRfd(&Pqc*E!&$gpBL(XBC zg+(Ysi{_{;b9N*_*Xnn8drx8_SO{F6!8h;cT@7X?GZ0dM(yWP^(g_&OjAl$pn z3ERSs+?feR@d4Nnh~ib1Ay3TAl}ugji;p$K^7UG!4JKQ0!K_e)|lU zeH(|X13UYv6Tivqxa*-IVnr9$iwU?yeoEhN=h;ZbYUz_w=Eaw$m2-E_6VIuYJDbTf zU)otH=JV!w9AF8l1Iw||yVDZZ?QI-jIdI*6x8G=CRO$L`3omt(Uqho zxeCA@I*D4k6{9pg%MW%>#NQkal2>ce2DZz=k=CF|%4?yu{uXzYVNC7Ob6ujqW;l)d z`{NaDTccUC$TWnpj22F@+=sCwXuhuZ2PQv;xqxZPROhc+~!$Jmjyc$#uwZJVZmQ#pwJyfDvAVJnlyPCF}Vd*!3SO_FKov&99KAQ@i7^3 zOXck?t^6ynRbktj(CO9RQ)1%<1tzjHgJ%4%d#k;?nBs;TQ8owc7jQkfAIoiUTUd zj)D!_G~|mHV(Bl<78iFcdTE}`)&-tgb>G2aXA?M$(^da<@Y3{a>3d_@d7)s4E>*=_ za@BdS44JrsI!UCM5$v!SWtoFd5+#Tfm?wo`hgojL^k>vvuZ%Q*YEsX?vb3!oJ$$a- zzcZ;%4pns6^hX`Nq}ls!nYdPc@@|>!4#;??8u(tp1wk~M0WE+KAPl$yAOW~i$_Stp zu#P}a4~CjT9wTo1WEA~(skZkju-%>Sc}2U*dcUj<>W_Oih8Tv0;?lS(q@r~jH0?ku z4KM_s2w%DdZw@jYo&S!U6m`$$$g?m%E0>Ll&n|xWb|dgkP~wBZB}P@ud*`b77>TOE z@Va9RdGO2+=8MNVZv0?_gWSq?!bPQV*@@+%KwWcUn%JYdP7F+cloo_iO{<9i!xQsF zo*H|ylIma2$=2d}>X&^h+mw--n>#4<}SiN^@R{5X6Z

2{u%HT;Bi0~fo8>aR=GCQ zP_^jv0x?M4e|n7Zq%=&ucKTjtOu~&L)UMB59rt}UFU9)|4RxnW*5FUD1!#qC*8WC% zyP$u`>>KB88L_;MTw?UXH@((b2V5 zu`Iw@Jf5iHi}5;~w>Ut0F$T&@ifq?NNyc^W1(cL!Dk6k$~v^yg~rEzWIQX;1V&ILe?qMNY!+O4QJ^Id@yL++@X1WOF~^0Sm)OOn<}}oS8=a%Q;eeX) zZU4^w4q>(SsmBw9-meLBN>$B*davfP$HdQo388 ziJr_8#cl1FgX@*JVe>^QZl=q?f*sKrXgx26#w&+qjYjW#^n&X3QAIt!ZsCI2{)Ujx zUn^xu_LU=KYYWu7% z2=%?Ohp)+OgH7PW+eE@$*)Y23^~GXp=AF=st!{%x7XdjYdazimIOip-UIUp*5(C4u zxFrq8gbx790CCmzi3#y=aDDW4ml)dC$5^&wG+PNLL@3!fDU0U!uQfvTcS1q$0AQHR zrx5uR?>*-``IMz|^AIuKENVI*DHYk4US_qw zOym{5L1O84t+jrQzafI>F5d=elsM6EiJt!tEcH(Ua$ODqSW${-#R%cDq1u#pMHi0{ z!}7TnT#ik~hnVHz=++UUu&o{aY>9mH;SkG~h)gGS!1KL^o=xq+i^U2%Hd-n-&p;-V zncyg9JVv0USqPyX(#u%4m}9H5QtwZ2O&H$!E4MgqZ@UD;x-jBWwD6iPhoRLc^YQda z(jR3xjG>CdA^i=3aDe=fst{&}#U3v(sMgh?R;d|Q%l2zL^RSgw5T9}1?MacYal+NM z3Sz`lB#XvU@pa_Hr73+-a)8$#+>k zTXvN`j>OsYr_gKMb2yQ%!B|#l7N{nCa!O@esY%G8BS4SsiguVx7x738QRoMb)<~$a zvCm6^aU*7paj|qIEt>4wYoyavBKppFF<3NqiV5Nu@TOl?iF$LNj7e9Em=n;SeI(DW&G^& z_RDXX`ij+c#MK;}&3b`2m143D0`vzAIblQRiR@_AT=9#}Z{~_67QQ#RXGec@ zq39CNMhh+yi^PM`H5ZBD>Bxq%`_!ZNTqFwY@1SRkh`xN0s2-Ptk87Yd2$lGY2=(cy lQ=3D+)}Vj7tRyP2nzVuAjvY5WV!{{j|*I*/status') + @login_required + def check_status(instance_id): + if not os.environ.get('MASTER', 'false').lower() == 'true': + return jsonify({'error': 'Access denied'}), 403 + + instance = Instance.query.get_or_404(instance_id) + status_info = check_instance_status(instance) + + # Update instance status in database + instance.status = status_info['status'] + instance.status_details = status_info['details'] + db.session.commit() + + return jsonify(status_info) + UPLOAD_FOLDER = '/app/uploads/profile_pics' if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER) diff --git a/templates/main/instances.html b/templates/main/instances.html index ba34f97..8da9ff8 100644 --- a/templates/main/instances.html +++ b/templates/main/instances.html @@ -57,7 +57,10 @@ {{ instance.payment_plan }} {{ instance.main_url }} - + {{ instance.status|title }} @@ -183,8 +186,68 @@ document.addEventListener('DOMContentLoaded', function() { addInstanceModal = new bootstrap.Modal(document.getElementById('addInstanceModal')); editInstanceModal = new bootstrap.Modal(document.getElementById('editInstanceModal')); addExistingInstanceModal = new bootstrap.Modal(document.getElementById('addExistingInstanceModal')); + + // Initialize tooltips + const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); + tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); + + // Check statuses on page load + checkAllInstanceStatuses(); + + // Set up periodic status checks (every 30 seconds) + setInterval(checkAllInstanceStatuses, 30000); }); +// Function to check status of all instances +async function checkAllInstanceStatuses() { + const statusBadges = document.querySelectorAll('[data-instance-id]'); + for (const badge of statusBadges) { + const instanceId = badge.dataset.instanceId; + await checkInstanceStatus(instanceId); + } +} + +// Function to check status of a single instance +async function checkInstanceStatus(instanceId) { + try { + const response = await fetch(`/instances/${instanceId}/status`); + if (!response.ok) throw new Error('Failed to check instance status'); + + const data = await response.json(); + const badge = document.querySelector(`[data-instance-id="${instanceId}"]`); + if (badge) { + badge.className = `badge bg-${data.status === 'active' ? 'success' : 'danger'}`; + badge.textContent = data.status.charAt(0).toUpperCase() + data.status.slice(1); + + // Parse the JSON string in status_details + let tooltipContent = data.status; + if (data.status_details) { + try { + const details = JSON.parse(data.status_details); + tooltipContent = `Status: ${details.status}\nTimestamp: ${details.timestamp}`; + if (details.database) { + tooltipContent += `\nDatabase: ${details.database}`; + } + } catch (e) { + tooltipContent = data.status_details; + } + } + badge.title = tooltipContent; + + // Update tooltip + const tooltip = bootstrap.Tooltip.getInstance(badge); + if (tooltip) { + tooltip.dispose(); + } + new bootstrap.Tooltip(badge); + } + } catch (error) { + console.error('Error checking instance status:', error); + } +} + // Show modals function showAddInstanceModal() { document.getElementById('addInstanceForm').reset();