Fix: Admin department binding and region visibility
1. Bind 'City-level' permits to 'SYSADMIN' department to ensure they are managed by admin. 2. Ensure Foshan 5 regions exist and Service Departments (FSSJSS etc.) are correctly bound to their regions. 3. Update specific permit binding logic to prevent leakage of 'City-level' data to district admins. 4. UI: Restrict region filter in Admin Console for non-admin users to their own region only.
This commit is contained in:
parent
06944cd251
commit
2440a02a2d
File diff suppressed because one or more lines are too long
|
|
@ -2264,7 +2264,7 @@
|
|||
<div class="header">
|
||||
<div class="header-info">
|
||||
<h1 id="pageTitle">🗃️ 管理员控制台</h1>
|
||||
<p>LawRisk 法律风险提示系统 - 管理员功能面板</p>
|
||||
<p>法律风险提示系统 - 管理员功能面板</p>
|
||||
</div>
|
||||
<div class="user-bar" id="userBar">
|
||||
<div class="user-avatar" id="userAvatar">U</div>
|
||||
|
|
@ -6112,21 +6112,91 @@
|
|||
// 构建区域-部门映射关系
|
||||
buildRegionDepartmentMapping();
|
||||
|
||||
// 渲染区域选项
|
||||
// 渲染区域选项
|
||||
const regionOptionsList = document.getElementById('regionOptionsList');
|
||||
if (regionOptionsList) {
|
||||
regionOptionsList.innerHTML = '';
|
||||
permitFilterOptions.regions.forEach(region => {
|
||||
|
||||
let visibleRegions = permitFilterOptions.regions;
|
||||
let isRestricted = false;
|
||||
|
||||
// 权限控制:如果不是 admin,且有部门区域信息,只显示所属区域
|
||||
if (currentUserProfile && currentUserProfile.role !== 'admin' &&
|
||||
currentUserProfile.department && currentUserProfile.department.region_name) {
|
||||
const userRegion = currentUserProfile.department.region_name;
|
||||
visibleRegions = visibleRegions.filter(r => (r.name || r) === userRegion);
|
||||
if (visibleRegions.length > 0) {
|
||||
isRestricted = true;
|
||||
}
|
||||
}
|
||||
|
||||
visibleRegions.forEach(region => {
|
||||
const div = document.createElement('div');
|
||||
div.style.padding = '6px 12px';
|
||||
|
||||
const checkedState = isRestricted ? 'checked' : '';
|
||||
// 如果受限,禁止鼠标交互(pointer-events: none),让用户无法取消
|
||||
const labelStyle = isRestricted
|
||||
? 'display: flex; align-items: center; cursor: default; pointer-events: none; opacity: 0.8;'
|
||||
: 'display: flex; align-items: center; cursor: pointer;';
|
||||
|
||||
div.innerHTML = `
|
||||
<label style="display: flex; align-items: center; cursor: pointer;">
|
||||
<input type="checkbox" name="regionFilter" value="${region.id || region}" onchange="onRegionSelectionChange()" style="margin-right: 8px;">
|
||||
<label style="${labelStyle}">
|
||||
<input type="checkbox" name="regionFilter" value="${region.id || region}"
|
||||
onchange="onRegionSelectionChange()"
|
||||
style="margin-right: 8px;" ${checkedState}>
|
||||
<span>${region.name || region}</span>
|
||||
</label>
|
||||
`;
|
||||
regionOptionsList.appendChild(div);
|
||||
});
|
||||
|
||||
// 如果受限,主动触发一次状态更新,确保“关联部门”被联动过滤,且文本显示正确
|
||||
if (isRestricted) {
|
||||
const userRegionName = visibleRegions[0].name || visibleRegions[0];
|
||||
|
||||
// 1. 更新顶部显示文本
|
||||
const selectedTextElement = document.getElementById('regionSelectedText');
|
||||
if (selectedTextElement) {
|
||||
selectedTextElement.textContent = userRegionName;
|
||||
selectedTextElement.style.color = '#333';
|
||||
}
|
||||
|
||||
// 2. 禁用下拉触发器
|
||||
const filterDropdown = document.getElementById('filterRegion');
|
||||
if (filterDropdown) {
|
||||
filterDropdown.onclick = null; // 移除点击事件
|
||||
filterDropdown.style.background = '#f3f4f6';
|
||||
filterDropdown.style.cursor = 'not-allowed';
|
||||
filterDropdown.style.color = '#666';
|
||||
filterDropdown.title = '您只能查看所属区域的数据';
|
||||
// 移除下拉箭头
|
||||
const arrow = filterDropdown.querySelector('span[style*="float: right"]');
|
||||
if (arrow) arrow.style.display = 'none';
|
||||
}
|
||||
|
||||
// 3. 隐藏全选选项(避免困惑)
|
||||
const regionSelectAllRow = document.querySelector('#regionOptions input[id="regionSelectAll"]')?.closest('div');
|
||||
if (regionSelectAllRow) {
|
||||
regionSelectAllRow.style.display = 'none';
|
||||
}
|
||||
|
||||
// 4. 触发逻辑更新
|
||||
setTimeout(() => {
|
||||
onRegionSelectionChange();
|
||||
}, 50);
|
||||
} else {
|
||||
// 恢复正常状态(如果是 Admin 切换回来)
|
||||
const filterDropdown = document.getElementById('filterRegion');
|
||||
if (filterDropdown) {
|
||||
filterDropdown.onclick = function () { toggleMultiSelect('regionOptions'); };
|
||||
filterDropdown.style.background = 'white';
|
||||
filterDropdown.style.cursor = 'pointer';
|
||||
const arrow = filterDropdown.querySelector('span[style*="float: right"]');
|
||||
if (arrow) arrow.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染主题选项
|
||||
|
|
|
|||
|
|
@ -1268,7 +1268,7 @@
|
|||
<div class="page">
|
||||
<header class="header">
|
||||
<div class="title">
|
||||
<h1>LawRisk 法律风险提示系统</h1>
|
||||
<h1>法律风险提示系统</h1>
|
||||
<p>超级管理员 · 用户、部门、主题与模板一站式管控</p>
|
||||
</div>
|
||||
<div class="user-chip" id="userChip">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
import os
|
||||
import sys
|
||||
import uuid
|
||||
import logging
|
||||
import pg8000.native as pg
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Add project root to path
|
||||
sys.path.append(os.getcwd())
|
||||
|
||||
from lawrisk.utils.env_loader import load_env
|
||||
load_env()
|
||||
|
||||
def _lic_pg_conn(autocommit: bool = False) -> pg.Connection:
|
||||
host = os.getenv("LIC_PG_HOST", "8.138.196.105") # Updated default to match known external IP
|
||||
port = int(os.getenv("LIC_PG_PORT", os.getenv("PG_PORT", "5432")))
|
||||
user = os.getenv("LIC_PG_USER", os.getenv("PG_USER", "postgres"))
|
||||
password = os.getenv("LIC_PG_PASSWORD", "")
|
||||
database = os.getenv("LIC_PG_DATABASE", "licensing_risks")
|
||||
|
||||
logger.info(f"Connecting to {host}:{port}/{database} as {user}")
|
||||
try:
|
||||
conn = pg.Connection(user=user, host=host, port=port, password=password, database=database)
|
||||
conn.autocommit = autocommit
|
||||
return conn
|
||||
except Exception as e:
|
||||
logger.error(f"Connection failed: {e}")
|
||||
raise
|
||||
|
||||
def main():
|
||||
try:
|
||||
conn = _lic_pg_conn()
|
||||
logger.info("Connected to database successfully.")
|
||||
|
||||
# 1. Get '市级' Region ID
|
||||
region_id_city = None
|
||||
for row in conn.run("SELECT id, name FROM regions WHERE name LIKE '市级%'"):
|
||||
region_id_city = row[0]
|
||||
logger.info(f"Found City Region: {row[1]} ({row[0]})")
|
||||
break
|
||||
|
||||
if not region_id_city:
|
||||
# Fallback if specific name differs
|
||||
for row in conn.run("SELECT id, name FROM regions WHERE name = '市级'"):
|
||||
region_id_city = row[0]
|
||||
break
|
||||
|
||||
if not region_id_city:
|
||||
logger.error("Could not find '市级' region. Exiting.")
|
||||
return
|
||||
|
||||
# 2. Get/Create Admin Department
|
||||
admin_dept_name = "系统管理部"
|
||||
admin_dept_code = "SYSADMIN"
|
||||
|
||||
result = conn.run("SELECT id FROM service_departments WHERE code = :code", code=admin_dept_code)
|
||||
|
||||
if result:
|
||||
admin_dept_id = result[0][0]
|
||||
logger.info(f"Found existing Admin Department: {admin_dept_id}")
|
||||
# Ensure it is bound to City Region
|
||||
conn.run("UPDATE service_departments SET region_id = :rid WHERE id = :id", rid=region_id_city, id=admin_dept_id)
|
||||
else:
|
||||
admin_dept_id = str(uuid.uuid4())
|
||||
logger.info(f"Creating new Admin Department: {admin_dept_id}")
|
||||
conn.run("""
|
||||
INSERT INTO service_departments (id, name, code, region_id, description, grade, unit_level)
|
||||
VALUES (:id, :name, :code, :rid, 'System Administrator Department', 100, 'municipal')
|
||||
""", id=admin_dept_id, name=admin_dept_name, code=admin_dept_code, rid=region_id_city)
|
||||
|
||||
# 3. Assign 'admin' user to this department
|
||||
conn.run("""
|
||||
UPDATE auth_users
|
||||
SET service_department_id = :dept_id,
|
||||
role = 'admin',
|
||||
grade = 100
|
||||
WHERE username = 'admin'
|
||||
""", dept_id=admin_dept_id)
|
||||
logger.info(f"Assigned 'admin' user to department {admin_dept_id}")
|
||||
|
||||
# 4. Bind City-level Permis to this Department
|
||||
# Find permits in the city region
|
||||
permits = conn.run("""
|
||||
SELECT p.id, p.name
|
||||
FROM region_permit_details rpd
|
||||
JOIN permits p ON p.id = rpd.permit_id
|
||||
WHERE rpd.region_id = :rid
|
||||
""", rid=region_id_city)
|
||||
|
||||
permit_ids = [row[0] for row in permits]
|
||||
logger.info(f"Found {len(permit_ids)} permits in '市级' region.")
|
||||
|
||||
if not permit_ids:
|
||||
logger.warning("No permits found to bind!")
|
||||
return
|
||||
|
||||
# Upsert permit_sources
|
||||
# We want to ensure specific records exist for these permits
|
||||
updated_count = 0
|
||||
inserted_count = 0
|
||||
|
||||
for pid in permit_ids:
|
||||
# Check if source exists
|
||||
existing = conn.run("""
|
||||
SELECT 1 FROM permit_sources WHERE region_id = :rid AND permit_id = :pid
|
||||
""", rid=region_id_city, pid=pid)
|
||||
|
||||
if existing:
|
||||
conn.run("""
|
||||
UPDATE permit_sources
|
||||
SET bound_department_id = :dept_id,
|
||||
uploader_department_id = COALESCE(uploader_department_id, :dept_id),
|
||||
updated_at = NOW()
|
||||
WHERE region_id = :rid AND permit_id = :pid
|
||||
""", dept_id=admin_dept_id, rid=region_id_city, pid=pid)
|
||||
updated_count += 1
|
||||
else:
|
||||
conn.run("""
|
||||
INSERT INTO permit_sources (
|
||||
region_id, permit_id, source_type, source_name,
|
||||
uploader_department_id, bound_department_id, created_at, updated_at
|
||||
) VALUES (
|
||||
:rid, :pid, 'system_init', 'Initial Binding',
|
||||
:dept_id, :dept_id, NOW(), NOW()
|
||||
)
|
||||
""", rid=region_id_city, pid=pid, dept_id=admin_dept_id)
|
||||
inserted_count += 1
|
||||
|
||||
logger.info(f"Binding complete. Updated: {updated_count}, Inserted: {inserted_count}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
if 'conn' in locals():
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
import os
|
||||
import sys
|
||||
import pg8000.native as pg
|
||||
|
||||
sys.path.append(os.getcwd())
|
||||
from lawrisk.utils.env_loader import load_env
|
||||
|
||||
load_env()
|
||||
|
||||
def main():
|
||||
try:
|
||||
conn = pg.Connection(
|
||||
user=os.getenv('LIC_PG_USER', 'postgres'),
|
||||
host=os.getenv('LIC_PG_HOST', '8.138.196.105'),
|
||||
port=int(os.getenv('LIC_PG_PORT', 5432)),
|
||||
password=os.getenv('LIC_PG_PASSWORD', ''),
|
||||
database=os.getenv('LIC_PG_DATABASE', 'licensing_risks')
|
||||
)
|
||||
print("Connected to DB.")
|
||||
|
||||
# 1. Investigate User FSSJSS
|
||||
print("-" * 30)
|
||||
print("Investigating User 'FSSJSS'")
|
||||
user_res = conn.run("""
|
||||
SELECT au.id, au.username, au.service_department_id, sd.name, sd.code, sd.parent_id
|
||||
FROM auth_users au
|
||||
JOIN service_departments sd ON sd.id = au.service_department_id
|
||||
WHERE au.username = 'fssjss'
|
||||
""")
|
||||
|
||||
if not user_res:
|
||||
print("User FSSJSS not found!")
|
||||
return
|
||||
|
||||
u_id, u_name, u_dept_id, u_dept_name, u_dept_code, u_parent_id = user_res[0]
|
||||
print(f"User: {u_name}")
|
||||
print(f"Department: {u_dept_name} ({u_dept_code}) ID: {u_dept_id}")
|
||||
print(f"Parent Dept ID: {u_parent_id}")
|
||||
|
||||
# 2. Investigate SYSADMIN Department
|
||||
print("-" * 30)
|
||||
print("Investigating Department 'SYSADMIN'")
|
||||
sys_res = conn.run("SELECT id, name, parent_id FROM service_departments WHERE code = 'SYSADMIN'")
|
||||
if not sys_res:
|
||||
print("SYSADMIN Department not found!")
|
||||
else:
|
||||
sys_id, sys_name, sys_parent = sys_res[0]
|
||||
print(f"SYSADMIN ID: {sys_id}, Name: {sys_name}, Parent: {sys_parent}")
|
||||
|
||||
if str(sys_id) == str(u_dept_id):
|
||||
print("ALERT: FSSJSS is directly in SYSADMIN department!")
|
||||
if str(sys_id) == str(u_parent_id):
|
||||
print("ALERT: FSSJSS is a direct child of SYSADMIN!")
|
||||
|
||||
# 3. Check Recursive Access for FSSJSS
|
||||
print("-" * 30)
|
||||
print("Checking accessible departments for FSSJSS...")
|
||||
|
||||
accessible_query = """
|
||||
WITH RECURSIVE sub AS (
|
||||
SELECT id, name, code FROM service_departments WHERE id = :root_id
|
||||
UNION ALL
|
||||
SELECT sd.id, sd.name, sd.code
|
||||
FROM service_departments sd
|
||||
JOIN sub ON sd.parent_id = sub.id
|
||||
)
|
||||
SELECT id, name, code FROM sub
|
||||
"""
|
||||
access_rows = conn.run(accessible_query, root_id=u_dept_id)
|
||||
accessible_ids = [str(row[0]) for row in access_rows]
|
||||
print(f"FSSJSS can see data from {len(accessible_ids)} departments.")
|
||||
for row in access_rows:
|
||||
print(f" - {row[1]} ({row[2]}) ID: {row[0]}")
|
||||
|
||||
if str(sys_id) in accessible_ids:
|
||||
print("CRITICAL: SYSADMIN is in the accessible list! (This implies SYSADMIN is a descendant of FSSJSS??)")
|
||||
else:
|
||||
print("OK: SYSADMIN is NOT in the accessible list.")
|
||||
|
||||
# 4. Investigate a City Permit
|
||||
print("-" * 30)
|
||||
print("Investigating a sample City Permit")
|
||||
# Find a permit in '市级'
|
||||
permit_res = conn.run("""
|
||||
SELECT ps.permit_id, ps.bound_department_id, ps.uploader_department_id, p.name
|
||||
FROM permit_sources ps
|
||||
JOIN region_permit_details rpd ON rpd.permit_id = ps.permit_id AND rpd.region_id = ps.region_id
|
||||
JOIN regions r ON r.id = rpd.region_id
|
||||
JOIN permits p ON p.id = ps.permit_id
|
||||
WHERE r.name = '市级'
|
||||
LIMIT 1
|
||||
""")
|
||||
|
||||
if permit_res:
|
||||
p_id, bound_id, uploader_id, p_name = permit_res[0]
|
||||
print(f"Permit: {p_name}")
|
||||
print(f"Bound Dept ID: {bound_id}")
|
||||
print(f"Uploader Dept ID: {uploader_id}")
|
||||
|
||||
if str(bound_id) == str(sys_id):
|
||||
print("Permit is correctly bound to SYSADMIN.")
|
||||
else:
|
||||
print(f"Permit is bound to {bound_id}, NOT SYSADMIN!")
|
||||
|
||||
if str(bound_id) in accessible_ids:
|
||||
print("CRITICAL: The bound department IS in FSSJSS's accessible list.")
|
||||
else:
|
||||
print("OK: The bound department is NOT in FSSJSS's accessible list.")
|
||||
else:
|
||||
print("No city permits found for check.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue