From f6bdb68d199d4c8848320445f1dd3b66ae8ccdde Mon Sep 17 00:00:00 2001 From: hyungi Date: Wed, 17 Sep 2025 13:02:38 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EA=B6=8C=ED=95=9C=20=EC=8B=9C=EC=8A=A4?= =?UTF-8?q?=ED=85=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 관리자 전용 사용자 관리 페이지 추가 - 사용자 추가/삭제 기능 (한글 ID 지원) - 비밀번호 변경 기능 - 권한별 메뉴 접근 제한 - 관리자: 모든 메뉴 접근 가능 - 일반 사용자: 일일공수, 부적합등록/조회만 가능 - 이미지 없이 부적합 등록 가능 - 목록 관리에서 이미지 수정 기능 - 작업 시간 확인 버튼 개선 - 부적합 조회 페이지 간소화 (시간순 나열) --- .../__pycache__/schemas.cpython-311.pyc | Bin 8582 -> 8861 bytes backend/database/schemas.py | 4 + .../routers/__pycache__/auth.cpython-311.pyc | Bin 7816 -> 8679 bytes backend/routers/auth.py | 31 +- frontend/admin.html | 369 ++++++++++++++++++ frontend/daily-work.html | 33 +- frontend/index.html | 44 ++- frontend/issue-view.html | 33 +- frontend/static/js/api.js | 8 + 9 files changed, 497 insertions(+), 25 deletions(-) create mode 100644 frontend/admin.html diff --git a/backend/database/__pycache__/schemas.cpython-311.pyc b/backend/database/__pycache__/schemas.cpython-311.pyc index 451dc04430f55d7f13148f815c4a477453163c43..807a84fad5ce27f43da8391b4eb9013dbd2a157b 100644 GIT binary patch delta 1484 zcmZ{jO-vI}5Xbv!Tj{dh)LG>an?NJekcy28)neNK_*R{uMHZ_@gO3#^k zmLx=uR>WGF)Pe~Q{g9ai`OSfdo)hQixR8u1pOYiNT3Bio37t~r<{&#(Nk`S3;L@Jz zou+Wj#6xq$MvrWEI;JnL(YAMGG6QL&XLGVEbvQBY>h3p;L@FEa`|Y1rQ;C7UPcb7z zRZ7{)3R&RkS-&Z-{=$O)q(z@{dtO-ZpJYmd#+4$v?y=L4yw}z>^3@ge)?Oy+$f{H< zu9MG(3s?lWf$Bd9QJZp9RMT-)r%7c%TBT~SsG>vp4b-Rl$*EP-hx|rm16}~uP=i`a z-yD8aNB7lAWfLTzj^3+PVw22OOYM$>bV1YTiKCXDYRJIATB{&;sOC+zKBMrS+~W zqLqd{I(>BADX5XY*tF7@OlQ+_4B4a+qtQj>Hsm+j=%Pg@r%xxv?HXDOS598;Zif_h zL5u+0X86OJ;ebN}oIpLmh3tUE#c?&fZ=Q-3!Zv`bnl-%wIC*^Suz;;J=6)dBxrc)V z@7C>uz>DmM#rfl~IAPA#J0Spj=!Vxr_X;~iC)Zo%X`(To-FHB08SH!JBu-iKPDiOt z$8o#r+AB4K*W~5m)avy&@H4MGQ|&Jq(f`{>Hw2(tZlqUq^K3#zXG9H+7PX4izlTsc z{{VXB%g2%B-vPwJ_W38kJDF+utVSad_c;LzNXk2NiN@VJz4Q%#JZGZ|7I&K~CKi;QHU9*SyfA10 delta 1146 zcmZXTUr1AN6vua_TkOu=xj)X`d)?F->BRpn*+OJ3hf{G_VwNRyF=gs}>A`n9$z> z+@n>pRx32$B;a3>$&IT1{~RjE)r+`>N%x){8f$)`HHsW_`j|1*I;``zt*+}sM*Lti z;WyJ)L%Zmv0pCfr5WpW&qhPYsfj3PuhRjpYj9^mmxp_fY!Y>6ybe_S?tg)A_5qa&q`s6AcXL~9E9*z z-cfvCF5yY5f_G#YFI(H7hm|+hHRBQ562YbN14B_^`mK2jd1Qi>zyU_G6ha*#Lhun{ z0vm>@Og34__mQKY;3P!(sJ&zo!dPd!4ZZlnw&rLN@9_;Om(CWliCnUf&iCW8y}?M? z@_qQy9)S+pt8rfpKv_1&glb zzEKi5BPXLge)HbxmTm`ev7Hlb@7b!Ipg=+bZ&YR=!DX!OtI&eW?g3~OL+ikD#kqTM zX%T*o_U#_rF~z0sl;P3rOltG99delMAopw=8ogo|R$PvIA}9t)n> zK61tV)%ePji15F{{{(;kVIk#v2>fO7Rr0;?J@Ay0q;jWoWaV($>oH#tCWtlvxn#BI LO~9&Jv#9?EqqOTa diff --git a/backend/database/schemas.py b/backend/database/schemas.py index 9e6d908..34e5893 100644 --- a/backend/database/schemas.py +++ b/backend/database/schemas.py @@ -32,6 +32,10 @@ class UserUpdate(BaseModel): role: Optional[UserRole] = None is_active: Optional[bool] = None +class PasswordChange(BaseModel): + current_password: str + new_password: str + class User(UserBase): id: int is_active: bool diff --git a/backend/routers/__pycache__/auth.cpython-311.pyc b/backend/routers/__pycache__/auth.cpython-311.pyc index 18d8b7f41779d43df98bf11adf25f819e7d8a111..e209c51bb0a62edc0e719cb8d24465c22814c579 100644 GIT binary patch delta 1174 zcmZuvOK2NM7@pCtR?=GDwPabZ^7(pPXq~x@r&Al_LNItZ)%=f>(|KFMa zpKnH9jtUQ(n|VOi#mJw_k;tA9hm|M&zca8GzhEw=`ZP(EG*K0`z#^yi-}CMh>SM}x z*TMk40uycT>_g%uyS6+)^JwjRuX@j9wD9Wbx?2r33cdTELWAn5TOc!VIL`hNQpzEv zWN64xzMw+SuWMhTK2oN@)N2vRlKnN=f6w7i^9sHnEuhtBoXEstS!$^{(57#Nn;t6bsOuQEGH-Q!UjTGp(qzh zMCwJkerUJVsEgDtcTBy#l3crpDjTIjK{JfS(%RZ)CV*NAtJ?CmLatF^oVbyf0!Imv z>oYUkQ@Qg)L%9#Hj^(DW-TdU*tr--iv7oNqJxO#Y#h_)Hs!&J}Fz9=dWf=_+SruzX zImuZk-uslM6ZngKJK&7~f>PzOEq(CYXZFav)wE!L-n8HV*2|MQK8ttS@pH$misNNO z{xu-*4+RG>0sc)8^b8;5?BSbMdfHA;?|fvl@-fRjnAn~8Dtj-xlYJhHn9*Ug>4M2# zK!fC;lNsfZP27OeA~6tsUsa*rxKa0MpmjW-?BI7vhz{4deYlhSkqLBLFm1!M!{LLD zB);6qdxQ|qcJ^-fP_IXX=bUcf@jsAuC9}E9g0c-|hXct}C1$41Sc$WC;_Pua@m+tV zZ%_GNaeOWCJqHjtZfm#OPB{!wpNFH4m-;ynXgOiHwAT#qq-ae~J<)X|mfq@L=fEes delta 467 zcmaFv++oYNoR^o20SKD7Ph}JfPUMqdl-j7ikC{<%@-t>jb){6b6zNp{6#i7sG`1Ag z7S?4zrK>^e!5~T}MJ<>?Q+@Lhmi>a_$;>cuAk6~A%|QG)XL3Jp=41t7%gG_)TIOsp zB_(_?76U^T2aKJ~1Y#8lmT<#F7#LDmQdlQ16c?S$C?Y!fw74iE*W`!ds=90`?3s)} z%Q^gtB0zc~L4*>BPy`ZH63&TvdHE#@DXBTBC8?90C3Nh$i&Q|;JRm|9L_~uKbr8V{ zA`C$UFNiP#61UhOT8lh^98H#@(8=>9^tjT1oMI4OI{CVU;^eiWR!SWFEj3dsrqo?x zSG~ZldY4an1@i?HzYBc+7y10J@cCch@ShwkX~68Ksk`~Hv_B(TDo}e-+GJl@S>Y-m zlM!qpFt|Q2Gcq#XnA{+%uz9X*F*Cd3MMkA7j7pO?%FA-|Ga7$j01`KN1t!}os!AvW PB|b30M8Q%;dO(8!_H|?b diff --git a/backend/routers/auth.py b/backend/routers/auth.py index a45294e..1080ee7 100644 --- a/backend/routers/auth.py +++ b/backend/routers/auth.py @@ -112,20 +112,39 @@ async def update_user( db.refresh(db_user) return db_user -@router.delete("/users/{user_id}") +@router.delete("/users/{username}") async def delete_user( - user_id: int, + username: str, current_admin: User = Depends(get_current_admin), db: Session = Depends(get_db) ): - db_user = db.query(User).filter(User.id == user_id).first() + db_user = db.query(User).filter(User.username == username).first() if not db_user: raise HTTPException(status_code=404, detail="User not found") - # 관리자는 삭제 불가 - if db_user.role == UserRole.ADMIN: - raise HTTPException(status_code=400, detail="Cannot delete admin user") + # hyungi 계정은 삭제 불가 + if db_user.username == "hyungi": + raise HTTPException(status_code=400, detail="Cannot delete primary admin user") db.delete(db_user) db.commit() return {"detail": "User deleted successfully"} + +@router.post("/change-password") +async def change_password( + password_change: schemas.PasswordChange, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + # 현재 비밀번호 확인 + if not verify_password(password_change.current_password, current_user.hashed_password): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Incorrect current password" + ) + + # 새 비밀번호 설정 + current_user.hashed_password = get_password_hash(password_change.new_password) + db.commit() + + return {"detail": "Password changed successfully"} diff --git a/frontend/admin.html b/frontend/admin.html new file mode 100644 index 0000000..5a63a56 --- /dev/null +++ b/frontend/admin.html @@ -0,0 +1,369 @@ + + + + + + 관리자 페이지 - 작업보고서 + + + + + + + + + + + + +
+
+
+

+ 작업보고서 시스템 - 관리자 +

+ +
+
+
+ + + + + +
+
+ +
+

+ 사용자 추가 +

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+ + +
+

+ 사용자 목록 +

+ +
+ +
+ +

로딩 중...

+
+
+
+
+ + + +
+ + + + + + diff --git a/frontend/daily-work.html b/frontend/daily-work.html index e79ae4f..e348aa8 100644 --- a/frontend/daily-work.html +++ b/frontend/daily-work.html @@ -110,7 +110,7 @@ @@ -253,6 +256,9 @@ } currentUser = user; + // 네비게이션 권한 체크 + updateNavigation(); + // 오늘 날짜로 초기화 document.getElementById('workDate').valueAsDate = new Date(); @@ -260,6 +266,27 @@ await loadRecentEntries(); }); + // 네비게이션 권한 업데이트 + function updateNavigation() { + const listBtn = document.getElementById('listBtn'); + const summaryBtn = document.getElementById('summaryBtn'); + const adminBtn = document.getElementById('adminBtn'); + + if (currentUser.role === 'admin') { + // 관리자는 모든 메뉴 표시 + listBtn.style.display = ''; + summaryBtn.style.display = ''; + adminBtn.style.display = ''; + adminBtn.innerHTML = '사용자 관리'; + } else { + // 일반 사용자는 제한된 메뉴만 표시 + listBtn.style.display = 'none'; + summaryBtn.style.display = 'none'; + adminBtn.style.display = ''; + adminBtn.innerHTML = '비밀번호 변경'; + } + } + // 잔업 토글 function toggleOvertime() { const section = document.getElementById('overtimeSection'); diff --git a/frontend/index.html b/frontend/index.html index c5542d7..728dc0f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -153,7 +153,7 @@ @@ -276,11 +279,8 @@ document.getElementById('loginScreen').classList.add('hidden'); document.getElementById('mainScreen').classList.remove('hidden'); - // 일반 사용자는 보고서 메뉴 숨김 - if (user.role === 'user') { - const reportBtn = document.querySelector('button[onclick="showSection(\'summary\')"]'); - if (reportBtn) reportBtn.style.display = 'none'; - } + // 권한에 따른 메뉴 표시/숨김 + updateNavigation(); loadIssues(); } @@ -299,11 +299,8 @@ document.getElementById('loginScreen').classList.add('hidden'); document.getElementById('mainScreen').classList.remove('hidden'); - // 일반 사용자는 보고서 메뉴 숨김 - if (currentUser.role === 'user') { - const reportBtn = document.querySelector('button[onclick="showSection(\'summary\')"]'); - if (reportBtn) reportBtn.style.display = 'none'; - } + // 권한에 따른 메뉴 표시/숨김 + updateNavigation(); loadIssues(); } catch (error) { @@ -316,6 +313,27 @@ AuthAPI.logout(); } + // 네비게이션 권한 업데이트 + function updateNavigation() { + const listBtn = document.getElementById('listBtn'); + const summaryBtn = document.getElementById('summaryBtn'); + const adminBtn = document.getElementById('adminBtn'); + + if (currentUser.role === 'admin') { + // 관리자는 모든 메뉴 표시 + listBtn.style.display = ''; + summaryBtn.style.display = ''; + adminBtn.style.display = ''; + adminBtn.innerHTML = '사용자 관리'; + } else { + // 일반 사용자는 제한된 메뉴만 표시 + listBtn.style.display = 'none'; + summaryBtn.style.display = 'none'; + adminBtn.style.display = ''; + adminBtn.innerHTML = '비밀번호 변경'; + } + } + // 섹션 전환 function showSection(section) { // 모든 섹션 숨기기 diff --git a/frontend/issue-view.html b/frontend/issue-view.html index 2bd316b..c027363 100644 --- a/frontend/issue-view.html +++ b/frontend/issue-view.html @@ -55,7 +55,7 @@

작업보고서 시스템

-
+