feat(admin): 슈퍼관리자가 admin 의 개별 프로그램 접근 제어
기존엔 admin 역할이면 모든 모듈을 무조건 전체 ON+잠금이라 슈퍼관리자도 admin 의 특정 프로그램 접근을 끌 수 없었다. - _normalize_modules: admin 강제 전체 ON 덮어쓰기 제거 → 저장된 칩 상태 존중 - has_module: admin 단축 통과 제거, 슈퍼관리자만 항상 전체. admin 은 칩 따름 - admin.html: admin 칩 잠금 해제(슈퍼관리자만 잠금), 역할→admin 전환 시 편의상 전체 ON(잠금 아님) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+6
-4
@@ -82,14 +82,14 @@ class UserStore:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _normalize_modules(modules: dict[str, Any] | None, *, is_admin: bool) -> dict[str, bool]:
|
def _normalize_modules(modules: dict[str, Any] | None, *, is_admin: bool) -> dict[str, bool]:
|
||||||
|
# 신규(modules 미지정) 사용자는 역할 기본값(admin=전체ON / user=전체OFF)으로 시작.
|
||||||
|
# 기존 값이 있으면 그대로 존중한다 — admin 이라도 슈퍼관리자가 개별 모듈을
|
||||||
|
# 끌 수 있다(예전엔 admin 을 무조건 전체 ON 으로 덮어썼다).
|
||||||
base = UserStore._default_modules(is_admin)
|
base = UserStore._default_modules(is_admin)
|
||||||
if isinstance(modules, dict):
|
if isinstance(modules, dict):
|
||||||
for key in MODULE_KEYS:
|
for key in MODULE_KEYS:
|
||||||
if key in modules:
|
if key in modules:
|
||||||
base[key] = bool(modules[key])
|
base[key] = bool(modules[key])
|
||||||
if is_admin:
|
|
||||||
for key in MODULE_KEYS:
|
|
||||||
base[key] = True
|
|
||||||
return base
|
return base
|
||||||
|
|
||||||
def get(self, email: str) -> dict[str, Any] | None:
|
def get(self, email: str) -> dict[str, Any] | None:
|
||||||
@@ -230,7 +230,9 @@ def is_admin(user_rec: dict[str, Any] | None) -> bool:
|
|||||||
def has_module(user_rec: dict[str, Any] | None, module: str) -> bool:
|
def has_module(user_rec: dict[str, Any] | None, module: str) -> bool:
|
||||||
if not user_rec:
|
if not user_rec:
|
||||||
return False
|
return False
|
||||||
if is_admin(user_rec):
|
# 슈퍼관리자는 항상 전체 접근. 일반 admin 은 칩(modules) 상태를 따른다 —
|
||||||
|
# 슈퍼관리자가 admin 의 개별 프로그램 접근을 제어할 수 있도록.
|
||||||
|
if user_rec.get("is_super_admin"):
|
||||||
return True
|
return True
|
||||||
mods = user_rec.get("modules") or {}
|
mods = user_rec.get("modules") or {}
|
||||||
if mods.get(module):
|
if mods.get(module):
|
||||||
|
|||||||
@@ -152,7 +152,7 @@
|
|||||||
{% if key in approver_keys %}is-approver{% endif %}"
|
{% if key in approver_keys %}is-approver{% endif %}"
|
||||||
data-module="{{ key }}"
|
data-module="{{ key }}"
|
||||||
aria-pressed="{{ 'true' if u.modules[key] else 'false' }}"
|
aria-pressed="{{ 'true' if u.modules[key] else 'false' }}"
|
||||||
{% if u.is_super_admin or u.role == 'admin' %}data-locked="true"{% endif %}>
|
{% if u.is_super_admin %}data-locked="true"{% endif %}>
|
||||||
{{ module_labels.get(key, key) }}{% if key in approver_keys %} 승인{% endif %}
|
{{ module_labels.get(key, key) }}{% if key in approver_keys %} 승인{% endif %}
|
||||||
</button>
|
</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -175,8 +175,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="erp-section-meta" style="margin-top: var(--sp-16);">
|
<p class="erp-section-meta" style="margin-top: var(--sp-16);">
|
||||||
※ <strong>{{ super_admin_email }}</strong> 은(는) 슈퍼 관리자, 권한 변경/삭제 불가.
|
※ <strong>{{ super_admin_email }}</strong> 은(는) 슈퍼 관리자, 권한 변경/삭제 불가(항상 전체 접근).
|
||||||
admin 역할은 모든 모듈 권한 자동 부여.
|
admin 역할은 기본 전체 ON 이지만 슈퍼관리자가 개별 프로그램 접근을 끌 수 있다.
|
||||||
<code>expense_approver</code> / <code>vacation_approver</code> 는 결재 승인 권한.
|
<code>expense_approver</code> / <code>vacation_approver</code> 는 결재 승인 권한.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -204,15 +204,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function syncPermChips(tr) {
|
function syncPermChips(tr) {
|
||||||
|
// 슈퍼관리자만 전체 ON + 잠금. 일반 admin 은 슈퍼관리자가 개별 제어 가능.
|
||||||
|
// 역할을 admin 으로 바꾸면 편의상 전체 ON(잠금 아님 — 다시 끌 수 있음).
|
||||||
const isSuper = tr.dataset.super === "true";
|
const isSuper = tr.dataset.super === "true";
|
||||||
const adminRole = tr.querySelector("select[data-field='role']").value === "admin";
|
const adminRole = tr.querySelector("select[data-field='role']").value === "admin";
|
||||||
tr.querySelectorAll(".erp-perm-chip").forEach((chip) => {
|
tr.querySelectorAll(".erp-perm-chip").forEach((chip) => {
|
||||||
if (isSuper || adminRole) {
|
if (isSuper) {
|
||||||
chip.classList.add("is-on");
|
chip.classList.add("is-on");
|
||||||
chip.setAttribute("aria-pressed", "true");
|
chip.setAttribute("aria-pressed", "true");
|
||||||
chip.setAttribute("data-locked", "true");
|
chip.setAttribute("data-locked", "true");
|
||||||
} else {
|
} else {
|
||||||
chip.removeAttribute("data-locked");
|
chip.removeAttribute("data-locked");
|
||||||
|
if (adminRole) {
|
||||||
|
chip.classList.add("is-on");
|
||||||
|
chip.setAttribute("aria-pressed", "true");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user