From 51f5943899bfbb0b36ca773f91a045a5a1ab0083 Mon Sep 17 00:00:00 2001 From: esam Date: Tue, 30 Sep 2025 01:17:38 -0400 Subject: [PATCH] odoo_log --- odex30_base/odoo_log_viewer/__init__.py | 2 + odex30_base/odoo_log_viewer/__manifest__.py | 22 +++++ .../odoo_log_viewer/controllers/__init__.py | 1 + .../odoo_log_viewer/controllers/main.py | 25 ++++++ .../data/ir_config_parameter.xml | 9 ++ .../odoo_log_viewer/models/__init__.py | 1 + .../odoo_log_viewer/models/log_handler.py | 67 +++++++++++++++ .../static/description/icon.png | Bin 0 -> 10367 bytes .../static/src/css/log_viewer_style.css | 28 +++++++ .../views/log_viewer_template.xml | 79 ++++++++++++++++++ 10 files changed, 234 insertions(+) create mode 100644 odex30_base/odoo_log_viewer/__init__.py create mode 100644 odex30_base/odoo_log_viewer/__manifest__.py create mode 100644 odex30_base/odoo_log_viewer/controllers/__init__.py create mode 100644 odex30_base/odoo_log_viewer/controllers/main.py create mode 100644 odex30_base/odoo_log_viewer/data/ir_config_parameter.xml create mode 100644 odex30_base/odoo_log_viewer/models/__init__.py create mode 100644 odex30_base/odoo_log_viewer/models/log_handler.py create mode 100644 odex30_base/odoo_log_viewer/static/description/icon.png create mode 100644 odex30_base/odoo_log_viewer/static/src/css/log_viewer_style.css create mode 100644 odex30_base/odoo_log_viewer/views/log_viewer_template.xml diff --git a/odex30_base/odoo_log_viewer/__init__.py b/odex30_base/odoo_log_viewer/__init__.py new file mode 100644 index 0000000..38718f0 --- /dev/null +++ b/odex30_base/odoo_log_viewer/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import controllers \ No newline at end of file diff --git a/odex30_base/odoo_log_viewer/__manifest__.py b/odex30_base/odoo_log_viewer/__manifest__.py new file mode 100644 index 0000000..155ea4f --- /dev/null +++ b/odex30_base/odoo_log_viewer/__manifest__.py @@ -0,0 +1,22 @@ +{ + 'name': 'Odoo Log Viewer', + 'version': '18.0.1.0.0', + 'category': 'Tools', + 'summary': 'View Odoo log in real-time as a web page', + 'sequence': 10, + 'author': 'Abdelrahman Eltayar', + 'depends': ['base', 'web'], + 'data': [ + 'views/log_viewer_template.xml', + 'data/ir_config_parameter.xml', + ], + 'assets': { + 'web.assets_frontend': [ + ('include', 'web._assets_helpers'), # إضافة هامة للتوافقية + 'odoo_log_viewer/static/src/css/log_viewer_style.css', + ], + }, + 'installable': True, + 'application': False, + 'auto_install': False, +} diff --git a/odex30_base/odoo_log_viewer/controllers/__init__.py b/odex30_base/odoo_log_viewer/controllers/__init__.py new file mode 100644 index 0000000..deec4a8 --- /dev/null +++ b/odex30_base/odoo_log_viewer/controllers/__init__.py @@ -0,0 +1 @@ +from . import main \ No newline at end of file diff --git a/odex30_base/odoo_log_viewer/controllers/main.py b/odex30_base/odoo_log_viewer/controllers/main.py new file mode 100644 index 0000000..f952011 --- /dev/null +++ b/odex30_base/odoo_log_viewer/controllers/main.py @@ -0,0 +1,25 @@ +from odoo import http, _ +from odoo.http import request + +class LogViewer(http.Controller): + + @http.route('/log_viewer', type='http', auth='public') + def log_viewer(self, password=None): + correct_password = request.env['ir.config_parameter'].sudo().get_param('odoo_log_viewer.password') + + if password == correct_password: + return request.render('odoo_log_viewer.log_viewer_template', {}) + else: + return request.render('odoo_log_viewer.password_template', {}) + + @http.route('/log_viewer/get_logs', type='json', auth='public') + def get_logs(self, password=None): + correct_password = request.env['ir.config_parameter'].sudo().get_param('odoo_log_viewer.password') + + if password == correct_password: + log_entries = request.env['log.handler.manager'].sudo().get_log_entries() + return {'log_content': '\n'.join(log_entries)} + + else: + return {'error': 'Invalid password'} + diff --git a/odex30_base/odoo_log_viewer/data/ir_config_parameter.xml b/odex30_base/odoo_log_viewer/data/ir_config_parameter.xml new file mode 100644 index 0000000..05c1e2f --- /dev/null +++ b/odex30_base/odoo_log_viewer/data/ir_config_parameter.xml @@ -0,0 +1,9 @@ + + + + + odoo_log_viewer.password + $ecret@)24 + + + diff --git a/odex30_base/odoo_log_viewer/models/__init__.py b/odex30_base/odoo_log_viewer/models/__init__.py new file mode 100644 index 0000000..e33386e --- /dev/null +++ b/odex30_base/odoo_log_viewer/models/__init__.py @@ -0,0 +1 @@ +from . import log_handler \ No newline at end of file diff --git a/odex30_base/odoo_log_viewer/models/log_handler.py b/odex30_base/odoo_log_viewer/models/log_handler.py new file mode 100644 index 0000000..a57e322 --- /dev/null +++ b/odex30_base/odoo_log_viewer/models/log_handler.py @@ -0,0 +1,67 @@ +import logging +from odoo import api, models + +# استخدام المسجل القياسي لإضافة رسائل تصحيح خاصة بهذا الموديول +_logger = logging.getLogger(__name__) + + +class MemoryLogHandler(logging.Handler): + def __init__(self): + super().__init__() + self.max_entries = 10000 # الاحتفاظ بآخر 10000 سجل + self.entries = [] + + def emit(self, record): + # تجنب تسجيل الطلبات القادمة من العارض نفسه لمنع حلقة لا نهائية + if '/log_viewer/get_logs' in getattr(record, 'message', ''): + return + + self.entries.append(self.format(record)) + + # إزالة أقدم سجل إذا تجاوزنا الحد الأقصى + if len(self.entries) > self.max_entries: + self.entries.pop(0) + + +class LogHandlerManager(models.AbstractModel): + _name = 'log.handler.manager' + _description = 'Log Handler Manager' + + # سنحتفظ بالمعالج كمتغير على مستوى الكلاس لضمان وجود نسخة واحدة فقط + _memory_handler = None + + @api.model + def _get_memory_handler(self): + # هذه الدالة الآن أصبحت أبسط، فقط تعيد المعالج الموجود + return LogHandlerManager._memory_handler + + @api.model + def _register_hook(self): + # هذه الدالة تُستدعى عند تحميل سجل الموديلات في أودو + if LogHandlerManager._memory_handler is None: + _logger.info("Initializing MemoryLogHandler for Odoo Log Viewer.") + + # 1. إنشاء معالج السجلات الخاص بنا + handler = MemoryLogHandler() + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + + # 2. الحصول على مسجل الجذر وإضافة معالجنا إليه + root_logger = logging.getLogger() + root_logger.addHandler(handler) + + # 3. !! التغيير الرئيسي هنا !! + # ضبط المستوى على INFO لتجاهل رسائل DEBUG المزعجة. + if root_logger.level > logging.INFO: + root_logger.setLevel(logging.INFO) + + LogHandlerManager._memory_handler = handler + _logger.info("MemoryLogHandler successfully attached to the root logger at INFO level.") + + @api.model + def get_log_entries(self): + handler = self._get_memory_handler() + if handler: + # إرجاع نسخة من القائمة لتجنب المشاكل المتعلقة بالتزامن + return list(handler.entries) + return [] diff --git a/odex30_base/odoo_log_viewer/static/description/icon.png b/odex30_base/odoo_log_viewer/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..eb46060203af384ca46ae493d70e769d7adfb81e GIT binary patch literal 10367 zcmd6NcTkgGx9(0bBB-Dk1nFvk2m%&R1c3kw1e7L4=?IGSB28+ns7NvNA~n*x6hVck zNTl~JMUgHb9Rh^A^E=-?bN)Ez&fJ-A=HAROyzhSZUc0Qd*Lt3{_X}-J)q~7j%m9Fc zc(p6K03hKb671Ux|E+j;QQ^Nm_m%K^`{2)SpY?P2&g7zY>plQ1we&wklqd@~EIj6Z z^``p`=ezD+mTq@|mzS5gt&{zID@&I<;?8b13G zb5G6sRFzK#Gb6?OuN;WDdpP3#o!ecbUpn^H3P#jkYr1lTl6CLg3zd-e2ZACh2lhQH z!nodE5~@C*wa-$!t4Z4G4Xr{4}sI4kq0q zIz^PWyB$UA^mr0Zy-FDc>dieVokK#lI&_Pn~ z@WvZQNievCH=;(}@av6Xw^)FhQ5>lzqSUlQr16$EC^$-xP>UDSqydW=N&Ig1HW$+6 zdGJL9OLVD`?nT%w`LHi0_)?ybfoKi%cY)*Cl&+}2vm zC$798?S0HEjwbkwn_Jqhe9$v6n5j!}(zO~xM@6wa-+qDwDV$%@zW?)J8&JRE7oI$cnR{J&y+f23kI9cL)ni*=t9XSR4X+Fw&JXmn zOc?It4j8rBU1|{LHH+$=6JF{0O*0iEF^r}%kH;LC&))vPpc%1J<`uA3N~$DIh?*bL zwl+b!;7(5mw>ox5b{ntzE{)jC4r_GMhSAAsyK$R?+I!H1NM8s0g!!8l%i$GU?Nx7l z9PEt|YzSOtmwMIJ4iUWaX`4kPN?=^vKfpdSV|mQ097SMTK8tTvlj-$5)T;w@okrf! zz6~D*%vrglr^IS&+2@UL6$!eLQ7cFAhJ7r*M4)p`i5xmU%`;u3WSHcc?=1CJ%~x^j>Ae0P;dc_ln^cRFPlw$wi$mM) zsKv*7jA%t*5y$XjY|qD~=+n8U63Ub995@?)<(c~JL&-X@@>P%at4Acg|DMhGS|nL? zoL(ynnrvMdzq3r!D5hiss-!4jhnj6(6Hsmi$c&PHC7<1&L!143EDRwd5>Y=DR56^h zw8U)c{dx1{Sf3{bcpHvXtH!eE#9U!XKFctgRUg6a(Qs-^(yj}8JzlcklLvT*4Gb7{ z9?v0u|9PI7@R+)i-CH(Nxi+*bH&(|j(FQ!{Z9A+(xV>f=X>u`eSp#1{gO&H6S$#8Gy=)lR@)iKHJQ}F} zah}=Tc@Op07la&r{^E%6oY%h!!m&Vesx#B9*EG;HX!kN*2pTlz&772yhAyF1rv&VuJf&r@U_IO8(MN3UAtR?uBTF)ic)*f}&ugMSn4cw>SHUt<_kh=JlPLk=SA@DUX3t zNXl`bl);PzD=OMqI^DV2ro60o>sDMJO;kig(q`Vo)Mxq*# zXMZur548QnPczx9q-|YqPKaoD{X8Q>sQHbos%W%C@3Vv4=x3>W4{)YD(@B@S)9{T; z#{Hh+`tDMr#B6Hi{jxvVcLqvb(|<`D?9v#z+S*d;LqfPD?`bQpv!-}cd)Chm1bkc4 zXWV{JXeP(9v200UIXh7ELptcz*q6`ubKsUM)&|>kg3xF$)Au7;8LIRD=f}umUI9dL zeO*HQMd7HEW#vvoIpbP#Z*7u&4Q6`^I;p+A_LQ|bJ4Z)-@tAkKtUyLo*fxyhNHlxh zg|U50dQ1Gc`!^GK8v)NheW`-M9QN&LPpO^BG;3KFfFA55PN>Hh`YHZpL6P4f+kX7W zOWwB5n2=iu9~c;Lpdi4Y5vG~woQ(w3teCm7#J5;1;6oE)%mZ%0zc|`t&F-Kl8elzIHLA2bG|C^NxwH_{^X;mE>E|^S zLLwA^dlLmCcXHZfP?0$VItkSNqf-ktD}ou@h|xCps8a2Y7zf5v7-mBHM9Z6W(Ha^n z^CL2psFt2bN>;1-;u)@#3Z$p#0t2fixCYMxGRjL$hT{2LX;cVDaYFjdbsKt6r!#HW z=ZDJE0YP+4jVpCqNvXwRFZp<-iDrz64-*6>Y6fqX_V3m2VGTBN97>5>r0eLoa;VzOAdQ_1fJ_ z_i_IhK|-WCvajS$u;%s#1x-;2QXFU3=L#HOm!c+SH$dz?Kgc^p-S@*>?u?In9^;z@S0&03t9E;Uuf+Icl&XpO4lsij*cx@X9%ub0m=KQ=LlJWI` z=853zeoL7@3QRw|D(Clfn~|_WkdJGOzALn0yX24tNG2K@!R>3Gs4E&+CO}dxq|!2! zy98fH^=^nIucB`zpeWZoRjc>fHwa<;Q4=6N=I5L;nkN*GTYbA8_ug? z@jjo*9CLdvFO$r+7Hmo%>==EjT$C#NGf?o!QH=Rrsvl$40nCJl+9zXlD}Y8`e{~Jni)4)3T5fn zm}lwN7^kdC$lC~hC68%#3AcRIKB>uFI57Vy&wA=~sJTpRfIN15vP~~2kP$^D(A$zz z`zt*n_!8D7jA*mT**kL=23;}usBS5))y?45X}n<;#d5WLjOP$V!W0XpRS{=KsZ62B zwzHPuoe{x-IjZxUNo(b0eWCBnqfB#Fjr#Z~CmtKktx(+`ci_xs+08Yp~mn-h< z7!949(-ZqR7EhFWfY;p`bo5!F8Lxbm2v9LLH@A^(`5+yjCdv#(izlPy(-Ja6JD-(F z^o4dHXqir+!CS^#ILhN3tYna2d|_n-Y#NX0YR=c-v}~*FBM|#i^_4aAua*4$wE~trj%{J zzBI716#U}%cGw8b1UeDo_nUAwFOfjnp*O*J#iw(*ZYu>G?c^Iu$*%2+|*;;p&i#b;G(mz(bKgeLbdD%*~+fzhI}R{6Km7ksSB z8$@$fy7?u#VwW?7eU^M`N=owTLTj#=S6Zbuh>C>Ny&StKk#BZ4@x>Tp3`DCzpQv9t z>#Pz3$64VfncCCg-qaRRH}$d5ztua@(A-<-;aTYb7Y?{mvC~Th{*1htI+G@e+VP>> z%TK3s6ntXaITOr%>!leftl|(Uv-nzl6;&C$m^gH^&33jXBky$XvsNjtkj1)Zh9UP| zwnxuFvn(nf`p26sy?;VP2ae=_@Zz!=-S~V5>dMl0(CAh9LU*RQ5?-cwfeK;Zf}_;d zkYFhPk2)&?;p;Vi#@w8O>)86#S=}2prT*cA_KC;mD9#fNygh4S9h3tQ!0hh2`OKwrQ#$)0oS2rF?;bKV zOF8rXSV&a8wdC(wow_)YwzDmo^ zD`@|#m7LJ${Vxwx0E1)*b^BYn6}mb0er9gDZ((iS*v4mly3b5ba(~J(a{EJ9*K}t@ zftsHQR0*UAm)pjQeBMayhzy=(p3l>$e(Us!RqH=LE@m^C>+BxqFXm0Rpt9~{)tS0H z_)i~w1cqkQrJep6-NA0p$XXbs?Wot^MT!+-`{dzD9Yle@-z|4Sdm?A==H#8WmsP*r z&`=WZ*_oGi+O+YX7~x_R!)0bm4d9m@UvO{luILYa{Tz~6rV-j}`E%5!w7G~eMwk^C zEluK=`QrJ6C^?gu9q%?YB+{7SB~}C-p?DS5Qz}vJ?bO!9#%B~7NIUk`k2$DfZ=F<- z)rkq%{jAqQbkCm4X>^LqZJ*S97yWQ7s5BjrZ4cULxz6}FcF4=Dy`!$R@@aJs&FusQ z==E;@StQ1mPi!1-#sU{3h*#}a9{4q03jeyr+EHgXuegS?L&;*sL^FQ8D9zcQKYcF3yZ>7D;JQrD?dk}*Q{QM?{i_p4 zkBipuUqS?joKDyn=6Cc??CshTo# z`*Y^*uIIlBdN~%uf+nn_1J1v1!Jp1;^@!-*Sre&zqa3=44woMte9e-DrUiH|cq_w* zk^zH-3JeAK!FcoDBwlAbXX9WP(%btWWX{Pc&^K+Z(OIA(X72w!#Ov_|&vnkP^!!5l z1pY04``;P_{wHB1ldD&swy!x?%;V|!n=&Z&;d)SC z!W9;XvQHTr8{3!ego<(h1Hg;@m-luaf5rWTj`wHV)Bm?;+XFgIK5VUiga2Lh0vYb6 z%J(3$eXWr{E*;B!L>&?eiI+-rmNR|EKH~fBBQOKE_aLA3fAMOt%EF7Q>$#iVQW;-4 zSuMC>m1oJ)KfGUk^D@ho(o%Sh;{sU6>sks0f?lSOmIPkAeTY81_4;Rf@0>f9eio`} zo>qtQ@%wpNoDsO_qMsU3gMDGB;=mqoqthe4i&}SL`s30iYlXO-?S>QeSnOM5mJi77 zZZE%hr60I*M#`^1=c^`P=CD6wR?fhmQw*T!4%C0SqmQXX0o5zt+uDh(leU!?xl(~Y zcvk~D#WNQJwl`m}6DADfOQ%}wj~v?%c%*tg2^be9GGa2xE6L8z&Vd2|f2o?01!RR- zz%teUE>q}wyo2msurbPqA9M&(L#_j97ghqsarj4`&cTC__?PjbfJvD#OtWhyN<%7o zC0Ty8YN-JtR|F9Mpc9(7EqE6s#v#HV3OxSmkxFDjh9G5^FN>V30@~MXzW(=qsWRj zNe`j}T$Y<*S`RnyjaZGDJ`Ab+L*#!X>?mOidJity%OP;WPlzH8`rux>dwM$$`Q?tqB$(gi?*tBiK}Mr?6RTA z463~Tr2$(XMy4|osv8{u_+y^C)VjHwPx62%3e+r+E@i=~I`o%ZRfxNEgH=v4MxOy8 zP*cA9iRcui9K7wmho0#uY7OO;UCA}4=>~I1blh*1%apz#^9qCu6_a+uSHej_f{qp3 z6c%PeCTdjT)8<5iQL$g8L7`c`$868^D$KbImOI+n+Ukf;c2#kJ?T&)&6Y|b#KKi;5 zq$pyv`heY8LwpL*1~R0#Wldd_*FDNaL`57ZIHrA%ak5@+ONJ|qFph>w#``jn#l*x`GIfs{4RTve@kP=`}2I(rs~H*>mPsLLeDwB z;LNKaNE^6(KNt6^%4?w@euisMM|+Q>qqF^b(h$_48mIG1#mw%Wo-BIh8YH^;u9arx=Qq>q zDIsttU^h7;qWb!`Y-O3*4()kZP*-MOl-i7DqH>>ED$9n~}p_)=gF z9P`aJPk_Tkf^n1y0zmj;A|5Wi$uJDRB{c>vw;rq3XTS}>M0nUY9g@JljbE(vGYfB# zmr73+YwP*{qG3?gMn7YI)CR|J6vF-1CyL};KLl)%{P&dt!JkgN+~ywsij7bpT!xTI z`sfCtNP!N-b1kNq@cnQ?wF#~;R&SU4`G(Yp%Z z8w8lA$Kb;SIFVqtxl0A>3`LNt-Xz=mA^PCQm#1GUp)SGHTi;dqg59Td^J3tq*UUxPqg>(!B zrk-G#+4n*d2ib5uEIQ%)(TmUd36XF`dC{uHkHTknzz;|xWCUtR&e#_)nRpQn@A2qi z1t1QiSD=2LiEozlgpUGPhvhrZ({5UlMg0)(gOT8)7W&zO$pRzrf=gU;=(pR;_l^7n zMyTo7CB{*HX2?t;6#%~u7|oXsW#4-|mx}nyh5}}gN-Bn)3Iwz?K$g9>$pBD*&$59Z z&>&qjvw#o7Q26B8aaO>81wnYXbc2T2zXiwv5TcQ=K9j4TU>kA(6q|TvANvy33`h$w zgT%v>;zt5{O{O&9q75P1@s}r{hS_e=K-*z7AQ|z@zOmVggNLHZiU^$DKU-o{MFC7f zv(Y_uK>6wAL00fa1%b=?>!1KIkCB9~?;}$jboZ~B0#bgvrtj<1J8%gjS0E~a>cLCBsibWdM&qjwIo`a4T{WNc}{(Qnz(@=>!5)j?$DE$N(HM71W1g$4SB9En9NqZ z*#orip~1&@DOx1#FJlF;@d!D3DS8nF6m-C4cGco9X99y109oNqIRhqT$a+l9cMTJD zk{upPpr64kB+TxH#RaVC>*)za?kRt-4PMbihf8g5=w4DcLw{p-L*#BE+Ui2ML&^*D zvmuUha7_h&&h&583n!_GsN(lO!YPVV;PfzfExEF$ds|fnrVvpXn_$80wF^lKfk{e1 z$q&NPv@6#oi$1ISGnHfRgET33r^}n;R zpXoXU)AjfAAZ0nCAb&a4x=7GU7N=x@=Xza>Qx0P3e)8go)ZM0=s|zFMWo2df1I8|j z;-kg#V4=OEBkdsfg?s%a!ethpTSGE;)-TaEj!4c{GqO-8eO6~&$;qs;p3iO3hAox` z?@JD~nF#8Ep1fP>$1eT|uchy|^R3d2o_jFlry77LX|5YQcLBr(KL^cU=)rRaB8}-Tp6Mw2vtq}SEab* z?5+uyi{S(?4c)N2b$q-~y+gAGjC1g?t$;dF;l4I0Khl4gt@-iq$^gYlMZBzF^XuBi zTto^34B;N|0MT(qns3_}<0~;cT1MBvikRP=wCq#H25IxJ#jlC z#PGCvlpYHxDyXUo&dL4YKGmuV{w$3P;Q;Pol%ch(-%^l2JP9Nemz2Dkv72?EkdKdE zItU1_OB~{=db)G+ULD({$1EU||M7BefvJ5|{h8Fn3$O|Kcq4>m9dBRS(@xRTRPHz7 z0QB#?t$IV3MSpW}VR@!?n+h#j;nS$sL(7}0y~W)ud#J zjR(57U}$JgR?wO?yFi`C91B=`gbaTYmBH}CZ+U%{5*2vu#C{N2${uKvY8>EwpG|o& z)fQ_nOJEp|TnRhpEiUx(#LikpSdMSSybNt)>_Pca!hPt3K0aWb9vrDQQalvP!l6OU zAst<|rv0*PvCbn(o_O()e(hk45@z^SnA^Sf@Q&V%8xgfdK-)b(Kc9YQA<3xPB$z|& z+UqFA`i{WXGR(3{sP2#+~!gFp_w|?hjImj4%fyOPf8z zOUG{CKUa>AzazT`c#rbAE@|yK59%)|ZBle&QGoEJPt5i?30_2cPsjwscgDKd>CW=U z5a$&{t;Eh9DzMlqaEqNtQ|_QRG9mzlPK`*3a{>h^0OMRl>Lyn8vmile-^poAG1WZn zJ=2*7vzY7pG&g7f*RZxk=}qBKU%;G>-%QYxw~^fkNH}S$>^h#{JAQ2yi6S z962urT(Xq#=10SBavVUV6eY#1m=onivm4xw!V4c27*mXG^_+nCm=O7wv`PnZ(H#Ju zrgubfJz9VW`p}KeZ^Z^3^~CCeZ65^8mD)wQr!Prt{`_se#{`K@Qrnb5^66E3`(M$0 z+l&L+NF&S+Ka7F9Dwh@YB~6e;nH3}^qL8S9EO`V4%xfD0kNc=?%5GN-hKvbo%DnyQ zf**@CAGd=S-Y~pE*_uskK&{YooMZ8Uaz}21!ii7x`) + + + + +