From 56d87eaa450eeffcde2455646a10102be01b79e1 Mon Sep 17 00:00:00 2001 From: CPABONG Date: Sun, 24 Aug 2025 18:41:11 +0900 Subject: [PATCH] =?UTF-8?q?=EC=84=B1=EB=8A=A5=EA=B0=9C=EC=84=A0=20?= =?UTF-8?q?=EB=AC=B8=EC=9E=90=EC=9D=B8=EC=A6=9D=EC=98=A4=EB=A5=98=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=ED=85=94=EB=A0=88=EA=B7=B8=EB=9E=A8=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=ED=8F=AC=ED=95=A8=20=EB=93=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__pycache__/telegram_utils.cpython-38.pyc | Bin 7313 -> 7679 bytes A_core/settings.py | 3 + A_core/telegram_utils.py | 5 + B_main/admin.py | 46 ++++++++- B_main/log_utils.py | 93 +++++++++++++++++- C_accounts/__pycache__/views.cpython-38.pyc | Bin 9282 -> 9803 bytes db.sqlite3 | Bin 618496 -> 618496 bytes 7 files changed, 140 insertions(+), 7 deletions(-) diff --git a/A_core/__pycache__/telegram_utils.cpython-38.pyc b/A_core/__pycache__/telegram_utils.cpython-38.pyc index a50912cc147be4e65f8f1b084117a9015d82155c..defed303b6bf2234a49f874844a85de7362f6a99 100644 GIT binary patch delta 708 zcmYk3-%Aux6vyY@8P}ORKlX?2FZE%9pcbE^2Pw;>dJu$yvWP9)+`U3V?859!NX&q> z8;AvJ*UD!0XB@WsbwmU@T^`gvyUhj-?D&OKkgbM8IN{DEKn+8I?P z9IWBZ=a<*_%xgX9>U{6e{sfh&Qbk#bCaFp_*by40kt&y!X^O^Z2Pg{dq;XJGicfQy z^vaXbMBhk(54!zX3gjrnF^KXiz~mapKJCED(YRCFXN zIi#pi0+kGv;t;CZ+#`-^wg?Q{5-?)(z^E+%V|EDGVYdT2Z32wfqYcUB9N8qcTtSGV z3Akm_`T*A;x~{WmI?moEXOF^Ox60#2ZhUg0Y?k%C`BLdh=AiE1SoR-2^Oxq{zns!N zw&J;UZ*I}Q_geRtroEX3|LK(O-(U9LOtInA+1u^}3+I+C!txh)=*F3id10+JXYTj&h7i*+W#WoPJ zBJinxC_ms=t~V<+=BK*Vc9019`1p>iUu$EB(}U*}<2-0a7_cGlnB2G!$ie?I(g8*T zTm)q0vN>j2<=jM}RQy+%?F4>UxKe#x}c?Y6041(23SN!hUuShw`8p M1$MG$5R-ks0Z3@y4gdfE delta 449 zcmXYtJxc>Y5QcZ=_U<-!xr<*(OjHChg<4sOq!DZcL6E40-{KS^HlAm9K~Vod+)~6` zBchd93)VJvB3Sqr#6|^0XM@Ae^Ue@h56s6YXQTzQnEwqvpAQ#*CAW~r09SOdbE7%JXetP~D5(LD7Ziq+?w@H4yNg;r^w5E?(=0?pggCCSY_zr+3!y?wK?n+zRYtV% zM@sybE`CXcpE83V*<7@KyW8^w0|@*}p&60|RLCPBu>+zF?hL^UAHCBz?X2@P+R%4> H6R0YGg2`dQ diff --git a/A_core/settings.py b/A_core/settings.py index 20b61e5..1be6b5e 100644 --- a/A_core/settings.py +++ b/A_core/settings.py @@ -38,6 +38,9 @@ TELEGRAM_BOT_TOKEN = "5094633886:AAF_Cy3mD-3rqKQMfbgpVO39jNZDXNFZN-o" # BotFath TELEGRAM_CHAT_ID = "5085456424" # getUpdates로 얻은 chat_id TELEGRAM_API_URL = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage" +# 텔레그램 알림 전송 여부 설정 +TELEGRAM_NOTIFICATIONS_ENABLED = True # True: 텔레그램 알림 전송, False: 텔레그램 알림 비활성화 + ALLOWED_HOSTS = ['www.sillaamp.com', 'sillaamp.com', '192.168.1.119', 'localhost', '127.0.0.1', '*'] CSRF_TRUSTED_ORIGINS = [ 'http://www.sillaamp.com', diff --git a/A_core/telegram_utils.py b/A_core/telegram_utils.py index 8f8a7be..428501d 100644 --- a/A_core/telegram_utils.py +++ b/A_core/telegram_utils.py @@ -163,6 +163,11 @@ def send_telegram_message_async(message, chat_id=None): message (str): 전송할 메시지 chat_id (str, optional): 채팅 ID """ + # 텔레그램 알림이 비활성화된 경우 전송하지 않음 + if not getattr(settings, 'TELEGRAM_NOTIFICATIONS_ENABLED', True): + print("[TELEGRAM_INFO] 텔레그램 알림이 비활성화되어 있습니다.") + return + def _send(): send_telegram_message(message, chat_id) diff --git a/B_main/admin.py b/B_main/admin.py index 106a807..41e7fe2 100644 --- a/B_main/admin.py +++ b/B_main/admin.py @@ -90,10 +90,10 @@ class PersonAdmin(admin.ModelAdmin): @admin.register(AccessLog) class AccessLogAdmin(admin.ModelAdmin): - list_display = ['timestamp', '사용자명', 'action_display', 'ip_address', 'description'] + list_display = ['timestamp', '사용자명', 'action_display', 'ip_address', '기기정보', '브라우저정보', 'description'] list_filter = ['action', 'timestamp', 'ip_address'] search_fields = ['user__username', 'person__이름', 'description', 'ip_address'] - readonly_fields = ['timestamp', 'user', 'person', 'action', 'description', 'ip_address', 'user_agent', 'session_key', 'metadata', '변경사항_상세보기'] + readonly_fields = ['timestamp', 'user', 'person', 'action', 'description', 'ip_address', 'user_agent', 'session_key', 'metadata', '변경사항_상세보기', '기기정보', '브라우저정보'] date_hierarchy = 'timestamp' ordering = ['-timestamp'] @@ -132,12 +132,52 @@ class AccessLogAdmin(admin.ModelAdmin): action_display.short_description = '활동' action_display.admin_order_field = 'action' + def 기기정보(self, obj): + if obj.metadata and 'device_info' in obj.metadata: + device_info = obj.metadata['device_info'] + # 기기별 아이콘 추가 + icons = { + 'iPhone': '📱', + 'iPad': '📱', + 'Android 폰': '📱', + 'Android 태블릿': '📱', + 'Windows 10/11': '💻', + 'Windows 8.1': '💻', + 'Windows 7': '💻', + 'Windows': '💻', + 'Mac': '💻', + 'Linux': '💻', + } + icon = icons.get(device_info, '🖥️') + return format_html('{} {}', icon, device_info) + return format_html('알 수 없음') + 기기정보.short_description = '기기' + + def 브라우저정보(self, obj): + if obj.metadata and 'browser_info' in obj.metadata: + browser_info = obj.metadata['browser_info'] + # 브라우저별 색상 추가 + colors = { + 'Chrome': '#4285f4', + 'Firefox': '#ff7139', + 'Safari': '#1b88ca', + 'Microsoft Edge': '#0078d4', + 'Opera': '#ff1b2d', + } + color = colors.get(browser_info, '#6c757d') + return format_html( + '{}', + color, browser_info + ) + return format_html('알 수 없음') + 브라우저정보.short_description = '브라우저' + fieldsets = ( ('기본 정보', { 'fields': ('timestamp', 'user', 'person', 'action', 'description') }), ('접속 정보', { - 'fields': ('ip_address', 'user_agent', 'session_key') + 'fields': ('ip_address', '기기정보', '브라우저정보', 'user_agent', 'session_key') }), ('상세 변경사항', { 'fields': ('변경사항_상세보기',), diff --git a/B_main/log_utils.py b/B_main/log_utils.py index 51219c8..4f1260c 100644 --- a/B_main/log_utils.py +++ b/B_main/log_utils.py @@ -6,13 +6,38 @@ from .models import AccessLog, Person def get_client_ip(request): - """클라이언트 IP 주소 가져오기""" + """ + 클라이언트의 실제 IP 주소를 추출 + 프록시, 로드밸런서, CDN 등을 고려하여 실제 IP를 찾음 + """ + # 프록시나 로드밸런서를 통한 실제 클라이언트 IP 확인 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: + # 첫 번째 IP가 실제 클라이언트 IP (쉼표로 구분된 경우) ip = x_forwarded_for.split(',')[0].strip() - else: - ip = request.META.get('REMOTE_ADDR') - return ip + if ip and ip != '127.0.0.1': + return ip + + # Cloudflare 등 CDN에서 사용하는 헤더 + cf_connecting_ip = request.META.get('HTTP_CF_CONNECTING_IP') + if cf_connecting_ip and cf_connecting_ip != '127.0.0.1': + return cf_connecting_ip + + # Nginx 등 리버스 프록시에서 사용 + x_real_ip = request.META.get('HTTP_X_REAL_IP') + if x_real_ip and x_real_ip != '127.0.0.1': + return x_real_ip + + # 기본 REMOTE_ADDR + remote_addr = request.META.get('REMOTE_ADDR', '알 수 없음') + + # 로컬 환경 표시 + if remote_addr == '127.0.0.1': + return f"{remote_addr} (로컬 개발환경)" + elif remote_addr.startswith('192.168.') or remote_addr.startswith('10.') or remote_addr.startswith('172.'): + return f"{remote_addr} (내부 네트워크)" + + return remote_addr def get_user_agent(request): @@ -20,6 +45,63 @@ def get_user_agent(request): return request.META.get('HTTP_USER_AGENT', '') +def get_device_info(user_agent): + """User-Agent에서 기기 정보 추출""" + if not user_agent: + return "알 수 없음" + + # 모바일 기기 체크 + if any(mobile in user_agent for mobile in ['iPhone', 'iPad', 'Android', 'Mobile']): + if 'iPhone' in user_agent: + return "iPhone" + elif 'iPad' in user_agent: + return "iPad" + elif 'Android' in user_agent: + if 'Mobile' in user_agent: + return "Android 폰" + else: + return "Android 태블릿" + else: + return "모바일" + + # 데스크톱 OS 체크 + if 'Windows NT' in user_agent: + if 'Windows NT 10.0' in user_agent: + return "Windows 10/11" + elif 'Windows NT 6.3' in user_agent: + return "Windows 8.1" + elif 'Windows NT 6.1' in user_agent: + return "Windows 7" + else: + return "Windows" + elif 'Macintosh' in user_agent or 'Mac OS X' in user_agent: + return "Mac" + elif 'Linux' in user_agent and 'Android' not in user_agent: + return "Linux" + + return "알 수 없음" + + +def get_browser_info(user_agent): + """User-Agent에서 브라우저 정보 추출""" + if not user_agent: + return "알 수 없음" + + # 브라우저 체크 (순서 중요 - Chrome이 Safari 문자열도 포함하므로) + if 'Edg/' in user_agent: + return "Microsoft Edge" + elif 'Chrome/' in user_agent and 'Safari/' in user_agent: + return "Chrome" + elif 'Firefox/' in user_agent: + return "Firefox" + elif 'Safari/' in user_agent and 'Chrome' not in user_agent: + return "Safari" + elif 'Opera' in user_agent or 'OPR/' in user_agent: + return "Opera" + + return "알 수 없음" + + def log_user_activity(request, action, description=None, user=None, metadata=None): """ 사용자 활동 로그 기록 @@ -49,10 +131,13 @@ def log_user_activity(request, action, description=None, user=None, metadata=Non metadata = {} # 요청 정보 추가 + user_agent = get_user_agent(request) metadata.update({ 'path': request.path, 'method': request.method, 'referer': request.META.get('HTTP_REFERER', ''), + 'device_info': get_device_info(user_agent), + 'browser_info': get_browser_info(user_agent), }) # 로그 생성 diff --git a/C_accounts/__pycache__/views.cpython-38.pyc b/C_accounts/__pycache__/views.cpython-38.pyc index 5d656206b00fe1af1f15b739ec2939574033327b..b47fc80546e6a043fb39e9ac5bc4eca4dcd8cf0f 100644 GIT binary patch delta 1889 zcmah}>u(fQ6u)<7_Bs3NcAtH0mu@YTmplYPiwOZ=q>?68C_-BU}Lwem%m~!sp;UOkNLLn(gJ_8t7GplDScE zN((rKNfC5g^$nAz=10wK+g!thQ+CaCLZC$Pvzw>|ZS5XvA*tnefkXP{TNog zw1J}iIrihLQqXfA%dQOfjgDM0IPUyaYPmtTJotJ!_MTJ9D$C<3feu?AMcsv-Vf56w z4xMOK*s>{Gah0g$o zB+aN9tHi3LWHSUd;90-rg^cCIf(K;5@B|NHYnp7WRd(@_x-*Ts^D!92%AK$gQ*TO= z78-dChAdD1HiEm^hWuE&yC%3QJ7R;o1;Oo*B{l|Yg`4^YNFYr++of8?2#@g21c9wN z4SReD+c3ou-oe9mio=kxJ4Gsj$JUUuSlagX0L*xaSkWfD9v;O}KDn%x)G-1(FqQ^8 zQ94aGdimNz;7;!0Ug#~#DU1=M6keLL#wy%}#JbGgWwr2Y8{GDdfL(Z{BLBQnWpxgz z_IL{9!S2?aF++>08hhW2<4%$?%8OB(l^UDnQDo&)2wd#qvByct{gnidRdH&lC96om z1`-DvtFO8Go2%;pF}o_=NVh%mwwpb$w?vO4%hsu^v8e`?;puV9bAsfRagvYx8-)3; z|A2R$dTn$t*}f7TBSZVeY@{bqQ(t`k)tZAD1;b>$_zzdpOg3-S#8M1vY}NLk>2Li?|-yqOiO;7EAk&vKh3nSrqe!#Ar0NIbFYer~ck2 z>9t!Q*DK5E`gb3!-MRJP(<}GOH`1Hzs0U=iq4{Cyo@u#1?uRv|>({Q-Z+|^Be^~kr zrFY%`=*HUW<#c`JPW{W3`(+GWy7Zv5oZhw`V!2Yk{_bi$GEKc~PL?JA!9`Z@7FT2E zMMwOUzFsVw6BpulGreq3`b|oaK~Zx|%`6y5Q(kg=R|T(ob8Z)`q>$F#%LgT(syQ_F(Yqg3#Ku9GSu3&fMw^z zy~Ivh5aHxT@mzA#K$v0(&8x(e01c=i#jS)?FSQ>>B8!pT`QEn zEgp9F(F?1oo*yYS#h<;`=|!=a8bEn7HAu_ix72QGt-AVxs{Ss9XFH|1BsTPq_Q-vW z8f>35*G6pLal^NPM#q236- Hg9`l{KhOa} delta 1260 zcmZ`&U1(HC6rMBp?(f~-?2o&5lg(;&6OFMNO2M}2Lw_ixXc0rzh#G{Gwr29%P^1Wb^~r~}uLaNC%@5YP%Y66DnKSo0=R4>A zcH;g=sZW#1IHTW}Pk#KOar&p!yP0&Jwh3DA(`u(|ZAVmROR^@%=saKJ;#;z?#>6d9F)xNmX@A3Rb02`6 zWv$O_MiKG(INf^>nE6vosvpxaL72wORc9fg;=p8F5*>yyd&{De0TtS#_mXsyLoTBj zmLaq-zY+4SFq>i1>>Tq_Ub>sX0v3BjtsE~)Z*qLD5;B4N$|mgx(xFlq+EdK1kfr~P zq&fp$uAA#I9|BitieXuW(N&!Cte8A;P&!nR{fcDWezL714pF)>tP;ZWOqUpr%-eMP z6xNC2W0{nxfMOlppqL01(+Dh>GO~~EwZ_Z)Ol3*ajuEvXNiH1QuzYKp;X!(tjfV)s zo=suVJ?SNvAsOyVKg}dyg9t70m$WF2N77!i+6SmbMqHw#Mj6YbG5!I&m{$X4-z~@i zw0az<@TF&+|58x*5ASrBD0T+a5|6z+?aU}8Zc*lX zG+|`z>(Sr^$-q1Pr@j5uf5sA>QnxG_9x*w2F23f&SsJR{$+g5U{mI3nDm24d{2XiL zw>5b|xt6;t>&vSCk1E7<%!g^l4^8T7VX$&Ow|KeTUcM%9R?M~MyxF!lw~jL;6T*or@ECt2FC0DM-v4XzB6tugQv-F`E$sqq^vhz$N{L zTZ09C#+`s!ebXJX^5U3&;7*c{SH|FT-Kb22|Dg-a(yQsJ)|H2_w0ULlHqhNjbr{6v zSal2FntnR`J$QPpRwuku8-=!hQhObiH#7Uwma}3gCk@=t&e9<75>ZBNMA{Gxv14Tuv3{iju79V!52%9*(|N@y;IEUjF_?&XplynI0uR zsYRJlRVLnPkzQt{S&5eAW>pp@UKZs+h34r-21cg31{S(TCJKfIRt8A+Z-1G~Db54{ Dvn(}p delta 172 zcmZp8pxW?2b%Hdb*hCp;MzO|(tqF`&e9<7Cxj2L)Y@M*l_;R^KMw>F;wmRi-al$`b2UXl~(Sl@aa|>XvB|p5q%| zTxpyi=2B6ZXIL0!Srnd|UgnXUhhV3tN HImMX(XU{nq