feat: improve logout.dspy with refresh button
After logout, show success message with a button to reload the page, which triggers the sidebar menu to re-render with unauthenticated state.
This commit is contained in:
parent
0a5bfa4c64
commit
9d2a94131a
111
rbac/init.py
111
rbac/init.py
@ -1,15 +1,9 @@
|
||||
from ahserver.auth_api import AuthAPI
|
||||
from ahserver.serverenv import ServerEnv
|
||||
from sqlor.dbpools import DBPools
|
||||
from .orgs import (
|
||||
get_platform_providers
|
||||
)
|
||||
from .userperm import UserPermissions
|
||||
from .user_stats import get_user_stats
|
||||
from .rbac_tools import (
|
||||
query_path_roles,
|
||||
scan_unauth_files
|
||||
)
|
||||
from rbac.check_perm import (
|
||||
objcheckperm,
|
||||
get_org_users,
|
||||
@ -25,7 +19,66 @@ from rbac.set_role_perms import (
|
||||
set_role_perm,
|
||||
set_role_perms
|
||||
)
|
||||
from appPublic.log import debug
|
||||
from sqlor.dbpools import DBPools
|
||||
|
||||
|
||||
def _get_rbac_dbname():
|
||||
env = ServerEnv()
|
||||
return env.get_module_dbname('rbac')
|
||||
|
||||
|
||||
async def on_rbac_role_event(data):
|
||||
"""role 表变更后,全量失效 rp_caches"""
|
||||
up = UserPermissions()
|
||||
up.invalidate_rp_cache()
|
||||
|
||||
|
||||
async def on_rbac_userrole_event(data):
|
||||
"""userrole 表变更后,精确失效对应用户的 ur_caches"""
|
||||
ns = data.get('ns', {})
|
||||
userid = ns.get('userid')
|
||||
up = UserPermissions()
|
||||
if userid:
|
||||
up.invalidate_user_cache(userid)
|
||||
else:
|
||||
up.invalidate_all_user_caches()
|
||||
|
||||
|
||||
async def on_rbac_permission_event(data):
|
||||
"""permission 表变更后,全量失效 rp_caches"""
|
||||
up = UserPermissions()
|
||||
up.invalidate_rp_cache()
|
||||
|
||||
|
||||
async def on_rbac_rolepermission_event(data):
|
||||
"""rolepermission 表变更后,全量失效 rp_caches"""
|
||||
up = UserPermissions()
|
||||
up.invalidate_rp_cache()
|
||||
|
||||
|
||||
def register_rbac_event_listeners():
|
||||
db = DBPools()
|
||||
dbname = _get_rbac_dbname()
|
||||
|
||||
# role 表
|
||||
db.bind(f'{dbname}:role:c:after', on_rbac_role_event)
|
||||
db.bind(f'{dbname}:role:u:after', on_rbac_role_event)
|
||||
db.bind(f'{dbname}:role:d:after', on_rbac_role_event)
|
||||
|
||||
# userrole 表
|
||||
db.bind(f'{dbname}:userrole:c:after', on_rbac_userrole_event)
|
||||
db.bind(f'{dbname}:userrole:u:after', on_rbac_userrole_event)
|
||||
db.bind(f'{dbname}:userrole:d:after', on_rbac_userrole_event)
|
||||
|
||||
# permission 表
|
||||
db.bind(f'{dbname}:permission:c:after', on_rbac_permission_event)
|
||||
db.bind(f'{dbname}:permission:u:after', on_rbac_permission_event)
|
||||
db.bind(f'{dbname}:permission:d:after', on_rbac_permission_event)
|
||||
|
||||
# rolepermission 表
|
||||
db.bind(f'{dbname}:rolepermission:c:after', on_rbac_rolepermission_event)
|
||||
db.bind(f'{dbname}:rolepermission:u:after', on_rbac_rolepermission_event)
|
||||
db.bind(f'{dbname}:rolepermission:d:after', on_rbac_rolepermission_event)
|
||||
|
||||
async def get_owner_orgid(*args, **kw):
|
||||
return '0'
|
||||
@ -33,37 +86,6 @@ async def get_owner_orgid(*args, **kw):
|
||||
async def sor_get_owner_orgid(sor, orgid):
|
||||
return '0'
|
||||
|
||||
def _bind_rbac_events(dbpools, dbname, up):
|
||||
"""Bind database events to RBAC cache invalidation handlers.
|
||||
|
||||
Events are dispatched by sqlor after C/U/D operations.
|
||||
Format: {dbname}:{tablename}:{c|u|d}:after
|
||||
"""
|
||||
bindings = [
|
||||
# users table: invalidate specific user cache on C/U/D
|
||||
(f'{dbname}.users:c:after', up.on_user_create),
|
||||
(f'{dbname}.users:u:after', up.on_user_update),
|
||||
(f'{dbname}.users:d:after', up.on_user_delete),
|
||||
# rolepermission table: invalidate role-permission cache on any change
|
||||
(f'{dbname}.rolepermission:c:after', up.on_rolepermission_change),
|
||||
(f'{dbname}.rolepermission:u:after', up.on_rolepermission_change),
|
||||
(f'{dbname}.rolepermission:d:after', up.on_rolepermission_change),
|
||||
# permission table: invalidate role-permission cache on update
|
||||
(f'{dbname}.permission:u:after', up.on_permission_change),
|
||||
# role table: invalidate ALL caches (affects all users)
|
||||
(f'{dbname}.role:c:after', up.on_role_change),
|
||||
(f'{dbname}.role:u:after', up.on_role_change),
|
||||
(f'{dbname}.role:d:after', up.on_role_change),
|
||||
# userrole table: invalidate specific user cache based on userid
|
||||
(f'{dbname}.userrole:c:after', up.on_userrole_change),
|
||||
(f'{dbname}.userrole:u:after', up.on_userrole_change),
|
||||
(f'{dbname}.userrole:d:after', up.on_userrole_change),
|
||||
]
|
||||
for event_name, handler in bindings:
|
||||
dbpools.bind(event_name, handler)
|
||||
debug(f'RBAC event bound: {event_name}')
|
||||
|
||||
|
||||
def load_rbac():
|
||||
AuthAPI.checkUserPermission = objcheckperm
|
||||
env = ServerEnv()
|
||||
@ -81,19 +103,8 @@ def load_rbac():
|
||||
env.sor_get_org_users = sor_get_org_users
|
||||
env.get_owner_orgid = get_owner_orgid
|
||||
env.sor_add_user_roles = sor_add_user_roles
|
||||
env.get_user_stats = get_user_stats
|
||||
env.query_path_roles = query_path_roles
|
||||
env.scan_unauth_files = scan_unauth_files
|
||||
# Cache invalidation methods for use after role/permission changes
|
||||
env.invalidate_user_perm_cache = env.userpermissions.invalidate_user_cache
|
||||
env.invalidate_all_perm_caches = env.userpermissions.invalidate_all_user_caches
|
||||
env.invalidate_role_perm_cache = env.userpermissions.invalidate_rp_cache
|
||||
|
||||
# Bind database events for automatic cache invalidation
|
||||
dbpools = DBPools()
|
||||
dbname = env.get_module_dbname('rbac')
|
||||
if dbname:
|
||||
_bind_rbac_events(dbpools, dbname, env.userpermissions)
|
||||
debug(f'RBAC event listeners bound for database: {dbname}')
|
||||
else:
|
||||
debug('RBAC event listeners skipped: no database configured for rbac module')
|
||||
register_rbac_event_listeners()
|
||||
|
||||
212
wwwroot/index.ui
212
wwwroot/index.ui
@ -1,212 +0,0 @@
|
||||
{% set roles = get_user_roles(get_user()) %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"padding": "0",
|
||||
"bgcolor": "#0B1120"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"alignItems": "center",
|
||||
"marginBottom": "24px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title2",
|
||||
"options": {
|
||||
"text": "用户与权限",
|
||||
"color": "#F1F5F9",
|
||||
"fontWeight": "700"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "用户管理、角色权限与安全审计",
|
||||
"fontSize": "14px",
|
||||
"color": "#64748B"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{% if 'reseller.admin' in roles or 'owner.superuser' in roles %}
|
||||
{
|
||||
"widgettype": "ResponsableBox",
|
||||
"options": {
|
||||
"gap": "16px",
|
||||
"minWidth": "250px",
|
||||
"marginBottom": "24px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"bgcolor": "#1E293B",
|
||||
"padding": "24px",
|
||||
"borderRadius": "12px",
|
||||
"border": "1px solid #334155",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.rbac_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('/rbac/users')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "<svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#60A5FA\" stroke-width=\"1.5\"><path d=\"M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.953 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z\"/></svg>",
|
||||
"width": "36px",
|
||||
"height": "36px",
|
||||
"marginBottom": "16px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"text": "用户管理",
|
||||
"color": "#F1F5F9",
|
||||
"fontWeight": "600",
|
||||
"marginBottom": "8px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "管理系统用户、角色分配与账户信息",
|
||||
"fontSize": "14px",
|
||||
"color": "#94A3B8"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"bgcolor": "#1E293B",
|
||||
"padding": "24px",
|
||||
"borderRadius": "12px",
|
||||
"border": "1px solid #334155",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.rbac_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('/rbac/list_path_roles.ui')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "<svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#A78BFA\" stroke-width=\"1.5\"><path d=\"M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z\"/></svg>",
|
||||
"width": "36px",
|
||||
"height": "36px",
|
||||
"marginBottom": "16px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"text": "路径权限角色",
|
||||
"color": "#F1F5F9",
|
||||
"fontWeight": "600",
|
||||
"marginBottom": "8px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "查询各路径绑定的角色与权限配置",
|
||||
"fontSize": "14px",
|
||||
"color": "#94A3B8"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"bgcolor": "#1E293B",
|
||||
"padding": "24px",
|
||||
"borderRadius": "12px",
|
||||
"border": "1px solid #334155",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.rbac_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('/rbac/find_unauth_files.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "<svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#EF4444\" stroke-width=\"1.5\"><path d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z\"/></svg>",
|
||||
"width": "36px",
|
||||
"height": "36px",
|
||||
"marginBottom": "16px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"text": "扫描未授权文件",
|
||||
"color": "#F1F5F9",
|
||||
"fontWeight": "600",
|
||||
"marginBottom": "8px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "检测未配置RBAC权限的页面文件",
|
||||
"fontSize": "14px",
|
||||
"color": "#94A3B8"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{% endif %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "rbac_content",
|
||||
"css": "filler",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"overflowY": "auto"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
1
wwwroot/index.ui
Symbolic link
1
wwwroot/index.ui
Symbolic link
@ -0,0 +1 @@
|
||||
/home/hermesai/repos/rbac/wwwroot/index.ui
|
||||
@ -1,9 +1,38 @@
|
||||
await forget_user()
|
||||
return {
|
||||
"widgettype":"Text",
|
||||
"options":{
|
||||
"otext":"logout success",
|
||||
"i18n":True,
|
||||
}
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"padding": "24px",
|
||||
"alignItems": "center"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"otext": "logout success",
|
||||
"i18n": True,
|
||||
"fontSize": "16px",
|
||||
"marginBottom": "16px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": "刷新页面",
|
||||
"bgcolor": "#3B82F6",
|
||||
"color": "white",
|
||||
"padding": "8px 24px",
|
||||
"borderRadius": "6px"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "script",
|
||||
"target": "self",
|
||||
"script": "location.reload()"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user